feat: PDF对照视图移植进沉浸式布局,交互优化与多项修复

- PDF对照视图:只在有结构化翻译数据时隐藏AI助手,修复确认框弹出时tab栏被隐藏
- 修复翻译模型名称错误(tongyi -> aliyun)
- 仅阅读功能:添加延迟确保IndexedDB事务提交后再跳转
- triggerReprocessWithMinerU完成后自动跳转到PDF对照视图
- 重置renderingTab防抖锁解决界面卡住问题
- 永久隐藏沉浸模式切换按钮
- 删除未使用的logo SVG文件
This commit is contained in:
肖应宇 2026-03-20 14:28:05 +08:00
parent 823d6c15a7
commit eb2a09b82f
17 changed files with 68 additions and 2206 deletions

View File

@ -494,6 +494,7 @@ body.toc-dock-resizing #toc-vs-dock-resize-handle {
/* ==================== 8. 沉浸模式切换按钮 ==================== */
#toggle-immersive-btn {
display: none !important; /* 永久隐藏 */
position: fixed !important;
top: var(--spacing-md) !important;
right: var(--spacing-md) !important;
@ -673,9 +674,9 @@ body.immersive-active #immersive-main-content-area .container {
flex-direction: column !important;
}
/* 沉浸模式下隐藏 PDF 对照按钮 */
/* 沉浸模式下显示 PDF 对照按钮(用户要求在沉浸模式中也能使用) */
body.immersive-active #tab-pdf-compare {
display: none !important;
display: flex !important;
}
/* Modal 和右键菜单的 z-index 提升 */

View File

@ -5,7 +5,6 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Paper Burner X - 您的一站式AI文献阅读与智能分析平台支持OCR、翻译、深度解析与高级管理。">
<title>Paper Burner X - AI文献阅读与智能分析平台</title>
<link rel="icon" type="image/svg+xml" href="public/pure.svg">
<!-- CDN 性能优化DNS 预连接 - 提前建立连接,节省 100-300ms --> <link rel="dns-prefetch" href="https://gcore.jsdelivr.net"> <link rel="preconnect" href="https://gcore.jsdelivr.net" crossorigin> <link rel="dns-prefetch" href="https://cdnjs.cloudflare.com"> <link rel="preconnect" href="https://cdnjs.cloudflare.com" crossorigin> <link rel="dns-prefetch" href="https://cdn.tailwindcss.com"> <link rel="preconnect" href="https://cdn.tailwindcss.com" crossorigin>
<!-- XSS 防护DOMPurify - 用于清理 AI 生成的 HTML 内容 -->
@ -238,12 +237,11 @@
bottom: -40px;
width: 300px;
height: 300px;
background-image: url('public/pure.svg');
background-repeat: no-repeat;
background-position: center;
background-size: contain;
极低透明度,仅作纹理
opacity: 0.07;
opacity: 0.07;
transform: rotate(-10deg);
pointer-events: none;
} */
@ -582,7 +580,6 @@
<!-- 移动端 Header
<header class="md:hidden bg-white/80 backdrop-blur-md border-b border-slate-200 px-4 py-3 flex items-center justify-between sticky top-0 z-10">
<a href="views/landing/landing-page.html" class="text-lg font-bold flex items-center gap-2 text-slate-800">
<img src="public/pure.svg" class="w-7 h-7" alt="PBX Logo">
PBX
</a>
<div class="flex items-center gap-3">
@ -1197,7 +1194,7 @@
<!-- 卡片 4: 文件上传 -->
<!-- ===================== -->
<div class="modern-card p-4 md:p-6">
<div class="section-header justify-between">
<!-- <div class="section-header justify-between">
<h2 class="section-title">
<div class="w-9 h-9 rounded-xl text-white flex items-center justify-center mr-3 shadow-md" style="background: var(--color-primary); box-shadow: var(--shadow-sm);">
<iconify-icon icon="mdi:upload" width="20"></iconify-icon>
@ -1208,7 +1205,7 @@
<input type="checkbox" id="batchModeToggle" class="rounded border-slate-300 focus:ring-indigo-500 h-4 w-4" style="color: var(--color-primary); accent-color: var(--color-primary);">
<span class="select-none font-medium">批量模式</span>
</label>
</div>
</div> -->
<!-- 拖拽区域 -->
<div id="dropZone" class="upload-zone-modern p-8 text-center mb-6 group cursor-pointer">
@ -1937,19 +1934,14 @@
// Desktop Sidebar Collapse Logic
const sidebarToggleBtn = document.getElementById('sidebarToggleBtn');
const sidebarToggleIcon = document.getElementById('sidebarToggleIcon');
const sidebarLogo = document.getElementById('sidebarLogo');
const LOGO_FULL = 'public/h_with_name.svg';
const LOGO_PURE = 'public/pure.svg';
function setSidebarState(collapsed) {
if (collapsed) {
appSidebar.classList.add('collapsed');
if (sidebarToggleIcon) sidebarToggleIcon.setAttribute('icon', 'carbon:side-panel-open');
if (sidebarLogo) sidebarLogo.src = LOGO_PURE;
} else {
appSidebar.classList.remove('collapsed');
if (sidebarToggleIcon) sidebarToggleIcon.setAttribute('icon', 'carbon:side-panel-close');
if (sidebarLogo) sidebarLogo.src = LOGO_FULL;
}
localStorage.setItem('pbx_sidebar_collapsed', collapsed);
}

File diff suppressed because it is too large Load Diff

View File

@ -2264,6 +2264,11 @@ async function handleReadClick() {
return;
}
// 添加短暂延迟确保 IndexedDB 事务完全提交
await new Promise(resolve => setTimeout(resolve, 100));
console.log('[仅阅读] 数据已保存,准备跳转...');
// 跳转到历史详情页面
window.location.href = `views/history/history_detail.html?id=${encodeURIComponent(recordId)}`;
} catch (error) {

View File

@ -276,12 +276,9 @@ window.ChatbotMessageRenderer = {
const isPurelyEmpty = (!m.content || String(m.content).trim() === '') && !m.reasoningContent && !m.toolCallHtml;
if (m.role === 'assistant' && isPurelyEmpty) {
// Determine the correct path for the logo based on the current page
const isHistoryDetail = window.location.pathname.includes('/history_detail.html');
const logoPath = isHistoryDetail ? '../../public/pure.svg' : 'public/pure.svg';
renderedContent = `
<div class="typing-indicator">
<img src="${logoPath}" class="typing-logo" alt="Thinking..." />
<iconify-icon icon="carbon:ai" class="typing-icon" width="24"></iconify-icon>
</div>
`;
} else {
@ -525,15 +522,11 @@ window.ChatbotMessageRenderer = {
* @returns {string} HTML字符串
*/
renderTypingIndicator: function() {
// Determine the correct path for the logo based on the current page
const isHistoryDetail = window.location.pathname.includes('/history_detail.html');
const logoPath = isHistoryDetail ? '../../public/pure.svg' : 'public/pure.svg';
return `
<div class="message-container assistant-message-container">
<div class="chat-bubble assistant typing-bubble">
<div class="typing-indicator">
<img src="${logoPath}" class="typing-logo" alt="Thinking..." />
<iconify-icon icon="carbon:ai" class="typing-icon" width="24"></iconify-icon>
</div>
</div>
</div>

View File

@ -729,7 +729,7 @@ async function triggerReprocessWithMinerU() {
pdfFile,
null, // mistralKeyObject - 使用 MinerU 不需要
null, // translationKeyObject - 使用后端代理不需要
'tongyi', // 使用通义模型,后端代理会处理
'aliyun', // 使用阿里云百炼(后端代理会处理)
null, // translationModelConfig
settings.maxTokensPerChunk || 2000,
settings.targetLanguage || 'Chinese',
@ -766,16 +766,21 @@ async function triggerReprocessWithMinerU() {
await saveResultToDB(window.data);
}
showToast('处理完成!', 'success');
showToast('处理完成!正在加载 PDF 对照视图...', 'success');
// 重置渲染锁
if (typeof renderingTab !== 'undefined') renderingTab = null;
// 刷新页面显示
if (typeof renderDetail === 'function') {
renderDetail();
await renderDetail();
}
// 自动跳转到原始文件标签页
if (typeof showTab === 'function') {
showTab('original-file');
// 自动跳转到 PDF 对照视图
if (typeof showTabImmediate === 'function') {
showTabImmediate('pdf-compare');
} else if (typeof showTab === 'function') {
showTab('pdf-compare');
}
} catch (error) {
@ -976,9 +981,17 @@ async function executeMinerUStructuredTranslation() {
addLog(`注意: 有 ${failedItems.length} 个片段翻译失败`);
}
addLog('正在加载 PDF 对照视图...');
addLog('正在刷新界面...');
// 短暂延迟后显示 PDF 对照视图
// 重置渲染锁,确保后续 showTab 可以执行
if (typeof renderingTab !== 'undefined') renderingTab = null;
// 刷新界面
if (typeof renderDetail === 'function') {
await renderDetail(); // await async 函数
}
// 延迟后显示 PDF 对照视图
setTimeout(() => {
if (typeof showTabImmediate === 'function') {
showTabImmediate('pdf-compare');

View File

@ -150,6 +150,10 @@ function showTabImmediate(tab) {
if (DOM_CACHE.layout.meta) DOM_CACHE.layout.meta.style.display = '';
if (DOM_CACHE.layout.tabsContainer) DOM_CACHE.layout.tabsContainer.style.display = '';
// 恢复 AI 智能助手显示(退出 PDF 对照模式时)
const chatbotArea = document.getElementById('immersive-chatbot-area');
if (chatbotArea) chatbotArea.style.display = '';
let html = '';
let contentContainerId = ''; // 用于 applyAnnotationsToContent
let activeContentElement = null; // 用于 applyAnnotationsToContent
@ -295,11 +299,6 @@ function showTabImmediate(tab) {
// ========== MinerU PDF 对照视图 ==========
if (DOM_CACHE.tabs.pdfCompare) DOM_CACHE.tabs.pdfCompare.classList.add('active');
// 隐藏顶部区域以获得更大空间 - 使用缓存
if (DOM_CACHE.layout.title) DOM_CACHE.layout.title.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';
// 检查是否有必要的结构化翻译数据
const hasStructuredData = data.metadata && data.metadata.originalPdfBase64 && data.metadata.contentListJson && data.metadata.translatedContentList;
@ -311,6 +310,13 @@ function showTabImmediate(tab) {
return;
}
// 有结构化数据时,隐藏顶部区域和 AI 智能助手以获得更大空间
if (DOM_CACHE.layout.title) DOM_CACHE.layout.title.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';
const chatbotArea = document.getElementById('immersive-chatbot-area');
if (chatbotArea) chatbotArea.style.display = 'none';
// 设置 HTML 容器
document.getElementById('tabContent').innerHTML = '<div id="pdf-compare-container"></div>';

View File

@ -125,7 +125,7 @@ class OcrSettingsManager {
this.elements.mineruToken.value = localStorage.getItem(this.keys.mineruToken) || '';
}
if (this.elements.mineruWorkerUrl) {
this.elements.mineruWorkerUrl.value = localStorage.getItem(this.keys.mineruWorkerUrl) || '';
this.elements.mineruWorkerUrl.value = localStorage.getItem(this.keys.mineruWorkerUrl) || 'http://localhost:3456';
}
if (this.elements.mineruEnableOcr) {
this.elements.mineruEnableOcr.checked = localStorage.getItem(this.keys.mineruEnableOcr) !== 'false';
@ -151,7 +151,7 @@ class OcrSettingsManager {
this.elements.doc2xToken.value = localStorage.getItem(this.keys.doc2xToken) || '';
}
if (this.elements.doc2xWorkerUrl) {
this.elements.doc2xWorkerUrl.value = localStorage.getItem(this.keys.doc2xWorkerUrl) || '';
this.elements.doc2xWorkerUrl.value = localStorage.getItem(this.keys.doc2xWorkerUrl) || 'http://localhost:3456';
}
if (this.elements.doc2xFormulaMode) {
this.elements.doc2xFormulaMode.value = localStorage.getItem(this.keys.doc2xFormulaMode) || 'dollar';
@ -240,7 +240,7 @@ class OcrSettingsManager {
localStorage.setItem(this.keys.mineruWorkerUrl, (cfg.workerUrl || '').replace(/\/+$/, ''));
localStorage.setItem(this.keys.mineruToken, cfg.token || '');
localStorage.setItem(this.keys.workerAuthKey, cfg.authKey || '');
localStorage.setItem(this.keys.mineruTokenMode, cfg.tokenMode || 'frontend');
localStorage.setItem(this.keys.mineruTokenMode, cfg.tokenMode || 'backend'); // 默认后端转发模式
localStorage.setItem(this.keys.mineruEnableOcr, cfg.enableOcr !== false);
localStorage.setItem(this.keys.mineruEnableFormula, cfg.enableFormula !== false);
localStorage.setItem(this.keys.mineruEnableTable, cfg.enableTable !== false);
@ -249,7 +249,7 @@ class OcrSettingsManager {
localStorage.setItem(this.keys.doc2xWorkerUrl, (cfg.workerUrl || '').replace(/\/+$/, ''));
localStorage.setItem(this.keys.doc2xToken, cfg.token || '');
localStorage.setItem(this.keys.workerAuthKey, cfg.authKey || '');
localStorage.setItem(this.keys.doc2xTokenMode, cfg.tokenMode || 'frontend');
localStorage.setItem(this.keys.doc2xTokenMode, cfg.tokenMode || 'backend'); // 默认后端转发模式
localStorage.setItem(this.keys.doc2xFormulaMode, cfg.formulaMode || 'dollar');
localStorage.setItem(this.keys.doc2xExportFormat, cfg.exportFormat || '');
}
@ -517,9 +517,9 @@ class OcrSettingsManager {
return {
engine: 'mineru',
token: localStorage.getItem(this.keys.mineruToken) || '',
workerUrl: (localStorage.getItem(this.keys.mineruWorkerUrl) || '').replace(/\/+$/, ''), // 去掉末尾斜杠
workerUrl: (localStorage.getItem(this.keys.mineruWorkerUrl) || 'http://localhost:3456').replace(/\/+$/, ''), // 去掉末尾斜杠
authKey: localStorage.getItem(this.keys.workerAuthKey) || '',
tokenMode: localStorage.getItem(this.keys.mineruTokenMode) || 'frontend',
tokenMode: localStorage.getItem(this.keys.mineruTokenMode) || 'backend', // 默认后端转发模式
enableOcr: localStorage.getItem(this.keys.mineruEnableOcr) !== 'false',
enableFormula: localStorage.getItem(this.keys.mineruEnableFormula) !== 'false',
enableTable: localStorage.getItem(this.keys.mineruEnableTable) !== 'false',
@ -530,9 +530,9 @@ class OcrSettingsManager {
return {
engine: 'doc2x',
token: localStorage.getItem(this.keys.doc2xToken) || '',
workerUrl: (localStorage.getItem(this.keys.doc2xWorkerUrl) || '').replace(/\/+$/, ''), // 去掉末尾斜杠
workerUrl: (localStorage.getItem(this.keys.doc2xWorkerUrl) || 'http://localhost:3456').replace(/\/+$/, ''), // 去掉末尾斜杠
authKey: localStorage.getItem(this.keys.workerAuthKey) || '',
tokenMode: localStorage.getItem(this.keys.doc2xTokenMode) || 'frontend',
tokenMode: localStorage.getItem(this.keys.doc2xTokenMode) || 'backend', // 默认后端转发模式
formulaMode: localStorage.getItem(this.keys.doc2xFormulaMode) || 'dollar',
exportFormat: localStorage.getItem(this.keys.doc2xExportFormat) || ''
};
@ -563,17 +563,17 @@ class OcrSettingsManager {
break;
case 'mineru':
// 前端透传模式需要 TokenWorker 配置模式不需要
// 后端转发模式tokenMode 不是 'frontend')不需要前端配置 Token
if (config.tokenMode === 'frontend' && !config.token) {
return { valid: false, message: '请配置 MinerU Token前端透传模式' };
}
if (!config.workerUrl) {
if (config.tokenMode === 'frontend' && !config.workerUrl) {
return { valid: false, message: '请配置 MinerU Worker URL' };
}
break;
case 'doc2x':
// 前端透传模式需要 TokenWorker 配置模式不需要
// 后端转发模式tokenMode 不是 'frontend')不需要前端配置 Token
if (config.tokenMode === 'frontend' && !config.token) {
return { valid: false, message: '请配置 Doc2X Token前端透传模式' };
}

View File

@ -66,10 +66,6 @@
// Settings Link
originalSettingsLink: '#settings-link'
},
logos: {
full: '../../public/h_with_name.svg',
pure: '../../public/pure.svg'
}
};
@ -146,18 +142,16 @@
* @param {boolean} collapsed - 是否折叠
*/
function setSidebarCollapsed(collapsed) {
if (!elements.appSidebar || !elements.sidebarLogo) return;
if (!elements.appSidebar) return;
if (collapsed) {
elements.appSidebar.classList.add('collapsed');
elements.sidebarLogo.src = CONFIG.logos.pure;
// 切换图标为"打开"图标
if (elements.sidebarToggleIcon) {
elements.sidebarToggleIcon.setAttribute('icon', 'carbon:side-panel-open');
}
} else {
elements.appSidebar.classList.remove('collapsed');
elements.sidebarLogo.src = CONFIG.logos.full;
// 切换图标为"关闭"图标
if (elements.sidebarToggleIcon) {
elements.sidebarToggleIcon.setAttribute('icon', 'carbon:side-panel-close');

View File

@ -60,9 +60,9 @@
*/
function renderMinerUConfig(container) {
// 从 localStorage 加载配置
const workerUrl = localStorage.getItem('ocrMinerUWorkerUrl') || '';
const workerUrl = localStorage.getItem('ocrMinerUWorkerUrl') || 'http://localhost:3456';
const authKey = localStorage.getItem('ocrWorkerAuthKey') || '';
const tokenMode = localStorage.getItem('ocrMinerUTokenMode') || 'frontend';
const tokenMode = localStorage.getItem('ocrMinerUTokenMode') || 'backend';
const token = localStorage.getItem('ocrMinerUToken') || '';
const configDiv = document.createElement('div');
@ -336,9 +336,9 @@
*/
function renderDoc2XConfig(container) {
// 从 localStorage 加载配置
const workerUrl = localStorage.getItem('ocrDoc2XWorkerUrl') || '';
const workerUrl = localStorage.getItem('ocrDoc2XWorkerUrl') || 'http://localhost:3456';
const authKey = localStorage.getItem('ocrWorkerAuthKey') || '';
const tokenMode = localStorage.getItem('ocrDoc2XTokenMode') || 'frontend';
const tokenMode = localStorage.getItem('ocrDoc2XTokenMode') || 'backend';
const token = localStorage.getItem('ocrDoc2XToken') || '';
const configDiv = document.createElement('div');

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 29 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 23 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 29 KiB

View File

@ -4,7 +4,6 @@
<meta charset="UTF-8">
<title>配图编辑器 - draw.io</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="icon" type="image/svg+xml" href="../../public/pure.svg">
<style>
@import url('https://fonts.googleapis.com/css2?family=Nunito:wght@300;400;500;600;700&display=swap');

View File

@ -3,7 +3,6 @@
<head>
<meta charset="UTF-8" />
<title>历史详情</title>
<link rel="icon" type="image/svg+xml" href="../../public/pure.svg" />
<!-- CDN 性能优化DNS 预连接 - 使用淘宝 npmmirror 镜像加速 -->
<link rel="dns-prefetch" href="https://registry.npmmirror.com" />
<link rel="preconnect" href="https://registry.npmmirror.com" crossorigin />
@ -105,8 +104,8 @@
</script>
</head>
<body class="immersive-pending">
<!-- Standard Immersive Mode Toggle Button -->
<button id="toggle-immersive-btn" class="tiny-round-btn hidden" style="display: none;" title="进入沉浸式布局">
<!-- Standard Immersive Mode Toggle Button 进入沉浸式布局-->
<button id="toggle-immersive-btn" class="tiny-round-btn disvisible hidden" style="display: none;" title="">
<i class="fas fa-expand-alt"></i>
<!-- Default icon for entering immersive mode -->
</button>
@ -190,12 +189,6 @@
<aside class="app-sidebar" id="appSidebar">
<!-- 侧边栏头部 -->
<div class="sidebar-header">
<!-- <img
src="../../public/h_with_name.svg"
alt="Paper Burner X"
class="sidebar-logo"
id="sidebarLogo"
/> -->
<!-- 占位元素 -->
<span></span>
<!-- 桌面端收起按钮 -->
@ -329,11 +322,6 @@
<!-- 移动端顶部栏 -->
<div class="mobile-header">
<div class="mobile-header-title">
<img
src="../../public/pure.svg"
alt="PBX"
class="mobile-header-logo"
/>
<span>文档详情</span>
</div>
<button class="mobile-menu-btn" id="mobileMenuBtn">
@ -829,6 +817,8 @@
<script src="../../js/storage/storage.js"></script>
<!-- 结构化重试需要 API/Translation/Structured 翻译模块 -->
<script src="../../js/api/api.js"></script>
<script src="../../js/process/utils.js"></script>
<script src="../../js/process/document.js"></script>
<script src="../../js/process/translation.js"></script>
<script src="../../js/process/mineru-structured-translation.js"></script>

View File

@ -5,7 +5,6 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Paper Burner X - 开源的、在浏览器中即开即用的 AI 工作站,专为扫除海量的 PDF 文献、复杂的公式和跨语言的障碍。支持前端 Agent 驱动的智能检索、高性能批量处理和完全本地化部署。">
<title>Paper Burner X - 开源 AI 文献工作站</title>
<link rel="icon" type="image/svg+xml" href="../../public/pure.svg">
<!-- Tailwind CSS & 依赖库 -->
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://gcore.jsdelivr.net/npm/iconify-icon@2.0.0/dist/iconify-icon.min.js"></script>
@ -557,7 +556,7 @@
<header id="mainHeader" class="glass-header">
<!-- Left: Logo -->
<div class="header-logo flex-shrink-0">
<img src="../../public/h_with_name.svg" alt="Paper Burner X">
<span class="text-xl font-bold text-gray-800">Paper Burner X</span>
</div>
<!-- Center: Update Pill (Optional, can be hidden if needed) -->
@ -592,8 +591,8 @@
<!-- Hero Section -->
<section class="hero-section pt-12 md:pt-16 min-h-[85vh] flex flex-col justify-center">
<div class="mb-6 md:mb-8 animate-float">
<img src="../../public/pure.svg" alt="Paper Burner X Logo" class="w-28 h-28 sm:w-36 sm:h-36 md:w-44 md:h-44 mx-auto drop-shadow-lg">
<div class="mb-6 md:mb-8">
<h2 class="text-3xl sm:text-4xl md:text-5xl font-bold text-gray-900 mx-auto">Paper Burner X</h2>
</div>
<!-- Hero Carousel Container -->

View File

@ -4,7 +4,6 @@
<meta charset="UTF-8">
<title>思维导图 - Markmap</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="icon" type="image/svg+xml" href="../../public/pure.svg">
<!-- Markmap 依赖 -->
<script src="https://gcore.jsdelivr.net/npm/d3@7"></script>