添加图标
This commit is contained in:
@@ -3,8 +3,9 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>DocuTranslate - 交互式文档翻译</title>
|
|
||||||
|
|
||||||
|
<title>DocuTranslate - 交互式文档翻译</title>
|
||||||
|
<link rel="icon" href="/static/favicon.ico" type="image/x-icon">
|
||||||
<!-- Bootstrap CSS -->
|
<!-- Bootstrap CSS -->
|
||||||
<link href="/static/bootstrap.css" rel="stylesheet" crossorigin="anonymous">
|
<link href="/static/bootstrap.css" rel="stylesheet" crossorigin="anonymous">
|
||||||
<!-- Bootstrap Icons -->
|
<!-- Bootstrap Icons -->
|
||||||
@@ -60,17 +61,21 @@
|
|||||||
transition: all 0.2s ease-in-out;
|
transition: all 0.2s ease-in-out;
|
||||||
background-color: var(--bs-body-bg);
|
background-color: var(--bs-body-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-drop-area.drag-over {
|
.file-drop-area.drag-over {
|
||||||
border-color: var(--bs-primary);
|
border-color: var(--bs-primary);
|
||||||
background-color: var(--bs-secondary-bg);
|
background-color: var(--bs-secondary-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-drop-area.file-selected {
|
.file-drop-area.file-selected {
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
border-color: var(--bs-success);
|
border-color: var(--bs-success);
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-drop-area.input-error {
|
.file-drop-area.input-error {
|
||||||
border-color: var(--bs-danger);
|
border-color: var(--bs-danger);
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-name-display.input-error-text {
|
.file-name-display.input-error-text {
|
||||||
color: var(--bs-danger);
|
color: var(--bs-danger);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
@@ -85,6 +90,7 @@
|
|||||||
#previewModal .modal-dialog {
|
#previewModal .modal-dialog {
|
||||||
max-width: 95vw;
|
max-width: 95vw;
|
||||||
}
|
}
|
||||||
|
|
||||||
#previewModal .modal-body {
|
#previewModal .modal-body {
|
||||||
height: 80vh;
|
height: 80vh;
|
||||||
}
|
}
|
||||||
@@ -95,6 +101,7 @@
|
|||||||
border: 1px solid var(--bs-border-color);
|
border: 1px solid var(--bs-border-color);
|
||||||
border-radius: .375rem;
|
border-radius: .375rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.preview-pane iframe, .preview-pane pre {
|
.preview-pane iframe, .preview-pane pre {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@@ -119,7 +126,7 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<div class="container-fluid main-container">
|
<div class="container-fluid main-container">
|
||||||
<div class="row gx-4">
|
<div class="row gx-4">
|
||||||
<!-- Left: Settings Panel -->
|
<!-- Left: Settings Panel -->
|
||||||
<div class="col-lg-4">
|
<div class="col-lg-4">
|
||||||
@@ -135,7 +142,8 @@
|
|||||||
<!-- Parsing Engine Settings -->
|
<!-- Parsing Engine Settings -->
|
||||||
<div class="accordion-item">
|
<div class="accordion-item">
|
||||||
<h2 class="accordion-header" id="headingOne">
|
<h2 class="accordion-header" id="headingOne">
|
||||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseOne" aria-expanded="false" aria-controls="collapseOne">
|
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"
|
||||||
|
data-bs-target="#collapseOne" aria-expanded="false" aria-controls="collapseOne">
|
||||||
<strong><i class="bi bi-file-earmark-binary me-2"></i>解析配置</strong>
|
<strong><i class="bi bi-file-earmark-binary me-2"></i>解析配置</strong>
|
||||||
</button>
|
</button>
|
||||||
</h2>
|
</h2>
|
||||||
@@ -151,9 +159,11 @@
|
|||||||
<div class="mb-3" id="mineruTokenGroup">
|
<div class="mb-3" id="mineruTokenGroup">
|
||||||
<label for="mineru_token" class="form-label">
|
<label for="mineru_token" class="form-label">
|
||||||
Mineru Token
|
Mineru Token
|
||||||
<a href="https://mineru.net/apiManage/token" target="_blank" class="ms-1" title="获取Mineru Token"><i class="bi bi-box-arrow-up-right"></i></a>
|
<a href="https://mineru.net/apiManage/token" target="_blank" class="ms-1"
|
||||||
|
title="获取Mineru Token"><i class="bi bi-box-arrow-up-right"></i></a>
|
||||||
</label>
|
</label>
|
||||||
<input type="password" class="form-control" id="mineru_token" name="mineru_token" placeholder="使用Mineru引擎时需要">
|
<input type="password" class="form-control" id="mineru_token"
|
||||||
|
name="mineru_token" placeholder="使用Mineru引擎时需要">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -162,7 +172,8 @@
|
|||||||
<!-- AI Settings -->
|
<!-- AI Settings -->
|
||||||
<div class="accordion-item">
|
<div class="accordion-item">
|
||||||
<h2 class="accordion-header" id="headingTwo">
|
<h2 class="accordion-header" id="headingTwo">
|
||||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo">
|
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"
|
||||||
|
data-bs-target="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo">
|
||||||
<strong><i class="bi bi-robot me-2"></i>翻译模型</strong>
|
<strong><i class="bi bi-robot me-2"></i>翻译模型</strong>
|
||||||
</button>
|
</button>
|
||||||
</h2>
|
</h2>
|
||||||
@@ -175,7 +186,9 @@
|
|||||||
<option value="https://api.openai.com/v1">OpenAI</option>
|
<option value="https://api.openai.com/v1">OpenAI</option>
|
||||||
<option value="https://open.bigmodel.cn/api/paas/v4">智谱AI</option>
|
<option value="https://open.bigmodel.cn/api/paas/v4">智谱AI</option>
|
||||||
<option value="https://api.deepseek.com/v1">DeepSeek</option>
|
<option value="https://api.deepseek.com/v1">DeepSeek</option>
|
||||||
<option value="https://dashscope.aliyuncs.com/compatible-mode/v1">阿里云百炼</option>
|
<option value="https://dashscope.aliyuncs.com/compatible-mode/v1">
|
||||||
|
阿里云百炼
|
||||||
|
</option>
|
||||||
<option value="https://www.dmxapi.cn/v1">DMXAPI</option>
|
<option value="https://www.dmxapi.cn/v1">DMXAPI</option>
|
||||||
<option value="https://openrouter.ai/api/v1">OpenRouter</option>
|
<option value="https://openrouter.ai/api/v1">OpenRouter</option>
|
||||||
<option value="https://ark.cn-beijing.volces.com/api/v3">火山引擎</option>
|
<option value="https://ark.cn-beijing.volces.com/api/v3">火山引擎</option>
|
||||||
@@ -189,13 +202,16 @@
|
|||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="apikey" class="form-label">
|
<label for="apikey" class="form-label">
|
||||||
API Key
|
API Key
|
||||||
<a href="#" target="_blank" class="ms-1" id="api_href" title="获取API Key"><i class="bi bi-box-arrow-up-right"></i></a>
|
<a href="#" target="_blank" class="ms-1" id="api_href"
|
||||||
|
title="获取API Key"><i class="bi bi-box-arrow-up-right"></i></a>
|
||||||
</label>
|
</label>
|
||||||
<input type="password" class="form-control" id="apikey" name="apikey" required placeholder="请输入您的API Key">
|
<input type="password" class="form-control" id="apikey" name="apikey" required
|
||||||
|
placeholder="请输入您的API Key">
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="model_id" class="form-label">模型 ID</label>
|
<label for="model_id" class="form-label">模型 ID</label>
|
||||||
<input type="text" class="form-control" id="model_id" name="model_id" required placeholder="例如: gpt-4o, glm-4">
|
<input type="text" class="form-control" id="model_id" name="model_id" required
|
||||||
|
placeholder="例如: gpt-4o, glm-4">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -204,7 +220,9 @@
|
|||||||
<!-- Translation Settings -->
|
<!-- Translation Settings -->
|
||||||
<div class="accordion-item">
|
<div class="accordion-item">
|
||||||
<h2 class="accordion-header" id="headingThree">
|
<h2 class="accordion-header" id="headingThree">
|
||||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseThree" aria-expanded="false" aria-controls="collapseThree">
|
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"
|
||||||
|
data-bs-target="#collapseThree" aria-expanded="false"
|
||||||
|
aria-controls="collapseThree">
|
||||||
<strong><i class="bi bi-translate me-2"></i>翻译配置</strong>
|
<strong><i class="bi bi-translate me-2"></i>翻译配置</strong>
|
||||||
</button>
|
</button>
|
||||||
</h2>
|
</h2>
|
||||||
@@ -221,19 +239,26 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="custom_prompt_translate" class="form-label">自定义Prompt</label>
|
<label for="custom_prompt_translate" class="form-label">自定义Prompt</label>
|
||||||
<textarea class="form-control" id="custom_prompt_translate" name="custom_prompt_translate" rows="3" placeholder="可选,如“人名保持原文不翻译”"></textarea>
|
<textarea class="form-control" id="custom_prompt_translate"
|
||||||
|
name="custom_prompt_translate" rows="3"
|
||||||
|
placeholder="可选,如“人名保持原文不翻译”"></textarea>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-check form-switch mb-2">
|
<div class="form-check form-switch mb-2">
|
||||||
<input class="form-check-input" type="checkbox" role="switch" id="formula_ocr" name="formula_ocr" checked>
|
<input class="form-check-input" type="checkbox" role="switch" id="formula_ocr"
|
||||||
|
name="formula_ocr" checked>
|
||||||
<label class="form-check-label" for="formula_ocr">公式识别</label>
|
<label class="form-check-label" for="formula_ocr">公式识别</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-check form-switch mb-2">
|
<div class="form-check form-switch mb-2">
|
||||||
<input class="form-check-input" type="checkbox" role="switch" id="code_ocr" name="code_ocr" checked>
|
<input class="form-check-input" type="checkbox" role="switch" id="code_ocr"
|
||||||
|
name="code_ocr" checked>
|
||||||
<label class="form-check-label" for="code_ocr">代码识别</label>
|
<label class="form-check-label" for="code_ocr">代码识别</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-check form-switch mb-2">
|
<div class="form-check form-switch mb-2">
|
||||||
<input class="form-check-input" type="checkbox" role="switch" id="refine_markdown" name="refine_markdown">
|
<input class="form-check-input" type="checkbox" role="switch"
|
||||||
<label class="form-check-label" for="refine_markdown">Markdown修复<span class="d-inline-block" tabindex="0" data-bs-toggle="tooltip" title="使用ai对解析后的文本先修复再翻译,现不推荐开启">
|
id="refine_markdown" name="refine_markdown">
|
||||||
|
<label class="form-check-label" for="refine_markdown">Markdown修复<span
|
||||||
|
class="d-inline-block" tabindex="0" data-bs-toggle="tooltip"
|
||||||
|
title="使用ai对解析后的文本先修复再翻译,现不推荐开启">
|
||||||
<i class="bi bi-question-circle"></i>
|
<i class="bi bi-question-circle"></i>
|
||||||
</span></label>
|
</span></label>
|
||||||
|
|
||||||
@@ -245,32 +270,49 @@
|
|||||||
<!-- Other Settings -->
|
<!-- Other Settings -->
|
||||||
<div class="accordion-item">
|
<div class="accordion-item">
|
||||||
<h2 class="accordion-header" id="headingFour">
|
<h2 class="accordion-header" id="headingFour">
|
||||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseFour" aria-expanded="false" aria-controls="collapseFour">
|
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"
|
||||||
|
data-bs-target="#collapseFour" aria-expanded="false"
|
||||||
|
aria-controls="collapseFour">
|
||||||
<strong><i class="bi bi-sliders me-2"></i>高级参数</strong>
|
<strong><i class="bi bi-sliders me-2"></i>高级参数</strong>
|
||||||
</button>
|
</button>
|
||||||
</h2>
|
</h2>
|
||||||
<div id="collapseFour" class="accordion-collapse collapse" aria-labelledby="headingFour">
|
<div id="collapseFour" class="accordion-collapse collapse" aria-labelledby="headingFour">
|
||||||
<div class="accordion-body">
|
<div class="accordion-body">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="chunk-size-slider" class="form-label d-flex justify-content-between">
|
<label for="chunk-size-slider"
|
||||||
|
class="form-label d-flex justify-content-between">
|
||||||
<span>分块大小: <span id="chunk-size-display"></span></span>
|
<span>分块大小: <span id="chunk-size-display"></span></span>
|
||||||
<button type="button" class="btn btn-sm btn-outline-secondary py-0 px-1 slider-reset-btn" id="chunk-size-reset">重置</button>
|
<button type="button"
|
||||||
|
class="btn btn-sm btn-outline-secondary py-0 px-1 slider-reset-btn"
|
||||||
|
id="chunk-size-reset">重置
|
||||||
|
</button>
|
||||||
</label>
|
</label>
|
||||||
<input type="range" class="form-range" min="1000" max="6000" step="100" id="chunk-size-slider" name="chunk_size">
|
<input type="range" class="form-range" min="1000" max="6000" step="100"
|
||||||
|
id="chunk-size-slider" name="chunk_size">
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="concurrent-slider" class="form-label d-flex justify-content-between">
|
<label for="concurrent-slider"
|
||||||
|
class="form-label d-flex justify-content-between">
|
||||||
<span>并发数: <span id="concurrent-display"></span></span>
|
<span>并发数: <span id="concurrent-display"></span></span>
|
||||||
<button type="button" class="btn btn-sm btn-outline-secondary py-0 px-1 slider-reset-btn" id="concurrent-reset">重置</button>
|
<button type="button"
|
||||||
|
class="btn btn-sm btn-outline-secondary py-0 px-1 slider-reset-btn"
|
||||||
|
id="concurrent-reset">重置
|
||||||
|
</button>
|
||||||
</label>
|
</label>
|
||||||
<input type="range" class="form-range" min="1" max="60" step="1" id="concurrent-slider" name="concurrent">
|
<input type="range" class="form-range" min="1" max="60" step="1"
|
||||||
|
id="concurrent-slider" name="concurrent">
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="temperature-slider" class="form-label d-flex justify-content-between">
|
<label for="temperature-slider"
|
||||||
|
class="form-label d-flex justify-content-between">
|
||||||
<span>Temperature: <span id="temperature-display"></span></span>
|
<span>Temperature: <span id="temperature-display"></span></span>
|
||||||
<button type="button" class="btn btn-sm btn-outline-secondary py-0 px-1 slider-reset-btn" id="temperature-reset">重置</button>
|
<button type="button"
|
||||||
|
class="btn btn-sm btn-outline-secondary py-0 px-1 slider-reset-btn"
|
||||||
|
id="temperature-reset">重置
|
||||||
|
</button>
|
||||||
</label>
|
</label>
|
||||||
<input type="range" class="form-range" min="0" max="2" step="0.1" id="temperature-slider" name="temperature">
|
<input type="range" class="form-range" min="0" max="2" step="0.1"
|
||||||
|
id="temperature-slider" name="temperature">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -297,7 +339,8 @@
|
|||||||
<div class="task-area" id="task-area-container">
|
<div class="task-area" id="task-area-container">
|
||||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
<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>任务列表</h4>
|
<h4 class="mb-0"><i class="bi bi-list-task me-2"></i>任务列表</h4>
|
||||||
<button class="btn btn-primary" id="addNewTaskBtn"><i class="bi bi-plus-circle-fill me-2"></i>新建任务</button>
|
<button class="btn btn-primary" id="addNewTaskBtn"><i class="bi bi-plus-circle-fill me-2"></i>新建任务
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="task-container">
|
<div id="task-container">
|
||||||
<!-- Task cards will be injected here -->
|
<!-- Task cards will be injected here -->
|
||||||
@@ -309,10 +352,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Task Card Template -->
|
<!-- Task Card Template -->
|
||||||
<template id="taskCardTemplate">
|
<template id="taskCardTemplate">
|
||||||
<div class="card mb-3 task-card">
|
<div class="card mb-3 task-card">
|
||||||
<div class="card-header d-flex justify-content-between align-items-center">
|
<div class="card-header d-flex justify-content-between align-items-center">
|
||||||
<span class="fw-bold">任务 ID: <code class="task-id-display"></code></span>
|
<span class="fw-bold">任务 ID: <code class="task-id-display"></code></span>
|
||||||
@@ -336,7 +379,8 @@
|
|||||||
<span class="status-message small text-muted">等待上传文件...</span>
|
<span class="status-message small text-muted">等待上传文件...</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="progress mt-1" role="progressbar" style="height: 5px; display: none;">
|
<div class="progress mt-1" role="progressbar" style="height: 5px; display: none;">
|
||||||
<div class="progress-bar progress-bar-striped progress-bar-animated" style="width: 100%"></div>
|
<div class="progress-bar progress-bar-striped progress-bar-animated"
|
||||||
|
style="width: 100%"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -345,32 +389,40 @@
|
|||||||
<div class="card-footer d-flex justify-content-between align-items-center">
|
<div class="card-footer d-flex justify-content-between align-items-center">
|
||||||
<div class="download-buttons" style="display: none;">
|
<div class="download-buttons" style="display: none;">
|
||||||
<button class="btn btn-sm btn-success preview-html-btn"><i class="bi bi-eye-fill me-1"></i>预览</button>
|
<button class="btn btn-sm btn-success preview-html-btn"><i class="bi bi-eye-fill me-1"></i>预览</button>
|
||||||
<button class="btn btn-sm btn-info download-pdf-btn"><i class="bi bi-file-earmark-pdf-fill me-1"></i>下载 PDF</button>
|
<button class="btn btn-sm btn-info download-pdf-btn"><i class="bi bi-file-earmark-pdf-fill me-1"></i>下载
|
||||||
|
PDF
|
||||||
|
</button>
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
<button type="button" class="btn btn-sm btn-secondary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
|
<button type="button" class="btn btn-sm btn-secondary dropdown-toggle" data-bs-toggle="dropdown"
|
||||||
|
aria-expanded="false">
|
||||||
<i class="bi bi-download me-1"></i>下载其他格式
|
<i class="bi bi-download me-1"></i>下载其他格式
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
<li><a class="dropdown-item download-html-link" href="#"><i class="bi bi-filetype-html me-2"></i>HTML</a></li>
|
<li><a class="dropdown-item download-html-link" href="#"><i
|
||||||
<li><a class="dropdown-item download-markdown-link" href="#"><i class="bi bi-markdown-fill me-2"></i>Markdown(嵌图)</a></li>
|
class="bi bi-filetype-html me-2"></i>HTML</a></li>
|
||||||
<li><a class="dropdown-item download-markdown-zip-link" href="#"><i class="bi bi-file-zip-fill me-2"></i>Markdown压缩包</a></li>
|
<li><a class="dropdown-item download-markdown-link" href="#"><i
|
||||||
|
class="bi bi-markdown-fill me-2"></i>Markdown(嵌图)</a></li>
|
||||||
|
<li><a class="dropdown-item download-markdown-zip-link" href="#"><i
|
||||||
|
class="bi bi-file-zip-fill me-2"></i>Markdown压缩包</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button class="btn btn-primary start-translate-btn ms-auto"><i class="bi bi-play-fill me-1"></i>开始翻译</button>
|
<button class="btn btn-primary start-translate-btn ms-auto"><i class="bi bi-play-fill me-1"></i>开始翻译
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- Preview Modal -->
|
<!-- Preview Modal -->
|
||||||
<div class="modal fade" id="previewModal" tabindex="-1" aria-labelledby="previewModalTitle" aria-hidden="true">
|
<div class="modal fade" id="previewModal" tabindex="-1" aria-labelledby="previewModalTitle" aria-hidden="true">
|
||||||
<div class="modal-dialog modal-fullscreen-xl-down">
|
<div class="modal-dialog modal-fullscreen-xl-down">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h5 class="modal-title" id="previewModalTitle">双语预览</h5>
|
<h5 class="modal-title" id="previewModalTitle">双语预览</h5>
|
||||||
<div class="btn-group me-auto ms-4" role="group">
|
<div class="btn-group me-auto ms-4" role="group">
|
||||||
<button type="button" class="btn btn-sm btn-primary" id="setBilingualViewBtn">双语</button>
|
<button type="button" class="btn btn-sm btn-primary" id="setBilingualViewBtn">双语</button>
|
||||||
<button type="button" class="btn btn-sm btn-outline-primary" id="setTranslatedOnlyViewBtn">仅译文</button>
|
<button type="button" class="btn btn-sm btn-outline-primary" id="setTranslatedOnlyViewBtn">仅译文
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
</div>
|
</div>
|
||||||
@@ -390,31 +442,46 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">关闭</button>
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">关闭</button>
|
||||||
<button type="button" class="btn btn-primary" id="printFromPreview"><i class="bi bi-printer-fill me-2"></i>打印/保存为PDF</button>
|
<button type="button" class="btn btn-primary" id="printFromPreview"><i
|
||||||
</div>
|
class="bi bi-printer-fill me-2"></i>打印/保存为PDF
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Hidden iframe for direct PDF printing -->
|
<!-- Hidden iframe for direct PDF printing -->
|
||||||
<iframe id="printFrame" style="display: none;"></iframe>
|
<iframe id="printFrame" style="display: none;"></iframe>
|
||||||
|
|
||||||
<!-- Theme Switcher -->
|
<!-- Theme Switcher -->
|
||||||
<div class="dropdown theme-switch">
|
<div class="dropdown theme-switch">
|
||||||
<button class="btn btn-secondary dropdown-toggle" type="button" id="theme-switcher" data-bs-toggle="dropdown" aria-expanded="false">
|
<button class="btn btn-secondary dropdown-toggle" type="button" id="theme-switcher" data-bs-toggle="dropdown"
|
||||||
|
aria-expanded="false">
|
||||||
<i class="bi bi-circle-half"></i>
|
<i class="bi bi-circle-half"></i>
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu" aria-labelledby="theme-switcher">
|
<ul class="dropdown-menu" aria-labelledby="theme-switcher">
|
||||||
<li><button class="dropdown-item" type="button" data-bs-theme-value="light"><i class="bi bi-sun-fill me-2"></i> Light</button></li>
|
<li>
|
||||||
<li><button class="dropdown-item" type="button" data-bs-theme-value="dark"><i class="bi bi-moon-stars-fill me-2"></i> Dark</button></li>
|
<button class="dropdown-item" type="button" data-bs-theme-value="light"><i class="bi bi-sun-fill me-2"></i>
|
||||||
<li><button class="dropdown-item active" type="button" data-bs-theme-value="auto"><i class="bi bi-circle-half me-2"></i> Auto</button></li>
|
Light
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<button class="dropdown-item" type="button" data-bs-theme-value="dark"><i
|
||||||
|
class="bi bi-moon-stars-fill me-2"></i> Dark
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<button class="dropdown-item active" type="button" data-bs-theme-value="auto"><i
|
||||||
|
class="bi bi-circle-half me-2"></i> Auto
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Bootstrap JS -->
|
<!-- Bootstrap JS -->
|
||||||
<script src="/static/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
|
<script src="/static/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
|
||||||
|
|
||||||
<script type="module">
|
<script type="module">
|
||||||
|
|
||||||
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]');
|
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]');
|
||||||
const tooltipList = [...tooltipTriggerList].map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl));
|
const tooltipList = [...tooltipTriggerList].map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl));
|
||||||
@@ -481,8 +548,21 @@
|
|||||||
|
|
||||||
// --- Utility Functions ---
|
// --- Utility Functions ---
|
||||||
const generateTaskId = () => Math.random().toString(36).substring(2, 10);
|
const generateTaskId = () => Math.random().toString(36).substring(2, 10);
|
||||||
const saveToStorage = (key, value) => { try { localStorage.setItem(key, value); } catch (e) { console.warn("Save to storage failed:", e); } };
|
const saveToStorage = (key, value) => {
|
||||||
const getFromStorage = (key, defaultValue = '') => { try { return localStorage.getItem(key) || defaultValue; } catch (e) { console.warn("Read from storage failed:", e); return defaultValue; } };
|
try {
|
||||||
|
localStorage.setItem(key, value);
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("Save to storage failed:", e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const getFromStorage = (key, defaultValue = '') => {
|
||||||
|
try {
|
||||||
|
return localStorage.getItem(key) || defaultValue;
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("Read from storage failed:", e);
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
function fileToBase64(file) {
|
function fileToBase64(file) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
@@ -629,7 +709,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function addEventListenersToCard(taskId) {
|
function addEventListenersToCard(taskId) {
|
||||||
const { elements, state } = tasks[taskId];
|
const {elements, state} = tasks[taskId];
|
||||||
|
|
||||||
elements.removeBtn.addEventListener('click', () => removeTask(taskId));
|
elements.removeBtn.addEventListener('click', () => removeTask(taskId));
|
||||||
|
|
||||||
@@ -666,7 +746,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleFileSelect(taskId) {
|
function handleFileSelect(taskId) {
|
||||||
const { elements, state } = tasks[taskId];
|
const {elements, state} = tasks[taskId];
|
||||||
const file = elements.fileInput.files[0];
|
const file = elements.fileInput.files[0];
|
||||||
if (file) {
|
if (file) {
|
||||||
state.file = file;
|
state.file = file;
|
||||||
@@ -681,7 +761,7 @@
|
|||||||
|
|
||||||
// --- Core Translation Logic ---
|
// --- Core Translation Logic ---
|
||||||
async function startTranslation(taskId) {
|
async function startTranslation(taskId) {
|
||||||
const { elements, state } = tasks[taskId];
|
const {elements, state} = tasks[taskId];
|
||||||
|
|
||||||
// --- Validation ---
|
// --- Validation ---
|
||||||
if (!state.file) {
|
if (!state.file) {
|
||||||
@@ -744,7 +824,7 @@
|
|||||||
|
|
||||||
const response = await fetch('/service/translate', {
|
const response = await fetch('/service/translate', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: {'Content-Type': 'application/json'},
|
||||||
body: JSON.stringify(payload)
|
body: JSON.stringify(payload)
|
||||||
});
|
});
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
@@ -772,12 +852,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function cancelTranslation(taskId) {
|
async function cancelTranslation(taskId) {
|
||||||
const { elements } = tasks[taskId];
|
const {elements} = tasks[taskId];
|
||||||
elements.startBtn.disabled = true;
|
elements.startBtn.disabled = true;
|
||||||
elements.startBtn.innerHTML = `<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> 正在取消...`;
|
elements.startBtn.innerHTML = `<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> 正在取消...`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/service/cancel/${taskId}`, { method: 'POST' });
|
const response = await fetch(`/service/cancel/${taskId}`, {method: 'POST'});
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
|
|
||||||
if (response.ok && result.cancelled) {
|
if (response.ok && result.cancelled) {
|
||||||
@@ -797,7 +877,7 @@
|
|||||||
// --- Polling ---
|
// --- Polling ---
|
||||||
function startPolling(taskId) {
|
function startPolling(taskId) {
|
||||||
stopPolling(taskId);
|
stopPolling(taskId);
|
||||||
const { intervals } = tasks[taskId];
|
const {intervals} = tasks[taskId];
|
||||||
intervals.log = setInterval(() => pollLogs(taskId), 2000);
|
intervals.log = setInterval(() => pollLogs(taskId), 2000);
|
||||||
intervals.status = setInterval(() => pollStatus(taskId), 1500);
|
intervals.status = setInterval(() => pollStatus(taskId), 1500);
|
||||||
pollLogs(taskId);
|
pollLogs(taskId);
|
||||||
@@ -805,7 +885,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function stopPolling(taskId) {
|
function stopPolling(taskId) {
|
||||||
const { intervals } = tasks[taskId];
|
const {intervals} = tasks[taskId];
|
||||||
if (intervals.log) clearInterval(intervals.log);
|
if (intervals.log) clearInterval(intervals.log);
|
||||||
if (intervals.status) clearInterval(intervals.status);
|
if (intervals.status) clearInterval(intervals.status);
|
||||||
intervals.log = null;
|
intervals.log = null;
|
||||||
@@ -813,7 +893,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function pollLogs(taskId) {
|
async function pollLogs(taskId) {
|
||||||
const { elements } = tasks[taskId];
|
const {elements} = tasks[taskId];
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/service/logs/${taskId}`);
|
const response = await fetch(`/service/logs/${taskId}`);
|
||||||
if (!response.ok) return;
|
if (!response.ok) return;
|
||||||
@@ -828,7 +908,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function pollStatus(taskId, isRestore = false) {
|
async function pollStatus(taskId, isRestore = false) {
|
||||||
const { elements, state } = tasks[taskId];
|
const {elements, state} = tasks[taskId];
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/service/status/${taskId}`);
|
const response = await fetch(`/service/status/${taskId}`);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
@@ -887,7 +967,7 @@
|
|||||||
|
|
||||||
// --- Download and Preview ---
|
// --- Download and Preview ---
|
||||||
function setupPreview(taskId) {
|
function setupPreview(taskId) {
|
||||||
const { state } = tasks[taskId];
|
const {state} = tasks[taskId];
|
||||||
if (!state.htmlUrl) return;
|
if (!state.htmlUrl) return;
|
||||||
|
|
||||||
// Clear previous content
|
// Clear previous content
|
||||||
@@ -941,12 +1021,14 @@
|
|||||||
try {
|
try {
|
||||||
translatedPreviewFrame.contentWindow.focus();
|
translatedPreviewFrame.contentWindow.focus();
|
||||||
translatedPreviewFrame.contentWindow.print();
|
translatedPreviewFrame.contentWindow.print();
|
||||||
} catch(e) { alert('打印失败,请使用浏览器打印功能。'); }
|
} catch (e) {
|
||||||
|
alert('打印失败,请使用浏览器打印功能。');
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function downloadPdf(taskId) {
|
function downloadPdf(taskId) {
|
||||||
const { elements, state } = tasks[taskId];
|
const {elements, state} = tasks[taskId];
|
||||||
if (!state.htmlUrl) return;
|
if (!state.htmlUrl) return;
|
||||||
|
|
||||||
elements.pdfBtn.disabled = true;
|
elements.pdfBtn.disabled = true;
|
||||||
@@ -1075,7 +1157,9 @@
|
|||||||
platformSelect.addEventListener('change', updatePlatformUI);
|
platformSelect.addEventListener('change', updatePlatformUI);
|
||||||
apikeyInput.addEventListener('input', e => saveToStorage(`translator_platform_${platformSelect.value}_apikey`, e.target.value));
|
apikeyInput.addEventListener('input', e => saveToStorage(`translator_platform_${platformSelect.value}_apikey`, e.target.value));
|
||||||
modelInput.addEventListener('input', e => saveToStorage(`translator_platform_${platformSelect.value}_model_id`, e.target.value));
|
modelInput.addEventListener('input', e => saveToStorage(`translator_platform_${platformSelect.value}_model_id`, e.target.value));
|
||||||
baseUrlInput.addEventListener('input', e => { if (platformSelect.value === 'custom') saveToStorage('translator_platform_custom_base_url', e.target.value); });
|
baseUrlInput.addEventListener('input', e => {
|
||||||
|
if (platformSelect.value === 'custom') saveToStorage('translator_platform_custom_base_url', e.target.value);
|
||||||
|
});
|
||||||
convertEnginSelect.addEventListener('change', updateConvertEnginUI);
|
convertEnginSelect.addEventListener('change', updateConvertEnginUI);
|
||||||
mineruTokenInput.addEventListener('input', e => saveToStorage('translator_mineru_token', e.target.value));
|
mineruTokenInput.addEventListener('input', e => saveToStorage('translator_mineru_token', e.target.value));
|
||||||
toLangSelect.addEventListener('change', e => saveToStorage('translator_to_lang', e.target.value));
|
toLangSelect.addEventListener('change', e => saveToStorage('translator_to_lang', e.target.value));
|
||||||
@@ -1148,6 +1232,6 @@
|
|||||||
|
|
||||||
// --- Start the application ---
|
// --- Start the application ---
|
||||||
document.addEventListener('DOMContentLoaded', init);
|
document.addEventListener('DOMContentLoaded', init);
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
Reference in New Issue
Block a user