paper-burner/test.html

650 lines
22 KiB
HTML
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.

<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<title>OCR 测试页面 - Mistral & MinerU</title>
<style>
body {
font-family: sans-serif;
padding: 40px;
max-width: 1200px;
margin: 0 auto;
}
h1 {
text-align: center;
margin-bottom: 30px;
}
.section {
border: 1px solid #ddd;
border-radius: 8px;
padding: 20px;
margin-bottom: 30px;
background-color: #f9f9f9;
}
.section h2 {
margin-top: 0;
padding-bottom: 10px;
border-bottom: 2px solid #ddd;
}
.section.mistral h2 {
color: #2196f3;
border-bottom-color: #2196f3;
}
.section.mineru h2 {
color: #ff9800;
border-bottom-color: #ff9800;
}
button {
padding: 12px 24px;
font-size: 16px;
cursor: pointer;
margin-right: 10px;
margin-bottom: 10px;
border: none;
border-radius: 4px;
transition: background-color 0.2s;
}
.btn-primary {
background-color: #4caf50;
color: white;
}
.btn-primary:hover {
background-color: #45a049;
}
.btn-secondary {
background-color: #2196f3;
color: white;
}
.btn-secondary:hover {
background-color: #1976d2;
}
.btn-mineru-primary {
background-color: #ff9800;
color: white;
}
.btn-mineru-primary:hover {
background-color: #f57c00;
}
.btn-mineru-secondary {
background-color: #795548;
color: white;
}
.btn-mineru-secondary:hover {
background-color: #5d4037;
}
.status {
margin-top: 15px;
padding: 10px;
background-color: #fff;
border-radius: 4px;
min-height: 50px;
color: #555;
font-family: monospace;
font-size: 14px;
}
.error {
color: red;
}
.success {
color: green;
}
.info {
color: #666;
font-size: 14px;
margin-top: 10px;
}
</style>
<!-- 引入必要的库 -->
<script src="js/lib/jszip.min.js"></script>
<script src="js/storage/storage.js"></script>
<script src="js/api/api.js"></script>
<script src="js/ui/ocr-settings.js"></script>
<script src="js/process/ocr-manager.js"></script>
<script src="js/process/ocr-adapters/mistral-adapter.js"></script>
<script src="js/process/ocr-adapters/mineru-adapter.js"></script>
<script src="js/process/mineru-structured-translation.js"></script>
<!-- 通过 index.js 动态加载所有处理脚本(包含 translation.js 和 main.js -->
<script src="js/process/index.js"></script>
<script src="js/history/history.js"></script>
<script>
// 由于已引入 ocr-settings.js不再需要手动创建 window.ocrSettingsManager
// ocr-settings.js 会在 DOMContentLoaded 时自动初始化
// ============ 工具函数 ============
function addLog(elementId, msg, type = "info") {
const statusDiv = document.getElementById(elementId);
if (!statusDiv) {
console.warn(`[addLog] Element ${elementId} not found`);
return;
}
const p = document.createElement("div");
p.textContent = `[${new Date().toLocaleTimeString()}] ${msg}`;
if (type === "error") p.className = "error";
if (type === "success") p.className = "success";
statusDiv.appendChild(p);
statusDiv.scrollTop = statusDiv.scrollHeight;
}
// 兼容 main.js 可能调用的全局 addProgressLog
window.addProgressLog = function (msg) {
// 尝试找到当前活动的状态区域
const activeStatus = document.querySelector(".status");
if (activeStatus) {
const p = document.createElement("div");
p.textContent = msg;
activeStatus.appendChild(p);
activeStatus.scrollTop = activeStatus.scrollHeight;
}
console.log("[addProgressLog]", msg);
};
function clearStatus(elementId) {
const el = document.getElementById(elementId);
if (el) el.innerHTML = "";
}
// 读取 input 文件夹第一个文件
async function fetchFirstInputFile() {
const response = await fetch(
"http://localhost:3456/api/local/read-first-input",
);
if (!response.ok) {
const errText = await response.text();
throw new Error(`获取文件失败:${response.status} ${errText}`);
}
// 解析文件名
const contentDisposition =
response.headers.get("Content-Disposition") || "";
let filename = "unknown.pdf";
const filenameMatch = contentDisposition.match(/filename="?([^"]+)"?/);
if (filenameMatch) {
filename = decodeURIComponent(filenameMatch[1]);
}
const blob = await response.blob();
const file = new File([blob], filename, {
type: blob.type || "application/pdf",
});
return { file, blob };
}
// 创建历史记录并跳转(使用原系统保存到数据库的逻辑)
async function createHistoryRecordAndNavigate(file, blob, statusDivId) {
const processedAt = new Date().toISOString();
const recordId = `${file.name}_${file.size}`;
// 读取 blob 为 base64
const arrayBuffer = await blob.arrayBuffer();
const base64 = arrayBufferToBase64(arrayBuffer);
// 构造与 processSinglePdf 一致的记录格式
const record = {
id: recordId,
name: file.name,
size: file.size,
time: processedAt,
ocr: "", // 没有 OCR 内容
translation: "", // 没有翻译内容
images: [],
ocrChunks: [],
translatedChunks: [],
fileType: file.name.split(".").pop().toLowerCase(),
targetLanguage: "zh-CN",
relativePath: file.name,
sourceArchive: null,
originalContent: null,
originalEncoding: null,
originalBinary: base64, // 保存文件内容
originalExtension: file.name.split(".").pop().toLowerCase(),
// OCR/翻译元信息
ocrEngine: "none",
ocrSource: null,
translationModelName: "none",
translationModelCustomName: null,
translationModelId: null,
batchId: null,
batchOrder: null,
batchTotal: null,
batchTemplate: null,
batchFormats: null,
batchStartedAt: null,
batchOutputLanguage: null,
batchOriginalIndex: null,
batchAttempt: null,
batchZip: null,
// MinerU 结构化翻译元数据(无)
metadata: {
originalPdfBase64: base64, // 用于历史详情页查看原始 PDF
},
};
if (typeof window.saveResultToDB === "function") {
await window.saveResultToDB(record);
addLog(statusDivId, `已创建历史记录ID: ${recordId}`, "success");
}
// 保存当前文档 ID
try {
localStorage.setItem("pbx_current_doc_id", recordId);
} catch (e) {
console.warn("Failed to save doc id to localStorage", e);
}
// 打开历史详情页
const historyUrl = `views/history/history_detail.html?id=${encodeURIComponent(recordId)}`;
window.open(historyUrl, "_blank");
return recordId;
}
// ============ Mistral 相关函数 ============
// Mistral: 读取并跳转(不做 OCR
async function mistralOpenHistory() {
const btn = document.getElementById("mistralOpenBtn");
const statusId = "mistralStatus";
btn.disabled = true;
clearStatus(statusId);
addLog(statusId, "正在读取 input 文件夹的第一个文件...");
try {
const { file, blob } = await fetchFirstInputFile();
addLog(
statusId,
`成功读取文件:${file.name} (${(file.size / 1024).toFixed(2)} KB)`,
);
addLog(statusId, "正在创建历史记录并跳转...");
await createHistoryRecordAndNavigate(file, blob, statusId);
addLog(statusId, "✅ 已打开历史详情页", "success");
} catch (err) {
addLog(statusId, `❌ 错误:${err.message}`, "error");
} finally {
btn.disabled = false;
}
}
// Mistral: 处理并跳转(执行 OCR
async function mistralProcessAndNavigate() {
const btn = document.getElementById("mistralProcessBtn");
const statusId = "mistralStatus";
btn.disabled = true;
clearStatus(statusId);
addLog(statusId, "正在读取 input 文件夹的第一个文件...");
try {
const { file, blob } = await fetchFirstInputFile();
addLog(
statusId,
`成功读取文件:${file.name} (${(file.size / 1024).toFixed(2)} KB)`,
);
addLog(statusId, "开始 Mistral OCR 处理...");
// 设置 localStorage 中的 OCR 配置
localStorage.setItem("ocrEngine", "mistral");
// 调用处理主逻辑
const result = await processSinglePdf(
file,
null, // mistral key 使用后端
null, // translation key
"none", // 翻译模型
null, // config
4000, // 默认 token 限制
"zh-CN", // 目标语言
async () => {}, // slot acquire
() => {}, // slot release
"", // sys prompt
"", // user prompt
false, // use custom
null, // batchContext
(f) => {
addLog(statusId, `处理成功: ${f.name}`, "success");
},
);
if (result.error) {
throw new Error(result.error);
}
addLog(statusId, "OCR 完成,正在跳转到详情页...");
const recordId = result.id || `${file.name}_${file.size}`;
// 等待 showHistoryDetail 函数可用
const waitForShowHistoryDetail = (callback, retries = 10) => {
if (typeof window.showHistoryDetail === "function") {
callback();
} else if (retries > 0) {
setTimeout(
() => waitForShowHistoryDetail(callback, retries - 1),
100,
);
} else {
window.location.href = `views/history/history_detail.html?id=${recordId}`;
}
};
waitForShowHistoryDetail(() => {
window.showHistoryDetail(recordId);
});
} catch (err) {
addLog(statusId, `❌ 错误:${err.message}`, "error");
} finally {
btn.disabled = false;
}
}
// ============ MinerU 相关函数 ============
// MinerU: 读取并跳转(不做 OCR
async function mineruOpenHistory() {
const btn = document.getElementById("mineruOpenBtn");
const statusId = "mineruStatus";
btn.disabled = true;
clearStatus(statusId);
addLog(statusId, "正在读取 input 文件夹的第一个文件...");
try {
const { file, blob } = await fetchFirstInputFile();
addLog(
statusId,
`成功读取文件:${file.name} (${(file.size / 1024).toFixed(2)} KB)`,
);
addLog(statusId, "正在创建历史记录并跳转...");
await createHistoryRecordAndNavigate(file, blob, statusId);
addLog(statusId, "✅ 已打开历史详情页", "success");
} catch (err) {
addLog(statusId, `❌ 错误:${err.message}`, "error");
} finally {
btn.disabled = false;
}
}
// MinerU: 处理并跳转(执行 OCR
async function mineruProcessAndNavigate() {
const btn = document.getElementById("mineruProcessBtn");
const statusId = "mineruStatus";
btn.disabled = true;
clearStatus(statusId);
addLog(statusId, "正在读取 input 文件夹的第一个文件...");
try {
const { file, blob } = await fetchFirstInputFile();
addLog(
statusId,
`成功读取文件:${file.name} (${(file.size / 1024).toFixed(2)} KB)`,
);
// 设置 localStorage 中的 OCR 配置
localStorage.setItem("ocrEngine", "mineru");
localStorage.setItem("ocrMinerUWorkerUrl", "http://localhost:3456");
localStorage.setItem("ocrMinerUEnableOcr", "true");
localStorage.setItem("ocrMinerUEnableFormula", "true");
localStorage.setItem("ocrMinerUEnableTable", "true");
localStorage.setItem("ocrMinerUTokenMode", "backend");
addLog(statusId, "开始 MinerU OCR 处理...");
// 调用处理主逻辑
const result = await processSinglePdf(
file,
null, // mineru key 使用后端
null, // translation key
"none", // 翻译模型
null, // config
4000, // 默认 token 限制
"zh-CN", // 目标语言
async () => {}, // slot acquire
() => {}, // slot release
"", // sys prompt
"", // user prompt
false, // use custom
null, // batchContext
(f) => {
addLog(statusId, `处理成功: ${f.name}`, "success");
},
);
if (result.error) {
throw new Error(result.error);
}
addLog(statusId, "OCR 完成,正在跳转到详情页...");
const recordId = result.id || `${file.name}_${file.size}`;
// 等待 showHistoryDetail 函数可用
const waitForShowHistoryDetail = (callback, retries = 10) => {
if (typeof window.showHistoryDetail === "function") {
callback();
} else if (retries > 0) {
setTimeout(
() => waitForShowHistoryDetail(callback, retries - 1),
100,
);
} else {
window.location.href = `views/history/history_detail.html?id=${recordId}`;
}
};
waitForShowHistoryDetail(() => {
window.showHistoryDetail(recordId);
});
} catch (err) {
addLog(statusId, `❌ 错误:${err.message}`, "error");
} finally {
btn.disabled = false;
}
}
// 从 localStorage 读取 API Key
// 通义百炼:结构化翻译测试
async function tongyiStructuredTranslation() {
const btn = document.getElementById("tongyiStructuredBtn");
const statusId = "tongyiStructuredStatus";
btn.disabled = true;
clearStatus(statusId);
addLog(statusId, "正在读取 input 文件夹的第一个文件...");
try {
// 等待 processModule 初始化完成
if (
typeof processModule === "undefined" ||
typeof processModule.translateMarkdown !== "function"
) {
addLog(statusId, "正在等待处理模块初始化...", "info");
await new Promise((resolve) => {
const checkInterval = setInterval(() => {
if (
typeof processModule !== "undefined" &&
typeof processModule.translateMarkdown === "function"
) {
clearInterval(checkInterval);
resolve();
}
}, 100);
});
addLog(statusId, "处理模块已初始化", "success");
}
const { file, blob } = await fetchFirstInputFile();
addLog(
statusId,
`成功读取文件:${file.name} (${(file.size / 1024).toFixed(2)} KB)`,
);
addLog(statusId, "开始 MinerU OCR 处理(结构化模式)...");
// 设置 localStorage 中的 OCR 配置
localStorage.setItem("ocrEngine", "mineru");
localStorage.setItem("ocrMinerUWorkerUrl", "http://localhost:3456");
localStorage.setItem("ocrMinerUEnableOcr", "true");
localStorage.setItem("ocrMinerUEnableFormula", "true");
localStorage.setItem("ocrMinerUEnableTable", "true");
localStorage.setItem("ocrMinerUTokenMode", "backend");
addLog(statusId, "开始 OCR 处理...");
// 由于使用后端代理无需前端API Key
const targetLanguageSelect = document.getElementById("targetLanguageSelect");
const targetLanguage = targetLanguageSelect ? targetLanguageSelect.value : "zh-CN"; // 默认为中文简体
// 调用处理主逻辑(包含结构化翻译)
const result = await processSinglePdf(
file,
null, // mineru key 使用后端
{
id: "tongyi-key",
value: "sk-proxy", // 使用占位符密钥,实际调用由后端处理
},
"proxy", // 使用 proxy 进行翻译
null, // config
4000, // 默认 token 限制
targetLanguage, // 目标语言
async () => {}, // slot acquire
() => {}, // slot release
"", // sys prompt
"", // user prompt
false, // use custom
null, // batchContext
(f) => {
addLog(statusId, `处理成功: ${f.name}`, "success");
},
);
if (result.error) {
throw new Error(result.error);
}
addLog(statusId, "✅ OCR + 翻译完成,正在跳转到详情页...");
const recordId = result.id || `${file.name}_${file.size}`;
// 等待 showHistoryDetail 函数可用
const waitForShowHistoryDetail = (callback, retries = 10) => {
if (typeof window.showHistoryDetail === "function") {
callback();
} else if (retries > 0) {
setTimeout(
() => waitForShowHistoryDetail(callback, retries - 1),
100,
);
} else {
window.location.href = `views/history/history_detail.html?id=${recordId}`;
}
};
waitForShowHistoryDetail(() => {
window.showHistoryDetail(recordId);
});
} catch (err) {
addLog(statusId, `❌ 错误:${err.message}`, "error");
} finally {
btn.disabled = false;
}
}
</script>
</head>
<body>
<h1>OCR 测试页面</h1>
<!-- Mistral 测试区域 -->
<div class="section mistral">
<h2>🔷 Mistral OCR 测试</h2>
<div class="info">
代理后端: http://localhost:3456/api/llm/mistral/...<br />
测试功能:读取 input 文件夹第一个 PDF 文件
</div>
<div style="margin-top: 15px">
<button
id="mistralOpenBtn"
class="btn-primary"
onclick="mistralOpenHistory()"
>
📂 读取并打开历史界面
</button>
<button
id="mistralProcessBtn"
class="btn-secondary"
onclick="mistralProcessAndNavigate()"
>
⚙️ 处理并跳转 (OCR)
</button>
</div>
<div id="mistralStatus" class="status"></div>
</div>
<!-- MinerU 测试区域 -->
<div class="section mineru">
<h2>🔶 MinerU OCR 测试</h2>
<div class="info">
代理后端: http://localhost:3456/mineru/...<br />
测试功能:读取 input 文件夹第一个 PDF 文件
</div>
<div style="margin-top: 15px">
<button
id="mineruOpenBtn"
class="btn-mineru-primary"
onclick="mineruOpenHistory()"
>
📂 读取并打开历史界面仅跳转不会OCR我平常使用这个按钮测试这个功能
</button>
<button
id="mineruProcessBtn"
class="btn-mineru-secondary"
onclick="mineruProcessAndNavigate()"
>
⚙️ 处理并跳转 (OCR)
</button>
</div>
<div id="mineruStatus" class="status"></div>
<div
style="margin-top: 20px; padding-top: 20px; border-top: 2px dashed #ccc"
>
<h3 style="margin-top: 0; color: #ff9800">📋 MinerU + Qwen 翻译</h3>
<div style="margin-top: 10px">
<label for="targetLanguageSelect" style="display: inline-block; width: 120px; margin-right: 10px;">目标语言:</label>
<select id="targetLanguageSelect" style="padding: 5px; border: 1px solid #ccc; border-radius: 4px;">
<option value="zh-CN">中文简体</option>
<option value="zh-TW">中文繁體</option>
<option value="en">English</option>
<option value="ja">日本語</option>
<option value="ko">한국어</option>
<option value="fr">Français</option>
<option value="de">Deutsch</option>
<option value="es">Español</option>
<option value="ru">Русский</option>
<option value="ar">العربية</option>
</select>
</div>
<div style="margin-top: 10px">
<button
id="tongyiStructuredBtn"
class="btn-mineru-primary"
onclick="tongyiStructuredTranslation()"
>
🔤 执行结构化翻译
</button>
</div>
<div
id="tongyiStructuredStatus"
class="status"
style="margin-top: 10px"
></div>
</div>
</div>
</body>
</html>