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

    <style>
        /* 复用原版 CSS */
        body { background-color: var(--bs-body-bg); }
        button:focus { outline: none !important; box-shadow: none !important; }
        .main-container { display: flex; flex-direction: column; height: 100vh; padding-top: 1rem; padding-bottom: 1rem; }
        .settings-panel { height: calc(100vh - 2rem); overflow-y: auto; padding-right: 15px; }
        .task-area { height: calc(100vh - 2rem); overflow-y: auto; }
        .task-card { transition: all 0.3s ease-in-out; }
        .log-area { height: 150px; background-color: var(--bs-tertiary-bg); border: 1px solid var(--bs-border-color); border-radius: .375rem; font-family: monospace; font-size: 0.8rem; padding: 10px; overflow-y: auto; white-space: pre-wrap; word-break: break-all; }
        .log-area-wrapper { position: relative; }
        .copy-log-btn { position: absolute; bottom: 5px; right: 5px; z-index: 10; opacity: 0.5; transition: all 0.2s ease-in-out; padding: 0.1rem 0.4rem; line-height: 1; }
        .copy-log-btn:hover { opacity: 1; }
        .file-drop-area { border: 2px dashed var(--bs-secondary-bg); border-radius: .375rem; padding: 2rem; text-align: center; cursor: pointer; transition: all 0.2s ease-in-out; background-color: var(--bs-body-bg); }
        .file-drop-area.drag-over { border-color: var(--bs-primary); background-color: var(--bs-secondary-bg); }
        .file-drop-area.file-selected { border-style: solid; border-color: var(--bs-success); background-color: var(--bs-success-bg-subtle); }
        .file-drop-area.input-error { border-color: var(--bs-danger); }
        #previewOffcanvas { --bs-offcanvas-width: 95vw; max-width: 1600px; }
        .preview-split-container { display: flex; flex-direction: row; height: 90%; }
        .preview-pane-wrapper { display: flex; flex-direction: column; overflow: hidden; width: 100%; height: 100%; } /* 修改: 增加宽高100%以在单栏模式撑满 */
        .preview-pane-wrapper .preview-pane { flex-grow: 1; border: 1px solid var(--bs-border-color); border-radius: .375rem; overflow: auto; position: relative; }
        .gutter { background-color: var(--bs-tertiary-bg); background-repeat: no-repeat; background-position: 50%; }
        .gutter.gutter-horizontal { cursor: col-resize; border-left: 1px solid var(--bs-border-color); border-right: 1px solid var(--bs-border-color); }
        .gutter.gutter-vertical { cursor: row-resize; border-top: 1px solid var(--bs-border-color); border-bottom: 1px solid var(--bs-border-color); }
        .preview-pane iframe { width: 100%; height: 100%; border: none; }
        .preview-pane pre { margin: 0; padding: 1rem; background-color: var(--bs-body-bg); white-space: pre; }
        .bottom-left-controls { position: fixed; bottom: 1rem; left: 1rem; z-index: 1050; display: flex; gap: 0.5rem; }
        .step-number { margin-right: 0.25rem; }
        @media (max-width: 991.98px) {
            .main-container { height: auto; padding-bottom: 6rem; }
            .settings-panel, .task-area { height: auto; overflow-y: visible; }
            .settings-panel { padding-right: 0; margin-bottom: 2rem; }
        }
        /* Vue Cloak to hide uncompiled templates */
        [v-cloak] { display: none; }
    </style>
</head>
<body>

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

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

                            <!-- 1. Workflow Selection -->
                            <div class="accordion-item">
                                <h2 class="accordion-header">
                                    <button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapseZero">
                                        <strong><span class="step-number">1</span><i class="bi bi-diagram-3 me-2"></i><span>{{ t('workflowTitle') }}</span></strong>
                                    </button>
                                </h2>
                                <div id="collapseZero" class="accordion-collapse collapse show">
                                    <div class="accordion-body">
                                        <select class="form-select" v-model="form.workflow_type" @change="saveSetting('translator_last_workflow', form.workflow_type)">
                                            <option value="markdown_based">{{ t('workflowOptionMarkdown') }}</option>
                                            <option value="txt">{{ t('workflowOptionTxt') }}</option>
                                            <option value="epub">{{ t('workflowOptionEpub') }}</option>
                                            <option value="docx">{{ t('workflowOptionDocx') }}</option>
                                            <option value="xlsx">{{ t('workflowOptionXlsx') }}</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">{{ t('mineruDeployBackendPipeline') }}</option>
                                                    <option value="transformers">{{ t('mineruDeployBackendTransformers') }}</option>
                                                    <option value="mlx-engine">{{ t('mineruDeployBackendMlx') }}</option>
                                                    <option value="vllm-engine">{{ t('mineruDeployBackendVllm') }}</option>
                                                    <option value="vllm-async-engine">{{ t('mineruDeployBackendVllmAsync') }}</option>
                                                </select>
                                            </div>
                                            <div class="row">
                                                <div class="col-md-6 mb-3">
                                                    <label class="form-label">{{ t('mineruDeployStartPageLabel') }}</label>
                                                    <input type="number" class="form-control" v-model="form.mineru_deploy_start_page" @input="saveSetting('mineru_deploy_start_page', form.mineru_deploy_start_page)" min="0">
                                                </div>
                                                <div class="col-md-6 mb-3">
                                                    <label class="form-label">{{ t('mineruDeployEndPageLabel') }}</label>
                                                    <input type="number" class="form-control" v-model="form.mineru_deploy_end_page" @input="saveSetting('mineru_deploy_end_page', form.mineru_deploy_end_page)" min="0">
                                                </div>
                                            </div>
                                            <div class="form-check form-switch mb-2">
                                                <input class="form-check-input" type="checkbox" role="switch" v-model="form.mineru_deploy_formula_enable" @change="saveSetting('mineru_deploy_formula_enable', form.mineru_deploy_formula_enable)">
                                                <label class="form-check-label">{{ t('mineruDeployFormulaEnableLabel') }}</label>
                                            </div>
                                        </div>

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

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

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

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

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

                                        <slider-control :label="t('chunkSizeLabel')" v-model="form.chunk_size" save-key="chunk_size" :default-val="defaultParams.chunk_size" :min="1000" :max="12000" :step="100" :t="t"></slider-control>
                                        <slider-control :label="t('concurrentLabel')" v-model="form.concurrent" save-key="concurrent" :default-val="defaultParams.concurrent" :min="1" :max="60" :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>
                                    </div>
                                </div>
                            </div>

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

const PlatformSelector = {
    props: ['platform', 'baseUrl', 'apiKey', 'modelId', 't', 'prefix', 'invalidApiKey', 'invalidBaseUrl', 'invalidModelId'],
    template: `
    <div>
        <div class="mb-2">
            <label class="form-label">{{ t('platformLabel') }}</label>
            <select class="form-select" :value="platform" @change="handlePlatformChange($event.target.value)">
                <option v-for="p in platforms" :value="p.val">{{ p.val === 'custom' ? t(p.label) : p.label }}</option>
            </select>
        </div>
        <div class="form-text mb-3">Base URL: <code ref="baseUrlDisplay">{{ baseUrl }}</code></div>
        <div class="mb-3" v-if="platform === 'custom'">
            <label class="form-label">{{ t('baseUrlLabel') }}</label>
            <input type="url" class="form-control" :class="{'is-invalid': invalidBaseUrl}"
                   :value="baseUrl" @input="handleBaseUrlChange($event.target.value)" required placeholder="OpenAi Compatible URL">
        </div>
        <div class="mb-3">
            <label class="form-label">API Key <a v-if="apiHref" :href="apiHref[0]" target="_blank" class="ms-1"><i class="bi bi-box-arrow-up-right"></i></a> <span class="ms-2 text-muted small">{{ apiHref && apiHref[1] ? t(apiHref[1]) : '' }}</span></label>
            <div class="input-group">
                <input :type="showPass?'text':'password'" class="form-control" :class="{'is-invalid': invalidApiKey}"
                       :value="apiKey" @input="handleApiKeyChange($event.target.value)" :placeholder="t('apiKeyPlaceholder')">
                <button class="btn btn-outline-secondary" type="button" @click="showPass=!showPass"><i class="bi" :class="showPass?'bi-eye':'bi-eye-slash'"></i></button>
            </div>
        </div>
        <div class="mb-3">
            <label class="form-label">{{ t('modelIdLabel') }}</label>
            <input type="text" class="form-control" :class="{'is-invalid': invalidModelId}"
                   :value="modelId" @input="handleModelChange($event.target.value)" required :placeholder="t('modelIdPlaceholder')">
        </div>
    </div>`,
    setup(props, {emit}) {
        const showPass = ref(false);
        const platforms = [
            {val: "custom", label: "platformCustom"},
            {val: "https://api.302.ai/v1", label: "302.AI"},
            {val: "https://api.openai.com/v1", label: "OpenAI"},
            {val: "https://generativelanguage.googleapis.com/v1beta/openai/", label: "Gemini"},
            {val: "https://api.deepseek.com/v1", label: "DeepSeek"},
            {val: "https://dashscope.aliyuncs.com/compatible-mode/v1", label: "阿里云百炼(DashScope)"},
            {val: "https://ark.cn-beijing.volces.com/api/v3", label: "火山引擎(volces)"},
            {val: "https://api.siliconflow.cn/v1", label: "硅基流动(siliconflow CN)"},
            {val: "https://open.bigmodel.cn/api/paas/v4", label: "智谱AI(bigmodel CN)"},
            {val: "https://www.dmxapi.cn/v1", label: "DMXAPI_CN"},
            {val: "https://www.dmxapi.com/v1", label: "DMXAPI_GLOBAL"},
            {val: "https://ai.juguang.chat/v1", label: "聚光AI(juguang CN)"},
            {val: "https://openrouter.ai/api/v1", label: "OpenRouter"},
            {val: "http://127.0.0.1:1234/v1", label: "LM Studio"},
            {val: "http://127.0.0.1:11434/v1", label: "Ollama"}
        ];
        const apiHrefMap = {
            "https://api.302.ai/v1": ["https://share.302.ai/BgRLAe", "apiHrefInfo302ai"],
            "https://openrouter.ai/api/v1": ["https://openrouter.ai/settings/keys", null],
            "https://api.openai.com/v1": ["https://platform.openai.com/api-keys", null],
            "https://api.deepseek.com/v1": ["https://platform.deepseek.com/api_keys", null],
            "https://open.bigmodel.cn/api/paas/v4": ["https://open.bigmodel.cn/usercenter/apikeys", null],
            "https://dashscope.aliyuncs.com/compatible-mode/v1": ["https://bailian.console.aliyun.com/?tab=model#/api-key", null],
            "https://ark.cn-beijing.volces.com/api/v3": ["https://console.volcengine.com/ark/region:ark+cn-beijing/apiKey?apikey=%7B%7D", null],
            "https://api.siliconflow.cn/v1": ["https://cloud.siliconflow.cn/account/ak", null],
            "https://ai.juguang.chat/v1": ["https://ai.juguang.chat/console/token", null],
            "https://www.dmxapi.cn/v1": ["https://www.dmxapi.cn/token", null],
            "https://www.dmxapi.com/v1": ["https://www.dmxapi.com/console/token", null],
            "https://generativelanguage.googleapis.com/v1beta/openai/": ["https://aistudio.google.com/u/0/apikey", null]
        };
        const apiHref = computed(() => apiHrefMap[props.baseUrl]);

        const save = (key, val) => localStorage.setItem(key, val);
        const handlePlatformChange = (val) => {
            emit('update:platform', val);
            save(`${props.prefix}_last_platform`, val);
        };
        const handleBaseUrlChange = (val) => {
            emit('update:baseUrl', val);
            emit('clearError', 'base_url');
            if(props.platform === 'custom') save(`${props.prefix}_custom_base_url`, val);
        };
        const handleApiKeyChange = (val) => {
            emit('update:apiKey', val);
            emit('clearError', 'api_key');
            save(`${props.prefix}_${props.platform}_apikey`, val);
        };
        const handleModelChange = (val) => {
            emit('update:modelId', val);
            emit('clearError', 'model_id');
            save(`${props.prefix}_${props.platform}_model_id`, val);
        };

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

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

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

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

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

        // Form Data
        const form = reactive({
            workflow_type: 'markdown_based',
            auto_workflow_enabled: true,
            convert_engine: 'mineru',
            mineru_token: '',
            model_version: 'vlm',
            mineru_deploy_base_url: 'http://127.0.0.1:8000',
            mineru_deploy_backend: 'pipeline',
            mineru_deploy_start_page: 0,
            mineru_deploy_end_page: 99999,
            mineru_deploy_formula_enable: true,
            formula_ocr: true,
            code_ocr: true,
            skip_translate: false,
            platform: 'https://api.302.ai/v1',
            base_url: '',
            api_key: '',
            model_id: '',
            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,
            glossary_generate_enable: false,
            glossary_agent_custom_prompt: '',
            glossary_agent_config_choice: 'same',
            glossary_agent_platform: 'https://api.302.ai/v1',
            glossary_agent_baseurl: '',
            glossary_agent_key: '',
            glossary_agent_model_id: '',
            glossary_agent_to_lang: 'Simplified Chinese',
            glossary_agent_custom_to_lang: '',
            glossary_agent_chunk_size: 1000,
            glossary_agent_concurrent: 5,
            glossary_agent_temperature: 0.7,
            glossary_agent_retry: 3,
            glossary_agent_thinking: 'default',
            glossary_agent_system_proxy_enable: false,
            glossary_agent_force_json: false
        });

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

        // 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; };

            form.workflow_type = get('translator_last_workflow', 'markdown_based');
            form.auto_workflow_enabled = getBool('translator_auto_workflow_enabled', true);
            form.convert_engine = get('translator_convert_engin', 'mineru');
            form.mineru_token = get('translator_mineru_token', '');
            form.model_version = get('translator_model_version', 'vlm');
            form.mineru_deploy_base_url = get('mineru_deploy_base_url', 'http://127.0.0.1:8000');
            form.mineru_deploy_backend = get('mineru_deploy_backend', 'pipeline');
            form.mineru_deploy_start_page = getNum('mineru_deploy_start_page', 0);
            form.mineru_deploy_end_page = getNum('mineru_deploy_end_page', 99999);
            form.mineru_deploy_formula_enable = getBool('mineru_deploy_formula_enable', true);
            form.formula_ocr = getBool('translator_formula_ocr', true);
            form.code_ocr = getBool('translator_code_ocr', true);
            form.skip_translate = getBool('translator_skip_translate', false);
            form.platform = get('translator_platform_last_platform', 'https://api.302.ai/v1');
            form.system_proxy_enable = getBool('translator_system_proxy_enable', false);
            form.force_json = getBool('translator_force_json', false);
            form.to_lang = get('translator_to_lang', 'Simplified Chinese');
            form.custom_to_lang = get('translator_custom_to_lang', '');
            form.thinking = get('translator_thinking_mode', 'disable');
            form.custom_prompt = get('custom_prompt', '');
            form.chunk_size = getNum('chunk_size', 1000);
            form.concurrent = getNum('concurrent', 5);
            form.temperature = getNum('temperature', 0.1);
            form.retry = getNum('retry', 3);
            form.glossary_generate_enable = getBool('glossary_generate_enable', false);
            form.glossary_agent_custom_prompt = get('glossary_agent_custom_prompt', '');
            form.glossary_agent_config_choice = get('glossary_agent_config_choice', 'same');
            form.glossary_agent_platform = get('glossary_agent_platform_last_platform', 'https://api.302.ai/v1');
            form.glossary_agent_to_lang = get('glossary_agent_to_lang', 'Simplified Chinese');
            form.glossary_agent_custom_to_lang = get('glossary_agent_custom_to_lang', '');
            form.glossary_agent_chunk_size = getNum('glossary_agent_chunk_size', 1000);
            form.glossary_agent_concurrent = getNum('glossary_agent_concurrent', 5);
            form.glossary_agent_temperature = getNum('glossary_agent_temperature', 0.7);
            form.glossary_agent_retry = getNum('glossary_agent_retry', 3);
            form.glossary_agent_thinking = get('glossary_agent_thinking_mode', 'default');
            form.glossary_agent_system_proxy_enable = getBool('glossary_agent_system_proxy_enable', false);
            form.glossary_agent_force_json = getBool('glossary_agent_force_json', false);

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

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

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

        watch(() => form.platform, (n) => updatePlatformParams(n, 'translator_platform', form));
        watch(() => form.glossary_agent_platform, (n) => updatePlatformParams(n, 'glossary_agent_platform', form, true));

        const t = (k) => {
            const dict = i18nData.value[currentLang.value] || i18nData.value['zh'] || {};
            return dict[k] || k;
        };
        const capitalize = (s) => s.charAt(0).toUpperCase() + s.slice(1);
        const saveSetting = (k, v) => localStorage.setItem(k, v);
        const 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' },
                'json': { titleKey: 'jsonSettingsTitleText', icon: 'bi-signpost-split', hasInsertMode: false },
            };
            return map[form.workflow_type];
        });

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

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

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

        // Task Management
        const createNewTask = (backendId = null) => {
            const uiId = 'card_' + Math.random().toString(36).substring(2, 9);
            const task = reactive({
                uiId, backendId, file: null, 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.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', docx:'docx', json:'json', srt:'srt', epub:'epub', html:'html', htm:'html', ass:'ass' };
                    if(map[ext]) form.workflow_type = map[ext];
                }
            }
        };
        const handleTaskFileDrop = (e, task) => handleTaskFileSelect(e, task);

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

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

            // Specific Workflow Params
            if(form.workflow_type === 'markdown_based') {
                basePayload.convert_engine = form.convert_engine;
                if(form.convert_engine === 'mineru') {
                    basePayload.mineru_token = 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 = form.mineru_deploy_base_url;
                    basePayload.mineru_deploy_backend = form.mineru_deploy_backend;
                    basePayload.mineru_deploy_formula_enable = form.mineru_deploy_formula_enable;
                    basePayload.mineru_deploy_start_page_id = form.mineru_deploy_start_page;
                    basePayload.mineru_deploy_end_page_id = form.mineru_deploy_end_page;
                } 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] };
                // Separator cleanup
                if(params.separator) {
                    if(form.workflow_type === 'ass') params.separator = params.separator; // keep \N
                    else params.separator = params.separator.replace(/\\n/g, '\n');
                }
                // Array handling for JSON/XLSX
                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' && params.translate_regions) {
                    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;
                }
                Object.assign(basePayload, params);
            }

            return basePayload;
        };

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

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

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

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

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

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

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

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

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

                    // NEW: 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)';
                            // Remove from local storage active list to prevent reload
                            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;

                    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); // New ref
        const originalPane = ref(null);
        const translatedFrame = ref(null);
        const previewTask = ref(null); // Track which task is being previewed for downloads

        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; // Set current task for preview menu
            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) => {
            // Visual notification
            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);
            // Auto remove
            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'
        }[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 a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = 'config.json'; a.click();
        };
        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);
                        alert(t('configImportSuccess'));
                    } catch(err) { alert(t('configImportError')); }
                };
                r.readAsText(f);
            }
            e.target.value = '';
        };

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

        onMounted(async () => {
            // I18n
            try { const res = await fetch("/static/i18nData.json"); i18nData.value = await res.json(); } catch(e) {
                i18nData.value = {
                    zh:{pageTitle:"DocuTranslate", tutorialBtn:"教程", projectContributeBtn:"项目协作", workflowTitle:"选择工作流", autoWorkflowLabel:"自动选择工作流"},
                    en:{pageTitle:"DocuTranslate", tutorialBtn:"Tutorial", projectContributeBtn:"Contribute", workflowTitle:"Select Workflow"}
                };
            }

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

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

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

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

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

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