修复了ui的bug

This commit is contained in:
xunbu
2025-05-16 22:15:40 +08:00
parent b73adc20ff
commit 24f0dd08e1
3 changed files with 563 additions and 517 deletions

15
.idea/workspace.xml generated
View File

@@ -6,10 +6,6 @@
<component name="ChangeListManager"> <component name="ChangeListManager">
<list default="true" id="6b18b44a-df57-4212-a857-9e291ebe5dd2" name="更改" comment=""> <list default="true" id="6b18b44a-df57-4212-a857-9e291ebe5dd2" name="更改" comment="">
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" /> <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/README.md" beforeDir="false" afterPath="$PROJECT_DIR$/README.md" afterDir="false" />
<change beforePath="$PROJECT_DIR$/docutranslate/agents/markdown_agent.py" beforeDir="false" afterPath="$PROJECT_DIR$/docutranslate/agents/markdown_agent.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/docutranslate/app.py" beforeDir="false" afterPath="$PROJECT_DIR$/docutranslate/app.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/pyproject.toml" beforeDir="false" afterPath="$PROJECT_DIR$/pyproject.toml" afterDir="false" />
</list> </list>
<option name="SHOW_DIALOG" value="false" /> <option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" /> <option name="HIGHLIGHT_CONFLICTS" value="true" />
@@ -76,7 +72,7 @@
&quot;RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager&quot;: &quot;true&quot;, &quot;RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager&quot;: &quot;true&quot;,
&quot;RunOnceActivity.git.unshallow&quot;: &quot;true&quot;, &quot;RunOnceActivity.git.unshallow&quot;: &quot;true&quot;,
&quot;git-widget-placeholder&quot;: &quot;main&quot;, &quot;git-widget-placeholder&quot;: &quot;main&quot;,
&quot;last_opened_file_path&quot;: &quot;C:/Users/jxgm/Desktop/FileTranslate/docutranslate/agents&quot;, &quot;last_opened_file_path&quot;: &quot;C:/Users/jxgm/Desktop/FileTranslate/dist/DocuTranslate&quot;,
&quot;node.js.detected.package.eslint&quot;: &quot;true&quot;, &quot;node.js.detected.package.eslint&quot;: &quot;true&quot;,
&quot;node.js.detected.package.tslint&quot;: &quot;true&quot;, &quot;node.js.detected.package.tslint&quot;: &quot;true&quot;,
&quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;, &quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;,
@@ -88,8 +84,8 @@
}</component> }</component>
<component name="RecentsManager"> <component name="RecentsManager">
<key name="CopyFile.RECENT_KEYS"> <key name="CopyFile.RECENT_KEYS">
<recent name="C:\Users\jxgm\Desktop\FileTranslate\docutranslate\agents" />
<recent name="C:\Users\jxgm\Desktop\FileTranslate\dist\DocuTranslate" /> <recent name="C:\Users\jxgm\Desktop\FileTranslate\dist\DocuTranslate" />
<recent name="C:\Users\jxgm\Desktop\FileTranslate\docutranslate\agents" />
<recent name="C:\Users\jxgm\Desktop\FileTranslate\dist" /> <recent name="C:\Users\jxgm\Desktop\FileTranslate\dist" />
<recent name="C:\Users\jxgm\Desktop\FileTranslate\dist\app" /> <recent name="C:\Users\jxgm\Desktop\FileTranslate\dist\app" />
<recent name="C:\Users\jxgm\Desktop\FileTranslate\tests\files" /> <recent name="C:\Users\jxgm\Desktop\FileTranslate\tests\files" />
@@ -624,6 +620,7 @@
<workItem from="1747299661166" duration="4649000" /> <workItem from="1747299661166" duration="4649000" />
<workItem from="1747311432043" duration="2883000" /> <workItem from="1747311432043" duration="2883000" />
<workItem from="1747380029603" duration="10381000" /> <workItem from="1747380029603" duration="10381000" />
<workItem from="1747403732304" duration="980000" />
</task> </task>
<servers /> <servers />
</component> </component>
@@ -631,7 +628,7 @@
<option name="version" value="3" /> <option name="version" value="3" />
</component> </component>
<component name="com.intellij.coverage.CoverageDataManagerImpl"> <component name="com.intellij.coverage.CoverageDataManagerImpl">
<SUITE FILE_PATH="coverage/filetranslate$app_test__1_.coverage" NAME="app_test (1) 覆盖结果" MODIFIED="1747399020079" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/tests" /> <SUITE FILE_PATH="coverage/filetranslate$app_test__1_.coverage" NAME="app_test (1) 覆盖结果" MODIFIED="1747404483499" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/tests" />
<SUITE FILE_PATH="coverage/filetranslate$test.coverage" NAME="test 覆盖结果" MODIFIED="1747301959211" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/tests" /> <SUITE FILE_PATH="coverage/filetranslate$test.coverage" NAME="test 覆盖结果" MODIFIED="1747301959211" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/tests" />
<SUITE FILE_PATH="coverage/filetranslate$convert.coverage" NAME="convert 覆盖结果" MODIFIED="1746963490689" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/docutranslate/utils" /> <SUITE FILE_PATH="coverage/filetranslate$convert.coverage" NAME="convert 覆盖结果" MODIFIED="1746963490689" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/docutranslate/utils" />
<SUITE FILE_PATH="coverage/PDFtranslate$PDFtranslater__1_.coverage" NAME="PDFtranslater (1) 覆盖结果" MODIFIED="1746633258205" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/pdftranslate_packages" /> <SUITE FILE_PATH="coverage/PDFtranslate$PDFtranslater__1_.coverage" NAME="PDFtranslater (1) 覆盖结果" MODIFIED="1746633258205" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/pdftranslate_packages" />
@@ -642,9 +639,9 @@
<SUITE FILE_PATH="coverage/filetranslate$test3.coverage" NAME="test3 覆盖结果" MODIFIED="1746884110572" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/tests" /> <SUITE FILE_PATH="coverage/filetranslate$test3.coverage" NAME="test3 覆盖结果" MODIFIED="1746884110572" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/tests" />
<SUITE FILE_PATH="coverage/filetranslate$app__1_.coverage" NAME="app (1) 覆盖结果" MODIFIED="1747136094477" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/tests" /> <SUITE FILE_PATH="coverage/filetranslate$app__1_.coverage" NAME="app (1) 覆盖结果" MODIFIED="1747136094477" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/tests" />
<SUITE FILE_PATH="coverage/filetranslate$markdown_splitter.coverage" NAME="markdown_splitter 覆盖结果" MODIFIED="1746805063874" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/docutranslate/utils" /> <SUITE FILE_PATH="coverage/filetranslate$markdown_splitter.coverage" NAME="markdown_splitter 覆盖结果" MODIFIED="1746805063874" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/docutranslate/utils" />
<SUITE FILE_PATH="coverage/filetranslate$agent.coverage" NAME="agent 覆盖结果" MODIFIED="1746805293987" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/docutranslate/Agents" />
<SUITE FILE_PATH="coverage/filetranslate$PDFtranslater__2_.coverage" NAME="PDFtranslater (2) 覆盖结果" MODIFIED="1746679546680" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/filetranslate_packages" />
<SUITE FILE_PATH="coverage/PDFtranslate$test.coverage" NAME="test 覆盖结果" MODIFIED="1746629433597" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/tests" /> <SUITE FILE_PATH="coverage/PDFtranslate$test.coverage" NAME="test 覆盖结果" MODIFIED="1746629433597" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/tests" />
<SUITE FILE_PATH="coverage/filetranslate$PDFtranslater__2_.coverage" NAME="PDFtranslater (2) 覆盖结果" MODIFIED="1746679546680" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/filetranslate_packages" />
<SUITE FILE_PATH="coverage/filetranslate$agent.coverage" NAME="agent 覆盖结果" MODIFIED="1746805293987" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/docutranslate/Agents" />
<SUITE FILE_PATH="coverage/filetranslate$agent_utils.coverage" NAME="agent_utils 覆盖结果" MODIFIED="1746708534311" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/docutranslate/utils" /> <SUITE FILE_PATH="coverage/filetranslate$agent_utils.coverage" NAME="agent_utils 覆盖结果" MODIFIED="1746708534311" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/docutranslate/utils" />
<SUITE FILE_PATH="coverage/filetranslate$.coverage" NAME="切分测试 覆盖结果" MODIFIED="1747187128847" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/tests" /> <SUITE FILE_PATH="coverage/filetranslate$.coverage" NAME="切分测试 覆盖结果" MODIFIED="1747187128847" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/tests" />
<SUITE FILE_PATH="coverage/filetranslate$test1.coverage" NAME="test1 覆盖结果" MODIFIED="1746936018440" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/tests" /> <SUITE FILE_PATH="coverage/filetranslate$test1.coverage" NAME="test1 覆盖结果" MODIFIED="1746936018440" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/tests" />

View File

@@ -15,10 +15,9 @@ from docutranslate.logger import translater_logger
# --- HTML模板 (JS part needs modification) --- # --- HTML模板 (JS part needs modification) ---
# language=HTML # language=HTML
HTML_TEMPLATE = """ HTML_TEMPLATE = """<!DOCTYPE html>
<!DOCTYPE html> <html lang="zh-CN">
<html lang="zh-CN"> <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>
@@ -138,21 +137,42 @@ HTML_TEMPLATE = """
} }
#fileDropArea.drag-over { #fileDropArea.drag-over {
border-color: #007bff; /* Pico primary color */ border-color: var(--pico-primary-focus); /* Pico primary color */
background-color: rgba(0, 123, 255, 0.05); background-color: var(--pico-primary-background);
} }
#fileDropArea p { #fileDropArea.file-selected {
border-color: var(--pico-form-element-valid-border-color, #2e7d32); /* Pico success color */
background-color: var(--pico-form-element-valid-background-color, #e8f5e9); /* Light green */
}
#fileDropArea p { /* General style for <p> inside drop area */
margin: 0.5rem 0; margin: 0.5rem 0;
color: #555; color: #555;
} \ }
\ /* #fileDropPrompt will be hidden/shown by JS using .hidden class */
#fileNameDisplay { #fileNameDisplay {
margin-top: 0.5rem; margin-top: 0.5rem;
font-style: italic; font-style: italic;
color: #333; color: #333;
} }
#fileNameDisplay.has-file {
font-style: normal;
font-weight: bold;
color: var(--pico-form-element-valid-border-color, #1a531d); /* Darker green or success color */
}
#fileDropArea.input-error {
border-color: var(--pico-form-element-invalid-border-color, #d32f2f) !important;
}
#fileNameDisplay.input-error-text {
color: var(--pico-form-element-invalid-border-color, #d32f2f) !important;
font-weight: bold;
}
@media (max-width: 768px) { @media (max-width: 768px) {
.form-grid { .form-grid {
@@ -160,9 +180,9 @@ HTML_TEMPLATE = """
} }
} }
</style> </style>
</head> </head>
<body> <body>
<main class="container"> <main class="container">
<h1> <h1>
<a href="https://github.com/xunbu/docutranslate" target="_blank">DocuTranslate</a> <a href="https://github.com/xunbu/docutranslate" target="_blank">DocuTranslate</a>
</h1> </h1>
@@ -173,7 +193,7 @@ HTML_TEMPLATE = """
<label for="file">文档选择</label> <label for="file">文档选择</label>
<div id="fileDropArea"> <div id="fileDropArea">
<input type="file" id="file" name="file" required style="display: none;"> <input type="file" id="file" name="file" required style="display: none;">
<p>点击此处选择文件,或将文件拖拽到这里</p> <p id="fileDropPrompt">点击此处选择文件,或将文件拖拽到这里</p>
<div id="fileNameDisplay">未选择文件</div> <div id="fileNameDisplay">未选择文件</div>
</div> </div>
</div> </div>
@@ -255,8 +275,8 @@ HTML_TEMPLATE = """
</div> </div>
<h4 style="margin-top: 1.5rem;">运行日志</h4> <h4 style="margin-top: 1.5rem;">运行日志</h4>
<div class="log-area" id="logArea"></div> <div class="log-area" id="logArea"></div>
</main> </main>
<div id="previewModal" class="modal"> <div id="previewModal" class="modal">
<div class="modal-content"> <div class="modal-content">
<span id="closeModalBtn" style="cursor:pointer; float:right;">×</span> <span id="closeModalBtn" style="cursor:pointer; float:right;">×</span>
<h3>HTML 预览</h3> <h3>HTML 预览</h3>
@@ -266,10 +286,10 @@ HTML_TEMPLATE = """
<button id="closePreviewBtn" class="outline">关闭</button> <button id="closePreviewBtn" class="outline">关闭</button>
</div> </div>
</div> </div>
</div> </div>
<iframe id="printFrame" style="display:none;"></iframe> <iframe id="printFrame" style="display:none;"></iframe>
<script> <script>
const platformSelect = document.getElementById('platform_select'); const platformSelect = document.getElementById('platform_select');
const baseUrlGroup = document.getElementById('baseUrlGroup'); const baseUrlGroup = document.getElementById('baseUrlGroup');
const baseUrlInput = document.getElementById('base_url'); const baseUrlInput = document.getElementById('base_url');
@@ -299,6 +319,7 @@ HTML_TEMPLATE = """
const fileInput = document.getElementById('file'); const fileInput = document.getElementById('file');
const fileDropArea = document.getElementById('fileDropArea'); const fileDropArea = document.getElementById('fileDropArea');
const fileNameDisplay = document.getElementById('fileNameDisplay'); const fileNameDisplay = document.getElementById('fileNameDisplay');
const fileDropPrompt = document.getElementById('fileDropPrompt'); // <-- 获取提示文字元素
let logPollIntervalId = null; let logPollIntervalId = null;
@@ -374,8 +395,18 @@ HTML_TEMPLATE = """
fileInput.addEventListener('change', () => { fileInput.addEventListener('change', () => {
if (fileInput.files.length > 0) { if (fileInput.files.length > 0) {
fileNameDisplay.textContent = `已选择: ${fileInput.files[0].name}`; fileNameDisplay.textContent = `已选择: ${fileInput.files[0].name}`;
fileDropArea.classList.add('file-selected');
fileNameDisplay.classList.add('has-file');
fileDropPrompt.classList.add('hidden'); // <-- 隐藏提示文字
fileDropArea.classList.remove('input-error');
fileNameDisplay.classList.remove('input-error-text');
statusMsg.textContent = '';
statusMsg.className = '';
} else { } else {
fileNameDisplay.textContent = '未选择文件'; fileNameDisplay.textContent = '未选择文件';
fileDropArea.classList.remove('file-selected');
fileNameDisplay.classList.remove('has-file');
fileDropPrompt.classList.remove('hidden'); // <-- 显示提示文字
} }
}); });
@@ -390,7 +421,9 @@ HTML_TEMPLATE = """
['dragenter', 'dragover'].forEach(eventName => { ['dragenter', 'dragover'].forEach(eventName => {
fileDropArea.addEventListener(eventName, () => { fileDropArea.addEventListener(eventName, () => {
if (!fileDropArea.classList.contains('file-selected')) {
fileDropArea.classList.add('drag-over'); fileDropArea.classList.add('drag-over');
}
}, false); }, false);
}); });
@@ -405,14 +438,12 @@ HTML_TEMPLATE = """
const files = dt.files; const files = dt.files;
if (files.length > 0) { if (files.length > 0) {
fileInput.files = files; // Assign dropped files to the input fileInput.files = files;
fileNameDisplay.textContent = `已选择: ${files[0].name}`;
// Manually trigger change event for any listeners on fileInput
const event = new Event('change', {bubbles: true}); const event = new Event('change', {bubbles: true});
fileInput.dispatchEvent(event); fileInput.dispatchEvent(event);
} }
}, false); \ }, false);
\
// --- End Drag and Drop --- // --- End Drag and Drop ---
@@ -455,8 +486,8 @@ HTML_TEMPLATE = """
submitButton.disabled = false; submitButton.disabled = false;
submitButton.removeAttribute('aria-busy'); submitButton.removeAttribute('aria-busy');
submitButton.textContent = '开始翻译'; submitButton.textContent = '开始翻译';
submitButton.classList.remove('secondary', 'contrast'); // PicoCSS: remove secondary/contrast submitButton.classList.remove('secondary', 'contrast');
submitButton.classList.add('primary'); // PicoCSS: add primary submitButton.classList.add('primary');
isTranslating = false; isTranslating = false;
if (status.download_ready && !status.error_flag) { if (status.download_ready && !status.error_flag) {
@@ -546,9 +577,9 @@ HTML_TEMPLATE = """
} else { // Task is still processing } else { // Task is still processing
submitButton.textContent = '取消翻译'; submitButton.textContent = '取消翻译';
submitButton.classList.remove('primary'); submitButton.classList.remove('primary');
submitButton.classList.add('secondary'); // PicoCSS: use secondary for cancel submitButton.classList.add('secondary');
isTranslating = true; isTranslating = true;
submitButton.disabled = false; // Enable button to allow cancellation submitButton.disabled = false;
submitButton.removeAttribute('aria-busy'); submitButton.removeAttribute('aria-busy');
downloadBtns.style.display = 'none'; downloadBtns.style.display = 'none';
} }
@@ -563,8 +594,8 @@ HTML_TEMPLATE = """
stopPolling(); stopPolling();
lastLogCount = 0; lastLogCount = 0;
logArea.innerHTML = ''; logArea.innerHTML = '';
pollLogs(); // Initial poll pollLogs();
pollStatus(); // Initial poll pollStatus();
logPollIntervalId = setInterval(pollLogs, 2000); logPollIntervalId = setInterval(pollLogs, 2000);
statusPollIntervalId = setInterval(pollStatus, 1500); statusPollIntervalId = setInterval(pollStatus, 1500);
} }
@@ -579,7 +610,7 @@ HTML_TEMPLATE = """
function loadSettings() { function loadSettings() {
platformSelect.value = getFromStorage('translator_last_platform', 'custom'); platformSelect.value = getFromStorage('translator_last_platform', 'custom');
updatePlatformUI(); // This will also load API key and model for the platform updatePlatformUI();
toLangSelect.value = getFromStorage('translator_to_lang', '中文'); toLangSelect.value = getFromStorage('translator_to_lang', '中文');
formulaCheckbox.checked = getFromStorage('translator_formula_ocr') === 'true'; formulaCheckbox.checked = getFromStorage('translator_formula_ocr') === 'true';
codeCheckbox.checked = getFromStorage('translator_code_ocr') === 'true'; codeCheckbox.checked = getFromStorage('translator_code_ocr') === 'true';
@@ -598,11 +629,10 @@ HTML_TEMPLATE = """
if (response.ok && result.cancelled) { if (response.ok && result.cancelled) {
statusMsg.textContent = result.message || '取消请求已发送。'; statusMsg.textContent = result.message || '取消请求已发送。';
statusMsg.className = ''; // Neutral message statusMsg.className = '';
} else { } else {
statusMsg.textContent = result.message || '取消失败。'; statusMsg.textContent = result.message || '取消失败。';
statusMsg.className = 'error-message'; statusMsg.className = 'error-message';
// Re-enable button as "Cancel Translation" if cancellation failed but task might still be running
submitButton.disabled = false; submitButton.disabled = false;
submitButton.textContent = '取消翻译'; submitButton.textContent = '取消翻译';
submitButton.removeAttribute('aria-busy'); submitButton.removeAttribute('aria-busy');
@@ -612,10 +642,9 @@ HTML_TEMPLATE = """
statusMsg.textContent = '取消请求发送失败。'; statusMsg.textContent = '取消请求发送失败。';
statusMsg.className = 'error-message'; statusMsg.className = 'error-message';
submitButton.disabled = false; submitButton.disabled = false;
submitButton.textContent = '取消翻译'; // Or '开始翻译' if we assume it stopped submitButton.textContent = '取消翻译';
submitButton.removeAttribute('aria-busy'); submitButton.removeAttribute('aria-busy');
} }
// Polling will handle the final state update for the button and status.
} }
form.addEventListener('submit', async function (event) { form.addEventListener('submit', async function (event) {
@@ -626,18 +655,30 @@ HTML_TEMPLATE = """
return; return;
} }
// Validate file input
if (fileInput.files.length === 0) { if (fileInput.files.length === 0) {
statusMsg.textContent = '请选择一个文件进行翻译。'; statusMsg.textContent = '请选择一个文件进行翻译。';
statusMsg.className = 'error-message'; statusMsg.className = 'error-message';
fileNameDisplay.textContent = '请选择文件!'; fileNameDisplay.textContent = '请选择文件!';
fileDropArea.classList.add('error-message'); // Optional: add error style to drop area fileNameDisplay.classList.add('input-error-text');
setTimeout(() => fileDropArea.classList.remove('error-message'), 2000); fileDropArea.classList.add('input-error');
fileDropPrompt.classList.remove('hidden'); // 确保错误时提示文字可见
setTimeout(() => {
fileDropArea.classList.remove('input-error');
fileNameDisplay.classList.remove('input-error-text');
if (fileNameDisplay.textContent === '请选择文件!') {
fileNameDisplay.textContent = '未选择文件';
}
// 如果没有文件被选中,提示文字应该保持可见
if (fileInput.files.length === 0) {
fileDropPrompt.classList.remove('hidden');
}
}, 3000);
return; return;
} }
stopPolling(); // Stop any existing polling stopPolling();
submitButton.disabled = true; submitButton.disabled = true;
submitButton.setAttribute('aria-busy', 'true'); submitButton.setAttribute('aria-busy', 'true');
submitButton.textContent = '初始化...'; submitButton.textContent = '初始化...';
@@ -654,12 +695,12 @@ HTML_TEMPLATE = """
if (response.ok && result.task_started) { if (response.ok && result.task_started) {
statusMsg.textContent = result.message || '任务已开始,正在处理...'; statusMsg.textContent = result.message || '任务已开始,正在处理...';
statusMsg.className = ''; statusMsg.className = '';
submitButton.textContent = '取消翻译'; // Change button text submitButton.textContent = '取消翻译';
submitButton.classList.remove('primary'); submitButton.classList.remove('primary');
submitButton.classList.add('secondary'); // Change button style submitButton.classList.add('secondary');
isTranslating = true; // Set translation flag isTranslating = true;
submitButton.removeAttribute('aria-busy'); // No longer busy submitting, now in "cancellable" state submitButton.removeAttribute('aria-busy');
startPolling(); // Start polling for status and logs startPolling();
} else { } else {
statusMsg.textContent = result.message || `请求失败 (${response.status})`; statusMsg.textContent = result.message || `请求失败 (${response.status})`;
statusMsg.className = 'error-message'; statusMsg.className = 'error-message';
@@ -679,9 +720,8 @@ HTML_TEMPLATE = """
} }
}); });
</script> </script>
</body> </body>
</html> \ </html>"""
"""
app = FastAPI() app = FastAPI()
@@ -734,16 +774,25 @@ class QueueAndHistoryHandler(logging.Handler):
@app.on_event("startup") @app.on_event("startup")
async def startup_event(): async def startup_event():
app.state.main_event_loop = asyncio.get_running_loop() app.state.main_event_loop = asyncio.get_running_loop()
if translater_logger.hasHandlers():
translater_logger.handlers.clear() # Clear ALL existing handlers (not just checking if any exist)
for handler in translater_logger.handlers[:]:
translater_logger.removeHandler(handler)
# Configure the new handler
queue_handler = QueueAndHistoryHandler(log_queue, log_history, MAX_LOG_HISTORY) queue_handler = QueueAndHistoryHandler(log_queue, log_history, MAX_LOG_HISTORY)
queue_handler.setLevel(logging.INFO) queue_handler.setLevel(logging.INFO)
queue_handler.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')) queue_handler.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s'))
translater_logger.addHandler(queue_handler)
translater_logger.propagate = False # 非常重要阻止日志向上传播到root logger
translater_logger.setLevel(logging.INFO) # 确保 translater_logger 本身的级别是 INFO
translater_logger.info("应用启动完成,日志队列/历史处理器已清除并重新配置。") # Add the handler and configure the logger
translater_logger.addHandler(queue_handler)
translater_logger.propagate = False
translater_logger.setLevel(logging.INFO)
# Clear the log history for a fresh start
log_history.clear()
translater_logger.info("应用启动完成,日志队列/历史处理器已正确配置。")
# --- Background Task Logic --- # --- Background Task Logic ---
async def _perform_translation(params: Dict[str, Any], file_contents: bytes, original_filename: str): async def _perform_translation(params: Dict[str, Any], file_contents: bytes, original_filename: str):

View File

@@ -1,6 +1,6 @@
[project] [project]
name = "docutranslate" name = "docutranslate"
version = "0.2.8" version = "0.2.9"
description = "文件翻译工具" description = "文件翻译工具"
readme = "README.md" readme = "README.md"
requires-python = ">=3.10" requires-python = ">=3.10"