修复了日志重复的问题
This commit is contained in:
13
.idea/workspace.xml
generated
13
.idea/workspace.xml
generated
@@ -6,6 +6,9 @@
|
|||||||
<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$/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" />
|
||||||
@@ -545,10 +548,10 @@
|
|||||||
<recent_temporary>
|
<recent_temporary>
|
||||||
<list>
|
<list>
|
||||||
<item itemvalue="Python.app_test (1)" />
|
<item itemvalue="Python.app_test (1)" />
|
||||||
|
<item itemvalue="Python.app" />
|
||||||
<item itemvalue="Python.test2" />
|
<item itemvalue="Python.test2" />
|
||||||
<item itemvalue="Python.test" />
|
<item itemvalue="Python.test" />
|
||||||
<item itemvalue="Python.切分测试" />
|
<item itemvalue="Python.切分测试" />
|
||||||
<item itemvalue="Python.app_test" />
|
|
||||||
</list>
|
</list>
|
||||||
</recent_temporary>
|
</recent_temporary>
|
||||||
</component>
|
</component>
|
||||||
@@ -628,20 +631,20 @@
|
|||||||
<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="1747441337057" 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="1747448568953" 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" />
|
||||||
<SUITE FILE_PATH="coverage/PDFtranslate$agent_utils.coverage" NAME="agent_utils 覆盖结果" MODIFIED="1746617703678" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/pdftranslate_packages/utils" />
|
<SUITE FILE_PATH="coverage/PDFtranslate$agent_utils.coverage" NAME="agent_utils 覆盖结果" MODIFIED="1746617703678" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/pdftranslate_packages/utils" />
|
||||||
<SUITE FILE_PATH="coverage/filetranslate$app2.coverage" NAME="app2 覆盖结果" MODIFIED="1747108180309" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/docutranslate" />
|
<SUITE FILE_PATH="coverage/filetranslate$app2.coverage" NAME="app2 覆盖结果" MODIFIED="1747108180309" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/docutranslate" />
|
||||||
<SUITE FILE_PATH="coverage/filetranslate$app.coverage" NAME="app 覆盖结果" MODIFIED="1747130360790" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/docutranslate" />
|
<SUITE FILE_PATH="coverage/filetranslate$app.coverage" NAME="app 覆盖结果" MODIFIED="1747448464521" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/docutranslate" />
|
||||||
<SUITE FILE_PATH="coverage/PDFtranslate$markdown_splitter.coverage" NAME="markdown_splitter 覆盖结果" MODIFIED="1746599883603" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/pdftranslate_packages/utils" />
|
<SUITE FILE_PATH="coverage/PDFtranslate$markdown_splitter.coverage" NAME="markdown_splitter 覆盖结果" MODIFIED="1746599883603" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/pdftranslate_packages/utils" />
|
||||||
<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/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.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/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" />
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ class MDTranslateAgent(Agent):
|
|||||||
形如<ph-abc123>的占位符不要改变
|
形如<ph-abc123>的占位符不要改变
|
||||||
code、latex和HTML只翻译说明文字,其余保持原文
|
code、latex和HTML只翻译说明文字,其余保持原文
|
||||||
公式必须表示为合法的latex公式,行内公式需被$正确包裹
|
公式必须表示为合法的latex公式,行内公式需被$正确包裹
|
||||||
去掉异常字词
|
去掉异常字词,修复错误格式
|
||||||
# 输出
|
# 输出
|
||||||
翻译后的markdown纯文本(不是markdown代码块)
|
翻译后的markdown纯文本(不是markdown代码块)
|
||||||
# 示例
|
# 示例
|
||||||
@@ -65,7 +65,7 @@ code、latex和HTML只翻译说明文字,其余保持原文
|
|||||||
hello<ph-aaaaaa>, what's your name?
|
hello<ph-aaaaaa>, what's your name?
|
||||||
输出:
|
输出:
|
||||||
你好<ph-aaaaaa>,你叫什么名字?
|
你好<ph-aaaaaa>,你叫什么名字?
|
||||||
## 公式要为合法latex(行内公式使用$包裹)
|
## 公式要为合法latex(行内公式必须使用$包裹)
|
||||||
输入:
|
输入:
|
||||||
The equation is E=mc 2. This is famous.
|
The equation is E=mc 2. This is famous.
|
||||||
({{c_0,c_1,c^2}})is a set.
|
({{c_0,c_1,c^2}})is a set.
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ HTML_TEMPLATE = """<!DOCTYPE html>
|
|||||||
background-color: #f5f5f5;
|
background-color: #f5f5f5;
|
||||||
border: 1px solid #e0e0e0;
|
border: 1px solid #e0e0e0;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
height: 200px;
|
height: 300px;
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
@@ -149,7 +149,8 @@ HTML_TEMPLATE = """<!DOCTYPE html>
|
|||||||
#fileDropArea p { /* General style for <p> inside drop area */
|
#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 */
|
/* #fileDropPrompt will be hidden/shown by JS using .hidden class */
|
||||||
|
|
||||||
|
|
||||||
@@ -167,7 +168,8 @@ HTML_TEMPLATE = """<!DOCTYPE html>
|
|||||||
|
|
||||||
#fileDropArea.input-error {
|
#fileDropArea.input-error {
|
||||||
border-color: var(--pico-form-element-invalid-border-color, #d32f2f) !important;
|
border-color: var(--pico-form-element-invalid-border-color, #d32f2f) !important;
|
||||||
}
|
} \
|
||||||
|
|
||||||
#fileNameDisplay.input-error-text {
|
#fileNameDisplay.input-error-text {
|
||||||
color: var(--pico-form-element-invalid-border-color, #d32f2f) !important;
|
color: var(--pico-form-element-invalid-border-color, #d32f2f) !important;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
@@ -315,17 +317,15 @@ HTML_TEMPLATE = """<!DOCTYPE html>
|
|||||||
const closePreviewBtn = document.getElementById('closePreviewBtn');
|
const closePreviewBtn = document.getElementById('closePreviewBtn');
|
||||||
const printFromPreview = document.getElementById('printFromPreview');
|
const printFromPreview = document.getElementById('printFromPreview');
|
||||||
|
|
||||||
// File input and drag-drop elements
|
|
||||||
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'); // <-- 获取提示文字元素
|
const fileDropPrompt = document.getElementById('fileDropPrompt');
|
||||||
|
|
||||||
|
|
||||||
let logPollIntervalId = null;
|
let logPollIntervalId = null;
|
||||||
let statusPollIntervalId = null;
|
let statusPollIntervalId = null;
|
||||||
let lastLogCount = 0;
|
// let lastLogCount = 0; // No longer needed for fetching logs
|
||||||
let isTranslating = false; // Flag to track translation state for cancel button
|
let isTranslating = false;
|
||||||
|
|
||||||
function saveToStorage(key, value) {
|
function saveToStorage(key, value) {
|
||||||
try {
|
try {
|
||||||
@@ -387,9 +387,8 @@ HTML_TEMPLATE = """<!DOCTYPE html>
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// --- Drag and Drop File Handling ---
|
|
||||||
fileDropArea.addEventListener('click', () => {
|
fileDropArea.addEventListener('click', () => {
|
||||||
fileInput.click(); // Trigger click on hidden file input
|
fileInput.click();
|
||||||
});
|
});
|
||||||
|
|
||||||
fileInput.addEventListener('change', () => {
|
fileInput.addEventListener('change', () => {
|
||||||
@@ -397,7 +396,7 @@ HTML_TEMPLATE = """<!DOCTYPE html>
|
|||||||
fileNameDisplay.textContent = `已选择: ${fileInput.files[0].name}`;
|
fileNameDisplay.textContent = `已选择: ${fileInput.files[0].name}`;
|
||||||
fileDropArea.classList.add('file-selected');
|
fileDropArea.classList.add('file-selected');
|
||||||
fileNameDisplay.classList.add('has-file');
|
fileNameDisplay.classList.add('has-file');
|
||||||
fileDropPrompt.classList.add('hidden'); // <-- 隐藏提示文字
|
fileDropPrompt.classList.add('hidden');
|
||||||
fileDropArea.classList.remove('input-error');
|
fileDropArea.classList.remove('input-error');
|
||||||
fileNameDisplay.classList.remove('input-error-text');
|
fileNameDisplay.classList.remove('input-error-text');
|
||||||
statusMsg.textContent = '';
|
statusMsg.textContent = '';
|
||||||
@@ -406,7 +405,7 @@ HTML_TEMPLATE = """<!DOCTYPE html>
|
|||||||
fileNameDisplay.textContent = '未选择文件';
|
fileNameDisplay.textContent = '未选择文件';
|
||||||
fileDropArea.classList.remove('file-selected');
|
fileDropArea.classList.remove('file-selected');
|
||||||
fileNameDisplay.classList.remove('has-file');
|
fileNameDisplay.classList.remove('has-file');
|
||||||
fileDropPrompt.classList.remove('hidden'); // <-- 显示提示文字
|
fileDropPrompt.classList.remove('hidden');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -444,12 +443,10 @@ HTML_TEMPLATE = """<!DOCTYPE html>
|
|||||||
}
|
}
|
||||||
}, false);
|
}, false);
|
||||||
|
|
||||||
// --- End Drag and Drop ---
|
|
||||||
|
|
||||||
|
|
||||||
async function pollLogs() {
|
async function pollLogs() {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/get-logs?since=${lastLogCount}`);
|
// const response = await fetch(`/get-logs?since=${lastLogCount}`); // OLD
|
||||||
|
const response = await fetch('/get-logs'); // NEW: No 'since' parameter
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
console.warn(`Log polling failed: ${response.status}`);
|
console.warn(`Log polling failed: ${response.status}`);
|
||||||
return;
|
return;
|
||||||
@@ -460,9 +457,9 @@ HTML_TEMPLATE = """<!DOCTYPE html>
|
|||||||
const escapedLog = log.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
const escapedLog = log.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
||||||
logArea.innerHTML += escapedLog + "<br>";
|
logArea.innerHTML += escapedLog + "<br>";
|
||||||
});
|
});
|
||||||
logArea.scrollTop = logArea.scrollHeight;
|
logArea.scrollTop = logArea.scrollHeight; // Scroll to bottom
|
||||||
}
|
}
|
||||||
lastLogCount = data.total_count;
|
// lastLogCount = data.total_count; // OLD: No longer tracking count this way
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn("Error polling logs:", error);
|
console.warn("Error polling logs:", error);
|
||||||
}
|
}
|
||||||
@@ -574,7 +571,7 @@ HTML_TEMPLATE = """<!DOCTYPE html>
|
|||||||
} else {
|
} else {
|
||||||
downloadBtns.style.display = 'none';
|
downloadBtns.style.display = 'none';
|
||||||
}
|
}
|
||||||
} else { // Task is still processing
|
} else {
|
||||||
submitButton.textContent = '取消翻译';
|
submitButton.textContent = '取消翻译';
|
||||||
submitButton.classList.remove('primary');
|
submitButton.classList.remove('primary');
|
||||||
submitButton.classList.add('secondary');
|
submitButton.classList.add('secondary');
|
||||||
@@ -592,10 +589,10 @@ HTML_TEMPLATE = """<!DOCTYPE html>
|
|||||||
|
|
||||||
function startPolling() {
|
function startPolling() {
|
||||||
stopPolling();
|
stopPolling();
|
||||||
lastLogCount = 0;
|
// lastLogCount = 0; // No longer needed
|
||||||
logArea.innerHTML = '';
|
logArea.innerHTML = ''; // Clear log area for new task
|
||||||
pollLogs();
|
pollLogs(); // Initial poll for logs
|
||||||
pollStatus();
|
pollStatus(); // Initial poll for status
|
||||||
logPollIntervalId = setInterval(pollLogs, 2000);
|
logPollIntervalId = setInterval(pollLogs, 2000);
|
||||||
statusPollIntervalId = setInterval(pollStatus, 1500);
|
statusPollIntervalId = setInterval(pollStatus, 1500);
|
||||||
}
|
}
|
||||||
@@ -605,7 +602,6 @@ HTML_TEMPLATE = """<!DOCTYPE html>
|
|||||||
if (statusPollIntervalId) clearInterval(statusPollIntervalId);
|
if (statusPollIntervalId) clearInterval(statusPollIntervalId);
|
||||||
logPollIntervalId = null;
|
logPollIntervalId = null;
|
||||||
statusPollIntervalId = null;
|
statusPollIntervalId = null;
|
||||||
setTimeout(pollLogs, 100);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadSettings() {
|
function loadSettings() {
|
||||||
@@ -617,7 +613,6 @@ HTML_TEMPLATE = """<!DOCTYPE html>
|
|||||||
refineCheckbox.checked = getFromStorage('translator_refine_markdown') === 'true';
|
refineCheckbox.checked = getFromStorage('translator_refine_markdown') === 'true';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async function cancelTranslation() {
|
async function cancelTranslation() {
|
||||||
submitButton.disabled = true;
|
submitButton.disabled = true;
|
||||||
submitButton.textContent = '正在取消...';
|
submitButton.textContent = '正在取消...';
|
||||||
@@ -661,14 +656,13 @@ HTML_TEMPLATE = """<!DOCTYPE html>
|
|||||||
fileNameDisplay.textContent = '请选择文件!';
|
fileNameDisplay.textContent = '请选择文件!';
|
||||||
fileNameDisplay.classList.add('input-error-text');
|
fileNameDisplay.classList.add('input-error-text');
|
||||||
fileDropArea.classList.add('input-error');
|
fileDropArea.classList.add('input-error');
|
||||||
fileDropPrompt.classList.remove('hidden'); // 确保错误时提示文字可见
|
fileDropPrompt.classList.remove('hidden');
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
fileDropArea.classList.remove('input-error');
|
fileDropArea.classList.remove('input-error');
|
||||||
fileNameDisplay.classList.remove('input-error-text');
|
fileNameDisplay.classList.remove('input-error-text');
|
||||||
if (fileNameDisplay.textContent === '请选择文件!') {
|
if (fileNameDisplay.textContent === '请选择文件!') {
|
||||||
fileNameDisplay.textContent = '未选择文件';
|
fileNameDisplay.textContent = '未选择文件';
|
||||||
}
|
}
|
||||||
// 如果没有文件被选中,提示文字应该保持可见
|
|
||||||
if (fileInput.files.length === 0) {
|
if (fileInput.files.length === 0) {
|
||||||
fileDropPrompt.classList.remove('hidden');
|
fileDropPrompt.classList.remove('hidden');
|
||||||
}
|
}
|
||||||
@@ -677,7 +671,6 @@ HTML_TEMPLATE = """<!DOCTYPE html>
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
stopPolling();
|
stopPolling();
|
||||||
submitButton.disabled = true;
|
submitButton.disabled = true;
|
||||||
submitButton.setAttribute('aria-busy', 'true');
|
submitButton.setAttribute('aria-busy', 'true');
|
||||||
@@ -686,7 +679,7 @@ HTML_TEMPLATE = """<!DOCTYPE html>
|
|||||||
statusMsg.textContent = '正在提交任务...';
|
statusMsg.textContent = '正在提交任务...';
|
||||||
statusMsg.className = '';
|
statusMsg.className = '';
|
||||||
downloadBtns.style.display = 'none';
|
downloadBtns.style.display = 'none';
|
||||||
lastLogCount = 0;
|
// lastLogCount = 0; // No longer needed
|
||||||
|
|
||||||
const formData = new FormData(form);
|
const formData = new FormData(form);
|
||||||
try {
|
try {
|
||||||
@@ -726,7 +719,7 @@ HTML_TEMPLATE = """<!DOCTYPE html>
|
|||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
|
|
||||||
# --- 全局配置 ---
|
# --- 全局配置 ---
|
||||||
log_queue = asyncio.Queue()
|
log_queue: Optional[asyncio.Queue] = None # Will be initialized in startup_event
|
||||||
current_state: Dict[str, Any] = {
|
current_state: Dict[str, Any] = {
|
||||||
"is_processing": False,
|
"is_processing": False,
|
||||||
"status_message": "空闲",
|
"status_message": "空闲",
|
||||||
@@ -737,63 +730,74 @@ current_state: Dict[str, Any] = {
|
|||||||
"original_filename_stem": None,
|
"original_filename_stem": None,
|
||||||
"task_start_time": 0,
|
"task_start_time": 0,
|
||||||
"task_end_time": 0,
|
"task_end_time": 0,
|
||||||
"current_task_ref": None, # Stores the asyncio.Task object
|
"current_task_ref": None,
|
||||||
}
|
}
|
||||||
templates = Jinja2Templates(directory=".")
|
templates = Jinja2Templates(directory=".")
|
||||||
MAX_LOG_HISTORY = 200
|
MAX_LOG_HISTORY = 200 # Max items for the persistent log_history list
|
||||||
log_history: List[str] = []
|
log_history: List[str] = [] # Keeps a longer history, not directly for "unread"
|
||||||
|
|
||||||
|
|
||||||
# --- 日志处理器 ---
|
# --- 日志处理器 ---
|
||||||
class QueueAndHistoryHandler(logging.Handler):
|
class QueueAndHistoryHandler(logging.Handler):
|
||||||
def __init__(self, queue: asyncio.Queue, history: List[str], max_history: int):
|
def __init__(self, queue_ref: asyncio.Queue, history_list_ref: List[str], max_history_items: int):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.queue = queue
|
self.queue = queue_ref
|
||||||
self.history = history
|
self.history_list = history_list_ref
|
||||||
self.max_history = max_history
|
self.max_history = max_history_items
|
||||||
|
|
||||||
def emit(self, record: logging.LogRecord):
|
def emit(self, record: logging.LogRecord):
|
||||||
log_entry = self.format(record)
|
log_entry = self.format(record)
|
||||||
print(log_entry)
|
|
||||||
self.history.append(log_entry)
|
# Add to the persistent history (capped)
|
||||||
if len(self.history) > self.max_history:
|
self.history_list.append(log_entry)
|
||||||
del self.history[:len(self.history) - self.max_history]
|
if len(self.history_list) > self.max_history:
|
||||||
|
del self.history_list[:len(self.history_list) - self.max_history]
|
||||||
|
|
||||||
|
# Add to the "unread" queue for frontend consumption
|
||||||
try:
|
try:
|
||||||
|
# Ensure self.queue is not None (it's initialized at startup)
|
||||||
|
if self.queue is not None:
|
||||||
main_loop = getattr(app.state, "main_event_loop", None)
|
main_loop = getattr(app.state, "main_event_loop", None)
|
||||||
if main_loop and main_loop.is_running():
|
if main_loop and main_loop.is_running():
|
||||||
main_loop.call_soon_threadsafe(self.queue.put_nowait, log_entry)
|
main_loop.call_soon_threadsafe(self.queue.put_nowait, log_entry)
|
||||||
else:
|
else:
|
||||||
# Fallback if loop isn't available or running (e.g. during shutdown)
|
self.queue.put_nowait(log_entry) # Fallback
|
||||||
self.queue.put_nowait(log_entry)
|
else:
|
||||||
|
print(f"CRITICAL: Log queue not initialized. Log: {log_entry}")
|
||||||
|
except asyncio.QueueFull:
|
||||||
|
print(f"Log queue is full. Log dropped: {log_entry}") # Or handle differently
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# Avoid crashing the logger if queue operations fail
|
print(f"Error putting log to queue: {e}. Log: {log_entry}")
|
||||||
print(f"Error putting log to queue: {e}")
|
|
||||||
|
|
||||||
|
|
||||||
# --- 应用生命周期事件 ---
|
# --- 应用生命周期事件 ---
|
||||||
@app.on_event("startup")
|
@app.on_event("startup")
|
||||||
async def startup_event():
|
async def startup_event():
|
||||||
|
global log_queue
|
||||||
app.state.main_event_loop = asyncio.get_running_loop()
|
app.state.main_event_loop = asyncio.get_running_loop()
|
||||||
|
log_queue = asyncio.Queue() # Initialize the global log_queue
|
||||||
|
|
||||||
# 清除所有现有的处理器
|
|
||||||
for handler in translater_logger.handlers[:]:
|
for handler in translater_logger.handlers[:]:
|
||||||
translater_logger.removeHandler(handler)
|
translater_logger.removeHandler(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.addHandler(queue_handler)
|
||||||
translater_logger.propagate = False # 这一点很重要,防止日志重复
|
translater_logger.propagate = False
|
||||||
translater_logger.setLevel(logging.INFO)
|
translater_logger.setLevel(logging.INFO)
|
||||||
|
|
||||||
# 清空日志历史,重新开始
|
|
||||||
log_history.clear()
|
log_history.clear()
|
||||||
|
while not log_queue.empty(): # Clear queue just in case
|
||||||
|
try:
|
||||||
|
log_queue.get_nowait()
|
||||||
|
except asyncio.QueueEmpty:
|
||||||
|
break
|
||||||
|
|
||||||
translater_logger.info("应用启动完成,日志队列/历史处理器已正确配置。")
|
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):
|
||||||
global current_state
|
global current_state
|
||||||
@@ -844,19 +848,17 @@ async def _perform_translation(params: Dict[str, Any], file_contents: bytes, ori
|
|||||||
translater_logger.info(f"翻译任务 '{original_filename}' 已被取消 (用时 {duration:.2f} 秒).")
|
translater_logger.info(f"翻译任务 '{original_filename}' 已被取消 (用时 {duration:.2f} 秒).")
|
||||||
current_state.update({
|
current_state.update({
|
||||||
"status_message": f"翻译任务已取消(若有转换任务仍会后台进行) (用时 {duration:.2f} 秒).",
|
"status_message": f"翻译任务已取消(若有转换任务仍会后台进行) (用时 {duration:.2f} 秒).",
|
||||||
"error_flag": False,
|
"error_flag": False, # Cancellation is not an error in this context
|
||||||
"download_ready": False,
|
"download_ready": False,
|
||||||
"markdown_content": None,
|
"markdown_content": None,
|
||||||
"html_content": None,
|
"html_content": None,
|
||||||
"task_end_time": end_time,
|
"task_end_time": end_time,
|
||||||
})
|
})
|
||||||
# Do not re-raise CancelledError, it's handled.
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
end_time = time.time()
|
end_time = time.time()
|
||||||
duration = end_time - current_state["task_start_time"]
|
duration = end_time - current_state["task_start_time"]
|
||||||
error_message = f"翻译失败: {e}"
|
error_message = f"翻译失败: {e}"
|
||||||
translater_logger.error(error_message, exc_info=True)
|
translater_logger.error(error_message, exc_info=True)
|
||||||
# tb_str = traceback.format_exc() # Not used directly, exc_info=True logs it
|
|
||||||
current_state.update({
|
current_state.update({
|
||||||
"status_message": f"翻译过程中发生错误 (用时 {duration:.2f} 秒): {e}",
|
"status_message": f"翻译过程中发生错误 (用时 {duration:.2f} 秒): {e}",
|
||||||
"error_flag": True,
|
"error_flag": True,
|
||||||
@@ -867,7 +869,7 @@ async def _perform_translation(params: Dict[str, Any], file_contents: bytes, ori
|
|||||||
})
|
})
|
||||||
finally:
|
finally:
|
||||||
current_state["is_processing"] = False
|
current_state["is_processing"] = False
|
||||||
current_state["current_task_ref"] = None # Clear the task reference
|
current_state["current_task_ref"] = None
|
||||||
translater_logger.info(f"后台翻译任务 '{original_filename}' 处理结束。")
|
translater_logger.info(f"后台翻译任务 '{original_filename}' 处理结束。")
|
||||||
|
|
||||||
|
|
||||||
@@ -879,7 +881,6 @@ async def main_page(request: Request):
|
|||||||
|
|
||||||
@app.post("/translate")
|
@app.post("/translate")
|
||||||
async def handle_translate(
|
async def handle_translate(
|
||||||
# No BackgroundTasks needed here for the main task
|
|
||||||
base_url: str = Form(...),
|
base_url: str = Form(...),
|
||||||
apikey: str = Form(...),
|
apikey: str = Form(...),
|
||||||
model_id: str = Form(...),
|
model_id: str = Form(...),
|
||||||
@@ -889,7 +890,7 @@ async def handle_translate(
|
|||||||
refine_markdown: bool = Form(False),
|
refine_markdown: bool = Form(False),
|
||||||
file: UploadFile = File(...)
|
file: UploadFile = File(...)
|
||||||
):
|
):
|
||||||
global current_state
|
global current_state, log_queue, log_history
|
||||||
if current_state["is_processing"] and \
|
if current_state["is_processing"] and \
|
||||||
current_state["current_task_ref"] and \
|
current_state["current_task_ref"] and \
|
||||||
not current_state["current_task_ref"].done():
|
not current_state["current_task_ref"].done():
|
||||||
@@ -898,13 +899,13 @@ async def handle_translate(
|
|||||||
content={"task_started": False, "message": "另一个翻译任务正在进行中,请稍后再试。"}
|
content={"task_started": False, "message": "另一个翻译任务正在进行中,请稍后再试。"}
|
||||||
)
|
)
|
||||||
|
|
||||||
if not file or not file.filename: # Check if a file was actually uploaded
|
if not file or not file.filename:
|
||||||
return JSONResponse(
|
return JSONResponse(
|
||||||
status_code=400,
|
status_code=400,
|
||||||
content={"task_started": False, "message": "没有选择文件或文件无效。"}
|
content={"task_started": False, "message": "没有选择文件或文件无效。"}
|
||||||
)
|
)
|
||||||
|
|
||||||
current_state["is_processing"] = True # Set this immediately
|
current_state["is_processing"] = True
|
||||||
original_filename_for_init = file.filename or "uploaded_file"
|
original_filename_for_init = file.filename or "uploaded_file"
|
||||||
|
|
||||||
current_state.update({
|
current_state.update({
|
||||||
@@ -916,17 +917,34 @@ async def handle_translate(
|
|||||||
"original_filename_stem": Path(original_filename_for_init).stem,
|
"original_filename_stem": Path(original_filename_for_init).stem,
|
||||||
"task_start_time": time.time(),
|
"task_start_time": time.time(),
|
||||||
"task_end_time": 0,
|
"task_end_time": 0,
|
||||||
"current_task_ref": None, # Will be set after task creation
|
"current_task_ref": None,
|
||||||
})
|
})
|
||||||
log_history.clear() # Clear logs for the new task
|
|
||||||
log_history.append(translater_logger.handlers[0].format(logging.LogRecord(
|
# Clear logs for the new task
|
||||||
|
log_history.clear()
|
||||||
|
if log_queue: # Ensure log_queue is initialized
|
||||||
|
while not log_queue.empty():
|
||||||
|
try:
|
||||||
|
log_queue.get_nowait()
|
||||||
|
except asyncio.QueueEmpty:
|
||||||
|
break
|
||||||
|
|
||||||
|
# Add initial log entry for the new task
|
||||||
|
# We create a LogRecord manually to ensure it goes through the formatter and handler
|
||||||
|
initial_log_msg = f"收到新的翻译请求: {original_filename_for_init}"
|
||||||
|
if translater_logger.handlers and isinstance(translater_logger.handlers[0], QueueAndHistoryHandler):
|
||||||
|
# Use the existing handler to format and queue/store the log
|
||||||
|
record = logging.LogRecord(
|
||||||
name=translater_logger.name, level=logging.INFO, pathname="", lineno=0,
|
name=translater_logger.name, level=logging.INFO, pathname="", lineno=0,
|
||||||
msg=f"收到新的翻译请求: {original_filename_for_init}", args=[], exc_info=None, func=""
|
msg=initial_log_msg, args=(), exc_info=None, func=""
|
||||||
)))
|
)
|
||||||
|
translater_logger.handlers[0].emit(record) # This will add to both queue and history
|
||||||
|
else: # Fallback if handler setup is unusual
|
||||||
|
translater_logger.info(initial_log_msg)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
file_contents = await file.read()
|
file_contents = await file.read()
|
||||||
original_filename = file.filename # Use the actual filename (already checked it's not None)
|
original_filename = file.filename
|
||||||
await file.close()
|
await file.close()
|
||||||
|
|
||||||
task_params = {
|
task_params = {
|
||||||
@@ -944,10 +962,10 @@ async def handle_translate(
|
|||||||
return JSONResponse(content={"task_started": True, "message": "翻译任务已成功启动,请稍候..."})
|
return JSONResponse(content={"task_started": True, "message": "翻译任务已成功启动,请稍候..."})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
translater_logger.error(f"启动翻译任务失败: {e}", exc_info=True)
|
translater_logger.error(f"启动翻译任务失败: {e}", exc_info=True)
|
||||||
current_state["is_processing"] = False # Reset processing flag
|
current_state["is_processing"] = False
|
||||||
current_state["status_message"] = f"启动任务失败: {e}"
|
current_state["status_message"] = f"启动任务失败: {e}"
|
||||||
current_state["error_flag"] = True
|
current_state["error_flag"] = True
|
||||||
current_state["current_task_ref"] = None # Ensure task ref is cleared
|
current_state["current_task_ref"] = None
|
||||||
return JSONResponse(status_code=500, content={"task_started": False, "message": f"启动翻译任务时出错: {e}"})
|
return JSONResponse(status_code=500, content={"task_started": False, "message": f"启动翻译任务时出错: {e}"})
|
||||||
|
|
||||||
|
|
||||||
@@ -963,8 +981,7 @@ async def cancel_translate_task():
|
|||||||
task_to_cancel: Optional[asyncio.Task] = current_state["current_task_ref"]
|
task_to_cancel: Optional[asyncio.Task] = current_state["current_task_ref"]
|
||||||
|
|
||||||
if not task_to_cancel or task_to_cancel.done():
|
if not task_to_cancel or task_to_cancel.done():
|
||||||
# Task might have finished or been cancelled just before this request arrived
|
current_state["is_processing"] = False
|
||||||
current_state["is_processing"] = False # Ensure state consistency
|
|
||||||
current_state["current_task_ref"] = None
|
current_state["current_task_ref"] = None
|
||||||
return JSONResponse(
|
return JSONResponse(
|
||||||
status_code=400,
|
status_code=400,
|
||||||
@@ -973,24 +990,17 @@ async def cancel_translate_task():
|
|||||||
|
|
||||||
translater_logger.info("收到取消翻译任务的请求。")
|
translater_logger.info("收到取消翻译任务的请求。")
|
||||||
task_to_cancel.cancel()
|
task_to_cancel.cancel()
|
||||||
current_state["status_message"] = "正在取消任务..." # Optimistic update
|
current_state["status_message"] = "正在取消任务..."
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Give the task a moment to process cancellation
|
|
||||||
await asyncio.wait_for(task_to_cancel, timeout=2.0)
|
await asyncio.wait_for(task_to_cancel, timeout=2.0)
|
||||||
except asyncio.CancelledError:
|
except asyncio.CancelledError:
|
||||||
translater_logger.info("任务已成功取消并结束。")
|
translater_logger.info("任务已成功取消并结束。")
|
||||||
# State update (is_processing=False, status_message="已取消") is handled by _perform_translation's finally/except block
|
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
translater_logger.warning("任务取消请求已发送,但任务未在2秒内结束。可能仍在清理中。")
|
translater_logger.warning("任务取消请求已发送,但任务未在2秒内结束。可能仍在清理中。")
|
||||||
# The task is cancelled, but it might take longer. Frontend polling will get the final state.
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# This might happen if the task errored out while we were waiting for it after cancellation.
|
|
||||||
translater_logger.error(f"等待任务取消时发生意外错误: {e}")
|
translater_logger.error(f"等待任务取消时发生意外错误: {e}")
|
||||||
# The task's own error handling should manage state.
|
|
||||||
|
|
||||||
# The final state (is_processing=False, specific status message) will be set by _perform_translation.
|
|
||||||
# This endpoint just initiates the cancellation.
|
|
||||||
return JSONResponse(content={"cancelled": True, "message": "取消请求已发送。请等待状态更新。"})
|
return JSONResponse(content={"cancelled": True, "message": "取消请求已发送。请等待状态更新。"})
|
||||||
|
|
||||||
|
|
||||||
@@ -1018,12 +1028,19 @@ async def get_status():
|
|||||||
|
|
||||||
|
|
||||||
@app.get("/get-logs")
|
@app.get("/get-logs")
|
||||||
async def get_logs(since: int = 0):
|
async def get_logs_from_queue(): # Renamed for clarity, though path is the same
|
||||||
global log_history
|
global log_queue
|
||||||
# Ensure 'since' is within bounds
|
new_logs = []
|
||||||
since = max(0, min(since, len(log_history)))
|
if log_queue: # Ensure log_queue is initialized
|
||||||
new_logs = log_history[since:]
|
while not log_queue.empty():
|
||||||
return JSONResponse(content={"logs": new_logs, "total_count": len(log_history)})
|
try:
|
||||||
|
log_entry = log_queue.get_nowait() # Consume from queue
|
||||||
|
new_logs.append(log_entry)
|
||||||
|
log_queue.task_done() # Important for queue management if using join() elsewhere
|
||||||
|
except asyncio.QueueEmpty:
|
||||||
|
break
|
||||||
|
# No total_count, as the frontend just appends what it receives
|
||||||
|
return JSONResponse(content={"logs": new_logs})
|
||||||
|
|
||||||
|
|
||||||
@app.get("/download/markdown/{filename_with_ext}")
|
@app.get("/download/markdown/{filename_with_ext}")
|
||||||
@@ -1032,7 +1049,6 @@ async def download_markdown(filename_with_ext: str):
|
|||||||
"original_filename_stem"]:
|
"original_filename_stem"]:
|
||||||
raise HTTPException(status_code=404, detail="Markdown 内容尚未准备好或不可用。")
|
raise HTTPException(status_code=404, detail="Markdown 内容尚未准备好或不可用。")
|
||||||
|
|
||||||
# Basic check to prevent arbitrary filename access, though content is from current_state
|
|
||||||
requested_stem = Path(filename_with_ext).stem.replace("_translated", "")
|
requested_stem = Path(filename_with_ext).stem.replace("_translated", "")
|
||||||
if requested_stem != current_state["original_filename_stem"]:
|
if requested_stem != current_state["original_filename_stem"]:
|
||||||
raise HTTPException(status_code=404, detail="请求的文件名与当前结果不符。")
|
raise HTTPException(status_code=404, detail="请求的文件名与当前结果不符。")
|
||||||
@@ -1058,8 +1074,8 @@ async def download_html(filename_with_ext: str):
|
|||||||
actual_filename = f"{current_state['original_filename_stem']}_translated.html"
|
actual_filename = f"{current_state['original_filename_stem']}_translated.html"
|
||||||
return HTMLResponse(
|
return HTMLResponse(
|
||||||
content=current_state["html_content"],
|
content=current_state["html_content"],
|
||||||
media_type="text/html", # For direct viewing, browser decides on download based on Content-Disposition
|
media_type="text/html",
|
||||||
headers={"Content-Disposition": f"attachment; filename=\"{actual_filename}\""} # Prompts download
|
headers={"Content-Disposition": f"attachment; filename=\"{actual_filename}\""}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "docutranslate"
|
name = "docutranslate"
|
||||||
version = "0.2.9"
|
version = "0.2.10"
|
||||||
description = "文件翻译工具"
|
description = "文件翻译工具"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.10"
|
requires-python = ">=3.10"
|
||||||
|
|||||||
Reference in New Issue
Block a user