feat: 完成MinerU结构化翻译pdf对照
This commit is contained in:
parent
e7b6c360c1
commit
e764a2b847
|
|
@ -15,6 +15,8 @@ body {
|
||||||
max-width: 1200px;
|
max-width: 1200px;
|
||||||
margin: 40px auto;
|
margin: 40px auto;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
background: var(--color-bg-base);
|
background: var(--color-bg-base);
|
||||||
border-radius: var(--radius-xl);
|
border-radius: var(--radius-xl);
|
||||||
/* 使用变量定义的轻量化阴影 */
|
/* 使用变量定义的轻量化阴影 */
|
||||||
|
|
@ -87,8 +89,8 @@ body {
|
||||||
.tab-content {
|
.tab-content {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
padding: 8px 0;
|
padding: 8px 0;
|
||||||
height: 100vh;
|
|
||||||
min-height: 300px;
|
min-height: 300px;
|
||||||
|
flex: 1;
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
/* 优化阅读体验的字体设置 */
|
/* 优化阅读体验的字体设置 */
|
||||||
font-size: 1.0625rem; /* 17px */
|
font-size: 1.0625rem; /* 17px */
|
||||||
|
|
|
||||||
13
index.html
13
index.html
|
|
@ -231,7 +231,7 @@
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
/* 新增:右侧大 Logo 背景装饰 */
|
/* 新增:右侧大 Logo 背景装饰 */
|
||||||
.workspace-header::after {
|
/* .workspace-header::after {
|
||||||
content: '';
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: -20px;
|
right: -20px;
|
||||||
|
|
@ -242,10 +242,11 @@
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-position: center;
|
background-position: center;
|
||||||
background-size: contain;
|
background-size: contain;
|
||||||
opacity: 0.07; /* 极低透明度,仅作纹理 */
|
极低透明度,仅作纹理
|
||||||
|
opacity: 0.07;
|
||||||
transform: rotate(-10deg);
|
transform: rotate(-10deg);
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
} */
|
||||||
.workspace-header-content {
|
.workspace-header-content {
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
|
@ -594,7 +595,7 @@
|
||||||
</nav>
|
</nav>
|
||||||
<!-- 删除界面会加载不出来 -->
|
<!-- 删除界面会加载不出来 -->
|
||||||
<div class="p-3 transition-all" id="sidebarFooter">
|
<div class="p-3 transition-all" id="sidebarFooter">
|
||||||
<div class="p-4 bg-slate-50 rounded-2xl border border-slate-100 overflow-hidden">
|
<div class="p-4 overflow-hidden">
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="text-center py-1 mt-1 nav-text transition-opacity">
|
<div class="text-center py-1 mt-1 nav-text transition-opacity">
|
||||||
|
|
@ -694,13 +695,13 @@
|
||||||
</div>
|
</div>
|
||||||
OCR 文档解析
|
OCR 文档解析
|
||||||
</h2>
|
</h2>
|
||||||
<div class="flex items-center">
|
<!-- <div class="flex items-center">
|
||||||
<span id="flashConfigTip" class="flash-tip-anim text-sm font-medium mr-2" style="color: var(--color-primary);">配置模型与Key</span>
|
<span id="flashConfigTip" class="flash-tip-anim text-sm font-medium mr-2" style="color: var(--color-primary);">配置模型与Key</span>
|
||||||
<button id="modelKeyManagerBtn" class="flex items-center gap-1.5 px-3 py-1.5 text-sm font-medium text-slate-600 bg-slate-100 hover:bg-slate-200 rounded-lg transition-colors" title="模型与Key管理">
|
<button id="modelKeyManagerBtn" class="flex items-center gap-1.5 px-3 py-1.5 text-sm font-medium text-slate-600 bg-slate-100 hover:bg-slate-200 rounded-lg transition-colors" title="模型与Key管理">
|
||||||
<iconify-icon icon="carbon:settings" width="18"></iconify-icon>
|
<iconify-icon icon="carbon:settings" width="18"></iconify-icon>
|
||||||
<span>设置</span>
|
<span>设置</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div> -->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- OCR 引擎选择(简化版) -->
|
<!-- OCR 引擎选择(简化版) -->
|
||||||
|
|
|
||||||
Binary file not shown.
BIN
input/pdf测试.pdf
BIN
input/pdf测试.pdf
Binary file not shown.
|
|
@ -535,6 +535,32 @@ async function triggerReprocess(includeTranslation) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示 Tab 按钮加载状态
|
||||||
|
* @param {string} tabId - Tab 按钮 ID
|
||||||
|
* @param {boolean} loading - 是否显示加载状态
|
||||||
|
*/
|
||||||
|
function setTabLoadingState(tabId, loading) {
|
||||||
|
const btn = document.getElementById(tabId);
|
||||||
|
if (!btn) return;
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
// 保存原始内容
|
||||||
|
btn.dataset.originalContent = btn.innerHTML;
|
||||||
|
// 显示加载动画
|
||||||
|
btn.innerHTML = `<div class="spinner" style="width: 18px; height: 18px; border: 2px solid #e5e7eb; border-top-color: #3b82f6; border-radius: 50%; animation: spin 1s linear infinite; margin: 0 auto;"></div>`;
|
||||||
|
btn.disabled = true;
|
||||||
|
btn.style.minWidth = '60px';
|
||||||
|
} else {
|
||||||
|
// 恢复原始内容
|
||||||
|
if (btn.dataset.originalContent) {
|
||||||
|
btn.innerHTML = btn.dataset.originalContent;
|
||||||
|
}
|
||||||
|
btn.disabled = false;
|
||||||
|
btn.style.minWidth = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理"Word文档"标签点击
|
* 处理"Word文档"标签点击
|
||||||
* 如果没有OCR数据,询问用户是否需要生成
|
* 如果没有OCR数据,询问用户是否需要生成
|
||||||
|
|
@ -561,7 +587,14 @@ async function handleOcrTabClick() {
|
||||||
);
|
);
|
||||||
|
|
||||||
if (confirmed) {
|
if (confirmed) {
|
||||||
await triggerReprocess(false); // 仅OCR,不翻译
|
// 显示加载状态
|
||||||
|
setTabLoadingState('tab-ocr', true);
|
||||||
|
try {
|
||||||
|
await triggerReprocess(false); // 仅OCR,不翻译
|
||||||
|
} finally {
|
||||||
|
// 恢复按钮状态
|
||||||
|
setTabLoadingState('tab-ocr', false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -591,7 +624,396 @@ async function handleTranslationTabClick() {
|
||||||
);
|
);
|
||||||
|
|
||||||
if (confirmed) {
|
if (confirmed) {
|
||||||
await triggerReprocess(true); // OCR + 翻译
|
// 显示加载状态
|
||||||
|
setTabLoadingState('tab-translation', true);
|
||||||
|
try {
|
||||||
|
await triggerReprocess(true); // OCR + 翻译
|
||||||
|
} finally {
|
||||||
|
// 恢复按钮状态
|
||||||
|
setTabLoadingState('tab-translation', false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示 PDF 对照确认对话框
|
||||||
|
* 询问用户是否进行 MinerU 结构化翻译
|
||||||
|
*/
|
||||||
|
async function showPdfCompareConfirmDialog() {
|
||||||
|
// 检查是否有 MinerU 数据(contentListJson)
|
||||||
|
const hasMinerUData = window.data && window.data.metadata && window.data.metadata.contentListJson;
|
||||||
|
|
||||||
|
if (!hasMinerUData) {
|
||||||
|
// 没有 MinerU 数据,询问是否重新使用 MinerU 处理
|
||||||
|
const confirmed = await showConfirmDialog(
|
||||||
|
'PDF 对照视图',
|
||||||
|
'当前文档未使用 MinerU 引擎处理,无法进行结构化翻译。\n\n是否重新使用 MinerU 引擎处理文档?\n\n处理完成后将自动进行翻译并显示 PDF 对照视图。',
|
||||||
|
'开始处理',
|
||||||
|
'取消'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (confirmed) {
|
||||||
|
// 使用 MinerU 重新处理并翻译
|
||||||
|
await triggerReprocessWithMinerU();
|
||||||
|
} else {
|
||||||
|
// 用户取消,跳转回原始文件标签页
|
||||||
|
if (typeof showTab === 'function') {
|
||||||
|
showTab('original-file');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 有 MinerU 数据,询问是否进行翻译
|
||||||
|
const confirmed = await showConfirmDialog(
|
||||||
|
'PDF 对照视图',
|
||||||
|
'当前文档尚未进行 MinerU 结构化翻译。是否现在开始翻译?\n\n翻译完成后将显示原文与译文的 PDF 对照视图。',
|
||||||
|
'开始翻译',
|
||||||
|
'取消'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (confirmed) {
|
||||||
|
await executeMinerUStructuredTranslation();
|
||||||
|
} else {
|
||||||
|
// 用户取消,跳转回原始文件标签页
|
||||||
|
if (typeof showTab === 'function') {
|
||||||
|
showTab('original-file');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用 MinerU 引擎重新处理文档并翻译
|
||||||
|
*/
|
||||||
|
async function triggerReprocessWithMinerU() {
|
||||||
|
const docId = window.docIdForLocalStorage;
|
||||||
|
const docName = window.data ? window.data.name : '未知文档';
|
||||||
|
|
||||||
|
if (!docId) {
|
||||||
|
showToast('无法获取文档ID,请刷新页面重试。', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否有原始 PDF 数据
|
||||||
|
const pdfBase64 = window.data?.metadata?.originalPdfBase64;
|
||||||
|
if (!pdfBase64) {
|
||||||
|
showToast('当前记录没有保存原始PDF数据,无法重新处理。', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示加载状态
|
||||||
|
setTabLoadingState('tab-pdf-compare', true);
|
||||||
|
|
||||||
|
// 保存当前 OCR 配置
|
||||||
|
const savedEngine = localStorage.getItem('ocrEngine');
|
||||||
|
const savedTranslationMode = localStorage.getItem('mineruTranslationMode');
|
||||||
|
const savedMineruMode = localStorage.getItem('mineruMode');
|
||||||
|
|
||||||
|
try {
|
||||||
|
showToast('正在使用 MinerU 处理文档...', 'info');
|
||||||
|
|
||||||
|
// 临时设置 OCR 配置为 MinerU + 结构化翻译模式
|
||||||
|
localStorage.setItem('ocrEngine', 'mineru');
|
||||||
|
localStorage.setItem('mineruTranslationMode', 'structured');
|
||||||
|
localStorage.setItem('mineruMode', 'txt');
|
||||||
|
|
||||||
|
// 将 PDF 转换为 Blob
|
||||||
|
const pdfBytes = Uint8Array.from(atob(pdfBase64), c => c.charCodeAt(0));
|
||||||
|
const pdfBlob = new Blob([pdfBytes], { type: 'application/pdf' });
|
||||||
|
const pdfFile = new File([pdfBlob], docName || 'document.pdf', { type: 'application/pdf' });
|
||||||
|
|
||||||
|
// 获取翻译模型配置 - 使用后端代理,不需要前端选择模型
|
||||||
|
const settings = typeof loadSettings === 'function' ? loadSettings() : {};
|
||||||
|
|
||||||
|
// 调用 OCR 处理
|
||||||
|
if (typeof window.processSinglePdf !== 'function') {
|
||||||
|
showToast('处理模块未加载,请刷新页面重试。', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用后端代理进行翻译,不需要传递 API Key
|
||||||
|
const result = await window.processSinglePdf(
|
||||||
|
pdfFile,
|
||||||
|
null, // mistralKeyObject - 使用 MinerU 不需要
|
||||||
|
null, // translationKeyObject - 使用后端代理不需要
|
||||||
|
'tongyi', // 使用通义模型,后端代理会处理
|
||||||
|
null, // translationModelConfig
|
||||||
|
settings.maxTokensPerChunk || 2000,
|
||||||
|
settings.targetLanguage || 'Chinese',
|
||||||
|
() => Promise.resolve(), // acquireSlot
|
||||||
|
() => {}, // releaseSlot
|
||||||
|
settings.defaultSystemPrompt || '',
|
||||||
|
settings.defaultUserPromptTemplate || '',
|
||||||
|
settings.useCustomPrompts || false,
|
||||||
|
null, // batchContext
|
||||||
|
() => {} // onFileSuccess
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result.error) {
|
||||||
|
throw new Error(result.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('[triggerReprocessWithMinerU] 处理结果:', result);
|
||||||
|
console.log('[triggerReprocessWithMinerU] metadata:', result.metadata);
|
||||||
|
console.log('[triggerReprocessWithMinerU] contentListJson:', result.metadata?.contentListJson);
|
||||||
|
|
||||||
|
// 更新 window.data
|
||||||
|
if (result.ocr) window.data.ocr = result.ocr;
|
||||||
|
if (result.translation) window.data.translation = result.translation;
|
||||||
|
if (result.images) window.data.images = result.images;
|
||||||
|
if (result.ocrChunks) window.data.ocrChunks = result.ocrChunks;
|
||||||
|
if (result.translatedChunks) window.data.translatedChunks = result.translatedChunks;
|
||||||
|
if (result.metadata) {
|
||||||
|
window.data.metadata = window.data.metadata || {};
|
||||||
|
Object.assign(window.data.metadata, result.metadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存到 IndexedDB
|
||||||
|
if (typeof saveResultToDB === 'function') {
|
||||||
|
await saveResultToDB(window.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
showToast('处理完成!', 'success');
|
||||||
|
|
||||||
|
// 刷新页面显示
|
||||||
|
if (typeof renderDetail === 'function') {
|
||||||
|
renderDetail();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 自动跳转到原始文件标签页
|
||||||
|
if (typeof showTab === 'function') {
|
||||||
|
showTab('original-file');
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[triggerReprocessWithMinerU] 处理失败:', error);
|
||||||
|
showToast(`处理失败: ${error.message}`, 'error');
|
||||||
|
} finally {
|
||||||
|
// 恢复原始 OCR 配置
|
||||||
|
if (savedEngine !== null) localStorage.setItem('ocrEngine', savedEngine);
|
||||||
|
else localStorage.removeItem('ocrEngine');
|
||||||
|
if (savedTranslationMode !== null) localStorage.setItem('mineruTranslationMode', savedTranslationMode);
|
||||||
|
else localStorage.removeItem('mineruTranslationMode');
|
||||||
|
if (savedMineruMode !== null) localStorage.setItem('mineruMode', savedMineruMode);
|
||||||
|
else localStorage.removeItem('mineruMode');
|
||||||
|
|
||||||
|
setTabLoadingState('tab-pdf-compare', false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行 MinerU 结构化翻译
|
||||||
|
* 通过后端 local-proxy 代理调用 LLM API,API Key 由后端管理
|
||||||
|
*/
|
||||||
|
async function executeMinerUStructuredTranslation() {
|
||||||
|
const logPrefix = '[MinerU结构化翻译]';
|
||||||
|
const PROXY_BASE = 'http://localhost:3456';
|
||||||
|
|
||||||
|
// 获取翻译配置
|
||||||
|
const settings = typeof loadSettings === 'function' ? loadSettings() : {};
|
||||||
|
const modelName = "tongyi";
|
||||||
|
|
||||||
|
|
||||||
|
// 显示进度容器
|
||||||
|
const tabContent = document.getElementById('tabContent');
|
||||||
|
tabContent.innerHTML = `
|
||||||
|
<div id="structured-translation-progress" style="padding: 24px;">
|
||||||
|
<div style="display: flex; align-items: center; margin-bottom: 16px;">
|
||||||
|
<div class="spinner" style="width: 24px; height: 24px; border: 3px solid #e5e7eb; border-top-color: #3b82f6; border-radius: 50%; animation: spin 1s linear infinite; margin-right: 12px;"></div>
|
||||||
|
<h3 style="margin: 0; font-size: 16px; color: #1f2937;">正在执行 MinerU 结构化翻译...</h3>
|
||||||
|
</div>
|
||||||
|
<div id="structured-translation-log" style="
|
||||||
|
background: #f9fafb;
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 12px;
|
||||||
|
max-height: 300px;
|
||||||
|
overflow-y: auto;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #6b7280;
|
||||||
|
">
|
||||||
|
<div class="log-entry">准备开始翻译...</div>
|
||||||
|
</div>
|
||||||
|
<div style="margin-top: 16px;">
|
||||||
|
<div class="progress-bar" style="
|
||||||
|
background: #e5e7eb;
|
||||||
|
border-radius: 8px;
|
||||||
|
height: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
">
|
||||||
|
<div id="structured-translation-progress-bar" style="
|
||||||
|
background: linear-gradient(90deg, #3b82f6, #8b5cf6);
|
||||||
|
height: 100%;
|
||||||
|
width: 0%;
|
||||||
|
transition: width 0.3s ease;
|
||||||
|
"></div>
|
||||||
|
</div>
|
||||||
|
<p id="structured-translation-status" style="margin: 8px 0 0 0; font-size: 13px; color: #6b7280;">进度: 0%</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<style>
|
||||||
|
@keyframes spin {
|
||||||
|
to { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
`;
|
||||||
|
|
||||||
|
const logEl = document.getElementById('structured-translation-log');
|
||||||
|
const progressBar = document.getElementById('structured-translation-progress-bar');
|
||||||
|
const statusEl = document.getElementById('structured-translation-status');
|
||||||
|
|
||||||
|
// 日志函数
|
||||||
|
const addLog = (msg) => {
|
||||||
|
const entry = document.createElement('div');
|
||||||
|
entry.className = 'log-entry';
|
||||||
|
entry.textContent = `[${new Date().toLocaleTimeString()}] ${msg}`;
|
||||||
|
logEl.appendChild(entry);
|
||||||
|
logEl.scrollTop = logEl.scrollHeight;
|
||||||
|
console.log(`${logPrefix} ${msg}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 进度回调
|
||||||
|
const onProgress = (progress) => {
|
||||||
|
const pct = progress.percentage || 0;
|
||||||
|
progressBar.style.width = `${pct}%`;
|
||||||
|
statusEl.textContent = `进度: ${pct}% - ${progress.message || ''}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
addLog('初始化翻译器...');
|
||||||
|
|
||||||
|
// 检查 MinerUStructuredTranslation 是否可用
|
||||||
|
if (typeof MinerUStructuredTranslation === 'undefined') {
|
||||||
|
throw new Error('MinerU 结构化翻译模块未加载');
|
||||||
|
}
|
||||||
|
|
||||||
|
const translator = new MinerUStructuredTranslation();
|
||||||
|
addLog('翻译器初始化完成');
|
||||||
|
|
||||||
|
// 获取全局 data 对象
|
||||||
|
const dataObj = window.data;
|
||||||
|
console.log('dataObj:', dataObj);
|
||||||
|
|
||||||
|
if (!dataObj || !dataObj.metadata || !dataObj.metadata.contentListJson) {
|
||||||
|
console.log(!dataObj ,!dataObj.metadata , !dataObj.metadata.contentListJson);
|
||||||
|
|
||||||
|
throw new Error('缺少必要的内容数据');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提取可翻译内容
|
||||||
|
const contentListJson = dataObj.metadata.contentListJson;
|
||||||
|
addLog(`提取可翻译内容...`);
|
||||||
|
|
||||||
|
const translatableContent = translator.extractTranslatableContent(contentListJson);
|
||||||
|
addLog(`提取了 ${translatableContent.length} 个片段`);
|
||||||
|
|
||||||
|
// 分批
|
||||||
|
const batches = translator.splitIntoBatches(translatableContent);
|
||||||
|
addLog(`分为 ${batches.length} 个批次`);
|
||||||
|
|
||||||
|
// 获取目标语言
|
||||||
|
const targetLang = settings.targetLanguage === 'custom'
|
||||||
|
? (settings.customTargetLanguageName || 'Chinese')
|
||||||
|
: (settings.targetLanguage || 'Chinese');
|
||||||
|
|
||||||
|
// 翻译选项 - 通过后端代理,不需要 API Key
|
||||||
|
const translationOptions = {
|
||||||
|
useBackendProxy: true,
|
||||||
|
proxyBase: PROXY_BASE,
|
||||||
|
provider: modelName === 'custom' ? 'aliyun' : modelName // 默认使用 aliyun/通义
|
||||||
|
};
|
||||||
|
|
||||||
|
// 执行翻译
|
||||||
|
addLog(`开始翻译 (模型: ${modelName}, 目标语言: ${targetLang}, 通过后端代理)...`);
|
||||||
|
|
||||||
|
const translatedContentList = await translator.translateBatches(
|
||||||
|
batches,
|
||||||
|
targetLang,
|
||||||
|
modelName,
|
||||||
|
null, // API Key 为 null,由后端代理处理
|
||||||
|
{
|
||||||
|
...translationOptions,
|
||||||
|
maxRetries: settings.structuredMaxRetries || 2,
|
||||||
|
retryDelay: settings.structuredRetryDelayMs || 800
|
||||||
|
},
|
||||||
|
onProgress,
|
||||||
|
() => Promise.resolve(), // acquireSlot
|
||||||
|
() => {} // releaseSlot
|
||||||
|
);
|
||||||
|
|
||||||
|
addLog('翻译完成,保存数据...');
|
||||||
|
|
||||||
|
// 保存到 metadata
|
||||||
|
if (!dataObj.metadata) dataObj.metadata = {};
|
||||||
|
dataObj.metadata.translatedContentList = translatedContentList;
|
||||||
|
dataObj.metadata.supportsStructuredTranslation = true;
|
||||||
|
|
||||||
|
// 收集失败项
|
||||||
|
const failedItems = [];
|
||||||
|
translatedContentList.forEach((it, idx) => {
|
||||||
|
if (it && it.failed === true) {
|
||||||
|
failedItems.push({
|
||||||
|
index: idx,
|
||||||
|
type: it.type,
|
||||||
|
page_idx: it.page_idx || 0,
|
||||||
|
text: translator.extractItemText ? translator.extractItemText(it) : (it.text || '')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
dataObj.metadata.failedStructuredItems = failedItems;
|
||||||
|
dataObj.metadata.structuredFailedCount = failedItems.length;
|
||||||
|
|
||||||
|
// 更新全局数据
|
||||||
|
if (typeof data !== 'undefined') {
|
||||||
|
data.metadata = dataObj.metadata;
|
||||||
|
}
|
||||||
|
window.data = dataObj;
|
||||||
|
|
||||||
|
// 保存到数据库
|
||||||
|
if (typeof saveResultToDB === 'function') {
|
||||||
|
await saveResultToDB(dataObj);
|
||||||
|
addLog('数据已保存到数据库');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (failedItems.length > 0) {
|
||||||
|
addLog(`注意: 有 ${failedItems.length} 个片段翻译失败`);
|
||||||
|
}
|
||||||
|
|
||||||
|
addLog('正在加载 PDF 对照视图...');
|
||||||
|
|
||||||
|
// 短暂延迟后显示 PDF 对照视图
|
||||||
|
setTimeout(() => {
|
||||||
|
if (typeof showTabImmediate === 'function') {
|
||||||
|
showTabImmediate('pdf-compare');
|
||||||
|
} else if (typeof showTab === 'function') {
|
||||||
|
showTab('pdf-compare');
|
||||||
|
}
|
||||||
|
}, 500);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`${logPrefix} 翻译失败:`, error);
|
||||||
|
addLog(`错误: ${error.message}`);
|
||||||
|
|
||||||
|
// 显示错误
|
||||||
|
tabContent.innerHTML = `
|
||||||
|
<div class="error-box" style="padding: 24px; text-align: center;">
|
||||||
|
<i class="fa fa-exclamation-triangle" style="font-size: 48px; color: #ef4444; margin-bottom: 16px;"></i>
|
||||||
|
<h3 style="margin: 0 0 12px 0; color: #991b1b;">翻译失败</h3>
|
||||||
|
<p style="margin: 0 0 16px 0; color: #6b7280;">${error.message}</p>
|
||||||
|
<button onclick="showTab('ocr')" style="
|
||||||
|
padding: 10px 20px;
|
||||||
|
background: #3b82f6;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
">返回 OCR 内容</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
if (typeof renderingTab !== 'undefined') renderingTab = null;
|
||||||
|
if (typeof console.timeEnd === 'function') console.timeEnd('[性能] showTab_总渲染');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -300,15 +300,14 @@ function showTabImmediate(tab) {
|
||||||
if (DOM_CACHE.layout.meta) DOM_CACHE.layout.meta.style.display = 'none';
|
if (DOM_CACHE.layout.meta) DOM_CACHE.layout.meta.style.display = 'none';
|
||||||
if (DOM_CACHE.layout.tabsContainer) DOM_CACHE.layout.tabsContainer.style.display = 'none';
|
if (DOM_CACHE.layout.tabsContainer) DOM_CACHE.layout.tabsContainer.style.display = 'none';
|
||||||
|
|
||||||
// 验证必要数据
|
// 检查是否有必要的结构化翻译数据
|
||||||
if (!data.metadata || !data.metadata.originalPdfBase64 || !data.metadata.contentListJson || !data.metadata.translatedContentList) {
|
const hasStructuredData = data.metadata && data.metadata.originalPdfBase64 && data.metadata.contentListJson && data.metadata.translatedContentList;
|
||||||
const warn = `<div class="warning-box" style="padding:12px;border:1px solid #fbbf24;background:#fffbeb;color:#92400e;border-radius:8px;">`
|
|
||||||
+ `无法进入"PDF对照":缺少必要的 MinerU 结构化翻译数据。`
|
if (!hasStructuredData) {
|
||||||
+ `</div>`;
|
// 缺少结构化翻译数据,弹出确认对话框询问用户
|
||||||
document.getElementById('tabContent').innerHTML = warn;
|
(async () => {
|
||||||
if (typeof window.refreshTocList === 'function') window.refreshTocList();
|
await showPdfCompareConfirmDialog();
|
||||||
renderingTab = null;
|
})();
|
||||||
console.timeEnd && console.timeEnd('[性能] showTab_总渲染');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -704,13 +704,23 @@ const fileType = fileToProcess.name.split('.').pop().toLowerCase();
|
||||||
// --- 翻译流程 (如果需要) ---
|
// --- 翻译流程 (如果需要) ---
|
||||||
if (selectedTranslationModelName !== 'none') {
|
if (selectedTranslationModelName !== 'none') {
|
||||||
const translationKeyValue = translationKeyObject ? translationKeyObject.value : null;
|
const translationKeyValue = translationKeyObject ? translationKeyObject.value : null;
|
||||||
if (!translationKeyValue) {
|
|
||||||
|
// 检查是否使用后端代理模式(不需要前端 API Key)
|
||||||
|
const useBackendProxy = selectedTranslationModelName === 'tongyi' || selectedTranslationModelName === 'aliyun';
|
||||||
|
|
||||||
|
if (!translationKeyValue && !useBackendProxy) {
|
||||||
if (typeof addProgressLog === "function") addProgressLog(`${logPrefix} 警告: 需要翻译但未提供有效的翻译 API Key。跳过翻译。`);
|
if (typeof addProgressLog === "function") addProgressLog(`${logPrefix} 警告: 需要翻译但未提供有效的翻译 API Key。跳过翻译。`);
|
||||||
currentTranslationContent = '[未翻译:缺少API Key]';
|
currentTranslationContent = '[未翻译:缺少API Key]';
|
||||||
ocrChunks = [currentMarkdownContent];
|
ocrChunks = [currentMarkdownContent];
|
||||||
translatedChunks = [currentTranslationContent];
|
translatedChunks = [currentTranslationContent];
|
||||||
} else {
|
} else {
|
||||||
if (typeof addProgressLog === "function") addProgressLog(`${logPrefix} 开始翻译 (${selectedTranslationModelName}, Key: ...${translationKeyValue.slice(-4)})`);
|
if (typeof addProgressLog === "function") {
|
||||||
|
if (useBackendProxy) {
|
||||||
|
addProgressLog(`${logPrefix} 开始翻译 (${selectedTranslationModelName}, 使用后端代理)`);
|
||||||
|
} else {
|
||||||
|
addProgressLog(`${logPrefix} 开始翻译 (${selectedTranslationModelName}, Key: ...${translationKeyValue.slice(-4)})`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ===== MinerU 结构化翻译检测 =====
|
// ===== MinerU 结构化翻译检测 =====
|
||||||
let shouldUseStructuredTranslation = false;
|
let shouldUseStructuredTranslation = false;
|
||||||
|
|
@ -776,6 +786,9 @@ const fileType = fileToProcess.name.split('.').pop().toLowerCase();
|
||||||
});
|
});
|
||||||
|
|
||||||
// 4. 执行批量翻译
|
// 4. 执行批量翻译
|
||||||
|
// 检查是否使用后端代理模式
|
||||||
|
const useBackendProxy = !translationKeyValue && (selectedTranslationModelName === 'tongyi' || selectedTranslationModelName === 'aliyun');
|
||||||
|
|
||||||
const translatedContentList = await structuredTranslator.translateBatches(
|
const translatedContentList = await structuredTranslator.translateBatches(
|
||||||
batches,
|
batches,
|
||||||
targetLanguageValue,
|
targetLanguageValue,
|
||||||
|
|
@ -783,6 +796,9 @@ const fileType = fileToProcess.name.split('.').pop().toLowerCase();
|
||||||
translationKeyValue,
|
translationKeyValue,
|
||||||
{
|
{
|
||||||
...translationOptions,
|
...translationOptions,
|
||||||
|
useBackendProxy,
|
||||||
|
provider: selectedTranslationModelName,
|
||||||
|
proxyBase: 'http://localhost:3456',
|
||||||
// 允许从设置自定义重试,若无则用默认
|
// 允许从设置自定义重试,若无则用默认
|
||||||
maxRetries: (typeof loadSettings === 'function' ? (loadSettings().structuredMaxRetries || undefined) : undefined),
|
maxRetries: (typeof loadSettings === 'function' ? (loadSettings().structuredMaxRetries || undefined) : undefined),
|
||||||
retryDelay: (typeof loadSettings === 'function' ? (loadSettings().structuredRetryDelayMs || undefined) : undefined)
|
retryDelay: (typeof loadSettings === 'function' ? (loadSettings().structuredRetryDelayMs || undefined) : undefined)
|
||||||
|
|
@ -1009,60 +1025,61 @@ const fileType = fileToProcess.name.split('.').pop().toLowerCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
const processedAt = new Date().toISOString();
|
const processedAt = new Date().toISOString();
|
||||||
if (typeof saveResultToDB === "function") {
|
|
||||||
// 准备元数据
|
|
||||||
const metadataToSave = {};
|
|
||||||
|
|
||||||
// 如果是 MinerU 结构化翻译,保存额外的元数据
|
// 准备元数据(在条件块外定义,以便返回值使用)
|
||||||
if (ocrResult && ocrResult.metadata) {
|
const metadataToSave = {};
|
||||||
// 保存 layoutJson 和 contentListJson
|
|
||||||
if (ocrResult.metadata.layoutJson) {
|
// 如果是 MinerU 结构化翻译,保存额外的元数据
|
||||||
metadataToSave.layoutJson = ocrResult.metadata.layoutJson;
|
if (ocrResult && ocrResult.metadata) {
|
||||||
}
|
// 保存 layoutJson 和 contentListJson
|
||||||
if (ocrResult.metadata.contentListJson) {
|
if (ocrResult.metadata.layoutJson) {
|
||||||
metadataToSave.contentListJson = ocrResult.metadata.contentListJson;
|
metadataToSave.layoutJson = ocrResult.metadata.layoutJson;
|
||||||
}
|
|
||||||
// 保存翻译后的结构化内容
|
|
||||||
if (ocrResult.metadata.translatedContentList) {
|
|
||||||
metadataToSave.translatedContentList = ocrResult.metadata.translatedContentList;
|
|
||||||
}
|
|
||||||
// 保存原始 PDF(转为 base64)
|
|
||||||
if (ocrResult.metadata.originalPdf) {
|
|
||||||
try {
|
|
||||||
const pdfBlob = ocrResult.metadata.originalPdf;
|
|
||||||
const pdfArrayBuffer = await pdfBlob.arrayBuffer();
|
|
||||||
metadataToSave.originalPdfBase64 = arrayBufferToBase64(pdfArrayBuffer);
|
|
||||||
if (typeof addProgressLog === "function") {
|
|
||||||
addProgressLog(`${logPrefix} 已保存原始 PDF (${Math.round(pdfBlob.size / 1024)} KB)`);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.warn(`${logPrefix} 保存原始 PDF 失败:`, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 标记支持结构化翻译
|
|
||||||
metadataToSave.supportsStructuredTranslation = ocrResult.metadata.supportsStructuredTranslation;
|
|
||||||
// 持久化结构化失败项统计(如存在)
|
|
||||||
if (Array.isArray(ocrResult.metadata.failedStructuredItems)) {
|
|
||||||
metadataToSave.failedStructuredItems = ocrResult.metadata.failedStructuredItems;
|
|
||||||
}
|
|
||||||
if (typeof ocrResult.metadata.structuredFailedCount === 'number') {
|
|
||||||
metadataToSave.structuredFailedCount = ocrResult.metadata.structuredFailedCount;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if (ocrResult.metadata.contentListJson) {
|
||||||
// 新增:对于所有 PDF 文件,如果还没有 originalPdfBase64,则从原始文件读取
|
metadataToSave.contentListJson = ocrResult.metadata.contentListJson;
|
||||||
if (fileType === 'pdf' && !metadataToSave.originalPdfBase64) {
|
}
|
||||||
|
// 保存翻译后的结构化内容
|
||||||
|
if (ocrResult.metadata.translatedContentList) {
|
||||||
|
metadataToSave.translatedContentList = ocrResult.metadata.translatedContentList;
|
||||||
|
}
|
||||||
|
// 保存原始 PDF(转为 base64)
|
||||||
|
if (ocrResult.metadata.originalPdf) {
|
||||||
try {
|
try {
|
||||||
const pdfArrayBuffer = await fileToProcess.arrayBuffer();
|
const pdfBlob = ocrResult.metadata.originalPdf;
|
||||||
|
const pdfArrayBuffer = await pdfBlob.arrayBuffer();
|
||||||
metadataToSave.originalPdfBase64 = arrayBufferToBase64(pdfArrayBuffer);
|
metadataToSave.originalPdfBase64 = arrayBufferToBase64(pdfArrayBuffer);
|
||||||
if (typeof addProgressLog === "function") {
|
if (typeof addProgressLog === "function") {
|
||||||
addProgressLog(`${logPrefix} 已保存原始 PDF 用于查看 (${Math.round(fileToProcess.size / 1024)} KB)`);
|
addProgressLog(`${logPrefix} 已保存原始 PDF (${Math.round(pdfBlob.size / 1024)} KB)`);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn(`${logPrefix} 保存原始 PDF 失败:`, e);
|
console.warn(`${logPrefix} 保存原始 PDF 失败:`, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 标记支持结构化翻译
|
||||||
|
metadataToSave.supportsStructuredTranslation = ocrResult.metadata.supportsStructuredTranslation;
|
||||||
|
// 持久化结构化失败项统计(如存在)
|
||||||
|
if (Array.isArray(ocrResult.metadata.failedStructuredItems)) {
|
||||||
|
metadataToSave.failedStructuredItems = ocrResult.metadata.failedStructuredItems;
|
||||||
|
}
|
||||||
|
if (typeof ocrResult.metadata.structuredFailedCount === 'number') {
|
||||||
|
metadataToSave.structuredFailedCount = ocrResult.metadata.structuredFailedCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增:对于所有 PDF 文件,如果还没有 originalPdfBase64,则从原始文件读取
|
||||||
|
if (fileType === 'pdf' && !metadataToSave.originalPdfBase64) {
|
||||||
|
try {
|
||||||
|
const pdfArrayBuffer = await fileToProcess.arrayBuffer();
|
||||||
|
metadataToSave.originalPdfBase64 = arrayBufferToBase64(pdfArrayBuffer);
|
||||||
|
if (typeof addProgressLog === "function") {
|
||||||
|
addProgressLog(`${logPrefix} 已保存原始 PDF 用于查看 (${Math.round(fileToProcess.size / 1024)} KB)`);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn(`${logPrefix} 保存原始 PDF 失败:`, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof saveResultToDB === "function") {
|
||||||
await saveResultToDB({
|
await saveResultToDB({
|
||||||
id: `${fileToProcess.name}_${fileToProcess.size}`,
|
id: `${fileToProcess.name}_${fileToProcess.size}`,
|
||||||
name: fileToProcess.name,
|
name: fileToProcess.name,
|
||||||
|
|
@ -1137,7 +1154,9 @@ const fileType = fileToProcess.name.split('.').pop().toLowerCase();
|
||||||
batchOutputLanguage: batchContext ? batchContext.outputLanguage : null,
|
batchOutputLanguage: batchContext ? batchContext.outputLanguage : null,
|
||||||
batchOriginalIndex: batchContext ? batchContext.originalIndex : null,
|
batchOriginalIndex: batchContext ? batchContext.originalIndex : null,
|
||||||
batchAttempt: batchContext ? batchContext.attempt : null,
|
batchAttempt: batchContext ? batchContext.attempt : null,
|
||||||
batchZip: batchContext ? batchContext.zipOutput : null
|
batchZip: batchContext ? batchContext.zipOutput : null,
|
||||||
|
// 返回 metadata,包含 contentListJson 等结构化翻译数据
|
||||||
|
metadata: Object.keys(metadataToSave).length > 0 ? metadataToSave : null
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1186,3 +1205,9 @@ if (typeof processModule !== 'undefined') {
|
||||||
} else {
|
} else {
|
||||||
console.warn('main.js: processModule is undefined at the point of assignment.');
|
console.warn('main.js: processModule is undefined at the point of assignment.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 也暴露到 window 上,以便在 history_detail.html 等页面使用
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
window.processSinglePdf = processSinglePdf;
|
||||||
|
console.log('main.js: processSinglePdf exposed to window');
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -701,7 +701,88 @@ ${jsonContent}
|
||||||
* @returns {Promise<string>}
|
* @returns {Promise<string>}
|
||||||
*/
|
*/
|
||||||
async callTranslationAPI(systemPrompt, userPrompt, model, apiKey, options = {}) {
|
async callTranslationAPI(systemPrompt, userPrompt, model, apiKey, options = {}) {
|
||||||
// 复用 translation.js 的配置构建逻辑
|
const settings = typeof loadSettings === 'function' ? loadSettings() : {};
|
||||||
|
const temperature = (settings.customModelSettings && settings.customModelSettings.temperature) || 0.5;
|
||||||
|
const maxTokens = (settings.customModelSettings && settings.customModelSettings.max_tokens) || 8000;
|
||||||
|
|
||||||
|
console.log('[MinerU Structured] callTranslationAPI 调用参数:', {
|
||||||
|
model,
|
||||||
|
hasApiKey: !!apiKey,
|
||||||
|
useBackendProxy: options.useBackendProxy,
|
||||||
|
options
|
||||||
|
});
|
||||||
|
|
||||||
|
// 后端代理模式 - API Key 由后端管理
|
||||||
|
if (options.useBackendProxy) {
|
||||||
|
const proxyBase = options.proxyBase || 'http://localhost:3456';
|
||||||
|
const provider = options.provider || 'aliyun';
|
||||||
|
|
||||||
|
// 后端代理端点映射
|
||||||
|
const providerEndpoints = {
|
||||||
|
'aliyun': `${proxyBase}/api/llm/aliyun/v1/chat/completions`,
|
||||||
|
'tongyi': `${proxyBase}/api/llm/tongyi/v1/chat/completions`,
|
||||||
|
'deepseek': `${proxyBase}/api/llm/deepseek/v1/chat/completions`,
|
||||||
|
'openai': `${proxyBase}/api/llm/openai/v1/chat/completions`,
|
||||||
|
'mistral': `${proxyBase}/api/llm/mistral/v1/chat/completions`,
|
||||||
|
'zhipu': `${proxyBase}/api/llm/zhipu/v4/chat/completions`,
|
||||||
|
'anthropic': `${proxyBase}/api/llm/anthropic/v1/messages`,
|
||||||
|
'gemini': `${proxyBase}/api/llm/gemini/v1beta/models/gemini-pro:generateContent`
|
||||||
|
};
|
||||||
|
|
||||||
|
const endpoint = providerEndpoints[provider] || providerEndpoints['aliyun'];
|
||||||
|
|
||||||
|
// 获取模型 ID
|
||||||
|
let modelId = 'qwen-turbo-latest';
|
||||||
|
try {
|
||||||
|
const modelConfig = typeof loadModelConfig === 'function' ? loadModelConfig(provider) : null;
|
||||||
|
if (modelConfig && (modelConfig.preferredModelId || modelConfig.modelId)) {
|
||||||
|
modelId = modelConfig.preferredModelId || modelConfig.modelId;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('[MinerU Structured] 加载模型配置失败,使用默认模型:', e);
|
||||||
|
}
|
||||||
|
|
||||||
|
const requestBody = {
|
||||||
|
model: modelId,
|
||||||
|
messages: [
|
||||||
|
{ role: "system", content: systemPrompt },
|
||||||
|
{ role: "user", content: userPrompt }
|
||||||
|
],
|
||||||
|
temperature,
|
||||||
|
max_tokens: maxTokens
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('[MinerU Structured] 后端代理模式:', { endpoint, provider, modelId });
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(endpoint, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(requestBody)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorText = await response.text();
|
||||||
|
throw new Error(`翻译 API 请求失败 (${response.status}): ${errorText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
const result = data?.choices?.[0]?.message?.content;
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
throw new Error('翻译 API 返回空结果');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清理指令块
|
||||||
|
return this._stripInstructionBlocks(result);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[MinerU Structured] 后端代理请求失败:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 原有逻辑 - 前端直接调用(需要 API Key)
|
||||||
if (typeof processModule === 'undefined' ||
|
if (typeof processModule === 'undefined' ||
|
||||||
typeof processModule.buildPredefinedApiConfig !== 'function' ||
|
typeof processModule.buildPredefinedApiConfig !== 'function' ||
|
||||||
typeof processModule.buildCustomApiConfig !== 'function') {
|
typeof processModule.buildCustomApiConfig !== 'function') {
|
||||||
|
|
@ -712,14 +793,6 @@ ${jsonContent}
|
||||||
throw new Error('callTranslationApi 函数不可用');
|
throw new Error('callTranslationApi 函数不可用');
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('[MinerU Structured] callTranslationAPI 调用参数:', {
|
|
||||||
model,
|
|
||||||
hasApiKey: !!apiKey,
|
|
||||||
options,
|
|
||||||
hasModelConfig: !!(options && options.modelConfig),
|
|
||||||
modelConfig: options ? options.modelConfig : null
|
|
||||||
});
|
|
||||||
|
|
||||||
// 构建 API 配置
|
// 构建 API 配置
|
||||||
let apiConfig;
|
let apiConfig;
|
||||||
if (model === 'custom') {
|
if (model === 'custom') {
|
||||||
|
|
@ -756,10 +829,6 @@ ${jsonContent}
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// 预设模型 - 从 translation.js 获取配置
|
// 预设模型 - 从 translation.js 获取配置
|
||||||
const settings = typeof loadSettings === 'function' ? loadSettings() : {};
|
|
||||||
const temperature = (settings.customModelSettings && settings.customModelSettings.temperature) || 0.5;
|
|
||||||
const maxTokens = (settings.customModelSettings && settings.customModelSettings.max_tokens) || 8000;
|
|
||||||
|
|
||||||
// 简化:仅支持常用模型
|
// 简化:仅支持常用模型
|
||||||
// 前端发出的请求源头
|
// 前端发出的请求源头
|
||||||
const predefinedConfigs = {
|
const predefinedConfigs = {
|
||||||
|
|
@ -811,17 +880,25 @@ ${jsonContent}
|
||||||
let result = await callTranslationApi(apiConfig, requestBody);
|
let result = await callTranslationApi(apiConfig, requestBody);
|
||||||
|
|
||||||
// 清理指令块(防止系统提示词泄露到翻译结果中)
|
// 清理指令块(防止系统提示词泄露到翻译结果中)
|
||||||
|
return this._stripInstructionBlocks(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清理指令块
|
||||||
|
* @param {string} result
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
_stripInstructionBlocks(result) {
|
||||||
if (typeof stripInstructionBlocks === 'function') {
|
if (typeof stripInstructionBlocks === 'function') {
|
||||||
result = stripInstructionBlocks(result);
|
return stripInstructionBlocks(result);
|
||||||
} else if (typeof processModule !== 'undefined' && typeof processModule.stripInstructionBlocks === 'function') {
|
} else if (typeof processModule !== 'undefined' && typeof processModule.stripInstructionBlocks === 'function') {
|
||||||
result = processModule.stripInstructionBlocks(result);
|
return processModule.stripInstructionBlocks(result);
|
||||||
} else {
|
} else {
|
||||||
// 回退:手动清理
|
// 回退:手动清理
|
||||||
if (typeof result === 'string') {
|
if (typeof result === 'string') {
|
||||||
result = result.replace(/\s*\[\[PBX_INSTR_START\]\][\s\S]*?\[\[PBX_INSTR_END\]\]\s*/gi, '').trim();
|
return result.replace(/\s*\[\[PBX_INSTR_START\]\][\s\S]*?\[\[PBX_INSTR_END\]\]\s*/gi, '').trim();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -596,7 +596,7 @@
|
||||||
class="btn-mineru-primary"
|
class="btn-mineru-primary"
|
||||||
onclick="mineruOpenHistory()"
|
onclick="mineruOpenHistory()"
|
||||||
>
|
>
|
||||||
📂 读取并打开历史界面
|
📂 读取并打开历史界面(仅跳转,不会OCR)我平常使用这个按钮测试这个功能
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
id="mineruProcessBtn"
|
id="mineruProcessBtn"
|
||||||
|
|
|
||||||
|
|
@ -823,6 +823,7 @@
|
||||||
<script src="../../js/process/ocr-adapters/doc2x-adapter.js"></script>
|
<script src="../../js/process/ocr-adapters/doc2x-adapter.js"></script>
|
||||||
<script src="../../js/process/ocr-adapters/local-adapter.js"></script>
|
<script src="../../js/process/ocr-adapters/local-adapter.js"></script>
|
||||||
<script src="../../js/process/ocr.js"></script>
|
<script src="../../js/process/ocr.js"></script>
|
||||||
|
<script src="../../js/process/main.js"></script>
|
||||||
|
|
||||||
<!-- OCR 设置管理器 -->
|
<!-- OCR 设置管理器 -->
|
||||||
<script src="../../js/ui/ocr-settings.js"></script>
|
<script src="../../js/ui/ocr-settings.js"></script>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue