Files
pdf-translate-web/static/js/main.js
coder 3479cbd04c fix: 实现对比查看功能
- 读取翻译结果文件内容
- 尝试从原PDF提取原文(OCR识别的文字)
- 按页面分块显示对比
- 前端支持分页对比展示
2026-04-16 23:45:57 +08:00

334 lines
11 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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;
// 更新按钮状态为翻译中
btnText.textContent = '翻译中...';
// 如果使用缓存,直接显示结果
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');
// 如果有分页数据,按页显示
if (data.pages && data.pages.length > 0) {
let html = '<div class="compare-container">';
for (const page of data.pages) {
html += `
<div class="compare-section mb-4">
<h5 class="text-center mb-3">第 ${page.page} 页</h5>
<div class="row">
<div class="col-md-6">
<div class="card">
<div class="card-header">原文OCR识别</div>
<div class="card-body"><pre style="white-space: pre-wrap;">${escapeHtml(data.original || '原文内容')}</pre></div>
</div>
</div>
<div class="col-md-6">
<div class="card">
<div class="card-header">译文</div>
<div class="card-body">${renderMarkdown(page.content)}</div>
</div>
</div>
</div>
</div>
`;
}
html += '</div>';
resultContent.innerHTML = html;
} else {
// 单一对比视图
resultContent.innerHTML = `
<div class="compare-container">
<div class="row">
<div class="col-md-6">
<div class="card">
<div class="card-header">原文</div>
<div class="card-body"><pre style="white-space: pre-wrap;">${escapeHtml(data.original || '无原文内容')}</pre></div>
</div>
</div>
<div class="col-md-6">
<div class="card">
<div class="card-header">译文</div>
<div class="card-body">${renderMarkdown(data.translated || '无译文内容')}</div>
</div>
</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);