- 使用 Flask context_processor 自动注入 site_config - 所有页面标题使用 site_name 配置 - 所有页面导航栏品牌使用 site_name 配置 - 所有页面底部使用 site_footer 配置 - 文件上传时使用 max_file_size 配置验证文件大小 - 显示最大文件限制提示
294 lines
8.9 KiB
JavaScript
294 lines
8.9 KiB
JavaScript
/**
|
||
* PDF翻译助手前端脚本
|
||
*/
|
||
|
||
// 当前翻译ID
|
||
let currentTranslationId = null;
|
||
let currentTaskId = null;
|
||
|
||
// 上传表单处理
|
||
document.getElementById('uploadForm').addEventListener('submit', async function(e) {
|
||
e.preventDefault();
|
||
|
||
const fileInput = document.getElementById('pdfFile');
|
||
const file = fileInput.files[0];
|
||
|
||
if (!file) {
|
||
alert('请选择PDF文件');
|
||
return;
|
||
}
|
||
|
||
if (!file.name.toLowerCase().endsWith('.pdf')) {
|
||
alert('只支持PDF文件');
|
||
return;
|
||
}
|
||
|
||
// 检查文件大小
|
||
const maxSizeMB = parseInt(document.getElementById('submitBtn').dataset.maxSize) || 50;
|
||
const fileSizeMB = file.size / (1024 * 1024);
|
||
if (fileSizeMB > maxSizeMB) {
|
||
alert(`文件大小超出限制(最大${maxSizeMB}MB,当前${fileSizeMB.toFixed(1)}MB)`);
|
||
return;
|
||
}
|
||
|
||
// 显示进度区域
|
||
document.getElementById('progressSection').style.display = 'block';
|
||
document.getElementById('resultSection').style.display = 'none';
|
||
document.getElementById('cacheNotice').style.display = 'none';
|
||
|
||
// 显示加载状态
|
||
const submitBtn = document.getElementById('submitBtn');
|
||
const btnText = document.getElementById('btnText');
|
||
const btnSpinner = document.getElementById('btnSpinner');
|
||
|
||
submitBtn.disabled = true;
|
||
btnText.textContent = '上传中...';
|
||
btnSpinner.style.display = 'inline-block';
|
||
|
||
// 构建表单数据
|
||
const formData = new FormData();
|
||
formData.append('file', file);
|
||
|
||
const instruction = document.getElementById('instruction')?.value;
|
||
if (instruction) {
|
||
formData.append('instruction', instruction);
|
||
}
|
||
|
||
try {
|
||
// 上传文件
|
||
const response = await fetch('/api/upload', {
|
||
method: 'POST',
|
||
body: formData
|
||
});
|
||
|
||
const result = await response.json();
|
||
|
||
if (!response.ok) {
|
||
throw new Error(result.error || '上传失败');
|
||
}
|
||
|
||
currentTranslationId = result.translation_id;
|
||
currentTaskId = result.task_id;
|
||
|
||
// 如果使用缓存,直接显示结果
|
||
if (result.from_cache) {
|
||
document.getElementById('cacheNotice').style.display = 'block';
|
||
showResult(currentTranslationId);
|
||
} else {
|
||
// 开始轮询进度
|
||
pollProgress(currentTaskId, currentTranslationId);
|
||
}
|
||
|
||
} catch (error) {
|
||
alert('上传失败: ' + error.message);
|
||
resetUploadButton();
|
||
}
|
||
});
|
||
|
||
// 轮询翻译进度
|
||
async function pollProgress(taskId, translationId) {
|
||
const progressBar = document.getElementById('progressBar');
|
||
const progressMessage = document.getElementById('progressMessage');
|
||
|
||
const poll = async () => {
|
||
try {
|
||
// 同时检查任务状态和翻译状态
|
||
const taskResponse = await fetch(`/api/task/${taskId}`);
|
||
const taskResult = await taskResponse.json();
|
||
|
||
const transResponse = await fetch(`/api/status/${translationId}`);
|
||
const transResult = await transResponse.json();
|
||
|
||
// 更新进度
|
||
if (taskResult.progress) {
|
||
progressBar.style.width = taskResult.progress + '%';
|
||
progressBar.textContent = taskResult.progress + '%';
|
||
}
|
||
|
||
if (taskResult.message) {
|
||
progressMessage.textContent = taskResult.message;
|
||
}
|
||
|
||
// 检查是否完成
|
||
if (taskResult.status === 'completed' || transResult.status === 'completed') {
|
||
progressBar.style.width = '100%';
|
||
progressBar.textContent = '100%';
|
||
progressMessage.textContent = '翻译完成!';
|
||
|
||
// 显示结果
|
||
setTimeout(() => showResult(translationId), 500);
|
||
return;
|
||
}
|
||
|
||
if (taskResult.status === 'failed') {
|
||
progressMessage.textContent = '翻译失败: ' + (taskResult.error || '未知错误');
|
||
resetUploadButton();
|
||
return;
|
||
}
|
||
|
||
// 继续轮询
|
||
setTimeout(poll, 2000);
|
||
|
||
} catch (error) {
|
||
console.error('轮询失败:', error);
|
||
setTimeout(poll, 3000);
|
||
}
|
||
};
|
||
|
||
poll();
|
||
}
|
||
|
||
// 显示翻译结果
|
||
async function showResult(translationId) {
|
||
const resultSection = document.getElementById('resultSection');
|
||
const resultContent = document.getElementById('resultContent');
|
||
|
||
try {
|
||
const response = await fetch(`/api/result/${translationId}`);
|
||
const result = await response.json();
|
||
|
||
if (!response.ok) {
|
||
throw new Error(result.error || '获取结果失败');
|
||
}
|
||
|
||
// 渲染Markdown内容
|
||
resultContent.innerHTML = renderMarkdown(result.content);
|
||
resultSection.style.display = 'block';
|
||
|
||
resetUploadButton();
|
||
|
||
} catch (error) {
|
||
alert('获取结果失败: ' + error.message);
|
||
resetUploadButton();
|
||
}
|
||
}
|
||
|
||
// 重置上传按钮
|
||
function resetUploadButton() {
|
||
const submitBtn = document.getElementById('submitBtn');
|
||
const btnText = document.getElementById('btnText');
|
||
const btnSpinner = document.getElementById('btnSpinner');
|
||
|
||
submitBtn.disabled = false;
|
||
btnText.textContent = '开始翻译';
|
||
btnSpinner.style.display = 'none';
|
||
}
|
||
|
||
// 下载结果
|
||
document.getElementById('downloadBtn')?.addEventListener('click', function() {
|
||
if (currentTranslationId) {
|
||
window.location.href = `/api/download/${currentTranslationId}`;
|
||
}
|
||
});
|
||
|
||
// 对比查看
|
||
document.getElementById('viewCompare')?.addEventListener('click', async function() {
|
||
if (!currentTranslationId) return;
|
||
|
||
try {
|
||
const response = await fetch(`/api/compare/${currentTranslationId}`);
|
||
const result = await response.json();
|
||
|
||
// 显示对比视图
|
||
showCompareView(result);
|
||
|
||
} catch (error) {
|
||
alert('获取对比失败: ' + error.message);
|
||
}
|
||
});
|
||
|
||
// 显示对比视图
|
||
function showCompareView(data) {
|
||
const resultContent = document.getElementById('resultContent');
|
||
|
||
resultContent.innerHTML = `
|
||
<div class="compare-container">
|
||
<div class="compare-panel original">
|
||
<h5>原文</h5>
|
||
<div class="content">${escapeHtml(data.original)}</div>
|
||
</div>
|
||
<div class="compare-panel translated">
|
||
<h5>译文</h5>
|
||
<div class="content">${renderMarkdown(data.translated)}</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
// 重新翻译
|
||
document.getElementById('retranslateBtn')?.addEventListener('click', async function() {
|
||
if (!currentTranslationId) return;
|
||
|
||
const instruction = document.getElementById('retranslateInstruction').value;
|
||
if (!instruction.trim()) {
|
||
alert('请输入翻译要求');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const response = await fetch(`/api/retranslate/${currentTranslationId}`, {
|
||
method: 'POST',
|
||
headers: {'Content-Type': 'application/json'},
|
||
body: JSON.stringify({instruction: instruction})
|
||
});
|
||
|
||
const result = await response.json();
|
||
|
||
if (!response.ok) {
|
||
throw new Error(result.error || '重译请求失败');
|
||
}
|
||
|
||
// 开始新的翻译任务
|
||
currentTranslationId = result.translation_id;
|
||
document.getElementById('progressSection').style.display = 'block';
|
||
document.getElementById('resultSection').style.display = 'none';
|
||
pollProgress(null, currentTranslationId);
|
||
|
||
} catch (error) {
|
||
alert('重译失败: ' + error.message);
|
||
}
|
||
});
|
||
|
||
// 简单Markdown渲染
|
||
function renderMarkdown(text) {
|
||
// 标题
|
||
text = text.replace(/^## (.*)$/gm, '<h2>$1</h2>');
|
||
text = text.replace(/^# (.*)$/gm, '<h1>$1</h1>');
|
||
|
||
// 分隔线
|
||
text = text.replace(/^---$/gm, '<hr>');
|
||
|
||
// 引用
|
||
text = text.replace(/^> (.*)$/gm, '<blockquote>$1</blockquote>');
|
||
|
||
// 段落
|
||
text = text.replace(/\n\n/g, '</p><p>');
|
||
text = '<p>' + text + '</p>';
|
||
|
||
return text;
|
||
}
|
||
|
||
// HTML转义
|
||
function escapeHtml(text) {
|
||
const div = document.createElement('div');
|
||
div.textContent = text;
|
||
return div.innerHTML;
|
||
}
|
||
|
||
// 检查用户登录状态
|
||
async function checkUserStatus() {
|
||
try {
|
||
const response = await fetch('/api/user/info');
|
||
const result = await response.json();
|
||
|
||
if (result.user) {
|
||
console.log('用户已登录:', result.user.username);
|
||
}
|
||
|
||
} catch (error) {
|
||
console.error('检查用户状态失败:', error);
|
||
}
|
||
}
|
||
|
||
// 页面加载时检查状态
|
||
document.addEventListener('DOMContentLoaded', checkUserStatus); |