<!DOCTYPE html>
<html lang="zh-CN" data-bs-theme="auto">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>DocuTranslate - 交互式文档翻译</title>
    <link rel="icon" href="/static/favicon.ico" type="image/x-icon">
    <!-- Bootstrap CSS -->
    <link href="/static/bootstrap.css" rel="stylesheet" crossorigin="anonymous">
    <!-- Bootstrap Icons -->
    <link rel="stylesheet" href="/static/bootstrap-icons.css">

    <style>
        /* 复用原版 CSS */
        body {
            background-color: var(--bs-body-bg);
        }

        button:focus {
            outline: none !important;
            box-shadow: none !important;
        }

        .main-container {
            display: flex;
            flex-direction: column;
            height: 100vh;
            padding-top: 1rem;
            padding-bottom: 1rem;
        }

        .settings-panel {
            height: calc(100vh - 2rem);
            overflow-y: auto;
            padding-right: 15px;
        }

        .task-area {
            height: calc(100vh - 2rem);
            overflow-y: auto;
        }

        .task-card {
            transition: all 0.3s ease-in-out;
        }

        .log-area {
            height: 150px;
            background-color: var(--bs-tertiary-bg);
            border: 1px solid var(--bs-border-color);
            border-radius: .375rem;
            font-family: monospace;
            font-size: 0.8rem;
            padding: 10px;
            overflow-y: auto;
            white-space: pre-wrap;
            word-break: break-all;
        }

        .log-area-wrapper {
            position: relative;
        }

        .copy-log-btn {
            position: absolute;
            bottom: 5px;
            right: 5px;
            z-index: 10;
            opacity: 0.5;
            transition: all 0.2s ease-in-out;
            padding: 0.1rem 0.4rem;
            line-height: 1;
        }

        .copy-log-btn:hover {
            opacity: 1;
        }

        .file-drop-area {
            border: 2px dashed var(--bs-secondary-bg);
            border-radius: .375rem;
            padding: 2rem;
            text-align: center;
            cursor: pointer;
            transition: all 0.2s ease-in-out;
            background-color: var(--bs-body-bg);
        }

        .file-drop-area.drag-over {
            border-color: var(--bs-primary);
            background-color: var(--bs-secondary-bg);
        }

        .file-drop-area.file-selected {
            border-style: solid;
            border-color: var(--bs-success);
            background-color: var(--bs-success-bg-subtle);
        }

        .file-drop-area.input-error {
            border-color: var(--bs-danger);
        }

        #previewOffcanvas {
            --bs-offcanvas-width: 95vw;
            max-width: 1600px;
        }

        .preview-split-container {
            display: flex;
            flex-direction: row;
            height: 90%;
        }

        .preview-pane-wrapper {
            display: flex;
            flex-direction: column;
            overflow: hidden;
            width: 100%;
            height: 100%;
        }

        .preview-pane-wrapper .preview-pane {
            flex-grow: 1;
            border: 1px solid var(--bs-border-color);
            border-radius: .375rem;
            overflow: auto;
            position: relative;
        }

        .gutter {
            background-color: var(--bs-tertiary-bg);
            background-repeat: no-repeat;
            background-position: 50%;
        }

        .gutter.gutter-horizontal {
            cursor: col-resize;
            border-left: 1px solid var(--bs-border-color);
            border-right: 1px solid var(--bs-border-color);
        }

        .gutter.gutter-vertical {
            cursor: row-resize;
            border-top: 1px solid var(--bs-border-color);
            border-bottom: 1px solid var(--bs-border-color);
        }

        .preview-pane iframe {
            width: 100%;
            height: 100%;
            border: none;
        }

        .preview-pane pre {
            margin: 0;
            padding: 1rem;
            background-color: var(--bs-body-bg);
            white-space: pre;
        }

        .bottom-left-controls {
            position: fixed;
            bottom: 1rem;
            left: 1rem;
            z-index: 1050;
            display: flex;
            gap: 0.5rem;
        }

        .step-number {
            margin-right: 0.25rem;
        }

        #translatedPreviewContainer .preview-pane {
            overflow: hidden;
        }

        #translatedPreviewContainer .preview-pane iframe {
            display: block;
            width: 100%;
            height: 100%;
        }

        .lang-list-container {
            max-height: 200px;
            overflow-y: auto;
            border: 1px solid var(--bs-border-color);
            border-radius: 0.375rem;
            padding: 0.5rem;
            background-color: var(--bs-body-bg);
        }

        @media (max-width: 991.98px) {
            .main-container {
                height: auto;
                padding-bottom: 6rem;
            }

            .settings-panel, .task-area {
                height: auto;
                overflow-y: visible;
            }

            .settings-panel {
                padding-right: 0;
                margin-bottom: 2rem;
            }
        }

        /* Vue Cloak to hide uncompiled templates */
        [v-cloak] {
            display: none;
        }
    </style>
</head>
<body>

<div id="app" v-cloak>
    <div class="container-fluid main-container">
        <div class="row gx-4">
            <!-- Left: Settings Panel -->
            <div class="col-lg-4">
                <div class="settings-panel">
                    <div class="d-flex justify-content-between align-items-center mb-3">
                        <div class="d-flex align-items-center">
                            <h4 class="mb-0 me-3 fw-bold" :title="t('pageTitle')">DocuTranslate</h4>
                            <div class="btn-group">
                                <button type="button" class="btn btn-sm btn-outline-info" data-bs-toggle="modal"
                                        data-bs-target="#tutorialModal">
                                    <i class="bi bi-question-circle-fill me-1"></i><span>{{ t('tutorialBtn') }}</span>
                                </button>
                                <button type="button" class="btn btn-sm btn-outline-warning" data-bs-toggle="modal"
                                        data-bs-target="#contributorsModal">
                                    <i class="bi bi-people-fill me-1"></i><span>{{ t('projectContributeBtn') }}</span>
                                </button>
                            </div>
                        </div>
                    </div>

                    <form id="translateForm" @submit.prevent>
                        <div class="accordion" id="settingsAccordion">

                            <!-- 1. Workflow Selection -->
                            <div class="accordion-item">
                                <h2 class="accordion-header">
                                    <button class="accordion-button" type="button" data-bs-toggle="collapse"
                                            data-bs-target="#collapseZero">
                                        <strong><span class="step-number">1</span><i
                                                class="bi bi-diagram-3 me-2"></i><span>{{ t('workflowTitle')
                                            }}</span></strong>
                                    </button>
                                </h2>
                                <div id="collapseZero" class="accordion-collapse collapse show">
                                    <div class="accordion-body">
                                        <select class="form-select" v-model="form.workflow_type"
                                                @change="saveSetting('translator_last_workflow', form.workflow_type)">
                                            <option value="markdown_based">{{ t('workflowOptionMarkdown') }}</option>
                                            <option value="docx">{{ t('workflowOptionDocx') }}</option>
                                            <option value="xlsx">{{ t('workflowOptionXlsx') }}</option>
                                            <option value="epub">{{ t('workflowOptionEpub') }}</option>
                                            <option value="txt">{{ t('workflowOptionTxt') }}</option>
                                            <option value="pptx">{{ t('workflowOptionPptx') }}</option>
                                            <option value="srt">{{ t('workflowOptionSrt') }}</option>
                                            <option value="ass">{{ t('workflowOptionAss') }}</option>
                                            <option value="json">{{ t('workflowOptionJson') }}</option>
                                            <option value="html">{{ t('workflowOptionHtml') }}</option>
                                        </select>
                                        <div class="form-check form-switch mt-2">
                                            <input class="form-check-input" type="checkbox" role="switch"
                                                   id="autoWorkflowSwitch" v-model="form.auto_workflow_enabled"
                                                   @change="saveSetting('translator_auto_workflow_enabled', form.auto_workflow_enabled)">
                                            <label class="form-check-label"
                                                   for="autoWorkflowSwitch">{{ t('autoWorkflowLabel') }}</label>
                                        </div>
                                    </div>
                                </div>
                            </div>

                            <!-- 2. Specific Workflow Settings (Conditional) -->
                            <div class="accordion-item" v-if="currentWorkflowConfig" v-show="true">
                                <h2 class="accordion-header">
                                    <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"
                                            data-bs-target="#collapseWorkflow">
                                        <strong><span class="step-number">{{ stepMap.specific }} </span><i
                                                :class="currentWorkflowConfig.icon + ' me-2'"></i><span>{{ t(currentWorkflowConfig.titleKey)
                                            }}</span></strong>
                                    </button>
                                </h2>
                                <div id="collapseWorkflow" class="accordion-collapse collapse">
                                    <div class="accordion-body">
                                        <!-- Common Insert Mode -->
                                        <div class="mb-3" v-if="currentWorkflowConfig.hasInsertMode">
                                            <label class="form-label">{{ t('insertModeLabel') }}</label>
                                            <select class="form-select"
                                                    v-model="workflowParams[form.workflow_type].insert_mode"
                                                    @change="saveWorkflowParam('insert_mode')">
                                                <option value="replace">{{ t('insertModeReplace') }}</option>
                                                <option value="append">{{ t('insertModeAppend') }}</option>
                                                <option value="prepend">{{ t('insertModePrepend') }}</option>
                                            </select>
                                            <div class="form-text">
                                                {{ t(currentWorkflowConfig.insertHelpKey || 'insertModeHelpTxt') }}
                                            </div>
                                        </div>
                                        <!-- Common Separator -->
                                        <div class="mb-3" v-if="currentWorkflowConfig.hasInsertMode"
                                             v-show="['append', 'prepend'].includes(workflowParams[form.workflow_type].insert_mode)">
                                            <label class="form-label">{{ t('separatorLabel') }}</label>
                                            <input type="text" class="form-control"
                                                   v-model="workflowParams[form.workflow_type].separator"
                                                   @input="saveWorkflowParam('separator')"
                                                   :placeholder="t(currentWorkflowConfig.separatorPlaceholderKey || 'separatorPlaceholderSimple')">
                                            <div class="form-text"
                                                 v-html="t(currentWorkflowConfig.separatorHelpKey || 'separatorHelp')"></div>
                                        </div>

                                        <!-- TXT Specific -->
                                        <div class="mb-3" v-if="form.workflow_type === 'txt'">
                                            <label class="form-label">{{ t('segmentModeLabel') }}</label>
                                            <select class="form-select" v-model="workflowParams.txt.segment_mode"
                                                    @change="saveWorkflowParam('segment_mode')">
                                                <option value="line">{{ t('segmentModeLine') }}</option>
                                                <option value="paragraph">{{ t('segmentModeParagraph') }}</option>
                                                <option value="none">{{ t('segmentModeNone') }}</option>
                                            </select>
                                            <div class="form-text">{{ t('segmentModeHelp') }}</div>
                                        </div>
                                        <!-- XLSX Specific -->
                                        <div class="mb-3" v-if="form.workflow_type === 'xlsx'">
                                            <label class="form-label">{{ t('xlsxTranslateRegionsLabel') }}</label>
                                            <textarea class="form-control"
                                                      v-model="workflowParams.xlsx.translate_regions"
                                                      @input="saveWorkflowParam('translate_regions')" rows="3"
                                                      :placeholder="t('xlsxTranslateRegionsPlaceholder')"></textarea>
                                        </div>
                                        <!-- JSON Specific -->
                                        <div class="mb-3" v-if="form.workflow_type === 'json'">
                                            <label class="form-label">{{ t('jsonPathLabel') }}</label>
                                            <textarea class="form-control" :class="{'is-invalid': errors.json_paths}"
                                                      v-model="workflowParams.json.json_paths"
                                                      @input="saveWorkflowParam('json_paths'); clearError('json_paths')"
                                                      rows="4" required
                                                      :placeholder="t('jsonPathPlaceholder')"></textarea>
                                            <div class="form-text" v-html="t('jsonPathHelp')"></div>
                                        </div>
                                    </div>
                                </div>
                            </div>

                            <!-- 3. Parsing Settings (Markdown Based Only) -->
                            <div class="accordion-item" v-if="form.workflow_type === 'markdown_based'">
                                <h2 class="accordion-header">
                                    <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"
                                            data-bs-target="#collapseOne">
                                        <strong><span class="step-number">{{ stepMap.parsing }} </span><i
                                                class="bi bi-file-earmark-binary me-2"></i><span>{{ t('parsingSettingsTitleText')
                                            }}</span></strong>
                                    </button>
                                </h2>
                                <div id="collapseOne" class="accordion-collapse collapse">
                                    <div class="accordion-body">
                                        <div class="mb-3">
                                            <label class="form-label">{{ t('parsingEngineLabel') }}</label>
                                            <select class="form-select" v-model="form.convert_engine"
                                                    @change="saveSetting('translator_convert_engin', form.convert_engine)">
                                                <option value="identity" v-if="showIdentityOption">
                                                    {{ t('engineOptionIdentity') || '已经是markdown' }}
                                                </option>
                                                <option v-for="eng in enginList" :key="eng" :value="eng">
                                                    {{ t('engineOption' + capitalize(eng)) || eng }}
                                                </option>
                                            </select>
                                            <div class="form-text">{{ t('parsingEngineHelp') }}</div>
                                        </div>

                                        <!-- Mineru Cloud Config -->
                                        <div v-if="form.convert_engine === 'mineru'">
                                            <div class="mb-3">
                                                <label class="form-label">Mineru Token <a
                                                        href="https://mineru.net/apiManage/token" target="_blank"
                                                        class="ms-1"><i
                                                        class="bi bi-box-arrow-up-right"></i></a></label>
                                                <div class="input-group">
                                                    <input :type="showMineruToken ? 'text' : 'password'"
                                                           autocomplete="new-password" class="form-control"
                                                           :class="{'is-invalid': errors.mineru_token}"
                                                           v-model="form.mineru_token"
                                                           @input="saveSetting('translator_mineru_token', form.mineru_token); clearError('mineru_token')"
                                                           :placeholder="t('mineruTokenPlaceholder')">
                                                    <button class="btn btn-outline-secondary" type="button"
                                                            @click="showMineruToken = !showMineruToken"><i class="bi"
                                                                                                           :class="showMineruToken ? 'bi-eye' : 'bi-eye-slash'"></i>
                                                    </button>
                                                </div>
                                            </div>
                                            <div class="mb-3">
                                                <label class="form-label">{{ t('modelVersionLabel') }}</label>
                                                <select class="form-select" v-model="form.model_version"
                                                        @change="saveSetting('translator_model_version', form.model_version)">
                                                    <option value="vlm">{{ t('modelVersionVlm') }}</option>
                                                    <option value="pipeline">{{ t('modelVersionPipline') }}</option>
                                                </select>
                                                <div class="form-text">{{ t('modelVersionHelp') }}</div>
                                            </div>
                                        </div>

                                        <!-- Mineru Local Deploy Config -->
                                        <div v-if="form.convert_engine === 'mineru_deploy'"
                                             class="border p-3 rounded mb-3">
                                            <div class="mb-3">
                                                <label class="form-label">{{ t('mineruDeployBaseUrlLabel') }}</label>
                                                <input type="url" class="form-control"
                                                       :class="{'is-invalid': errors.mineru_deploy_base_url}"
                                                       v-model="form.mineru_deploy_base_url"
                                                       @input="saveSetting('mineru_deploy_base_url', form.mineru_deploy_base_url); clearError('mineru_deploy_base_url')"
                                                       required :placeholder="t('mineruDeployBaseUrlPlaceholder')">
                                            </div>
                                            <div class="mb-3">
                                                <label class="form-label">{{ t('mineruDeployBackendLabel') }}</label>
                                                <select class="form-select" v-model="form.mineru_deploy_backend"
                                                        @change="saveSetting('mineru_deploy_backend', form.mineru_deploy_backend)">
                                                    <option value="pipeline">pipeline</option>
                                                    <option value="vlm-transformers">vlm-transformers</option>
                                                    <option value="vlm-mlx-engine">vlm-mlx-engine</option>
                                                    <option value="vlm-vllm-async-engine">vlm-vllm-async-engine</option>
                                                    <option value="vlm-lmdeploy-engine">vlm-lmdeploy-engine</option>
                                                    <option value="vlm-http-client">vlm-http-client</option>
                                                </select>
                                            </div>

                                            <!-- Condition: If Backend is Pipeline, show Lang List -->
                                            <div class="mb-3" v-if="form.mineru_deploy_backend === 'pipeline'">
                                                <label class="form-label">{{ t('mineruDeployLangListLabel') }}</label>
                                                <div class="lang-list-container">
                                                    <div class="form-check" v-for="lang in mineruLangOptions"
                                                         :key="lang.val">
                                                        <input class="form-check-input" type="checkbox"
                                                               :value="lang.val"
                                                               :id="'langCheck-'+lang.val"
                                                               v-model="form.mineru_deploy_lang_list"
                                                               @change="saveSettingArray('mineru_deploy_lang_list', form.mineru_deploy_lang_list)">
                                                        <label class="form-check-label" :for="'langCheck-'+lang.val">
                                                            {{ lang.label }}
                                                        </label>
                                                    </div>
                                                </div>
                                            </div>

                                            <!-- Condition: If Backend is vlm-http-client, show Server URL -->
                                            <div class="mb-3" v-if="form.mineru_deploy_backend === 'vlm-http-client'">
                                                <label class="form-label">{{ t('mineruDeployServerUrlLabel') }}</label>
                                                <input type="url" class="form-control"
                                                       v-model="form.mineru_deploy_server_url"
                                                       @input="saveSetting('mineru_deploy_server_url', form.mineru_deploy_server_url)"
                                                       :placeholder="t('mineruDeployServerUrlPlaceholder')">
                                            </div>

                                            <div class="row">
                                                <div class="col-md-6 mb-3">
                                                    <label class="form-label">{{ t('mineruDeployStartPageLabel')
                                                        }}</label>
                                                    <input type="number" class="form-control"
                                                           v-model="form.mineru_deploy_start_page"
                                                           @input="saveSetting('mineru_deploy_start_page', form.mineru_deploy_start_page)"
                                                           min="0">
                                                </div>
                                                <div class="col-md-6 mb-3">
                                                    <label class="form-label">{{ t('mineruDeployEndPageLabel')
                                                        }}</label>
                                                    <input type="number" class="form-control"
                                                           v-model="form.mineru_deploy_end_page"
                                                           @input="saveSetting('mineru_deploy_end_page', form.mineru_deploy_end_page)"
                                                           min="0">
                                                </div>
                                            </div>
                                            <div class="form-check form-switch mb-2">
                                                <input class="form-check-input" type="checkbox" role="switch"
                                                       v-model="form.mineru_deploy_formula_enable"
                                                       @change="saveSetting('mineru_deploy_formula_enable', form.mineru_deploy_formula_enable)">
                                                <label class="form-check-label">{{ t('mineruDeployFormulaEnableLabel')
                                                    }}</label>
                                            </div>
                                        </div>

                                        <div class="border-top mt-3 pt-3">
                                            <div class="form-check form-switch mb-2" v-if="ocrOptions.showFormula">
                                                <input class="form-check-input" type="checkbox" role="switch"
                                                       v-model="form.formula_ocr"
                                                       @change="saveSetting('translator_formula_ocr', form.formula_ocr)">
                                                <label class="form-check-label">{{ t('formulaOcrLabel') }}</label>
                                            </div>
                                            <div class="form-check form-switch mb-2" v-if="ocrOptions.showCode">
                                                <input class="form-check-input" type="checkbox" role="switch"
                                                       v-model="form.code_ocr"
                                                       @change="saveSetting('translator_code_ocr', form.code_ocr)">
                                                <label class="form-check-label">{{ t('codeOcrLabel') }}</label>
                                            </div>
                                        </div>
                                    </div>
                                </div>
                            </div>

                            <!-- 4. AI Settings -->
                            <div class="accordion-item">
                                <h2 class="accordion-header">
                                    <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"
                                            data-bs-target="#collapseTwo">
                                        <strong><span class="step-number">{{ stepMap.ai }} </span><i
                                                class="bi bi-robot me-2"></i><span>{{ t('aiSettingsTitleText') }}</span></strong>
                                    </button>
                                </h2>
                                <div id="collapseTwo" class="accordion-collapse collapse">
                                    <div class="accordion-body">
                                        <div class="form-check form-switch mb-3">
                                            <input class="form-check-input" type="checkbox" role="switch"
                                                   v-model="form.skip_translate"
                                                   @change="saveSetting('translator_skip_translate', form.skip_translate)">
                                            <label class="form-check-label">{{ t('skipTranslationLabel') }}</label>
                                        </div>

                                        <div v-show="!form.skip_translate">
                                            <platform-selector
                                                    v-model:platform="form.platform"
                                                    v-model:base-url="form.base_url"
                                                    v-model:api-key="form.api_key"
                                                    v-model:model-id="form.model_id"
                                                    v-model:provider="form.provider"
                                                    :invalid-api-key="errors.api_key"
                                                    :invalid-base-url="errors.base_url"
                                                    :invalid-model-id="errors.model_id"
                                                    @clear-error="clearError"
                                                    :t="t" prefix="translator_platform"></platform-selector>

                                            <div class="form-check form-switch mb-3">
                                                <input class="form-check-input" type="checkbox" role="switch"
                                                       v-model="form.system_proxy_enable"
                                                       @change="saveSetting('translator_system_proxy_enable', form.system_proxy_enable)">
                                                <label class="form-check-label">{{ t('systemProxyLabel') }}</label>
                                            </div>
                                            <div class="d-flex align-items-center mb-3">
                                                <div class="form-check form-switch">
                                                    <input class="form-check-input" type="checkbox" role="switch"
                                                           v-model="form.force_json"
                                                           @change="saveSetting('translator_force_json', form.force_json)">
                                                    <label class="form-check-label">{{ t('forceJson') }}</label>
                                                </div>
                                                <i class="bi bi-question-circle ms-2" data-bs-toggle="tooltip"
                                                   :title="t('forceJsonTooltip')"></i>
                                            </div>
                                        </div>
                                    </div>
                                </div>
                            </div>

                            <!-- 5. Translation Settings -->
                            <div class="accordion-item" v-show="!form.skip_translate">
                                <h2 class="accordion-header">
                                    <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"
                                            data-bs-target="#collapseThree">
                                        <strong><span class="step-number">{{ stepMap.trans }} </span><i
                                                class="bi bi-translate me-2"></i><span>{{ t('translationSettingsTitleText')
                                            }}</span></strong>
                                    </button>
                                </h2>
                                <div id="collapseThree" class="accordion-collapse collapse">
                                    <div class="accordion-body">
                                        <div class="mb-3">
                                            <label class="form-label">{{ t('targetLanguageLabel') }}</label>
                                            <select class="form-select" v-model="form.to_lang"
                                                    @change="saveSetting('translator_to_lang', form.to_lang)">
                                                <option value="Simplified Chinese">中文(简体中文)</option>
                                                <option value="English">英文(English)</option>
                                                <option value="Spanish">西班牙文(Español)</option>
                                                <option value="French">法文(Français)</option>
                                                <option value="German">德文(Deutsch)</option>
                                                <option value="Japanese">日文(日本語)</option>
                                                <option value="Korean">韩文(한국어)</option>
                                                <option value="Russian">俄文(Русский)</option>
                                                <option value="Portuguese">葡萄牙文(Português)</option>
                                                <option value="Arabic">阿拉伯文(العَرَبِيَّة)</option>
                                                <option value="Vietnamese">越南文(tiếng Việt)</option>
                                                <option value="custom">{{ t('targetLanguageCustom') }}</option>
                                            </select>
                                            <div class="mt-2" v-if="form.to_lang === 'custom'">
                                                <input type="text" class="form-control"
                                                       :class="{'is-invalid': errors.custom_to_lang}"
                                                       v-model="form.custom_to_lang"
                                                       @input="saveSetting('translator_custom_to_lang', form.custom_to_lang); clearError('custom_to_lang')"
                                                       :placeholder="t('customLangPlaceholder')">
                                            </div>
                                        </div>
                                        <div class="mb-3">
                                            <label class="form-label">{{ t('thinkingModeLabel') }}</label>
                                            <i class="bi bi-question-circle ms-2" data-bs-toggle="tooltip"
                                               :title="t('thinkingModeTooltip')"></i>
                                            <div class="btn-group w-100" role="group">
                                                <input type="radio" class="btn-check" value="enable" id="thinkEn"
                                                       v-model="form.thinking"
                                                       @change="saveSetting('translator_thinking_mode', 'enable')">
                                                <label class="btn btn-outline-primary"
                                                       for="thinkEn">{{ t('thinkingModeEnable') }}</label>
                                                <input type="radio" class="btn-check" value="disable" id="thinkDis"
                                                       v-model="form.thinking"
                                                       @change="saveSetting('translator_thinking_mode', 'disable')">
                                                <label class="btn btn-outline-primary"
                                                       for="thinkDis">{{ t('thinkingModeDisable') }}</label>
                                                <input type="radio" class="btn-check" value="default" id="thinkDef"
                                                       v-model="form.thinking"
                                                       @change="saveSetting('translator_thinking_mode', 'default')">
                                                <label class="btn btn-outline-primary"
                                                       for="thinkDef">{{ t('thinkingModeDefault') }}</label>
                                            </div>
                                        </div>
                                        <div class="mb-3">
                                            <label class="form-label">{{ t('customPromptLabel') }}</label>
                                            <textarea class="form-control" v-model="form.custom_prompt"
                                                      @input="saveSetting('custom_prompt', form.custom_prompt)" rows="3"
                                                      :placeholder="t('customPromptPlaceholder')"></textarea>
                                        </div>

                                        <slider-control :label="t('chunkSizeLabel')" v-model="form.chunk_size"
                                                        save-key="chunk_size" :default-val="defaultParams.chunk_size"
                                                        :min="1000" :max="12000" :step="100" :t="t"></slider-control>
                                        <slider-control :label="t('concurrentLabel')" v-model="form.concurrent"
                                                        save-key="concurrent" :default-val="defaultParams.concurrent"
                                                        :min="1" :max="120" :step="1" :t="t"></slider-control>
                                        <slider-control label="Temperature" v-model="form.temperature"
                                                        save-key="temperature" :default-val="defaultParams.temperature"
                                                        :min="0" :max="2" :step="0.1" :t="t"></slider-control>
                                        <slider-control :label="t('retryLabel')" v-model="form.retry" save-key="retry"
                                                        :default-val="defaultParams.retry" :min="1" :max="6" :step="1"
                                                        :t="t"></slider-control>

                                        <!-- New RPM/TPM Settings [Vertical Layout] -->
                                        <div class="mb-3">
                                            <label class="form-label">RPM <small class="text-muted">({{ t('rpmLabel')
                                                }})</small></label>
                                            <input type="number" class="form-control" v-model="form.rpm"
                                                   @input="saveSetting('rpm', form.rpm)"
                                                   min="1" :placeholder="t('unlimitedPlaceholder')">
                                        </div>
                                        <div class="mb-3">
                                            <label class="form-label">TPM <small class="text-muted">({{ t('tpmLabel')
                                                }})</small></label>
                                            <input type="number" class="form-control" v-model="form.tpm"
                                                   @input="saveSetting('tpm', form.tpm)"
                                                   min="1" :placeholder="t('unlimitedPlaceholder')">
                                        </div>

                                    </div>
                                </div>
                            </div>

                            <!-- 6. Glossary Settings -->
                            <div class="accordion-item">
                                <h2 class="accordion-header">
                                    <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"
                                            data-bs-target="#collapseGlossary">
                                        <strong><span class="step-number">{{ stepMap.glossary }} </span><i
                                                class="bi bi-journal-bookmark me-2"></i><span>{{ t('glossaryGenTitle')
                                            }}</span></strong>
                                    </button>
                                </h2>
                                <div id="collapseGlossary" class="accordion-collapse collapse">
                                    <div class="accordion-body">
                                        <div class="mb-3">
                                            <label class="form-label">{{ t('glossaryLabel') }}</label>
                                            <input class="form-control" type="file" @change="handleGlossaryFiles"
                                                   multiple accept=".csv" ref="glossaryInput">
                                            <div class="form-text">{{ t('glossaryHelp') }}</div>
                                            <div class="btn-group mt-2">
                                                <button type="button" class="btn btn-sm btn-outline-info"
                                                        @click="openGlossaryModal">
                                                    <i class="bi bi-card-list me-1"></i><span>{{ t('viewGlossaryBtn') }} <span
                                                        v-if="glossaryCount">({{glossaryCount}})</span></span>
                                                </button>
                                                <button type="button" class="btn btn-sm btn-outline-danger"
                                                        @click="clearGlossary">
                                                    <i class="bi bi-trash me-1"></i><span>{{ t('clearGlossaryBtn')
                                                    }}</span>
                                                </button>
                                            </div>
                                        </div>

                                        <div class="form-check form-switch mb-3 border-top pt-3">
                                            <input class="form-check-input" type="checkbox" role="switch"
                                                   v-model="form.glossary_generate_enable"
                                                   @change="saveSetting('glossary_generate_enable', form.glossary_generate_enable)">
                                            <label class="form-check-label">{{ t('glossaryGenEnableLabel') }}</label>
                                        </div>

                                        <div v-if="form.glossary_generate_enable">
                                            <div class="mb-3">
                                                <label class="form-label">{{ t('glossaryCustomPromptLabel') }}</label>
                                                <textarea class="form-control"
                                                          v-model="form.glossary_agent_custom_prompt"
                                                          @input="saveSetting('glossary_agent_custom_prompt', form.glossary_agent_custom_prompt)"
                                                          rows="3"
                                                          :placeholder="t('glossaryCustomPromptPlaceholder')"></textarea>
                                            </div>
                                            <div class="mb-3">
                                                <label class="form-label">{{ t('glossaryGenConfigLabel') }}</label>
                                                <div class="btn-group w-100">
                                                    <input type="radio" class="btn-check" value="same" id="gSame"
                                                           v-model="form.glossary_agent_config_choice"
                                                           @change="saveSetting('glossary_agent_config_choice', 'same')">
                                                    <label class="btn btn-outline-primary"
                                                           for="gSame">{{ t('glossaryGenConfigSame') }}</label>
                                                    <input type="radio" class="btn-check" value="custom" id="gCustom"
                                                           v-model="form.glossary_agent_config_choice"
                                                           @change="saveSetting('glossary_agent_config_choice', 'custom')">
                                                    <label class="btn btn-outline-primary"
                                                           for="gCustom">{{ t('glossaryGenConfigCustom') }}</label>
                                                </div>
                                            </div>

                                            <div v-if="form.glossary_agent_config_choice === 'custom'"
                                                 class="border p-3 rounded">
                                                <platform-selector
                                                        v-model:platform="form.glossary_agent_platform"
                                                        v-model:base-url="form.glossary_agent_baseurl"
                                                        v-model:api-key="form.glossary_agent_key"
                                                        v-model:model-id="form.glossary_agent_model_id"
                                                        v-model:provider="form.glossary_agent_provider"
                                                        :t="t" prefix="glossary_agent_platform"></platform-selector>

                                                <div class="mb-3">
                                                    <label class="form-label">{{ t('targetLanguageLabel') }}</label>
                                                    <select class="form-select" v-model="form.glossary_agent_to_lang"
                                                            @change="saveSetting('glossary_agent_to_lang', form.glossary_agent_to_lang)">
                                                        <option value="Simplified Chinese">中文(简体中文)</option>
                                                        <option value="English">英文(English)</option>
                                                        <option value="Spanish">西班牙文(Español)</option>
                                                        <option value="French">法文(Français)</option>
                                                        <option value="German">德文(Deutsch)</option>
                                                        <option value="Japanese">日文(日本語)</option>
                                                        <option value="Korean">韩文(한국어)</option>
                                                        <option value="Russian">俄文(Русский)</option>
                                                        <option value="Portuguese">葡萄牙文(Português)</option>
                                                        <option value="Arabic">阿拉伯文(العَرَبِيَّة)</option>
                                                        <option value="Vietnamese">越南文(tiếng Việt)</option>
                                                        <option value="custom">{{ t('targetLanguageCustom') }}</option>
                                                    </select>
                                                    <div class="mt-2" v-if="form.glossary_agent_to_lang === 'custom'">
                                                        <input type="text" class="form-control"
                                                               v-model="form.glossary_agent_custom_to_lang"
                                                               @input="saveSetting('glossary_agent_custom_to_lang', form.glossary_agent_custom_to_lang)"
                                                               :placeholder="t('customLangPlaceholder')">
                                                    </div>
                                                </div>

                                                <slider-control :label="t('chunkSizeLabel')"
                                                                v-model="form.glossary_agent_chunk_size"
                                                                save-key="glossary_agent_chunk_size"
                                                                :default-val="defaultParams.chunk_size" :min="1000"
                                                                :max="8000" :step="100" :t="t"></slider-control>
                                                <slider-control :label="t('concurrentLabel')"
                                                                v-model="form.glossary_agent_concurrent"
                                                                save-key="glossary_agent_concurrent"
                                                                :default-val="defaultParams.concurrent" :min="1"
                                                                :max="120" :step="1" :t="t"></slider-control>
                                                <slider-control label="Temperature"
                                                                v-model="form.glossary_agent_temperature"
                                                                save-key="glossary_agent_temperature" :default-val="0.7"
                                                                :min="0" :max="2" :step="0.1" :t="t"></slider-control>
                                                <slider-control :label="t('retryLabel')"
                                                                v-model="form.glossary_agent_retry"
                                                                save-key="glossary_agent_retry"
                                                                :default-val="defaultParams.retry" :min="1" :max="6"
                                                                :step="1" :t="t"></slider-control>

                                                <!-- Glossary Agent RPM/TPM [Vertical Layout] -->
                                                <div class="mb-3">
                                                    <label class="form-label">RPM <small
                                                            class="text-muted">({{ t('rpmLabel')
                                                        }})</small></label>
                                                    <input type="number" class="form-control"
                                                           v-model="form.glossary_agent_rpm"
                                                           @input="saveSetting('glossary_agent_rpm', form.glossary_agent_rpm)"
                                                           min="1" :placeholder="t('unlimitedPlaceholder')">
                                                </div>
                                                <div class="mb-3">
                                                    <label class="form-label">TPM <small
                                                            class="text-muted">({{ t('tpmLabel')
                                                        }})</small></label>
                                                    <input type="number" class="form-control"
                                                           v-model="form.glossary_agent_tpm"
                                                           @input="saveSetting('glossary_agent_tpm', form.glossary_agent_tpm)"
                                                           min="1" :placeholder="t('unlimitedPlaceholder')">
                                                </div>

                                                <div class="mb-3">
                                                    <label class="form-label">{{ t('thinkingModeLabel') }}</label>
                                                    <div class="btn-group w-100">
                                                        <input type="radio" class="btn-check" value="enable"
                                                               id="gtEnable" v-model="form.glossary_agent_thinking"
                                                               @change="saveSetting('glossary_agent_thinking_mode', 'enable')">
                                                        <label class="btn btn-outline-primary"
                                                               for="gtEnable">{{ t('thinkingModeEnable') }}</label>
                                                        <input type="radio" class="btn-check" value="disable"
                                                               id="gtDisable" v-model="form.glossary_agent_thinking"
                                                               @change="saveSetting('glossary_agent_thinking_mode', 'disable')">
                                                        <label class="btn btn-outline-primary"
                                                               for="gtDisable">{{ t('thinkingModeDisable') }}</label>
                                                        <input type="radio" class="btn-check" value="default"
                                                               id="gtDefault" v-model="form.glossary_agent_thinking"
                                                               @change="saveSetting('glossary_agent_thinking_mode', 'default')">
                                                        <label class="btn btn-outline-primary"
                                                               for="gtDefault">{{ t('thinkingModeDefault') }}</label>
                                                    </div>
                                                </div>
                                                <div class="form-check form-switch mb-3">
                                                    <input class="form-check-input" type="checkbox" role="switch"
                                                           v-model="form.glossary_agent_system_proxy_enable"
                                                           @change="saveSetting('glossary_agent_system_proxy_enable', form.glossary_agent_system_proxy_enable)">
                                                    <label class="form-check-label">{{ t('systemProxyLabel') }}</label>
                                                </div>
                                                <div class="form-check form-switch mb-3">
                                                    <input class="form-check-input" type="checkbox" role="switch"
                                                           v-model="form.glossary_agent_force_json"
                                                           @change="saveSetting('glossary_agent_force_json', form.glossary_agent_force_json)">
                                                    <label class="form-check-label">{{ t('forceJson') }}</label>
                                                </div>
                                            </div>
                                        </div>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </form>

                    <!-- Import/Export -->
                    <div class="d-flex justify-content-center gap-2 mt-4">
                        <button type="button" class="btn btn-outline-primary" @click="configFile.click()"><i
                                class="bi bi-box-arrow-in-down me-1"></i><span>{{ t('importConfigBtn') }}</span>
                        </button>
                        <button type="button" class="btn btn-outline-secondary" @click="exportConfig"><i
                                class="bi bi-box-arrow-up me-1"></i><span>{{ t('exportConfigBtn') }}</span></button>
                    </div>
                    <input type="file" ref="configFile" class="d-none" accept=".json" @change="importConfig">

                    <!-- Project Info -->
                    <div class="mt-4 text-center text-muted small project-info">
                        <p class="bi bi-github mb-2"> GitHub主页(欢迎star❤): <br/><a
                                href="https://github.com/xunbu/docutranslate" target="_blank">https://github.com/xunbu/docutranslate</a>
                        </p>
                        <p class="bi bi-tencent-qq mb-2"> 交流QQ群: 1047781902 </p>
                        <p class="bi mb-0">version:<span>{{ version ? 'v' + version : '' }}</span></p>
                    </div>
                </div>
            </div>

            <!-- Right: Task Area -->
            <div class="col-lg-8">
                <div class="task-area">
                    <div class="d-flex justify-content-between align-items-center mb-3">
                        <h4 class="mb-0"><i class="bi bi-list-task me-2"></i><span>{{ t('taskListTitle') }}</span></h4>
                        <button class="btn btn-primary" @click="createNewTask()"><i
                                class="bi bi-plus-circle-fill me-2"></i><span>{{ t('newTaskBtn') }}</span></button>
                    </div>
                    <div id="task-container">
                        <div v-if="tasks.length === 0" class="text-center text-muted mt-5">
                            <img src="/static/favicon.ico" alt="LOGO" style="width:10%;min-width: 55px; height: auto;">
                            <p class="mt-3">{{ t('noTaskPlaceholder') }}</p>
                        </div>
                        <div v-for="task in tasks" :key="task.uiId" class="card mb-3 task-card">
                            <div class="card-header d-flex justify-content-between align-items-center">
                                <span class="fw-bold"><span>{{ t('taskCardIdLabel')
                                    }}</span>: <code>{{ task.backendId || t('taskCardIdPlaceholder') }}</code></span>
                                <button type="button" class="btn-close" @click="removeTask(task)"></button>
                            </div>
                            <div class="card-body">
                                <div class="row">
                                    <div class="col-md-5">
                                        <input type="file" class="d-none" :id="'fileInput-' + task.uiId"
                                               @change="handleTaskFileSelect($event, task)">
                                        <div class="file-drop-area"
                                             :class="{'drag-over': task.isDragOver, 'file-selected': !!task.file, 'input-error': task.validationError}"
                                             @click="triggerFileInput(task.uiId)"
                                             @dragenter.prevent="task.isDragOver = true"
                                             @dragover.prevent="task.isDragOver = true"
                                             @dragleave.prevent="task.isDragOver = false"
                                             @drop.prevent="handleTaskFileDrop($event, task)">
                                            <div v-if="!task.file" class="file-drop-default">
                                                <i class="bi bi-cloud-arrow-up fs-1"></i>
                                                <p class="mb-0">{{ t('taskCardFileDrop') }}</p>
                                            </div>
                                            <div v-else class="file-drop-selected">
                                                <i class="bi bi-check-circle-fill fs-1 text-success"></i>
                                                <p class="mb-0 mt-2 fw-bold text-success">{{ t('taskCardFileSelected')
                                                    }}</p>
                                            </div>
                                        </div>
                                        <div class="mt-2" v-if="task.file || task.fileName">
                                            <span class="fw-bold">{{ t('taskCardFilenameLabel') }} </span><span
                                                class="text-success">{{ task.fileName || task.file.name }}</span>
                                        </div>
                                    </div>
                                    <div class="col-md-7">
                                        <h6><i class="bi bi-terminal me-2"></i><span>{{ t('taskCardLogLabel') }}</span>
                                        </h6>
                                        <div class="log-area-wrapper">
                                            <div class="log-area" v-html="task.logs" :id="'log-' + task.uiId"></div>
                                            <button type="button" class="btn btn-sm btn-outline-secondary copy-log-btn"
                                                    @click="copyLog($event, task.logs)" data-bs-toggle="tooltip"
                                                    :title="t('copyLogsTooltip')">
                                                <i class="bi bi-clipboard"></i>
                                            </button>
                                        </div>
                                        <div class="mt-2">
                                            <span class="small"
                                                  :class="task.statusClass">{{ task.statusMessage || t('taskCardStatusWaiting')
                                                }}</span>
                                            <div v-if="task.isProcessing" class="spinner-border spinner-border-sm ms-2"
                                                 role="status"></div>
                                        </div>
                                    </div>
                                </div>
                            </div>
                            <div class="card-footer d-flex justify-content-between align-items-center">
                                <div class="download-buttons" v-if="task.downloads">
                                    <button class="btn btn-sm btn-success me-1" v-if="task.downloads.html"
                                            @click="openPreview(task)"><i
                                            class="bi bi-eye-fill me-1"></i><span>{{ t('taskCardPreviewBtn') }}</span>
                                    </button>
                                    <div class="btn-group me-1">
                                        <button type="button" class="btn btn-sm btn-secondary dropdown-toggle"
                                                data-bs-toggle="dropdown"><i
                                                class="bi bi-download me-1"></i><span>{{ t('taskCardDownloadBtn')
                                            }}</span></button>
                                        <ul class="dropdown-menu">
                                            <li v-for="(link, key) in task.downloads" :key="key">
                                                <a class="dropdown-item" :href="link"><i class="bi me-2"
                                                                                         :class="getFileIcon(key)"></i>{{ key === 'markdown_zip' ? t('downloadMdZip') : (key === 'markdown' ? t('downloadMdEmbedded') : key.toUpperCase())
                                                    }}</a>
                                            </li>
                                            <li v-if="task.downloads.html">
                                                <a class="dropdown-item" href="#"
                                                   @click.prevent="printPdf(task.downloads.html)"><i
                                                        class="bi bi-file-earmark-pdf me-2"></i>PDF</a>
                                            </li>
                                        </ul>
                                    </div>
                                    <div class="btn-group"
                                         v-if="task.attachment && Object.keys(task.attachment).length">
                                        <button type="button" class="btn btn-sm btn-info dropdown-toggle"
                                                data-bs-toggle="dropdown"><i
                                                class="bi bi-paperclip me-1"></i><span>{{ t('taskCardAttachmentBtn')
                                            }}</span></button>
                                        <ul class="dropdown-menu">
                                            <li v-for="(link, name) in task.attachment" :key="name"><a
                                                    class="dropdown-item" :href="link"><i
                                                    class="bi bi-file-earmark-arrow-down me-2"></i>{{ name }}</a></li>
                                        </ul>
                                    </div>
                                </div>
                                <button class="btn ms-auto" :class="task.isTranslating ? 'btn-danger' : 'btn-primary'"
                                        @click="toggleTaskState(task)" :disabled="task.initializing">
                                    <span v-if="task.initializing"><span
                                            class="spinner-border spinner-border-sm"></span> {{ t('btn_initializing') }}</span>
                                    <span v-else-if="task.isTranslating"><i
                                            class="bi bi-stop-circle-fill me-1"></i>{{ t('btn_cancelTranslation')
                                        }}</span>
                                    <span v-else-if="task.isFinished"><i
                                            class="bi bi-arrow-clockwise me-1"></i>{{ t('btn_reTranslate') }}</span>
                                    <span v-else><i class="bi bi-play-fill me-1"></i>{{ t('taskCardStartBtn') }}</span>
                                </button>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <!-- Modals -->
    <div class="modal fade" id="glossaryModal" tabindex="-1">
        <div class="modal-dialog modal-dialog-centered modal-dialog-scrollable">
            <div class="modal-content">
                <div class="modal-header"><h5 class="modal-title">{{ t('glossaryModalTitle') }}</h5>
                    <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
                </div>
                <div class="modal-body">
                    <table class="table table-striped table-hover">
                        <thead>
                        <tr>
                            <th>{{ t('glossaryTableSource') }}</th>
                            <th>{{ t('glossaryTableDestination') }}</th>
                        </tr>
                        </thead>
                        <tbody>
                        <tr v-for="(dst, src) in glossaryData" :key="src">
                            <td>{{ src }}</td>
                            <td>{{ dst }}</td>
                        </tr>
                        <tr v-if="Object.keys(glossaryData).length === 0">
                            <td colspan="2" class="text-center text-muted">{{ t('glossaryEmpty') }}</td>
                        </tr>
                        </tbody>
                    </table>
                </div>
                <div class="modal-footer">
                    <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{{ t('closeBtn') }}</button>
                </div>
            </div>
        </div>
    </div>

    <div class="modal fade" id="tutorialModal" tabindex="-1">
        <div class="modal-dialog modal-lg modal-dialog-centered modal-dialog-scrollable">
            <div class="modal-content">
                <div class="modal-header"><h5 class="modal-title"><i
                        class="bi bi-book-half me-2"></i>{{ t('tutorialModalTitle') }}</h5>
                    <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
                </div>
                <div class="modal-body" v-html="t('tutorialModalBody')"></div>
                <div class="modal-footer">
                    <button type="button" class="btn btn-primary" data-bs-dismiss="modal">{{ t('tutorialUnderstandBtn')
                        }}
                    </button>
                </div>
            </div>
        </div>
    </div>

    <div class="modal fade" id="contributorsModal" tabindex="-1">
        <div class="modal-dialog modal-dialog-centered">
            <div class="modal-content">
                <div class="modal-header"><h5 class="modal-title"><i
                        class="bi bi-heart-fill me-2 text-danger"></i>{{ t('contributorsModalTitle') }}</h5>
                    <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
                </div>
                <div class="modal-body">
                    <p>{{ t('contributorsPara1') }}</p>
                    <p>{{ t('contributorsPara2') }}</p>
                    <div class="alert alert-success mt-4" role="alert">
                        <p>{{ t('contributorsWelcome') }}</p>
                        <hr>
                        <p class="mb-0">
                            <a href="https://github.com/xunbu/docutranslate" target="_blank"
                               class="btn btn-info btn-sm ms-2"><i
                                    class="bi bi-github me-1"></i><span>{{ t('contributorsGithub') }}</span></a>
                            <a href="https://github.com/xunbu/docutranslate/pulls" target="_blank"
                               class="btn btn-success btn-sm ms-2"><i
                                    class="bi bi-git me-1"></i><span>{{ t('contributorsPR') }}</span></a>
                            <a href="https://github.com/xunbu/docutranslate/issues" target="_blank"
                               class="btn btn-warning btn-sm ms-2"><i
                                    class="bi bi-bug-fill me-1"></i><span>{{ t('contributorsIssue') }}</span></a>
                        </p>
                        <hr>
                        <p>{{ t('contributorsQQ') }}</p>
                    </div>
                </div>
                <div class="modal-footer">
                    <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{{ t('closeBtn') }}</button>
                </div>
            </div>
        </div>
    </div>

    <!-- Preview Offcanvas -->
    <div class="offcanvas offcanvas-end" tabindex="-1" id="previewOffcanvas" ref="previewOffcanvas">
        <div class="offcanvas-header border-bottom">
            <h5 class="offcanvas-title">
                {{ previewMode === 'bilingual' ? t('preview_bilingual') : t('preview_translatedOnly') }}</h5>
            <div class="btn-group me-auto ms-4">
                <button class="btn btn-sm" :class="previewMode === 'bilingual' ? 'btn-primary' : 'btn-outline-primary'"
                        @click="setPreviewMode('bilingual')">{{ t('previewBilingualBtn') }}
                </button>
                <button class="btn btn-sm"
                        :class="previewMode === 'translatedOnly' ? 'btn-primary' : 'btn-outline-primary'"
                        @click="setPreviewMode('translatedOnly')">{{ t('previewTranslatedOnlyBtn') }}
                </button>
            </div>
            <button class="btn btn-sm btn-outline-secondary ms-2"
                    :class="{active: syncScrollEnabled, 'btn-primary': syncScrollEnabled}" @click="toggleSyncScroll"
                    :title="t('syncScrollTooltip')"><i class="bi"
                                                       :class="syncScrollEnabled ? 'bi-link' : 'bi-link-45deg'"></i>
            </button>

            <!-- New Download Dropdown in Preview Header -->
            <div class="btn-group ms-2" v-if="previewTask && previewTask.downloads">
                <button type="button" class="btn btn-sm btn-outline-primary dropdown-toggle" data-bs-toggle="dropdown">
                    <i class="bi bi-download me-1"></i><span>{{ t('taskCardDownloadBtn') }}</span>
                </button>
                <ul class="dropdown-menu dropdown-menu-end">
                    <li v-for="(link, key) in previewTask.downloads" :key="key">
                        <a class="dropdown-item" :href="link">
                            <i class="bi me-2" :class="getFileIcon(key)"></i>
                            {{ key === 'markdown_zip' ? t('downloadMdZip') : (key === 'markdown' ? t('downloadMdEmbedded') : key.toUpperCase())
                            }}
                        </a>
                    </li>
                    <li v-if="previewTask.downloads.html">
                        <a class="dropdown-item" href="#" @click.prevent="printPdf(previewTask.downloads.html)">
                            <i class="bi bi-file-earmark-pdf me-2"></i>PDF
                        </a>
                    </li>
                </ul>
            </div>

            <button type="button" class="btn-close ms-2" data-bs-dismiss="offcanvas"></button>
        </div>
        <div class="offcanvas-body d-flex flex-column p-2">
            <div class="preview-split-container flex-grow-1" ref="splitContainer">
                <div id="originalPreviewContainer" class="preview-pane-wrapper" v-show="previewMode === 'bilingual'">
                    <h6 class="text-center text-muted small">{{ t('previewOriginal') }}</h6>
                    <div class="preview-pane" ref="originalPane"></div>
                </div>
                <div id="translatedPreviewContainer" class="preview-pane-wrapper">
                    <h6 class="text-center text-muted small">{{ t('previewTranslated') }}</h6>
                    <div class="preview-pane">
                        <iframe ref="translatedFrame" src="about:blank"></iframe>
                    </div>
                </div>
            </div>
        </div>
    </div>
    <iframe id="printFrame" ref="printFrame" style="display: none;"></iframe>

    <!-- Controls -->
    <div class="bottom-left-controls">
        <div class="dropdown">
            <button class="btn btn-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown"><i
                    class="bi bi-translate"></i></button>
            <ul class="dropdown-menu">
                <li><a class="dropdown-item" :class="{active: currentLang==='zh'}" href="#"
                       @click.prevent="setLang('zh')">中文</a></li>
                <li><a class="dropdown-item" :class="{active: currentLang==='en'}" href="#"
                       @click.prevent="setLang('en')">English</a></li>
            </ul>
        </div>
        <div class="dropdown">
            <button class="btn btn-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown"><i
                    class="bi bi-circle-half"></i></button>
            <ul class="dropdown-menu">
                <li>
                    <button class="dropdown-item" @click="setTheme('light')"><i class="bi bi-sun-fill me-2"></i> Light
                    </button>
                </li>
                <li>
                    <button class="dropdown-item" @click="setTheme('dark')"><i class="bi bi-moon-stars-fill me-2"></i>
                        Dark
                    </button>
                </li>
                <li>
                    <button class="dropdown-item" @click="setTheme('auto')"><i class="bi bi-circle-half me-2"></i> Auto
                    </button>
                </li>
            </ul>
        </div>
    </div>
</div>

<script src="/static/bootstrap.bundle.min.js"></script>
<script src="/static/vue.global.prod.js"></script>
<script src="/static/split.min.js"></script>
<script src="/static/papaparse.min.js"></script>

<script>
    const {createApp, ref, reactive, computed, watch, onMounted, nextTick} = Vue;

    const SliderControl = {
        props: ['label', 'modelValue', 'min', 'max', 'step', 'defaultVal', 't', 'saveKey'],
        template: `
          <div class="mb-3">
            <label class="form-label d-flex justify-content-between">
              <span>{{ label }}: <span>{{ modelValue }}</span></span>
              <button type="button" class="btn btn-sm btn-outline-secondary py-0 px-1 slider-reset-btn"
                      :style="{visibility: modelValue != defaultVal ? 'visible':'hidden'}" @click="reset">
                {{ t('resetBtn') }}
              </button>
            </label>
            <input type="range" class="form-range" :min="min" :max="max" :step="step" :value="modelValue"
                   @input="update">
          </div>`,
        setup(props, {emit}) {
            const update = (e) => {
                const val = Number(e.target.value);
                emit('update:modelValue', val);
                if (props.saveKey) localStorage.setItem(props.saveKey, val);
            };
            const reset = () => {
                emit('update:modelValue', props.defaultVal);
                if (props.saveKey) localStorage.setItem(props.saveKey, props.defaultVal);
            };
            return {update, reset};
        }
    };

    const KNOWN_PLATFORMS = [
        {val: "custom", label: "platformCustom", provider: "default"},
        {val: "https://api.302.ai/v1", label: "302.AI", provider: ""},
        {val: "https://api.openai.com/v1", label: "OpenAI", provider: "default"},
        {
            val: "https://generativelanguage.googleapis.com/v1beta/openai/",
            label: "Gemini",
            provider: "generativelanguage.googleapis.com"
        },
        {val: "https://api.deepseek.com/v1", label: "DeepSeek", provider: ""},
        {
            val: "https://dashscope.aliyuncs.com/compatible-mode/v1",
            label: "阿里云百炼(DashScope)",
            provider: "aliyuncs"
        },
        {
            val: "https://ark.cn-beijing.volces.com/api/v3",
            label: "火山引擎(volces)",
            provider: "volces"
        },
        {val: "https://api.siliconflow.cn/v1", label: "硅基流动(siliconflow CN)", provider: "siliconflow"},
        {val: "https://open.bigmodel.cn/api/paas/v4", label: "智谱AI(bigmodel CN)", provider: "bigmodel"},
        {val: "https://www.dmxapi.cn/v1", label: "DMXAPI_CN", provider: ""},
        {val: "https://www.dmxapi.com/v1", label: "DMXAPI_GLOBAL", provider: ""},
        {val: "https://ai.juguang.chat/v1", label: "聚光AI(juguang CN)", provider: ""},
        {val: "https://openrouter.ai/api/v1", label: "OpenRouter", provider: ""},
        {val: "http://127.0.0.1:1234/v1", label: "LM Studio", provider: ""},
        {val: "http://127.0.0.1:11434/v1", label: "Ollama", provider: "ollama"}
    ];

    const PlatformSelector = {
        props: ['platform', 'baseUrl', 'apiKey', 'modelId', 'provider', 't', 'prefix', 'invalidApiKey', 'invalidBaseUrl', 'invalidModelId'],
        template: `
          <div>
            <div class="mb-2">
              <label class="form-label">{{ t('platformLabel') }}</label>
              <select class="form-select" :value="platform" @change="handlePlatformChange($event.target.value)">
                <option v-for="p in platforms" :value="p.val">{{ p.val === 'custom' ? t(p.label) : p.label }}</option>
              </select>
            </div>

            <div class="mb-3" v-if="platform === 'custom'">
              <label class="form-label">{{ t('providerLabel') }}</label>
              <select class="form-select" :value="provider" @change="handleProviderChange($event.target.value)">
                <option v-for="prov in providers" :value="prov">{{ prov }}</option>
              </select>
            </div>

            <div class="form-text mb-3">Base URL: <code ref="baseUrlDisplay">{{ baseUrl }}</code></div>
            <div class="mb-3" v-if="platform === 'custom'">
              <label class="form-label">{{ t('baseUrlLabel') }}</label>
              <input type="url" class="form-control" :class="{'is-invalid': invalidBaseUrl}"
                     :value="baseUrl" @input="handleBaseUrlChange($event.target.value)" required
                     placeholder="OpenAi Compatible URL">
            </div>
            <div class="mb-3">
              <label class="form-label">API Key <a v-if="apiHref" :href="apiHref[0]" target="_blank" class="ms-1"><i
                  class="bi bi-box-arrow-up-right"></i></a> <span
                  class="ms-2 text-muted small">{{ apiHref && apiHref[1] ? t(apiHref[1]) : '' }}</span></label>
              <div class="input-group">
                <input :type="showPass?'text':'password'" class="form-control" :class="{'is-invalid': invalidApiKey}"
                       :value="apiKey" @input="handleApiKeyChange($event.target.value)"
                       :placeholder="t('apiKeyPlaceholder')">
                <button class="btn btn-outline-secondary" type="button" @click="showPass=!showPass"><i class="bi"
                                                                                                       :class="showPass?'bi-eye':'bi-eye-slash'"></i>
                </button>
              </div>
            </div>
            <div class="mb-3">
              <label class="form-label">{{ t('modelIdLabel') }}</label>
              <input type="text" class="form-control" :class="{'is-invalid': invalidModelId}"
                     :value="modelId" @input="handleModelChange($event.target.value)" required
                     :placeholder="t('modelIdPlaceholder')">
            </div>
          </div>`,
        setup(props, {emit}) {
            const showPass = ref(false);
            const platforms = KNOWN_PLATFORMS;

            // ProviderType Literal values
            const providers = [
                "default", "ollama", "bigmodel", "aliyuncs", "volces", "google", "siliconflow"
            ];

            const apiHrefMap = {
                "https://api.302.ai/v1": ["https://share.302.ai/BgRLAe", "apiHrefInfo302ai"],
                "https://openrouter.ai/api/v1": ["https://openrouter.ai/settings/keys", null],
                "https://api.openai.com/v1": ["https://platform.openai.com/api-keys", null],
                "https://api.deepseek.com/v1": ["https://platform.deepseek.com/api_keys", null],
                "https://open.bigmodel.cn/api/paas/v4": ["https://open.bigmodel.cn/usercenter/apikeys", null],
                "https://dashscope.aliyuncs.com/compatible-mode/v1": ["https://bailian.console.aliyun.com/?tab=model#/api-key", null],
                "https://ark.cn-beijing.volces.com/api/v3": ["https://console.volcengine.com/ark/region:ark+cn-beijing/apiKey?apikey=%7B%7D", null],
                "https://api.siliconflow.cn/v1": ["https://cloud.siliconflow.cn/account/ak", null],
                "https://ai.juguang.chat/v1": ["https://ai.juguang.chat/console/token", null],
                "https://www.dmxapi.cn/v1": ["https://www.dmxapi.cn/token", null],
                "https://www.dmxapi.com/v1": ["https://www.dmxapi.com/console/token", null],
                "https://generativelanguage.googleapis.com/v1beta/openai/": ["https://aistudio.google.com/u/0/apikey", null]
            };
            const apiHref = computed(() => apiHrefMap[props.baseUrl]);

            const save = (key, val) => localStorage.setItem(key, val);
            const handlePlatformChange = (val) => {
                emit('update:platform', val);
                save(`${props.prefix}_last_platform`, val);

                // Determine provider based on platform
                const selected = platforms.find(p => p.val === val);
                if (val === 'custom') {
                    emit('update:provider', 'default');
                } else if (selected) {
                    emit('update:provider', selected.provider);
                }
            };
            const handleBaseUrlChange = (val) => {
                emit('update:baseUrl', val);
                emit('clearError', 'base_url');
                if (props.platform === 'custom') save(`${props.prefix}_custom_base_url`, val);
            };
            const handleApiKeyChange = (val) => {
                emit('update:apiKey', val);
                emit('clearError', 'api_key');
                save(`${props.prefix}_${props.platform}_apikey`, val);
            };
            const handleModelChange = (val) => {
                emit('update:modelId', val);
                emit('clearError', 'model_id');
                save(`${props.prefix}_${props.platform}_model_id`, val);
            };
            const handleProviderChange = (val) => {
                emit('update:provider', val);
                save(`${props.prefix}_${props.platform}_provider`, val);
            };

            return {
                showPass,
                platforms,
                providers,
                apiHref,
                handlePlatformChange,
                handleBaseUrlChange,
                handleApiKeyChange,
                handleModelChange,
                handleProviderChange
            };
        }
    };

    const mineruLangOptions = [
        {val: "ch", label: "ch"},
        {val: "ch_server", label: "ch_server"},
        {val: "ch_lite", label: "ch_lite"},
        {val: "en", label: "en"},
        {val: "korean", label: "korean"},
        {val: "japan", label: "japan"},
        {val: "chinese_cht", label: "chinese_cht"},
        {val: "ta", label: "ta"},
        {val: "te", label: "te"},
        {val: "ka", label: "ka"},
        {val: "th", label: "th"},
        {val: "el", label: "el"},
        {val: "latin", label: "latin"},
        {val: "arabic", label: "arabic"},
        {val: "east_slavic", label: "east_slavic"},
        {val: "cyrillic", label: "cyrillic"},
        {val: "devanagari", label: "devanagari"}
    ];

    createApp({
        components: {SliderControl, PlatformSelector},
        setup() {
            const version = ref("");
            const currentLang = ref(localStorage.getItem('ui_language') || 'zh');
            const i18nData = ref({});
            const glossaryData = ref({});
            const tasks = ref([]);
            const enginList = ref([]);
            const defaultParams = reactive({});

            // Refs for DOM elements
            const glossaryInput = ref(null);
            const configFile = ref(null);
            const previewOffcanvas = ref(null);

            // UI State
            const showMineruToken = ref(false);
            const previewMode = ref('bilingual');
            const syncScrollEnabled = ref(localStorage.getItem('ui_sync_scroll_enabled') === 'true');
            const showIdentityOption = ref(true);

            // Validation State
            const errors = reactive({
                model_id: false,
                api_key: false,
                base_url: false,
                mineru_token: false,
                mineru_deploy_base_url: false,
                custom_to_lang: false,
                json_paths: false
            });

            const clearError = (field) => {
                if (errors[field]) errors[field] = false;
            };

            // Form Data
            const form = reactive({
                workflow_type: 'markdown_based',
                auto_workflow_enabled: true,
                convert_engine: 'mineru',
                mineru_token: '',
                model_version: 'vlm',
                mineru_deploy_base_url: 'http://127.0.0.1:8000',
                mineru_deploy_backend: 'pipeline',
                mineru_deploy_start_page: 0,
                mineru_deploy_end_page: 99999,
                mineru_deploy_formula_enable: true,
                mineru_deploy_lang_list: [],
                mineru_deploy_server_url: '',
                formula_ocr: true,
                code_ocr: true,
                skip_translate: false,
                platform: 'https://api.302.ai/v1',
                base_url: '',
                api_key: '',
                model_id: '',
                provider: 'api.openai.com', // Default provider
                system_proxy_enable: false,
                force_json: false,
                to_lang: 'Simplified Chinese',
                custom_to_lang: '',
                thinking: 'disable',
                custom_prompt: '',
                chunk_size: 1000,
                concurrent: 5,
                temperature: 0.1,
                retry: 3,
                rpm: null, // New RPM
                tpm: null, // New TPM
                glossary_generate_enable: false,
                glossary_agent_custom_prompt: '',
                glossary_agent_config_choice: 'same',
                glossary_agent_platform: 'https://api.302.ai/v1',
                glossary_agent_baseurl: '',
                glossary_agent_key: '',
                glossary_agent_model_id: '',
                glossary_agent_provider: 'api.openai.com', // Default glossary provider
                glossary_agent_to_lang: 'Simplified Chinese',
                glossary_agent_custom_to_lang: '',
                glossary_agent_chunk_size: 1000,
                glossary_agent_concurrent: 5,
                glossary_agent_temperature: 0.7,
                glossary_agent_retry: 3,
                glossary_agent_thinking: 'default',
                glossary_agent_system_proxy_enable: false,
                glossary_agent_force_json: false,
                glossary_agent_rpm: null, // New Glossary Agent RPM
                glossary_agent_tpm: null  // New Glossary Agent TPM
            });

            // Nested Params for specific workflows
            const workflowParams = reactive({
                txt: {insert_mode: 'replace', separator: '\\n', segment_mode: 'line'},
                xlsx: {insert_mode: 'replace', separator: '\\n', translate_regions: ''},
                docx: {insert_mode: 'replace', separator: ''},
                srt: {insert_mode: 'replace', separator: '\\n'},
                epub: {insert_mode: 'replace', separator: ''},
                html: {insert_mode: 'replace', separator: ''},
                ass: {insert_mode: 'replace', separator: '\\N'},
                json: {json_paths: ''},
                pptx: {insert_mode: 'replace', separator: '\\n'}
            });

            // Load config from localStorage
            const loadConfig = () => {
                const get = (k, def) => localStorage.getItem(k) || def;
                const getBool = (k, def) => {
                    const v = localStorage.getItem(k);
                    return v === null ? def : v === 'true';
                };
                const getNum = (k, def) => {
                    const v = localStorage.getItem(k);
                    return v ? Number(v) : def;
                };
                const getNumOrNull = (k) => {
                    const v = localStorage.getItem(k);
                    return (v === null || v === '' || v === 'null') ? null : Number(v);
                };

                form.workflow_type = get('translator_last_workflow', 'markdown_based');
                form.auto_workflow_enabled = getBool('translator_auto_workflow_enabled', true);
                form.convert_engine = get('translator_convert_engin', 'mineru');
                form.mineru_token = get('translator_mineru_token', '');
                form.model_version = get('translator_model_version', 'vlm');
                form.mineru_deploy_base_url = get('mineru_deploy_base_url', 'http://127.0.0.1:8000');
                form.mineru_deploy_backend = get('mineru_deploy_backend', 'pipeline');
                form.mineru_deploy_start_page = getNum('mineru_deploy_start_page', 0);
                form.mineru_deploy_end_page = getNum('mineru_deploy_end_page', 99999);
                form.mineru_deploy_formula_enable = getBool('mineru_deploy_formula_enable', true);
                form.mineru_deploy_server_url = get('mineru_deploy_server_url', '');
                const savedLangList = localStorage.getItem('mineru_deploy_lang_list');
                form.mineru_deploy_lang_list = savedLangList ? JSON.parse(savedLangList) : [];

                form.formula_ocr = getBool('translator_formula_ocr', true);
                form.code_ocr = getBool('translator_code_ocr', true);
                form.skip_translate = getBool('translator_skip_translate', false);
                form.platform = get('translator_platform_last_platform', 'https://api.302.ai/v1');
                form.system_proxy_enable = getBool('translator_system_proxy_enable', false);
                form.force_json = getBool('translator_force_json', false);
                form.to_lang = get('translator_to_lang', 'Simplified Chinese');
                form.custom_to_lang = get('translator_custom_to_lang', '');
                form.thinking = get('translator_thinking_mode', 'disable');
                form.custom_prompt = get('custom_prompt', '');
                form.chunk_size = getNum('chunk_size', 1000);
                form.concurrent = getNum('concurrent', 5);
                form.temperature = getNum('temperature', 0.1);
                form.retry = getNum('retry', 3);
                form.rpm = getNumOrNull('rpm'); // Load RPM
                form.tpm = getNumOrNull('tpm'); // Load TPM

                // Determine Provider
                const platObj = KNOWN_PLATFORMS.find(p => p.val === form.platform);
                if (form.platform === 'custom') {
                    // 修正：读取组件实际保存的 Key (translator_platform_custom_provider)
                    form.provider = get('translator_platform_custom_provider', 'default');
                } else {
                    form.provider = platObj ? platObj.provider : '';
                }

                form.glossary_generate_enable = getBool('glossary_generate_enable', false);
                form.glossary_agent_custom_prompt = get('glossary_agent_custom_prompt', '');
                form.glossary_agent_config_choice = get('glossary_agent_config_choice', 'same');
                form.glossary_agent_platform = get('glossary_agent_platform_last_platform', 'https://api.302.ai/v1');
                form.glossary_agent_to_lang = get('glossary_agent_to_lang', 'Simplified Chinese');
                form.glossary_agent_custom_to_lang = get('glossary_agent_custom_to_lang', '');
                form.glossary_agent_chunk_size = getNum('glossary_agent_chunk_size', 1000);
                form.glossary_agent_concurrent = getNum('glossary_agent_concurrent', 5);
                form.glossary_agent_temperature = getNum('glossary_agent_temperature', 0.7);
                form.glossary_agent_retry = getNum('glossary_agent_retry', 3);
                form.glossary_agent_thinking = get('glossary_agent_thinking_mode', 'default');
                form.glossary_agent_system_proxy_enable = getBool('glossary_agent_system_proxy_enable', false);
                form.glossary_agent_force_json = getBool('glossary_agent_force_json', false);
                form.glossary_agent_rpm = getNumOrNull('glossary_agent_rpm'); // Load Glossary RPM
                form.glossary_agent_tpm = getNumOrNull('glossary_agent_tpm'); // Load Glossary TPM

                // Determine Glossary Provider
                const gPlatObj = KNOWN_PLATFORMS.find(p => p.val === form.glossary_agent_platform);
                if (form.glossary_agent_platform === 'custom') {
                    form.glossary_agent_provider = get('glossary_agent_platform_custom_provider', 'default');
                } else {
                    form.glossary_agent_provider = gPlatObj ? gPlatObj.provider : '';
                }

                // Restore workflow specific params
                ['txt', 'xlsx', 'docx', 'srt', 'epub', 'html', 'ass', 'pptx'].forEach(t => {
                    workflowParams[t].insert_mode = get(`translator_${t}_insert_mode`, 'replace');
                    if (workflowParams[t].separator !== undefined) workflowParams[t].separator = get(`translator_${t}_separator`, workflowParams[t].separator);
                });
                workflowParams.txt.segment_mode = get('translator_txt_segment_mode', 'line');
                workflowParams.xlsx.translate_regions = get('translator_xlsx_translate_regions', '');
                workflowParams.json.json_paths = get('translator_json_paths', '');

                // Trigger platform updates to load API keys/models
                updatePlatformParams(form.platform, 'translator_platform', form);
                updatePlatformParams(form.glossary_agent_platform, 'glossary_agent_platform', form, true);
            };

            // --- 新增：专门用于将当前 form 数据全部写入 localStorage 的函数 ---
            const saveAllSettings = () => {
                const s = (k, v) => localStorage.setItem(k, v); // 简写保存函数
                const f = form;

                // 1. 基础配置映射 (手动映射那些名字不一样的)
                s('translator_last_workflow', f.workflow_type);
                s('translator_auto_workflow_enabled', f.auto_workflow_enabled);
                s('translator_convert_engin', f.convert_engine);
                s('translator_mineru_token', f.mineru_token);
                s('translator_model_version', f.model_version);

                // Mineru Deploy 相关
                s('mineru_deploy_base_url', f.mineru_deploy_base_url);
                s('mineru_deploy_backend', f.mineru_deploy_backend);
                s('mineru_deploy_start_page', f.mineru_deploy_start_page);
                s('mineru_deploy_end_page', f.mineru_deploy_end_page);
                s('mineru_deploy_formula_enable', f.mineru_deploy_formula_enable);
                s('mineru_deploy_server_url', f.mineru_deploy_server_url);
                s('mineru_deploy_lang_list', JSON.stringify(f.mineru_deploy_lang_list));

                // 杂项
                s('translator_formula_ocr', f.formula_ocr);
                s('translator_code_ocr', f.code_ocr);
                s('translator_skip_translate', f.skip_translate);
                s('translator_platform_last_platform', f.platform);
                s('translator_system_proxy_enable', f.system_proxy_enable);
                s('translator_force_json', f.force_json);
                s('translator_to_lang', f.to_lang);
                s('translator_custom_to_lang', f.custom_to_lang);
                s('translator_thinking_mode', f.thinking);

                // 名字一样的直接存
                s('custom_prompt', f.custom_prompt);
                s('chunk_size', f.chunk_size);
                s('concurrent', f.concurrent);
                s('temperature', f.temperature);
                s('retry', f.retry);
                s('rpm', f.rpm || '');
                s('tpm', f.tpm || '');

                // 平台相关 (API Key 等)
                s(`translator_platform_${f.platform}_apikey`, f.api_key);
                s(`translator_platform_${f.platform}_model_id`, f.model_id);
                s('translator_provider', f.provider);
                if (f.platform === 'custom') s('translator_platform_custom_base_url', f.base_url);

                // 2. 术语表相关
                s('glossary_generate_enable', f.glossary_generate_enable);
                s('glossary_agent_custom_prompt', f.glossary_agent_custom_prompt);
                s('glossary_agent_config_choice', f.glossary_agent_config_choice);
                s('glossary_agent_platform_last_platform', f.glossary_agent_platform);
                s('glossary_agent_to_lang', f.glossary_agent_to_lang);
                s('glossary_agent_custom_to_lang', f.glossary_agent_custom_to_lang);
                s('glossary_agent_chunk_size', f.glossary_agent_chunk_size);
                s('glossary_agent_concurrent', f.glossary_agent_concurrent);
                s('glossary_agent_temperature', f.glossary_agent_temperature);
                s('glossary_agent_retry', f.glossary_agent_retry);
                s('glossary_agent_thinking_mode', f.glossary_agent_thinking);
                s('glossary_agent_system_proxy_enable', f.glossary_agent_system_proxy_enable);
                s('glossary_agent_force_json', f.glossary_agent_force_json);
                s('glossary_agent_rpm', f.glossary_agent_rpm || '');
                s('glossary_agent_tpm', f.glossary_agent_tpm || '');

                // 术语表平台 Key
                s(`glossary_agent_platform_${f.glossary_agent_platform}_apikey`, f.glossary_agent_key);
                s(`glossary_agent_platform_${f.glossary_agent_platform}_model_id`, f.glossary_agent_model_id);
                s('glossary_agent_provider', f.glossary_agent_provider);
                if (f.glossary_agent_platform === 'custom') s('glossary_agent_platform_custom_base_url', f.glossary_agent_baseurl);

                // 3. 自动循环保存所有具体工作流参数 (txt, docx, xlsx...)
                for (const [wfType, params] of Object.entries(workflowParams)) {
                    for (const [key, val] of Object.entries(params)) {
                        s(`translator_${wfType}_${key}`, val);
                    }
                }
            };

            const updatePlatformParams = (plat, prefix, target, isGlossary = false) => {
                const get = (k) => localStorage.getItem(k) || '';
                if (isGlossary) {
                    target.glossary_agent_key = get(`${prefix}_${plat}_apikey`);
                    target.glossary_agent_model_id = get(`${prefix}_${plat}_model_id`);
                    target.glossary_agent_baseurl = plat === 'custom' ? get(`${prefix}_custom_base_url`) : plat;
                } else {
                    target.api_key = get(`${prefix}_${plat}_apikey`);
                    target.model_id = get(`${prefix}_${plat}_model_id`);
                    target.base_url = plat === 'custom' ? get(`${prefix}_custom_base_url`) : plat;
                }
            };

            watch(() => form.platform, (n) => {
                updatePlatformParams(n, 'translator_platform', form);
            });

            watch(() => form.glossary_agent_platform, (n) => {
                updatePlatformParams(n, 'glossary_agent_platform', form, true);
            });

            const t = (k) => {
                const dict = i18nData.value[currentLang.value] || i18nData.value['zh'] || {};
                return dict[k] || k;
            };
            const capitalize = (s) => s.charAt(0).toUpperCase() + s.slice(1);
            const saveSetting = (k, v) => localStorage.setItem(k, v);
            const saveSettingArray = (k, v) => localStorage.setItem(k, JSON.stringify(v));

            const saveWorkflowParam = (keySuffix) => {
                const wf = form.workflow_type;
                localStorage.setItem(`translator_${wf}_${keySuffix}`, workflowParams[wf][keySuffix]);
            };

            const currentWorkflowConfig = computed(() => {
                const map = {
                    'txt': {
                        titleKey: 'txtSettingsTitleText',
                        icon: 'bi-filetype-txt',
                        hasInsertMode: true,
                        insertHelpKey: 'insertModeHelpTxt'
                    },
                    'docx': {
                        titleKey: 'docxSettingsTitleText',
                        icon: 'bi-file-earmark-word',
                        hasInsertMode: true,
                        insertHelpKey: 'insertModeHelpDocx'
                    },
                    'xlsx': {
                        titleKey: 'xlsxSettingsTitleText',
                        icon: 'bi-file-earmark-spreadsheet',
                        hasInsertMode: true,
                        insertHelpKey: 'insertModeHelpXlsx'
                    },
                    'srt': {
                        titleKey: 'srtSettingsTitleText',
                        icon: 'bi-file-text',
                        hasInsertMode: true,
                        insertHelpKey: 'insertModeHelpSrt'
                    },
                    'epub': {
                        titleKey: 'epubSettingsTitleText',
                        icon: 'bi-book',
                        hasInsertMode: true,
                        insertHelpKey: 'insertModeHelpEpub'
                    },
                    'html': {
                        titleKey: 'htmlSettingsTitleText',
                        icon: 'bi-filetype-html',
                        hasInsertMode: true,
                        insertHelpKey: 'insertModeHelpHtml'
                    },
                    'ass': {
                        titleKey: 'assSettingsTitleText',
                        icon: 'bi-file-easel',
                        hasInsertMode: true,
                        insertHelpKey: 'insertModeHelpAss',
                        separatorHelpKey: 'separatorHelpAss',
                        separatorPlaceholderKey: 'separatorPlaceholderAss'
                    },
                    'pptx': {
                        titleKey: 'pptxSettingsTitleText',
                        icon: 'bi-file-slides',
                        hasInsertMode: true,
                        insertHelpKey: 'insertModeHelpPptx'
                    },
                    'json': {titleKey: 'jsonSettingsTitleText', icon: 'bi-signpost-split', hasInsertMode: false},
                };
                return map[form.workflow_type];
            });

            // Dynamic Step Numbering
            const stepMap = computed(() => {
                let step = 2;
                const map = {specific: 0, parsing: 0, ai: 0, trans: 0, glossary: 0};
                if (currentWorkflowConfig.value) map.specific = step++;
                if (form.workflow_type === 'markdown_based') map.parsing = step++;
                map.ai = step++;
                if (!form.skip_translate) map.trans = step++;
                map.glossary = step++;
                return map;
            });

            const ocrOptions = computed(() => ({
                showFormula: ['mineru', 'docling'].includes(form.convert_engine),
                showCode: form.convert_engine === 'docling'
            }));
            const glossaryCount = computed(() => Object.keys(glossaryData.value).length);

            const handleGlossaryFiles = (e) => {
                const files = e.target.files;
                if (!files.length) return;
                Array.from(files).forEach(f => {
                    Papa.parse(f, {
                        header: true, skipEmptyLines: true,
                        complete: (res) => {
                            if (res.data) res.data.forEach(r => {
                                if (r.src && r.dst) glossaryData.value[r.src.trim()] = r.dst.trim();
                            });
                        }
                    });
                });
            };
            const clearGlossary = () => {
                glossaryData.value = {};
                if (glossaryInput.value) glossaryInput.value.value = '';
            };
            const openGlossaryModal = () => new bootstrap.Modal(document.getElementById('glossaryModal')).show();

            // Task Management
            const createNewTask = (backendId = null) => {
                const uiId = 'card_' + Math.random().toString(36).substring(2, 9);
                const task = reactive({
                    uiId, backendId, file: null, fileName: '', logs: '', statusMessage: '',
                    statusClass: 'text-muted', isTranslating: false, isFinished: false, isProcessing: false,
                    validationError: false, downloads: null, attachment: null, initializing: false, isDragOver: false
                });
                tasks.value.unshift(task);
                if (backendId) {
                    task.isTranslating = true;
                    pollStatus(task);
                }
            };

            const removeTask = async (task) => {
                if (task.backendId) try {
                    await fetch(`/service/release/${task.backendId}`, {method: 'POST'});
                } catch (e) {
                }
                tasks.value = tasks.value.filter(t => t.uiId !== task.uiId);
                saveActiveTasks();
            };

            const saveActiveTasks = () => {
                if (window.location.pathname.includes('/admin')) return;
                const ids = tasks.value.map(t => t.backendId).filter(id => id);
                localStorage.setItem('active_task_ids', JSON.stringify(ids));
            };

            const handleTaskFileSelect = (e, task) => {
                const f = e.target.files ? e.target.files[0] : e.dataTransfer.files[0];
                if (f) {
                    task.file = f;
                    task.fileName = f.name; // Save filename explicitly
                    task.validationError = false;
                    task.isDragOver = false;
                    if (form.auto_workflow_enabled) {
                        const ext = f.name.split('.').pop().toLowerCase();
                        const map = {
                            txt: 'txt',
                            xlsx: 'xlsx',
                            csv: 'xlsx',
                            xls: 'xlsx',
                            docx: 'docx',
                            doc: 'docx',
                            json: 'json',
                            srt: 'srt',
                            epub: 'epub',
                            html: 'html',
                            htm: 'html',
                            ass: 'ass',
                            pptx: 'pptx',
                            ppt: 'pptx'
                        };
                        const newWorkflow = map[ext] || 'markdown_based';
                        form.workflow_type = newWorkflow;
                        saveSetting('translator_last_workflow', newWorkflow);
                    }
                }
            };
            const handleTaskFileDrop = (e, task) => handleTaskFileSelect(e, task);

            const triggerFileInput = (uiId) => {
                const el = document.getElementById('fileInput-' + uiId);
                if (el) el.click();
            };

            const buildPayload = () => {
                // 辅助函数：将空字符串转换为 null，确保后端 exclude_none 生效
                const emptyToNull = (val) => (!val && val !== 0 && val !== false) ? null : val;

                // Clone basic form
                const basePayload = {
                    skip_translate: form.skip_translate,
                    base_url: emptyToNull(form.base_url),
                    api_key: form.api_key || "",
                    model_id: emptyToNull(form.model_id),
                    provider: emptyToNull(form.provider), // Add provider
                    to_lang: form.to_lang === 'custom' ? form.custom_to_lang : form.to_lang,
                    thinking: form.thinking,
                    chunk_size: Number(form.chunk_size),
                    concurrent: Number(form.concurrent),
                    temperature: Number(form.temperature),
                    retry: Number(form.retry),
                    custom_prompt: emptyToNull(form.custom_prompt),
                    glossary_dict: Object.keys(glossaryData.value).length ? glossaryData.value : null,
                    system_proxy_enable: form.system_proxy_enable,
                    force_json: form.force_json,
                    glossary_generate_enable: form.glossary_generate_enable,
                    workflow_type: form.workflow_type,
                    rpm: emptyToNull(form.rpm),
                    tpm: emptyToNull(form.tpm)
                };

                // Agent Config
                if (basePayload.glossary_generate_enable) {
                    const isCustom = form.glossary_agent_config_choice === 'custom';
                    basePayload.glossary_agent_config = {
                        base_url: isCustom ? emptyToNull(form.glossary_agent_baseurl) : basePayload.base_url,
                        api_key: isCustom ? (form.glossary_agent_key || "") : basePayload.api_key,
                        model_id: isCustom ? emptyToNull(form.glossary_agent_model_id) : basePayload.model_id,
                        provider: isCustom ? emptyToNull(form.glossary_agent_provider) : basePayload.provider, // Add provider
                        to_lang: isCustom ? (form.glossary_agent_to_lang === 'custom' ? form.glossary_agent_custom_to_lang : form.glossary_agent_to_lang) : basePayload.to_lang,
                        custom_prompt: emptyToNull(form.glossary_agent_custom_prompt),
                        temperature: isCustom ? Number(form.glossary_agent_temperature) : basePayload.temperature,
                        concurrent: isCustom ? Number(form.glossary_agent_concurrent) : basePayload.concurrent,
                        retry: isCustom ? Number(form.glossary_agent_retry) : basePayload.retry,
                        thinking: isCustom ? form.glossary_agent_thinking : basePayload.thinking,
                        system_proxy_enable: isCustom ? form.glossary_agent_system_proxy_enable : basePayload.system_proxy_enable,
                        chunk_size: isCustom ? Number(form.glossary_agent_chunk_size) : basePayload.chunk_size,
                        force_json: isCustom ? form.glossary_agent_force_json : basePayload.force_json,
                        rpm: isCustom ? emptyToNull(form.glossary_agent_rpm) : basePayload.rpm,
                        tpm: isCustom ? emptyToNull(form.glossary_agent_tpm) : basePayload.tpm
                    };
                }

                // Specific Workflow Params
                if (form.workflow_type === 'markdown_based') {
                    basePayload.convert_engine = form.convert_engine;
                    if (form.convert_engine === 'mineru') {
                        basePayload.mineru_token = emptyToNull(form.mineru_token);
                        basePayload.model_version = form.model_version;
                        basePayload.formula_ocr = form.formula_ocr;
                    } else if (form.convert_engine === 'mineru_deploy') {
                        basePayload.mineru_deploy_base_url = emptyToNull(form.mineru_deploy_base_url);
                        basePayload.mineru_deploy_backend = form.mineru_deploy_backend;
                        basePayload.mineru_deploy_formula_enable = form.mineru_deploy_formula_enable;
                        basePayload.mineru_deploy_start_page_id = parseInt(form.mineru_deploy_start_page) || 0;
                        basePayload.mineru_deploy_end_page_id = parseInt(form.mineru_deploy_end_page) || 99999;
                        if (basePayload.mineru_deploy_backend === 'pipeline') {
                            basePayload.mineru_deploy_lang_list = form.mineru_deploy_lang_list.length > 0 ? form.mineru_deploy_lang_list : null;
                        }
                        if (basePayload.mineru_deploy_backend === 'vlm-http-client') {
                            basePayload.mineru_deploy_server_url = emptyToNull(form.mineru_deploy_server_url);
                        }
                    } else if (form.convert_engine === 'docling') {
                        basePayload.code_ocr = form.code_ocr;
                        basePayload.formula_ocr = form.formula_ocr;
                    }
                } else {
                    const params = {...workflowParams[form.workflow_type]};
                    if (params.separator) {
                        if (form.workflow_type === 'ass') params.separator = params.separator;
                        else params.separator = params.separator.replace(/\\n/g, '\n');
                    }
                    if (form.workflow_type === 'json') {
                        params.json_paths = params.json_paths.split('\n').map(p => p.trim()).filter(p => p);
                    } else if (form.workflow_type === 'xlsx') {
                        if (params.translate_regions && typeof params.translate_regions === 'string' && params.translate_regions.trim()) {
                            params.translate_regions = params.translate_regions.split('\n').map(p => p.trim()).filter(p => p);
                            if (params.translate_regions.length === 0) delete params.translate_regions;
                        } else {
                            delete params.translate_regions;
                        }
                    }
                    Object.assign(basePayload, params);
                }

                return basePayload;
            };

            const validateForm = () => {
                let isValid = true;
                Object.keys(errors).forEach(k => errors[k] = false);

                if (!form.skip_translate) {
                    if (!form.model_id) {
                        errors.model_id = true;
                        isValid = false;
                    }
                    if (form.platform === 'custom' && !form.base_url) {
                        errors.base_url = true;
                        isValid = false;
                    }
                    if (form.to_lang === 'custom' && !form.custom_to_lang) {
                        errors.custom_to_lang = true;
                        isValid = false;
                    }
                }

                if (form.workflow_type === 'markdown_based') {
                    if (form.convert_engine === 'mineru' && !form.mineru_token) {
                        errors.mineru_token = true;
                        isValid = false;
                    }
                    if (form.convert_engine === 'mineru_deploy' && !form.mineru_deploy_base_url) {
                        errors.mineru_deploy_base_url = true;
                        isValid = false;
                    }
                } else if (form.workflow_type === 'json') {
                    if (!workflowParams.json.json_paths || !workflowParams.json.json_paths.trim()) {
                        errors.json_paths = true;
                        isValid = false;
                    }
                }

                if (!isValid) {
                    nextTick(() => {
                        const errorEl = document.querySelector('.is-invalid');
                        if (errorEl) {
                            errorEl.scrollIntoView({behavior: 'smooth', block: 'center'});
                            errorEl.focus();
                        }
                    });
                }
                return isValid;
            };

            const toggleTaskState = async (task) => {
                if (task.isTranslating) {
                    task.statusMessage = t('status_cancelling');
                    if (task.backendId) await fetch(`/service/cancel/${task.backendId}`, {method: 'POST'});
                } else if (task.isFinished) {
                    task.isFinished = false;
                    task.logs = '';
                    task.downloads = null;
                    toggleTaskState(task);
                } else {
                    if (!task.file) {
                        task.validationError = true;
                        return;
                    }

                    if (!validateForm()) {
                        task.statusMessage = t('status_fillRequired') || "请检查左侧配置项 (Please check settings)";
                        task.statusClass = 'text-danger';
                        return;
                    }

                    task.initializing = true;
                    const reader = new FileReader();
                    reader.onload = async () => {
                        const base64 = reader.result.split(',')[1];
                        try {
                            const res = await fetch('/service/translate', {
                                method: 'POST',
                                headers: {'Content-Type': 'application/json'},
                                body: JSON.stringify({
                                    file_name: task.file.name,
                                    file_content: base64,
                                    payload: buildPayload()
                                })
                            });
                            const data = await res.json();
                            if (res.ok && data.task_started) {
                                task.backendId = data.task_id;
                                task.isTranslating = true;
                                saveActiveTasks();
                                pollStatus(task);
                                task.statusMessage = data.message;
                            } else {
                                throw new Error(data.message || data.detail || 'Error');
                            }
                        } catch (e) {
                            task.statusMessage = e.message;
                            task.statusClass = 'text-danger';
                            task.isTranslating = false;
                        } finally {
                            task.initializing = false;
                        }
                    };
                    reader.readAsDataURL(task.file);
                }
            };

            const pollStatus = (task) => {
                const interval = setInterval(async () => {
                    if (!task.isTranslating) {
                        clearInterval(interval);
                        return;
                    }
                    try {
                        // Fetch Logs
                        const logRes = await fetch(`/service/logs/${task.backendId}`);
                        const logData = await logRes.json();
                        if (logData.logs && logData.logs.length) {
                            task.logs += logData.logs.map(l => l.replace(/</g, "&lt;").replace(/>/g, "&gt;")).join('<br>') + '<br>';
                            nextTick(() => {
                                const logEl = document.getElementById('log-' + task.uiId);
                                if (logEl) logEl.scrollTop = logEl.scrollHeight;
                            });
                        }

                        // Fetch Status
                        const statRes = await fetch(`/service/status/${task.backendId}`);

                        // Handle 404 (Task not found / Expired)
                        if (!statRes.ok) {
                            if (statRes.status === 404) {
                                clearInterval(interval);
                                task.isTranslating = false;
                                task.isProcessing = false;
                                task.statusClass = 'text-danger';
                                task.statusMessage = '任务不存在或已过期 (Task not found)';
                                const savedIds = JSON.parse(localStorage.getItem('active_task_ids') || '[]');
                                const newIds = savedIds.filter(id => id !== task.backendId);
                                localStorage.setItem('active_task_ids', JSON.stringify(newIds));
                            }
                            return;
                        }

                        const statData = await statRes.json();
                        task.statusMessage = statData.status_message;
                        task.isProcessing = statData.is_processing;

                        // Recover filename if task was restored and file object is missing
                        if (statData.original_filename && !task.fileName) {
                            task.fileName = statData.original_filename;
                        }

                        if (!statData.is_processing) {
                            clearInterval(interval);
                            task.isTranslating = false;
                            task.isFinished = true;
                            if (statData.download_ready && !statData.error_flag) {
                                task.downloads = statData.downloads;
                                task.attachment = statData.attachment;
                                task.statusClass = 'text-success';
                            } else {
                                task.statusClass = 'text-danger';
                                task.statusMessage = statData.error_flag ? (statData.status_message || 'Failed') : statData.status_message;
                            }
                        }
                    } catch (e) {
                    }
                }, 1500);
            };

            // Preview Logic
            const splitInstance = ref(null);
            const splitContainer = ref(null);
            const originalPane = ref(null);
            const translatedFrame = ref(null);
            const previewTask = ref(null);

            const initSplit = () => {
                if (splitInstance.value) {
                    splitInstance.value.destroy();
                    splitInstance.value = null;
                }
                const isMobile = window.innerWidth < 992;
                if (splitContainer.value) {
                    splitContainer.value.style.flexDirection = isMobile ? 'column' : 'row';
                }
                if (previewMode.value === 'bilingual') {
                    nextTick(() => {
                        const el1 = document.getElementById('originalPreviewContainer');
                        const el2 = document.getElementById('translatedPreviewContainer');
                        if (el1 && el2) {
                            splitInstance.value = Split(['#originalPreviewContainer', '#translatedPreviewContainer'], {
                                sizes: [50, 50], minSize: 150, gutterSize: 10,
                                direction: isMobile ? 'vertical' : 'horizontal',
                                cursor: isMobile ? 'row-resize' : 'col-resize'
                            });
                        }
                    });
                }
                setupSyncScroll();
            };

            const openPreview = (task) => {
                previewTask.value = task;
                const offcanvas = new bootstrap.Offcanvas(document.getElementById('previewOffcanvas'));
                offcanvas.show();

                // Load Original Content
                originalPane.value.innerHTML = '';
                if (task.file) {
                    const ext = task.file.name.split('.').pop().toLowerCase();
                    if (['txt', 'md', 'json', 'html', 'js', 'py', 'css', 'java', 'c', 'cpp'].includes(ext) || task.file.type.startsWith('text/')) {
                        task.file.text().then(txt => originalPane.value.innerHTML = `<pre>${txt}</pre>`);
                    } else if (['pdf'].includes(ext) || task.file.type === 'application/pdf') {
                        const iframe = document.createElement('iframe');
                        iframe.src = URL.createObjectURL(task.file);
                        originalPane.value.appendChild(iframe);
                    } else {
                        originalPane.value.innerHTML = `<p class="p-3 text-muted">${t('preview_cantPreviewType')} (${ext})</p>`;
                    }
                } else {
                    originalPane.value.innerHTML = `<p class="p-3 text-muted">${t('preview_noOriginalCache')}</p>`;
                }

                // Load Translated Content
                if (translatedFrame.value) translatedFrame.value.src = 'about:blank';
                fetch(task.downloads.html).then(r => r.text()).then(h => {
                    if (translatedFrame.value) translatedFrame.value.srcdoc = h;
                });

                // Re-init Split.js and Sync Scroll listeners
                setTimeout(initSplit, 300);
            };

            const setupSyncScroll = () => {
                let isScrolling = false;
                const onScroll = (src, tgt) => {
                    if (!syncScrollEnabled.value || isScrolling) return;
                    const pct = src.scrollTop / (src.scrollHeight - src.clientHeight);
                    tgt.scrollTop = pct * (tgt.scrollHeight - tgt.clientHeight);
                    isScrolling = true;
                    requestAnimationFrame(() => isScrolling = false);
                };

                if (originalPane.value) originalPane.value.onscroll = () => {
                    if (translatedFrame.value && translatedFrame.value.contentWindow)
                        onScroll(originalPane.value, translatedFrame.value.contentWindow.document.documentElement);
                };

                if (translatedFrame.value) translatedFrame.value.onload = () => {
                    const win = translatedFrame.value.contentWindow;
                    if (win) win.onscroll = () => onScroll(win.document.documentElement, originalPane.value);
                };
            };

            const setPreviewMode = (m) => {
                previewMode.value = m;
                setTimeout(initSplit, 100);
            };

            const toggleSyncScroll = () => {
                syncScrollEnabled.value = !syncScrollEnabled.value;
                localStorage.setItem('ui_sync_scroll_enabled', syncScrollEnabled.value);
            };

            const printPdf = (url) => {
                const msg = t('pdf_preparing') || "正在准备打印，请稍候...";
                const toastContainer = document.createElement('div');
                toastContainer.className = 'toast-container position-fixed top-0 start-50 translate-middle-x p-3';
                toastContainer.style.zIndex = '1090';
                toastContainer.innerHTML = `
                <div class="toast align-items-center text-bg-primary border-0 fade show" role="alert" aria-live="assertive" aria-atomic="true">
                    <div class="d-flex">
                        <div class="toast-body">
                            <i class="bi bi-printer-fill me-2"></i>${msg}
                        </div>
                        <button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
                    </div>
                </div>
            `;
                document.body.appendChild(toastContainer);
                setTimeout(() => {
                    const t = toastContainer.querySelector('.toast');
                    if (t) {
                        t.classList.remove('show');
                        setTimeout(() => {
                            if (toastContainer.parentNode) toastContainer.remove();
                        }, 500);
                    }
                }, 3000);

                const ifr = document.getElementById('printFrame');
                fetch(url).then(r => r.text()).then(h => {
                    ifr.srcdoc = h;
                    ifr.onload = () => {
                        setTimeout(() => {
                            ifr.contentWindow.focus();
                            ifr.contentWindow.print();
                        }, 500);
                    };
                });
            };

            const copyLog = (e, l) => {
                navigator.clipboard.writeText(l.replace(/<br>/g, '\n')).then(() => {
                    const btn = e.currentTarget;
                    const icon = btn.querySelector('i');
                    btn.classList.replace('btn-outline-secondary', 'btn-success');
                    icon.classList.replace('bi-clipboard', 'bi-check-lg');
                    setTimeout(() => {
                        btn.classList.replace('btn-success', 'btn-outline-secondary');
                        icon.classList.replace('bi-check-lg', 'bi-clipboard');
                    }, 2000);
                });
            };

            const getFileIcon = (t) => ({
                markdown: 'bi-markdown',
                markdown_zip: 'bi-file-zip',
                docx: 'bi-filetype-docx',
                json: 'bi-filetype-json',
                txt: 'bi-filetype-txt',
                xlsx: 'bi-filetype-xlsx',
                csv: 'bi-filetype-csv',
                srt: 'bi-file-text',
                epub: 'bi-book',
                ass: 'bi-file-easel',
                html: 'bi-filetype-html',
                pptx: 'bi-file-slides'
            }[t] || 'bi-file-earmark');

            const exportConfig = () => {
                const config = {form: form, workflowParams: workflowParams};
                const blob = new Blob([JSON.stringify(config, null, 2)], {type: 'application/json'});
                const url = URL.createObjectURL(blob);
                const a = document.createElement('a');
                a.href = url;
                a.download = 'config.json';
                a.click();
                setTimeout(() => {
                    URL.revokeObjectURL(url);
                }, 100);
            };
            const importConfig = (e) => {
                const f = e.target.files[0];
                if (f) {
                    const r = new FileReader();
                    r.onload = (ev) => {
                        try {
                            const data = JSON.parse(ev.target.result);
                            if (data.form) Object.assign(form, data.form);
                            if (data.workflowParams) Object.assign(workflowParams, data.workflowParams);
                            saveAllSettings();
                            alert(t('configImportSuccess'));
                        } catch (err) {
                            alert(t('configImportError'));
                        }
                    };
                    r.readAsText(f);
                }
                e.target.value = '';
            };

            const setLang = (l) => {
                currentLang.value = l;
                localStorage.setItem('ui_language', l);
                document.documentElement.lang = l === 'zh' ? 'zh-CN' : 'en';
            };
            const setTheme = (t) => {
                localStorage.setItem('theme', t);
                if (t === 'auto') document.documentElement.setAttribute('data-bs-theme', window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
                else document.documentElement.setAttribute('data-bs-theme', t);
            };

            onMounted(async () => {
                // I18n
                try {
                    const res = await fetch("/static/i18nData.json");
                    i18nData.value = await res.json();
                } catch (e) {
                    i18nData.value = {
                        zh: {
                            pageTitle: "DocuTranslate",
                            tutorialBtn: "教程",
                            projectContributeBtn: "项目协作",
                            workflowTitle: "选择工作流",
                            autoWorkflowLabel: "自动选择工作流",
                            workflowOptionPptx: "PPTX 演示文稿",
                            pptxSettingsTitleText: "PPTX 设置",
                            mineruDeployServerUrlLabel: "Server URL",
                            mineruDeployLangListLabel: "语言列表 (Pipeline模式)",
                            mineruDeployServerUrlPlaceholder: "http://127.0.0.1:30000",
                        },
                        en: {
                            pageTitle: "DocuTranslate",
                            tutorialBtn: "Tutorial",
                            projectContributeBtn: "Contribute",
                            workflowTitle: "Select Workflow",
                            workflowOptionPptx: "PPTX Presentation",
                            pptxSettingsTitleText: "PPTX Settings",
                            mineruDeployServerUrlLabel: "Server URL",
                            mineruDeployLangListLabel: "Language List (Pipeline Mode)",
                            mineruDeployServerUrlPlaceholder: "http://127.0.0.1:30000",
                        }
                    };
                }

                // Backend Metadata
                try {
                    const [metaRes, enginRes, paramsRes] = await Promise.all([
                        fetch("/service/meta"), fetch('/service/engin-list'), fetch("/service/default-params")
                    ]);
                    const meta = await metaRes.json();
                    version.value = meta.version;
                    enginList.value = await enginRes.json();
                    Object.assign(defaultParams, await paramsRes.json());
                } catch (e) {
                    console.error("Backend init failed", e);
                }

                loadConfig();
                setTheme(localStorage.getItem('theme') || 'auto');

                // Restore tasks
                if (window.location.pathname.includes('/admin')) {
                    document.title = "DocuTranslate - Admin Panel";
                    try {
                        const r = await fetch('/service/task-list');
                        const ids = await r.json();
                        if (ids) ids.reverse().forEach(id => createNewTask(id));
                    } catch (e) {
                    }
                } else {
                    const savedIds = JSON.parse(localStorage.getItem('active_task_ids') || '[]');
                    if (savedIds.length) savedIds.forEach(id => createNewTask(id));
                    else createNewTask();
                }

                new bootstrap.Tooltip(document.body, {selector: '[data-bs-toggle="tooltip"]'});

                // Global resize handler for preview
                window.addEventListener('resize', () => {
                    if (document.getElementById('previewOffcanvas').classList.contains('show')) {
                        initSplit();
                    }
                });
            });

            return {
                version, currentLang, i18nData, glossaryData, glossaryCount, tasks, enginList, defaultParams,
                form, workflowParams, showMineruToken, previewMode, syncScrollEnabled, showIdentityOption,
                errors, clearError,
                t, createNewTask, removeTask, handleTaskFileSelect, handleTaskFileDrop, toggleTaskState,
                handleGlossaryFiles, clearGlossary, openGlossaryModal,
                openPreview, setPreviewMode, toggleSyncScroll, printPdf, copyLog, getFileIcon,
                exportConfig, importConfig, setLang, setTheme, capitalize,
                currentWorkflowConfig, ocrOptions, stepMap,
                originalPane, translatedFrame, splitInstance, splitContainer, previewTask,
                saveSetting, saveWorkflowParam, saveSettingArray,
                glossaryInput, configFile, triggerFileInput,
                mineruLangOptions // Export new constant
            };
        }
    }).mount('#app');
</script>
</body>
</html>
