<!DOCTYPE html>
<html lang="en" 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;
        }

        .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>
                        <!-- Language & Theme Controls -->
                        <div class="d-flex gap-2">
                            <div class="dropdown">
                                <button class="btn btn-outline-secondary btn-sm dropdown-toggle" type="button" data-bs-toggle="dropdown">
                                    <i class="bi bi-translate me-1"></i><span>{{ {zh:'中文',en:'English',id:'Bahasa'}[currentLang] || 'Language' }}</span>
                                </button>
                                <ul class="dropdown-menu dropdown-menu-end">
                                    <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>
                                    <li><a class="dropdown-item" :class="{active: currentLang==='id'}" href="#"
                                           @click.prevent="setLang('id')">Bahasa Indonesia</a></li>
                                </ul>
                            </div>
                            <div class="dropdown">
                                <button class="btn btn-outline-secondary btn-sm dropdown-toggle" type="button" data-bs-toggle="dropdown"><i
                                        class="bi bi-circle-half"></i></button>
                                <ul class="dropdown-menu dropdown-menu-end">
                                    <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>

                    <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-auto-engine">vlm-auto-engine</option>
                                                    <option value="vlm-http-client">vlm-http-client</option>
                                                    <option value="hybrid-auto-engine">hybrid-auto-engine</option>
                                                    <option value="hybrid-http-client">hybrid-http-client</option>
                                                </select>
                                            </div>

                                            <div class="mb-3">
                                                <label class="form-label">{{ t('mineruDeployParseMethodLabel') }}</label>
                                                <select class="form-select" v-model="form.mineru_deploy_parse_method"
                                                        @change="saveSetting('mineru_deploy_parse_method', form.mineru_deploy_parse_method)">
                                                    <option value="auto">auto</option>
                                                    <option value="txt">txt</option>
                                                    <option value="ocr">ocr</option>
                                                </select>
                                            </div>

                                            <!-- Condition: If Backend is Pipeline or Hybrid, show Lang List -->
                                            <div class="mb-3" v-if="['pipeline', 'hybrid-auto-engine', 'hybrid-http-client'].includes(form.mineru_deploy_backend)">
                                                <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 or hybrid-http-client, show Server URL -->
                                            <div class="mb-3" v-if="['vlm-http-client', 'hybrid-http-client'].includes(form.mineru_deploy_backend)">
                                                <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 class="form-check form-switch mb-2">
                                                <input class="form-check-input" type="checkbox" role="switch"
                                                       v-model="form.mineru_deploy_table_enable"
                                                       @change="saveSetting('mineru_deploy_table_enable', form.mineru_deploy_table_enable)">
                                                <label class="form-check-label">{{ t('mineruDeployTableEnableLabel')
                                                    }}</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">
                                            <model-preset-selector
                                                    v-model:model-preset="form.model_preset"
                                                    :presets="modelPresets"
                                                    :invalid-model-preset="errors.model_preset"
                                                    @clear-error="clearError"
                                                    :t="t"></model-preset-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="Indonesian">印尼文(Bahasa Indonesia)</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><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>
                                </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 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 modal-lg">
            <div class="modal-content">
                <div class="modal-header"><h5 class="modal-title">{{ t('glossaryModalTitle') }}
                    <span v-if="glossaryDirty" class="badge bg-warning ms-2">{{ t('glossaryUnsaved') }}</span>
                </h5>
                    <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
                </div>
                <div class="modal-body">
                    <div class="alert alert-info py-2 small mb-2">{{ t('glossarySharedHint') }}</div>
                    <table class="table table-sm table-hover align-middle">
                        <thead>
                        <tr>
                            <th style="width:32%">{{ t('glossaryColZh') }}</th>
                            <th style="width:32%">{{ t('glossaryColEn') }}</th>
                            <th style="width:32%">{{ t('glossaryColId') }}</th>
                            <th style="width:4%"></th>
                        </tr>
                        </thead>
                        <tbody>
                        <tr v-for="(row, i) in glossaryRows" :key="i">
                            <td><input class="form-control form-control-sm" v-model="row.zh" @input="glossaryDirty=true"></td>
                            <td><input class="form-control form-control-sm" v-model="row.en" @input="glossaryDirty=true"></td>
                            <td><input class="form-control form-control-sm" v-model="row.id" @input="glossaryDirty=true"></td>
                            <td class="text-center">
                                <button type="button" class="btn btn-sm btn-outline-danger py-0 px-1"
                                        @click="deleteGlossaryRow(i)" :title="t('glossaryDeleteRow')"><i class="bi bi-x-lg"></i></button>
                            </td>
                        </tr>
                        <tr v-if="glossaryRows.length === 0">
                            <td colspan="4" class="text-center text-muted">{{ t('glossaryEmpty') }}</td>
                        </tr>
                        </tbody>
                    </table>
                </div>
                <div class="modal-footer">
                    <button type="button" class="btn btn-outline-secondary" @click="exportGlossaryCsv">
                        <i class="bi bi-download me-1"></i>{{ t('glossaryExportCsv') }}
                    </button>
                    <button type="button" class="btn btn-outline-primary" @click="addGlossaryRow">
                        <i class="bi bi-plus-lg me-1"></i>{{ t('glossaryAddRow') }}
                    </button>
                    <button type="button" class="btn btn-primary" @click="saveGlossary" :disabled="!glossaryDirty">
                        <i class="bi bi-save me-1"></i>{{ t('glossarySave') }}
                    </button>
                    <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>

    <!-- Header controls now in left panel top-right -->
</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 ModelPresetSelector = {
        props: ['modelPreset', 'presets', 't', 'invalidModelPreset'],
        template: `
          <div>
            <div class="mb-3">
              <label class="form-label">{{ t('modelPresetLabel') }}</label>
              <select class="form-select" :class="{'is-invalid': invalidModelPreset}"
                      :value="modelPreset" :disabled="!presets.length"
                      @change="handlePresetChange($event.target.value)">
                <option value="" disabled>{{ presets.length ? t('modelPresetPlaceholder') : t('modelPresetEmpty') }}</option>
                <option v-for="preset in presets" :key="preset.id" :value="preset.id">{{ preset.label }}</option>
              </select>
              <div class="form-text mt-2" v-if="presets.length">{{ t('modelPresetRuntimeHint') }}</div>
              <div class="form-text mt-2" v-else>{{ t('modelPresetEmpty') }}</div>
            </div>
          </div>`,
        setup(props, {emit}) {
            const handlePresetChange = (val) => {
                emit('update:modelPreset', val);
                emit('clearError', 'model_preset');
                localStorage.setItem('translator_model_preset', val);
            };

            return {
                handlePresetChange
            };
        }
    };

    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, ModelPresetSelector},
        setup() {
            const version = ref("");
            function detectBrowserLang() {
                const nav = navigator.language || navigator.userLanguage || '';
                const lang = nav.split('-')[0].toLowerCase();
                if (['zh', 'en', 'id'].includes(lang)) return lang;
                if (lang === 'zh') return 'zh';
                return 'en';  // default to English for unrecognized languages
            }
            const currentLang = ref(localStorage.getItem('ui_language') || detectBrowserLang());
            const i18nData = ref({});
            const glossaryData = ref({});
            const glossaryRows = ref([]);      // 共享术语表三列行 [{zh,en,id}]
            const glossaryDirty = ref(false);   // 是否有未保存的编辑
            const glossarySaving = ref(false);
            const tasks = ref([]);
            const enginList = ref([]);
            const defaultParams = reactive({});
            const modelPresets = ref([]);
            const defaultModelPreset = ref('');

            // 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_preset: 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: 'hybrid-auto-engine', // Updated default
                mineru_deploy_parse_method: 'auto', // Added
                mineru_deploy_start_page: 0,
                mineru_deploy_end_page: 99999,
                mineru_deploy_formula_enable: true,
                mineru_deploy_table_enable: true, // Added
                mineru_deploy_lang_list: [],
                mineru_deploy_server_url: '',
                formula_ocr: true,
                code_ocr: true,
                skip_translate: false,
                model_preset: '',
                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
            });

            // 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);
                };
                const validPresetIds = modelPresets.value.map(p => p.id);
                const fallbackPreset = validPresetIds.includes(defaultModelPreset.value)
                    ? defaultModelPreset.value
                    : (validPresetIds[0] || '');

                form.workflow_type = get('translator_last_workflow', 'docx');
                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', 'hybrid-auto-engine');
                form.mineru_deploy_parse_method = get('mineru_deploy_parse_method', 'auto');
                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_table_enable = getBool('mineru_deploy_table_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.model_preset = get('translator_model_preset', fallbackPreset);
                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', 'default');
                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

                if (!validPresetIds.includes(form.model_preset)) {
                    form.model_preset = fallbackPreset;
                }

                // 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', '');
            };

            // --- 新增：专门用于将当前 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_parse_method', f.mineru_deploy_parse_method);
                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_table_enable', f.mineru_deploy_table_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_model_preset', f.model_preset);
                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 || '');

                // 2. 自动循环保存所有具体工作流参数 (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 t = (k) => {
                const dict = i18nData.value[currentLang.value] || i18nData.value['en'] || {};
                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 syncModelPresetSelection = () => {
                const validPresetIds = modelPresets.value.map(p => p.id);
                const fallbackPreset = validPresetIds.includes(defaultModelPreset.value)
                    ? defaultModelPreset.value
                    : (validPresetIds[0] || '');
                if (!validPresetIds.includes(form.model_preset)) {
                    form.model_preset = fallbackPreset;
                }
            };

            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};
                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++;
                return map;
            });

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

            // --- 三列术语表（中文/English/Bahasa）本地工具函数 ---
            const LANG_COL_ALIASES = {
                'zh':'zh','zh-cn':'zh','zh-hans':'zh','zh-hant':'zh','chinese':'zh',
                'simplified chinese':'zh','traditional chinese':'zh','中文':'zh','简体中文':'zh','繁体中文':'zh','汉语':'zh',
                'en':'en','en-us':'en','en-gb':'en','english':'en','英文':'en','英语':'en',
                'id':'id','id-id':'id','indonesian':'id','bahasa indonesia':'id','印尼文':'id','印尼语':'id','印度尼西亚语':'id'
            };
            const langToCol = (lang) => {
                if (!lang) return null;
                return LANG_COL_ALIASES[String(lang).trim().toLowerCase()] || null;
            };
            // 根据当前目标语言，把三列行编译成 {src: dst}（供翻译引擎消费，契约与旧 glossaryData 一致）
            const buildGlossaryDict = () => {
                const targetCol = langToCol(form.to_lang === 'custom' ? form.custom_to_lang : form.to_lang);
                if (!targetCol || !glossaryRows.value.length) return null;
                const result = {};
                for (const row of glossaryRows.value) {
                    const target = (row[targetCol] || '').trim();
                    if (!target) continue;
                    for (const col of ['zh','en','id']) {
                        if (col === targetCol) continue;
                        const src = (row[col] || '').trim();
                        if (src && !(src in result)) result[src] = target;
                    }
                }
                return Object.keys(result).length ? result : null;
            };

            const addGlossaryRow = () => {
                glossaryRows.value.push({zh:'', en:'', id:''});
                glossaryDirty.value = true;
            };
            const deleteGlossaryRow = (i) => {
                glossaryRows.value.splice(i, 1);
                glossaryDirty.value = true;
            };
            const loadGlossaryFromServer = async () => {
                try {
                    const res = await fetch('/service/glossary');
                    const data = await res.json();
                    glossaryRows.value = (data && Array.isArray(data.rows)) ? data.rows.map(r => ({
                        zh: String(r.zh||''), en: String(r.en||''), id: String(r.id||'')
                    })) : [];
                    glossaryDirty.value = false;
                } catch (e) {
                    console.error('Failed to load shared glossary', e);
                }
            };
            const saveGlossary = async () => {
                if (glossarySaving.value) return;
                glossarySaving.value = true;
                try {
                    const rows = glossaryRows.value
                        .map(r => ({zh:(r.zh||'').trim(), en:(r.en||'').trim(), id:(r.id||'').trim()}))
                        .filter(r => r.zh || r.en || r.id);
                    const res = await fetch('/service/glossary', {
                        method: 'POST',
                        headers: {'Content-Type': 'application/json'},
                        body: JSON.stringify({rows})
                    });
                    if (!res.ok) throw new Error('HTTP ' + res.status);
                    const data = await res.json();
                    glossaryRows.value = (data.rows || []).map(r => ({
                        zh: String(r.zh||''), en: String(r.en||''), id: String(r.id||'')
                    }));
                    glossaryDirty.value = false;
                    alert(t('glossarySaved'));
                } catch (e) {
                    alert(t('glossarySaveFailed') + ': ' + e.message);
                } finally {
                    glossarySaving.value = false;
                }
            };
            const exportGlossaryCsv = async () => {
                // 从服务端拉取数据库实际内容再导出（避免导出未保存的本地脏数据）
                let rows = glossaryRows.value;
                try {
                    const res = await fetch('/service/glossary');
                    const data = await res.json();
                    if (data && Array.isArray(data.rows)) rows = data.rows;
                } catch (e) { console.error('export fetch failed, using local', e); }
                const esc = (s) => {
                    s = String(s||'');
                    return /[",\n]/.test(s) ? '"' + s.replace(/"/g,'""') + '"' : s;
                };
                let csv = 'zh,en,id\n';
                rows.forEach(r => { csv += [esc(r.zh), esc(r.en), esc(r.id)].join(',') + '\n'; });
                // 加 UTF-8 BOM，保证 Excel 正确识别中文
                const blob = new Blob(['﻿' + csv], {type: 'text/csv;charset=utf-8'});
                const url = URL.createObjectURL(blob);
                const a = document.createElement('a');
                a.href = url; a.download = 'glossary.csv'; a.click();
                URL.revokeObjectURL(url);
            };

            const handleGlossaryFiles = (e) => {
                const files = e.target.files;
                if (!files.length) return;
                Array.from(files).forEach(f => {
                    Papa.parse(f, {
                        header: true, skipEmptyLines: true,
                        // 剥离 BOM + 规范化表头（小写、去空白），兼容 Excel 另存的 CSV
                        transformHeader: (h) => String(h||'').replace(/^﻿/, '').trim().toLowerCase(),
                        complete: (res) => {
                            if (!res.data) return;
                            let added = 0;
                            res.data.forEach(r => {
                                const row = {zh:'', en:'', id:''};
                                if ('zh' in r || 'en' in r || 'id' in r) {
                                    row.zh = (r.zh||'').trim(); row.en = (r.en||'').trim(); row.id = (r.id||'').trim();
                                } else if ('src' in r && 'dst' in r) {
                                    // 旧 2 列：src 视为英文、dst 视为中文（最常见英译中场景）
                                    row.zh = (r.dst||'').trim(); row.en = (r.src||'').trim();
                                } else { return; }
                                if (row.zh || row.en || row.id) {
                                    glossaryRows.value.push(row);
                                    glossaryDirty.value = true;
                                    added++;
                                }
                            });
                            if (added) alert(t('glossaryImported').replace('{n}', added));
                        }
                    });
                });
                if (glossaryInput.value) glossaryInput.value.value = '';
            };
            const clearGlossary = async () => {
                // 清空当前编辑并从服务端重新拉取（避免误删共享数据）
                await loadGlossaryFromServer();
            };
            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,
                    model_preset: emptyToNull(form.model_preset),
                    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: buildGlossaryDict(),
                    system_proxy_enable: form.system_proxy_enable,
                    force_json: form.force_json,
                    workflow_type: form.workflow_type,
                    rpm: emptyToNull(form.rpm),
                    tpm: emptyToNull(form.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_parse_method = form.mineru_deploy_parse_method; // Added
                        basePayload.mineru_deploy_formula_enable = form.mineru_deploy_formula_enable;
                        basePayload.mineru_deploy_table_enable = form.mineru_deploy_table_enable; // Added
                        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;

                        // Condition: Pipeline or Hybrid backends support lang_list
                        if (['pipeline', 'hybrid-auto-engine', 'hybrid-http-client'].includes(basePayload.mineru_deploy_backend)) {
                             basePayload.mineru_deploy_lang_list = form.mineru_deploy_lang_list.length > 0 ? form.mineru_deploy_lang_list : null;
                        }

                        // Condition: HTTP Client backends need server_url
                        if (['vlm-http-client', 'hybrid-http-client'].includes(basePayload.mineru_deploy_backend)) {
                            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_preset) {
                        errors.model_preset = 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);
                            ['platform', 'base_url', 'api_key', 'model_id', 'provider'].forEach((key) => {
                                if (key in form) delete form[key];
                            });
                            syncModelPresetSelection();
                            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);
                const langMap = {zh: 'zh-CN', en: 'en', id: 'id'};
                document.documentElement.lang = langMap[l] || 'en';
                const dict = i18nData.value[l] || i18nData.value['en'] || {};
                document.title = dict['pageTitle'] || 'DocuTranslate';
            };
            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();

                    // Backward compat: ensure id lang pack exists on servers not yet updated
                    if (!i18nData.value.id) {
                        console.warn('id language pack missing, using en as fallback');
                        i18nData.value.id = i18nData.value.en || {};
                    }

                    // Add new missing translations for Mineru Deploy
                    const extraZh = {
                        mineruDeployParseMethodLabel: "解析方法 (Parse Method)",
                        mineruDeployTableEnableLabel: "表格识别 (Table Recognition)"
                    };
                    const extraEn = {
                        mineruDeployParseMethodLabel: "Parse Method",
                        mineruDeployTableEnableLabel: "Table Recognition"
                    };
                    if(i18nData.value.zh) Object.assign(i18nData.value.zh, extraZh);
                    if(i18nData.value.en) Object.assign(i18nData.value.en, extraEn);

                } catch (e) {
                    console.error("i18n load failed", e);
                    i18nData.value = {
                        zh: {
                            pageTitle: "DocuTranslate",
                            tutorialBtn: "教程",
                            projectContributeBtn: "项目协作",
                            workflowTitle: "选择工作流",
                            autoWorkflowLabel: "自动选择工作流",
                            modelPresetLabel: "模型预设",
                            modelPresetPlaceholder: "请选择模型预设",
                            modelPresetEmpty: "请先在服务端环境变量中配置模型预设",
                            modelPresetRuntimeHint: "运行时将从服务端环境变量读取供应商、模型端点与 API Key。",
                            workflowOptionPptx: "PPTX 演示文稿",
                            pptxSettingsTitleText: "PPTX 设置",
                            mineruDeployServerUrlLabel: "Server URL",
                            mineruDeployLangListLabel: "语言列表 (Pipeline模式)",
                            mineruDeployServerUrlPlaceholder: "http://127.0.0.1:30000",
                            mineruDeployParseMethodLabel: "解析方法 (Parse Method)",
                            mineruDeployTableEnableLabel: "表格识别 (Table Recognition)"
                        },
                        en: {
                            pageTitle: "DocuTranslate",
                            tutorialBtn: "Tutorial",
                            projectContributeBtn: "Contribute",
                            workflowTitle: "Select Workflow",
                            modelPresetLabel: "Model Preset",
                            modelPresetPlaceholder: "Select a model preset",
                            modelPresetEmpty: "Configure model presets in server environment variables first",
                            modelPresetRuntimeHint: "Provider, endpoint, and API key will be loaded from server environment variables at runtime.",
                            workflowOptionPptx: "PPTX Presentation",
                            pptxSettingsTitleText: "PPTX Settings",
                            mineruDeployServerUrlLabel: "Server URL",
                            mineruDeployLangListLabel: "Language List (Pipeline Mode)",
                            mineruDeployServerUrlPlaceholder: "http://127.0.0.1:30000",
                            mineruDeployParseMethodLabel: "Parse Method",
                            mineruDeployTableEnableLabel: "Table Recognition"
                        },
                        id: {
                            pageTitle: "DocuTranslate",
                            tutorialBtn: "Tutorial",
                            projectContributeBtn: "Kolaborasi",
                            workflowTitle: "Pilih Alur Kerja",
                            autoWorkflowLabel: "Pilih Otomatis",
                            modelPresetLabel: "Preset Model",
                            modelPresetPlaceholder: "Pilih preset model",
                            modelPresetEmpty: "Konfigurasi preset di server",
                            modelPresetRuntimeHint: "Provider, endpoint, dan API key akan dibaca dari environment server.",
                            workflowOptionPptx: "Presentasi PPTX",
                            pptxSettingsTitleText: "Pengaturan PPTX",
                            mineruDeployServerUrlLabel: "Server URL",
                            mineruDeployLangListLabel: "Daftar Bahasa",
                            mineruDeployServerUrlPlaceholder: "http://127.0.0.1:30000",
                            mineruDeployParseMethodLabel: "Parse Method",
                            mineruDeployTableEnableLabel: "Table Recognition"
                        }
                    };
                }

                // Set initial page title based on detected language
                const initDict = i18nData.value[currentLang.value] || i18nData.value['en'] || {};
                document.title = initDict['pageTitle'] || 'DocuTranslate';

                // Backend Metadata
                try {
                    const [metaRes, enginRes, paramsRes, configRes] = await Promise.all([
                        fetch("/service/meta"), fetch('/service/engin-list'), fetch("/service/default-params"),
                        fetch("/api/config")
                    ]);
                    const meta = await metaRes.json();
                    version.value = meta.version;
                    enginList.value = await enginRes.json();
                    Object.assign(defaultParams, await paramsRes.json());
                    const envConfig = await configRes.json().catch(() => ({}));
                    modelPresets.value = Array.isArray(envConfig.model_presets) ? envConfig.model_presets : [];
                    defaultModelPreset.value = envConfig.default_model_preset || (modelPresets.value[0]?.id || '');
                    if (defaultModelPreset.value && !localStorage.getItem('translator_model_preset')) {
                        localStorage.setItem('translator_model_preset', defaultModelPreset.value);
                    }
                    if (envConfig.rpm != null && !localStorage.getItem('rpm')) {
                        localStorage.setItem('rpm', String(envConfig.rpm));
                    }
                    if (envConfig.tpm != null && !localStorage.getItem('tpm')) {
                        localStorage.setItem('tpm', String(envConfig.tpm));
                    }
                } catch (e) {
                    console.error("Backend init failed", e);
                }

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

                // Load shared trilingual glossary (全公司通用，默认引用)
                await loadGlossaryFromServer();

                // 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, glossaryRows, glossaryDirty, glossarySaving, tasks, enginList, defaultParams,
                modelPresets,
                form, workflowParams, showMineruToken, previewMode, syncScrollEnabled, showIdentityOption,
                errors, clearError,
                t, createNewTask, removeTask, handleTaskFileSelect, handleTaskFileDrop, toggleTaskState,
                handleGlossaryFiles, clearGlossary, openGlossaryModal,
                addGlossaryRow, deleteGlossaryRow, saveGlossary, exportGlossaryCsv,
                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>
