paper-burner/index.html

2039 lines
128 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">
<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>
<!-- 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 内容 -->
<script src="https://gcore.jsdelivr.net/npm/dompurify@3.0.6/dist/purify.min.js"></script>
<!-- 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>
<script src="https://gcore.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script src="https://gcore.jsdelivr.net/npm/js-base64@3.7.5/base64.min.js"></script>
<script src="https://gcore.jsdelivr.net/npm/file-saver@2.0.5/dist/FileSaver.min.js"></script>
<script src="https://gcore.jsdelivr.net/npm/jszip@3.10.1/dist/jszip.min.js"></script>
<!-- dagre.js for graph layout (层次化图布局算法) -->
<script src="https://gcore.jsdelivr.net/npm/graphlib@2.1.8/dist/graphlib.min.js"></script>
<script src="https://gcore.jsdelivr.net/npm/dagre@0.8.5/dist/dagre.min.js"></script>
<!-- docx-preview.js for Word preview -->
<script src="https://gcore.jsdelivr.net/npm/docx-preview@0.3.7/dist/docx-preview.min.js"></script>
<link rel="stylesheet" href="https://gcore.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css">
<link rel="stylesheet" href="css/history_detail/01-foundation/variables.css">
<link rel="stylesheet" href="css/history_detail/05-utilities/buttons.css">
<link rel="stylesheet" href="css/history_detail/04-features/markdown-enhancements.css">
<link rel="stylesheet" href="css/history_detail/04-features/math-display.css">
<link rel="stylesheet" href="css/_deprecated/chatbot-model-config-modal.css">
<script src="https://gcore.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.js"></script>
<script src="https://gcore.jsdelivr.net/npm/marked/marked.min.js"></script>
<!-- Markdown-it for AST-based processing (新架构) -->
<script src="https://gcore.jsdelivr.net/npm/markdown-it@14.0.0/dist/markdown-it.min.js"></script>
<script>
// failback: 如果 CDN 加载失败,自动加载本地 jszip
if (typeof JSZip === 'undefined') {
var script = document.createElement('script');
script.src = 'js/lib/jszip.min.js';
document.head.appendChild(script);
}
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/animejs/3.2.1/anime.min.js"></script>
<style>
/* 谷歌字体 Inter */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
/* 基础字体设置 */
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
background-color: #f8fafc; /* 更中性的应用背景色 */
}
/* App Shell Layout */
.app-shell {
display: flex;
height: 100vh;
overflow: hidden;
}
.app-sidebar {
width: var(--sidebar-width);
background-color: var(--color-bg-base);
border-right: 1px solid var(--color-border-light);
display: flex;
flex-direction: column;
flex-shrink: 0;
/* Mobile defaults: fixed off-screen */
position: fixed;
top: 0;
bottom: 0;
left: 0;
z-index: 50;
transform: translateX(-100%);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
@media (min-width: 768px) {
.app-sidebar {
position: static;
transform: none;
border-top-right-radius: 1.5rem;
border-bottom-right-radius: 1.5rem;
}
/* 侧边栏收起状态 */
.app-sidebar.collapsed {
width: 76px;
}
.app-sidebar.collapsed .px-6 {
padding-left: 0;
padding-right: 0;
}
/* 收起时头部垂直排列并居中 */
.app-sidebar.collapsed #sidebarHeader {
flex-direction: column;
align-items: center; /* 关键:水平居中 */
gap: 1.5rem;
padding-top: 1.5rem;
padding-bottom: 1rem;
}
.app-sidebar.collapsed #sidebarLogo {
width: 32px;
height: 32px;
object-fit: contain;
}
.app-sidebar.collapsed .nav-text,
.app-sidebar.collapsed .nav-section-title,
.app-sidebar.collapsed #sidebarHistoryToggleBtn,
.app-sidebar.collapsed #sidebarHistoryQuickList {
opacity: 0;
pointer-events: none;
display: none;
}
.app-sidebar.collapsed .nav-item,
.app-sidebar.collapsed #sidebarHistoryMainBtn {
justify-content: center;
padding-left: 0;
padding-right: 0;
}
.app-sidebar.collapsed .nav-icon {
margin-right: 0;
}
.app-sidebar.collapsed #sidebarHistoryMainBtn {
justify-content: center; /* 确保历史记录图标居中 */
border-radius: 0.5rem;
}
.app-sidebar.collapsed #sidebarFooter {
display: none;
}
.app-sidebar.collapsed #sidebarFooterCollapsed {
display: flex;
}
.app-sidebar.collapsed #sidebarDivider {
margin-left: 1rem;
margin-right: 1rem;
}
}
.app-sidebar.mobile-open {
transform: translateX(0);
box-shadow: 4px 0 24px rgba(0,0,0,0.1);
}
.sidebar-overlay {
position: fixed;
inset: 0;
background-color: rgba(0,0,0,0.4);
z-index: 40;
opacity: 0;
pointer-events: none;
transition: opacity 0.3s linear;
backdrop-filter: blur(2px);
}
.sidebar-overlay.show {
opacity: 1;
pointer-events: auto;
}
.app-main {
flex: 1;
overflow-y: auto;
overflow-x: hidden;
position: relative;
width: 100%; /* Ensure it takes full width on mobile when sidebar is hidden */
}
.nav-item {
display: flex;
align-items: center;
padding: var(--sidebar-nav-item-padding);
color: var(--slate-600);
border-radius: var(--radius-lg);
transition: all 0.2s ease;
font-weight: 500;
font-size: var(--sidebar-nav-item-font-size);
margin-bottom: 0.25rem;
user-select: none;
}
.nav-item:hover {
background-color: var(--slate-100);
color: var(--slate-900);
}
.nav-item.active {
background-color: var(--indigo-50);
color: var(--indigo-600);
font-weight: 600;
}
.nav-item .nav-icon {
margin-right: 0.75rem;
font-size: 1.25rem; /* 20px */
color: #94a3b8; /* slate-400 */
transition: color 0.2s ease;
}
.nav-item:hover .nav-icon {
color: #64748b; /* slate-500 */
}
.nav-item.active .nav-icon {
color: var(--color-primary);
}
/* 侧边栏子菜单项 */
.nav-sub-item {
display: flex;
align-items: center;
padding: 0.5rem 0.75rem 0.5rem 2.75rem; /* 左侧留出空间对齐文字 */
font-size: 0.875rem; /* 14px */
color: #64748b;
border-radius: 0.5rem;
transition: all 0.15s;
text-decoration: none;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.nav-sub-item:hover {
color: #0f172a;
background-color: #f8fafc;
}
/* Workspace Header */
.workspace-header {
background: linear-gradient(135deg, var(--indigo-50) 0%, var(--slate-50) 100%);
border-radius: 1.5rem;
padding: 2.5rem;
margin-bottom: 2.5rem;
border: 1px solid var(--slate-200);
position: relative;
overflow: hidden;
}
.workspace-header::before {
content: '';
position: absolute;
top: -30%;
right: -10%;
width: 400px;
height: 400px;
background: radial-gradient(circle, rgba(99, 102, 241, 0.06) 0%, rgba(255,255,255,0) 70%);
border-radius: 50%;
pointer-events: none;
}
/* 新增:右侧大 Logo 背景装饰 */
/* .workspace-header::after {
content: '';
position: absolute;
right: -20px;
bottom: -40px;
width: 300px;
height: 300px;
background-repeat: no-repeat;
background-position: center;
background-size: contain;
极低透明度,仅作纹理
opacity: 0.07;
transform: rotate(-10deg);
pointer-events: none;
} */
.workspace-header-content {
position: relative;
z-index: 1;
max-width: 600px;
}
/* 背景渐变 (保留用于特定区域,如果需要) */
.gradient-bg {
background-color: #F3F6FA;
}
/* 卡片渐变 */
.gradient-card {
background: linear-gradient(135deg, #ffffff 0%, #f9fbff 100%);
}
/* 通用过渡效果 */
.transition-all {
transition: all 0.3s ease;
}
/* 文件列表项样式 */
.file-list-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.5rem 0.75rem;
border-radius: 0.5rem;
background-color: #f8f9fa;
margin-bottom: 0.5rem;
border: 1px solid #e9ecef;
}
.file-list-item:last-child {
margin-bottom: 0;
}
/* API Key 输入框样式 */
textarea.api-key-input {
min-height: 60px; /* 最小高度 */
resize: vertical; /* 允许垂直方向调整大小 */
}
/* 闪烁提示动画基类 */
.flash-tip-anim {
opacity: 0;
transition: opacity 0.5s;
will-change: opacity;
}
/* 闪烁提示显示状态 */
.flash-tip-anim.show {
opacity: 1;
}
/* 闪烁提示隐藏状态 */
.flash-tip-anim.hide {
opacity: 0;
}
/* Skeleton Character Rows */
.skeleton-bg .skeleton-row {
border-radius: 8px;
background: linear-gradient(90deg, #e5e7eb 25%, #f3f6fa 50%, #e5e7eb 75%);
background-size: 200% 100%;
animation: shimmer 1.8s infinite linear;
}
.skeleton-fake-text {
color: #d1d5db;
font-size: 1rem;
font-family: 'Fira Mono', 'Consolas', monospace;
font-weight: bold;
opacity: 0.5;
letter-spacing: 0.1em;
animation: shimmer 1.8s infinite linear;
user-select: none;
}
.skeleton-fake-text.text-lg {
font-size: 1.25rem;
}
/* New Top-Blue to Bottom-White Gradient for Landing Page Background */
.ocean-gradient-bg {
background: linear-gradient(to bottom, #93C5FD 0%, #FFFFFF 100%); /* Tailwind blue-300 to white */
}
/* ===== 版权声明弹窗美化与动画 ===== */
#copyrightModal {
backdrop-filter: blur(6px) saturate(120%);
background: rgba(30, 41, 59, 0.35) !important; /* 深色毛玻璃 */
animation: copyrightModalFadeIn 0.4s cubic-bezier(.4,2,.6,1) both;
}
@keyframes copyrightModalFadeIn {
0% { opacity: 0; }
100% { opacity: 1; }
}
.copyright-card-ani {
background: linear-gradient(135deg, #f8fafc 60%, #e0e7ef 100%);
box-shadow: 0 8px 32px 0 rgba(31, 41, 55, 0.18), 0 1.5px 6px 0 rgba(59,130,246,0.08);
border-radius: 1.5rem;
border: 1.5px solid #e0e7ef;
animation: copyrightCardPopIn 0.5s cubic-bezier(.4,2,.6,1) both;
max-width: 900px;
transform: scale(0.9);
transform-origin: top center;
}
@keyframes copyrightCardPopIn {
0% { transform: scale(0.92) translateY(40px); opacity: 0; }
100% { transform: scale(1) translateY(0); opacity: 1; }
}
.copyright-card-ani h2 {
letter-spacing: 0.02em;
background: linear-gradient(90deg, var(--indigo-600) 30%, var(--indigo-400) 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.copyright-card-ani .main-btn {
background: var(--color-primary);
color: white;
font-weight: 600;
box-shadow: var(--shadow-sm);
border-radius: var(--radius-lg);
transition: all 0.2s ease;
}
.copyright-card-ani .main-btn:hover {
background: var(--color-primary-hover);
box-shadow: var(--shadow-md);
}
.copyright-card-ani .iconify-icon {
vertical-align: middle;
}
.feature-card-modern {
position: relative;
overflow: hidden;
border-radius: 20px;
padding: clamp(16px, 2vw, 20px);
background: linear-gradient(130deg, rgba(99, 102, 241, 0.08), rgba(255,255,255,0.98));
border: 1px solid rgba(99, 102, 241, 0.14);
box-shadow: var(--shadow-lg);
transition: transform 0.25s ease, box-shadow 0.25s ease;
}
.feature-card-modern::before {
content: '';
position: absolute;
inset: 0;
background: linear-gradient(135deg, rgba(255,255,255,0.32), rgba(99, 102, 241, 0.18));
opacity: 0;
transition: opacity 0.3s ease;
}
.feature-card-modern:hover {
transform: translateY(-4px);
box-shadow: var(--shadow-xl);
}
.feature-card-modern:hover::before {
opacity: 1;
}
.feature-card-modern > * {
position: relative;
z-index: 1;
}
.feature-icon-ring {
display: inline-flex;
align-items: center;
justify-content: center;
width: 42px;
height: 42px;
border-radius: 14px;
background: linear-gradient(135deg, rgba(255,255,255,0.9), rgba(99, 102, 241, 0.16));
box-shadow: inset 0 1px 0 rgba(255,255,255,0.5);
}
.feature-meta {
display: flex;
flex-wrap: wrap;
gap: 0.3rem;
margin-top: 0.6rem;
}
.feature-meta span {
display: inline-flex;
align-items: center;
padding: 0.28rem 0.65rem;
border-radius: 999px;
font-size: 0.75rem;
font-weight: 600;
letter-spacing: 0.01em;
backdrop-filter: blur(8px);
}
.feature-card-inner {
display: flex;
align-items: flex-start;
gap: 14px;
}
.feature-card-inner h4 {
margin-bottom: 0.35rem;
}
/* 文件预览模态框动画 */
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes fadeOut {
from { opacity: 1; }
to { opacity: 0; }
}
@keyframes slideUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Phase 2.3: Core Workflow Enhancements */
.modern-card {
background: var(--color-bg-base);
border: 1px solid var(--color-border-light);
box-shadow: var(--shadow-sm);
border-radius: var(--radius-xl);
transition: box-shadow 0.3s ease, border-color 0.3s ease;
}
.modern-card:hover {
box-shadow: var(--shadow-md);
border-color: var(--color-border-medium);
}
.section-header {
display: flex;
align-items: center;
margin-bottom: 1.5rem;
padding-bottom: 1rem;
border-bottom: 1px solid var(--color-border-light);
}
.section-title {
font-size: var(--font-size-md);
font-weight: var(--font-weight-semibold);
color: var(--slate-800);
display: flex;
align-items: center;
gap: 0.5rem;
}
.upload-zone-modern {
border: 2px dashed var(--slate-300);
border-radius: var(--radius-xl);
background-color: var(--slate-50);
transition: all 0.2s ease;
}
.upload-zone-modern:hover, .upload-zone-modern.drag-over {
border-color: var(--color-primary);
background-color: var(--indigo-50);
}
.btn-primary-large {
background: var(--color-primary);
color: white;
border-radius: var(--radius-lg);
transition: all 0.2s ease;
box-shadow: var(--shadow-sm);
}
.btn-primary-large:hover {
background: var(--color-primary-hover);
box-shadow: var(--shadow-md);
transform: translateY(-1px);
}
.btn-primary-large:active {
background: var(--color-primary-active);
transform: translateY(0);
}
/* 全局入场动画 */
@keyframes fadeInUp {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
.animate-fade-in-up {
animation: fadeInUp 0.6s cubic-bezier(0.16, 1, 0.3, 1) both;
}
/* 悬浮历史记录按钮 (极简风格) */
.floating-history-btn {
position: fixed;
bottom: 2rem;
right: 2rem;
z-index: 40;
/* 使用 buttons.css 中的样式,这里只保留定位 */
}
@media (max-width: 768px) {
.floating-history-btn {
bottom: 1.5rem;
right: 1.5rem;
opacity: 0.8; /* 移动端稍微不那么透明,因为没有 hover */
}
}
</style>
</head>
<body class="bg-slate-50 min-h-screen">
<!-- Mobile Sidebar Overlay -->
<div id="sidebarOverlay" class="sidebar-overlay md:hidden"></div>
<div class="app-shell">
<!-- ===================== -->
<!-- 侧边栏 (Responsive) -->
<!-- ===================== -->
<aside id="appSidebar" class="app-sidebar">
<nav class="flex-1 px-3 py-4 space-y-1 overflow-y-auto custom-scrollbar overflow-x-hidden">
<!-- 可折叠的历史记录菜单 (Split Action) -->
<div id="historyMenuContainer" class="mb-4">
<div
class="flex items-stretch select-none group bg-white border border-slate-200 overflow-hidden">
<div id="sidebarHistoryMainBtn"
class="flex-1 flex items-center px-4 py-3 text-sm font-medium text-slate-600 hover:bg-[#000f33] hover:text-[#ffffff] cursor-pointer transition-all"
title="打开完整历史记录面板">
<!-- <iconify-icon icon="carbon:time"
class="mr-3 text-lg text-[#000f33] group-hover:text-[#ffffff] transition-colors"></iconify-icon> -->
<span>历史记录</span>
</div>
<button id="sidebarHistoryToggleBtn"
class="flex items-center justify-center px-3 hover:bg-[#000f33] text-slate-300 hover:text-slate-500 cursor-pointer transition-colors"
title="展开/收起最近记录">
<iconify-icon icon="carbon:chevron-right" class="transition-transform duration-200"
id="sidebarHistoryChevron" width="16"></iconify-icon>
</button>
</div>
<div id="sidebarHistoryQuickList"
class="hidden py-1 space-y-0.5 pl-2 transition-all bg-white border border-slate-200">
<!-- JS 将在此处填充最近记录 -->
<div class="px-3 py-2 text-xs text-slate-400 text-center">加载中...</div>
</div>
</div>
<div class="my-4 border-t border-slate-100 mx-3 transition-all" id="sidebarDivider"></div>
</nav>
</aside>
<!-- ===================== -->
<!-- 主内容区 -->
<!-- ===================== -->
<main class="app-main flex flex-col">
<!-- 移动端 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">
PBX
</a>
<div class="flex items-center gap-3">
<button id="mobileHistoryBtn" class="text-slate-600 p-1">
<iconify-icon icon="carbon:time" width="20"></iconify-icon>
</button>
<button id="mobileMenuBtn" class="text-slate-600 p-1">
<iconify-icon icon="carbon:menu" width="24"></iconify-icon>
</button>
</div>
</header> -->
<!-- 滚动内容容器 -->
<div class="flex-1 overflow-y-auto custom-scrollbar">
<div id="mainAppContainer" class="container h-full flex flex-col justify-center mx-auto px-4 py-6 md:py-8 max-w-6xl animate-fade-in-up">
<!-- ===================== -->
<!-- 顶部欢迎区 (Modern Dashboard Style) -->
<!-- ===================== -->
<!-- <div class="workspace-header">
<div class="workspace-header-content">
<h1 class="text-3xl md:text-4xl font-bold text-slate-900 mb-4 tracking-tight" style="color: var(--slate-900);">
可学可学可学可学可学测试提交后vercel更新是否成功
</h1>
<p class="text-lg text-slate-600 leading-relaxed" style="color: var(--slate-600);">
配置多种OCR 引擎与 AI 翻译模型。上传文档后Paper Burner X将给您流畅的阅读与处理体验。
</p>
</div>
</div> -->
<!-- ===================== -->
<!-- 主功能区 -->
<!-- ===================== -->
<div>
<div class="space-y-6">
<!-- ===================== -->
<!-- 卡片 1: OCR 文档解析 -->
<!-- ===================== -->
<div class="modern-card p-4 md:p-6 hidden">
<div class="mb-6">
<h2 class="text-xl font-semibold text-gray-800 mb-4 flex items-center">
<iconify-icon icon="carbon:api" class="mr-2 text-indigo-500" width="24"></iconify-icon>
API 密钥设置
</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label for="mistralApiKeys" class="block text-sm font-medium text-gray-700 mb-1">Mistral API Keys <span class="text-red-500">*</span> (每行一个)</label>
<div class="relative">
<textarea id="mistralApiKeys" rows="3" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 transition-all api-key-input" placeholder="输入您的 Mistral API Key(s),每行一个"></textarea>
</div>
</div>
<div>
<label for="translationApiKeys" class="block text-sm font-medium text-gray-700 mb-1">翻译 API Keys (可选, 每行一个)</label>
<div class="relative">
<textarea id="translationApiKeys" rows="3" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 transition-all api-key-input" placeholder="输入翻译服务 API Key(s),每行一个 (可选)"></textarea>
</div>
</div>
</div>
</div>
<!-- ===================== -->
<!-- OCR 设置 -->
<!-- ===================== -->
<div>
<div class="section-header justify-between">
<h2 class="section-title">
<div class="w-9 h-9 rounded-xl bg-gradient-to-br from-indigo-50 to-indigo-100/80 border border-indigo-100 text-indigo-600 flex items-center justify-center mr-3 shadow-sm" style="background: var(--indigo-50); color: var(--color-primary); border-color: var(--indigo-100);">
<iconify-icon icon="carbon:scan" width="20"></iconify-icon>
</div>
OCR 文档解析
</h2>
<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>
<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>
<span>设置</span>
</button>
</div>
</div>
<!-- OCR 引擎选择(简化版) -->
<div>
<label for="ocrEngine" class="block text-sm font-medium text-gray-700 mb-1">OCR 引擎</label>
<select id="ocrEngine" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 transition-all" style="border-color: var(--color-border-medium); outline-color: var(--color-primary);">
<option value="none">不需要 OCR仅文本</option>
<option value="local">本地解析(仅文本 PDF</option>
<option value="mistral">Mistral OCR</option>
<option value="mineru">MinerU</option>
<option value="doc2x">Doc2X</option>
</select>
<div id="localOcrHint" class="text-xs text-gray-500 mt-1 hidden">
<strong>本地解析</strong>:适用于文字型 PDF非扫描件免费且快速但不支持图片和复杂排版的识别
</div>
</div>
<!-- MinerU 翻译模式配置(仅当选择 MinerU 时显示) -->
<div id="mineruTranslationModeConfig" class="hidden mt-4">
<div class="border border-gray-200 rounded-lg bg-gray-50 p-4">
<label class="block text-sm font-medium text-gray-700 mb-3">MinerU 翻译模式</label>
<div class="space-y-2">
<label class="flex items-center gap-3 cursor-pointer">
<input type="radio" name="mineruTranslationMode" value="standard" checked class="w-4 h-4">
<div>
<div class="text-sm text-gray-800">标准翻译模式</div>
<div class="text-xs text-gray-500">将 MinerU 输出的 Markdown 按段落翻译</div>
</div>
</label>
<label class="flex items-center gap-3 cursor-pointer">
<input type="radio" name="mineruTranslationMode" value="structured" class="w-4 h-4">
<div>
<div class="text-sm text-gray-800">结构化翻译模式</div>
<div class="text-xs text-gray-500">基于 JSON 进行翻译,支持 PDF 对照显示</div>
</div>
</label>
</div>
</div>
</div>
</div>
</div>
<!-- ===================== -->
<!-- 卡片 2: 翻译与分析 -->
<!-- ===================== -->
<div class="modern-card p-4 md:p-6 hidden">
<div class="section-header">
<h2 class="section-title">
<div class="w-9 h-9 rounded-xl border flex items-center justify-center mr-3 shadow-sm" style="background: var(--indigo-50); color: var(--color-primary); border-color: var(--indigo-100);">
<iconify-icon icon="carbon:language" width="20"></iconify-icon>
</div>
翻译与分析
</h2>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- 翻译模型选择 -->
<div>
<label for="translationModel" class="block text-sm font-medium text-gray-700 mb-1">翻译和分析模型</label>
<select id="translationModel" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 transition-all" style="border-color: var(--color-border-medium); outline-color: var(--color-primary);">
<option value="none">不需要翻译</option>
<option value="mistral">Mistral Large</option>
<option value="deepseek">deepseek</option>
<option value="gemini">gemini</option>
<option value="deeplx">DeepLX (DeepL 接口)</option>
<option value="tongyi">通义百炼</option>
<option value="volcano">火山引擎</option>
<option value="custom">自定义模型</option>
</select>
<div id="translationModelDefaultHint" class="text-xs text-gray-500 mt-1"></div>
</div>
<!-- 目标语言选择 -->
<div>
<label for="targetLanguage" class="block text-sm font-medium text-gray-700 mb-1">目标语言</label>
<select id="targetLanguage" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 transition-all" style="border-color: var(--color-border-medium); outline-color: var(--color-primary);">
<option value="chinese">中文</option>
<option value="english">English</option>
<option value="japanese">日本語</option>
<option value="korean">한국어</option>
<option value="french">Français</option>
<option value="custom">自定义</option>
</select>
<!-- 自定义目标语言名称输入区域 -->
<div id="customTargetLanguageContainer" class="hidden mt-2">
<label for="customTargetLanguageInput" class="block text-sm font-medium text-gray-700 mb-1">自定义目标语言名称</label>
<input type="text" id="customTargetLanguageInput" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 transition-all" placeholder="例如: Spanish" style="border-color: var(--color-border-medium); outline-color: var(--color-primary);">
</div>
</div>
<!-- Gemini 模型信息(选择 Gemini 时显示) -->
<div id="geminiModelInfo" class="md:col-span-2 hidden">
<div class="mt-2 border border-gray-200 rounded-lg bg-gray-50 p-3">
<div class="flex items-center justify-between">
<div class="text-sm text-gray-700 font-medium">Gemini 默认模型</div>
<div class="flex items-center gap-2">
<span id="geminiKeysCountHint" class="text-xs text-gray-500"></span>
<button id="geminiDetectBtn" type="button" class="px-2 py-1 text-xs border rounded hover:bg-white">检测可用模型</button>
</div>
</div>
<div class="mt-2 space-y-2">
<div class="flex flex-col sm:flex-row sm:items-center sm:gap-2">
<select id="geminiModelSelect" class="flex-1 px-3 py-1.5 border border-gray-300 rounded-md text-sm focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 transition-colors" style="border-color: var(--color-border-medium); outline-color: var(--color-primary);">
<!-- 动态填充,可为空则仅显示占位提示 -->
</select>
<button id="geminiModelSearchBtn" type="button" class="mt-2 sm:mt-0 px-3 py-1.5 text-sm border border-gray-300 rounded-md text-gray-600 hover:text-indigo-600 hover:border-indigo-400 transition-colors flex items-center gap-1 disabled:opacity-60 disabled:cursor-not-allowed" style="border-color: var(--color-border-medium); color: var(--slate-600);">
<iconify-icon icon="carbon:search" width="16"></iconify-icon>
搜索模型
</button>
</div>
<p id="geminiModelHint" class="text-xs text-gray-500">若列表为空,请先点击上方“检测可用模型”。</p>
</div>
</div>
</div>
<!-- DeepLX 模型信息(选择 DeepLX 时显示) -->
<div id="deeplxModelInfo" class="md:col-span-2 hidden">
<div class="mt-2 border border-gray-200 rounded-lg bg-gray-50 p-3">
<div class="flex items-center justify-between">
<div class="text-sm text-gray-700 font-medium">DeepLX 接口设置</div>
<span id="deeplxKeysCountHint" class="text-xs text-gray-500"></span>
</div>
<div class="mt-2 space-y-2">
<label for="deeplxEndpointTemplateInput" class="text-xs text-gray-500 block">接口模板(使用 &lt;api-key&gt; 或 {API_KEY} 占位符)</label>
<div class="flex items-center gap-2">
<input id="deeplxEndpointTemplateInput" type="text" class="flex-1 px-3 py-1.5 border border-gray-300 rounded-md text-sm focus:ring-1 focus:ring-blue-500 focus:border-blue-500 transition-colors" placeholder="https://api.deeplx.org/&lt;api-key&gt;/translate">
<button id="deeplxEndpointResetBtn" type="button" class="px-2 py-1 text-xs border rounded hover:bg-white">恢复默认</button>
</div>
<p class="text-xs text-gray-500 leading-5">默认模板会在占位符位置插入当前使用的 API Key例如 <code>https://api.deeplx.org/&lt;api-key&gt;/translate</code>。如需自建代理,可在此输入自定义地址。</p>
<p id="deeplxTargetLangHint" class="text-xs leading-5" style="color: var(--indigo-700);"></p>
<div id="deeplxLangTable" class="mt-2 text-xs text-gray-600 leading-5"></div>
</div>
</div>
</div>
<!-- DeepSeek 模型信息(选择 DeepSeek 时显示) -->
<div id="deepseekModelInfo" class="md:col-span-2 hidden">
<div class="mt-2 border border-gray-200 rounded-lg bg-gray-50 p-3">
<div class="flex items-center justify-between">
<div class="text-sm text-gray-700 font-medium">DeepSeek 默认模型</div>
<div class="flex items-center gap-2">
<span id="deepseekKeysCountHint" class="text-xs text-gray-500"></span>
<button id="deepseekDetectBtn" type="button" class="px-2 py-1 text-xs border rounded hover:bg-white">检测可用模型</button>
</div>
</div>
<div class="mt-2 space-y-2">
<div class="flex flex-col sm:flex-row sm:items-center sm:gap-2">
<select id="deepseekModelSelect" class="flex-1 px-3 py-1.5 border border-gray-300 rounded-md text-sm focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 transition-colors" style="border-color: var(--color-border-medium); outline-color: var(--color-primary);"></select>
<button id="deepseekModelSearchBtn" type="button" class="mt-2 sm:mt-0 px-3 py-1.5 text-sm border border-gray-300 rounded-md text-gray-600 hover:text-indigo-600 hover:border-indigo-400 transition-colors flex items-center gap-1 disabled:opacity-60 disabled:cursor-not-allowed" style="border-color: var(--color-border-medium); color: var(--slate-600);">
<iconify-icon icon="carbon:search" width="16"></iconify-icon>
搜索模型
</button>
</div>
<p id="deepseekModelHint" class="text-xs text-gray-500">若列表为空,请先点击上方“检测可用模型”。</p>
</div>
</div>
</div>
<!-- 通义 模型信息(选择通义时显示) -->
<div id="tongyiModelInfo" class="md:col-span-2 hidden">
<div class="mt-2 border border-gray-200 rounded-lg bg-gray-50 p-3">
<div class="flex items-center justify-between">
<div class="text-sm text-gray-700 font-medium">通义 默认模型</div>
<div class="flex items-center gap-2">
<span id="tongyiKeysCountHint" class="text-xs text-gray-500"></span>
<button id="tongyiDetectBtn" type="button" class="px-2 py-1 text-xs border rounded hover:bg-white">检测可用模型</button>
</div>
</div>
<div class="mt-2 space-y-2">
<div class="flex flex-col sm:flex-row sm:items-center sm:gap-2">
<select id="tongyiModelSelect" class="flex-1 px-3 py-1.5 border border-gray-300 rounded-md text-sm focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 transition-colors" style="border-color: var(--color-border-medium); outline-color: var(--color-primary);"></select>
<button id="tongyiModelSearchBtn" type="button" class="mt-2 sm:mt-0 px-3 py-1.5 text-sm border border-gray-300 rounded-md text-gray-600 hover:text-indigo-600 hover:border-indigo-400 transition-colors flex items-center gap-1 disabled:opacity-60 disabled:cursor-not-allowed" style="border-color: var(--color-border-medium); color: var(--slate-600);">
<iconify-icon icon="carbon:search" width="16"></iconify-icon>
搜索模型
</button>
</div>
<p id="tongyiModelHint" class="text-xs text-gray-500">若列表为空,请先点击上方“检测可用模型”。</p>
</div>
</div>
</div>
<!-- 火山 模型信息(选择火山时显示) -->
<div id="volcanoModelInfo" class="md:col-span-2 hidden">
<div class="mt-2 border border-gray-200 rounded-lg bg-gray-50 p-3">
<div class="flex items-center justify-between">
<div class="text-sm text-gray-700 font-medium">火山 默认模型</div>
<div class="flex items-center gap-2">
<span id="volcanoKeysCountHint" class="text-xs text-gray-500"></span>
<button id="volcanoDetectBtn" type="button" class="px-2 py-1 text-xs border rounded hover:bg-white">检测可用模型</button>
</div>
</div>
<div class="mt-2">
<select id="volcanoModelSelect" class="w-full px-3 py-1.5 border border-gray-300 rounded-md text-sm focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 transition-colors" style="border-color: var(--color-border-medium); outline-color: var(--color-primary);"></select>
<p id="volcanoModelHint" class="mt-1 text-xs text-gray-500">若列表为空,请手动输入或在设置中选择。</p>
</div>
</div>
</div>
<!-- 自定义源站点设置区域 (选择自定义模型时显示) -->
<div id="customSourceSiteContainer" class="md:col-span-2 hidden">
<div class="pt-4">
<!-- 展开/收起自定义源站点设置的按钮 -->
<button id="customSourceSiteToggle" type="button" class="text-sm text-gray-500 hover:text-gray-700 flex items-center">
<iconify-icon icon="carbon:settings-adjust" class="mr-1" width="16"></iconify-icon>
<span>自定义源站点设置</span>
<iconify-icon id="customSourceSiteToggleIcon" icon="carbon:chevron-down" class="ml-1" width="16"></iconify-icon>
</button>
<!-- 自定义源站点详细配置区 -->
<div id="customSourceSite" class="hidden mt-2 border-2 border-dashed border-gray-300 rounded-lg p-4">
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<!-- 选择已保存的自定义源站点 -->
<div>
<label for="customSourceSiteSelect" class="block text-sm font-medium text-gray-700 mb-1">选择自定义源站点</label>
<select id="customSourceSiteSelect" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 transition-all" style="border-color: var(--color-border-medium); outline-color: var(--color-primary);">
<!-- Options will be populated by JavaScript -->
</select>
</div>
<!-- 显示选定源站点的基本信息 -->
<div id="customSourceSiteInfo" class="mt-3 w-full border border-gray-200 rounded-lg bg-gray-50 p-0 hidden md:col-span-2">
<!-- 站点信息将由JS动态填充 -->
</div>
<!-- 管理API Key及检测模型的按钮通常根据选择的源站点动态显示/隐藏) -->
<div class="mt-2 flex justify-end w-full md:col-span-2">
<button id="manageSourceSiteKeyBtn" class="px-3 py-1.5 text-sm text-white rounded transition-colors flex items-center hidden" style="background-color: var(--color-primary);">
<iconify-icon icon="carbon:api" class="mr-1" width="16"></iconify-icon>
管理该站点 API Key
</button>
<button id="detectModelsBtn" type="button" class="hidden px-3 py-1.5 text-sm text-white rounded transition-colors flex items-center" style="background-color: var(--color-primary);">
<iconify-icon icon="carbon:model-alt" class="mr-1" width="16"></iconify-icon>
检测可用模型
</button>
</div>
</div>
</div>
</div>
</div>
<!-- ===== 翻译备择库管理(多术语库) ===== -->
<div id="glossaryManagerSection" class="md:col-span-2 hidden">
<div class="bg-white/90 backdrop-blur-sm border border-dashed rounded-2xl shadow-sm p-5 md:p-6 space-y-5" style="border-color: var(--color-border-light);">
<div class="flex flex-col md:flex-row md:items-start md:justify-between gap-4">
<div>
<div class="flex items-center gap-2 text-lg font-semibold text-slate-800">
<iconify-icon icon="carbon:dictionary" width="22" style="color: var(--color-primary);"></iconify-icon>
<span>翻译备择库管理</span>
<span id="glossarySetsCountHint" class="ml-1 inline-flex items-center text-xs font-medium rounded-full px-2 py-0.5" style="color: var(--color-primary); background-color: var(--indigo-50); border-color: var(--indigo-100);">启用 0 / 0</span>
</div>
<p class="mt-2 text-sm text-slate-500 leading-relaxed">
支持维护多套术语库,命中时自动注入翻译提示,帮助保持术语一致性。可按项目导入导出,灵活协同。
</p>
</div>
<label class="inline-flex items-center gap-2 bg-slate-50 border border-slate-200 rounded-full px-4 py-2 text-sm text-slate-600 select-none">
<input id="enableGlossaryToggle" type="checkbox" class="h-4 w-4 border-slate-300 rounded" style="color: var(--color-primary); accent-color: var(--color-primary);">
<span>启用翻译备择库</span>
</label>
</div>
<div class="flex flex-wrap items-center gap-2 md:gap-3">
<button id="addGlossarySetBtn" class="inline-flex items-center gap-1.5 px-3 py-1.5 text-sm font-medium text-slate-700 bg-white border border-slate-200 rounded-lg hover:text-indigo-600 hover:shadow-sm transition-all" style="border-color: var(--color-border-medium);">
<iconify-icon icon="carbon:add-alt" width="18"></iconify-icon>新增术语库
</button>
<button id="importGlossarySetBtn" class="inline-flex items-center gap-1.5 px-3 py-1.5 text-sm font-medium text-slate-700 bg-white border border-slate-200 rounded-lg hover:text-indigo-600 hover:shadow-sm transition-all" style="border-color: var(--color-border-medium);">
<iconify-icon icon="carbon:import" width="18"></iconify-icon>导入术语库
</button>
<input id="importGlossarySetFile" type="file" accept="application/json" class="hidden">
<button id="exportAllGlossarySetsBtn" class="inline-flex items-center gap-1.5 px-3 py-1.5 text-sm font-medium text-slate-700 bg-white border border-slate-200 rounded-lg hover:text-indigo-600 hover:shadow-sm transition-all" style="border-color: var(--color-border-medium);">
<iconify-icon icon="carbon:export" width="18"></iconify-icon>导出全部术语库
</button>
</div>
<div id="glossarySetsTable" class="space-y-3"></div>
<!-- 编辑面板 -->
<div id="glossaryEditorPanel" class="hidden border border-slate-200 rounded-xl bg-slate-50/70 p-4 md:p-5" data-editing-id="">
<!-- 工具栏容器(搜索、批量操作等) -->
<div id="glossaryEditorToolbar"></div>
<!-- 条目列表容器 -->
<div id="glossaryEntriesTable"></div>
<!-- 分页容器 -->
<div id="glossaryEditorPagination"></div>
</div>
</div>
</div>
</div>
</div>
<!-- ===================== -->
<!-- 卡片 3: 高级设置 -->
<!-- ===================== -->
<div class="modern-card p-4 md:p-6 hidden">
<!-- 展开/收起高级设置的头部 -->
<div id="advancedSettingsToggle" class="section-header justify-between cursor-pointer select-none hover:opacity-80 transition-opacity -mb-4 pb-4">
<h2 class="section-title">
<div class="w-8 h-8 rounded-lg bg-slate-100 text-slate-600 flex items-center justify-center mr-3">
<iconify-icon icon="carbon:settings-adjust" width="20"></iconify-icon>
</div>
高级设置
</h2>
<iconify-icon icon="carbon:chevron-down" class="text-slate-400 transition-transform duration-300" width="24" id="advancedSettingsIcon"></iconify-icon>
</div>
<!-- 高级设置详细内容区 -->
<div id="advancedSettings" class="hidden pt-4">
<div class="pt-2">
<h3 class="text-base font-semibold text-slate-800 mb-4 flex items-center">
<iconify-icon icon="carbon:settings-check" class="mr-2" style="color: var(--color-primary);"></iconify-icon>
性能与流程
</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8">
<!-- 文件处理并发数 -->
<div>
<label for="concurrencyLevel" class="block text-sm font-medium text-gray-700 mb-1">文件处理并发数</label>
<input type="number" id="concurrencyLevel" min="1" max="50" value="1"
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 transition-all text-sm" style="border-color: var(--color-border-medium); outline-color: var(--color-primary);">
<p class="text-xs text-gray-500 mt-1">同时处理的文件数量。默认 1。</p>
</div>
<!-- 翻译任务并发数 -->
<div>
<label for="translationConcurrencyLevel" class="block text-sm font-medium text-gray-700 mb-1">翻译任务并发数</label>
<input type="number" id="translationConcurrencyLevel" min="1" max="150" value="15"
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 transition-all text-sm" style="border-color: var(--color-border-medium); outline-color: var(--color-primary);">
<p class="text-xs text-gray-500 mt-1">同时进行的翻译 API 调用数量。默认 15。</p>
</div>
<!-- 翻译分段最大Token数 -->
<div class="md:col-span-2">
<label for="maxTokensPerChunk" class="block text-sm font-medium text-gray-700 mb-1">翻译分段最大Token数 (<span id="maxTokensPerChunkValue">2000</span>)</label>
<input type="range" id="maxTokensPerChunk" min="500" max="10000" step="100" value="1000"
class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer" style="accent-color: var(--color-primary);">
<p class="text-xs text-gray-500 mt-1">长文档翻译时,按此限制分段。默认 2000。</p>
</div>
<!-- 跳过已处理文件选项 -->
<div class="md:col-span-2 flex items-center mt-2">
<input type="checkbox" id="skipProcessedFiles" class="rounded focus:ring-indigo-500 h-4 w-4 mr-2 flex-shrink-0" style="color: var(--color-primary); accent-color: var(--color-primary);">
<label for="skipProcessedFiles" class="text-sm text-gray-600">跳过已处理过的文件 (基于文件名和大小)</label>
</div>
</div>
<h3 class="text-lg font-semibold text-gray-800 mb-4 border-b pb-2 flex items-center"><iconify-icon icon="carbon:edit" class="mr-2 text-gray-600"></iconify-icon>翻译提示词管理</h3>
<!-- 提示词模式选择(单选,三选一) -->
<div class="mb-4">
<div class="flex items-center space-x-6 mb-3">
<label class="flex items-center">
<input type="radio" name="promptMode" value="pool" id="promptModePool" class="rounded focus:ring-indigo-500 h-4 w-4 mr-2" style="color: var(--color-primary); accent-color: var(--color-primary);">
<span class="text-sm font-medium text-gray-700">提示词池</span>
</label>
<label class="flex items-center">
<input type="radio" name="promptMode" value="builtin" id="promptModeBuiltin" class="rounded focus:ring-indigo-500 h-4 w-4 mr-2" checked style="color: var(--color-primary); accent-color: var(--color-primary);">
<span class="text-sm font-medium text-gray-700">内置提示词</span>
</label>
<label class="flex items-center">
<input type="radio" name="promptMode" value="custom" id="promptModeCustom" class="rounded focus:ring-indigo-500 h-4 w-4 mr-2" style="color: var(--color-primary); accent-color: var(--color-primary);">
<span class="text-sm font-medium text-gray-700">单个自定义</span>
</label>
</div>
<p class="text-xs text-gray-500">选择翻译提示词的使用方式。提示词池提供多样表达且保持风格一致。</p>
</div>
<!-- 单个自定义提示区域 -->
<div id="customPromptsContainer" class="hidden space-y-4 mb-6">
<div>
<label for="defaultSystemPrompt" class="block text-sm font-medium text-gray-700 mb-1">自定义系统提示</label>
<textarea id="defaultSystemPrompt" rows="3" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 transition-all api-key-input text-sm" placeholder="输入系统提示词..." style="border-color: var(--color-border-medium);"></textarea>
</div>
<div>
<label for="defaultUserPromptTemplate" class="block text-sm font-medium text-gray-700 mb-1">自定义用户提示模板</label>
<textarea id="defaultUserPromptTemplate" rows="6" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 transition-all api-key-input text-sm" placeholder="请将以下内容翻译为${targetLangName}&#10;&#10;${content}" style="border-color: var(--color-border-medium);"></textarea>
<p class="text-xs text-gray-500 mt-1">可用占位符:<code>${targetLangName}</code>, <code>${content}</code></p>
</div>
</div>
<!-- 提示词池管理区域 -->
<div id="promptPoolContainer" class="hidden">
<!-- 参考提示词输入(去除橙色,改为中性) -->
<div id="ppRefSection" class="bg-gray-50 rounded-lg p-4 mb-4 border border-gray-200">
<h4 class="font-medium text-gray-800 mb-3 flex items-center">
<iconify-icon icon="carbon:template" class="mr-2" width="18" style="color: var(--color-primary);"></iconify-icon>
参考提示词 (AI生成基础)
</h4>
<div class="space-y-3">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">参考系统提示词</label>
<textarea id="referenceSystemPrompt" rows="3" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-1 focus:ring-indigo-500 text-sm" placeholder="你是专业的翻译助手,请准确翻译用户提供的内容。" style="border-color: var(--color-border-medium); outline-color: var(--color-primary);"></textarea>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">参考用户提示模板</label>
<textarea id="referenceUserPrompt" rows="4" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-1 focus:ring-indigo-500 text-sm" placeholder="请将以下内容翻译为${targetLangName}&#10;&#10;${content}" style="border-color: var(--color-border-medium); outline-color: var(--color-primary);"></textarea>
<p class="text-xs text-gray-500 mt-1">必须包含 <code>${targetLangName}</code><code>${content}</code> 占位符</p>
</div>
</div>
</div>
<!-- 提示词池控制面板 -->
<div class="rounded-lg p-4 mb-4" style="background-color: var(--indigo-50);">
<div id="ppControlHeader" class="flex justify-between items-center mb-3">
<div class="flex items-center">
<iconify-icon icon="carbon:ai-status-running" class="mr-2" width="20" style="color: var(--color-primary);"></iconify-icon>
<span class="font-medium" style="color: var(--indigo-800);">AI生成提示词池</span>
<span id="poolStatsText" class="ml-2 text-sm" style="color: var(--color-primary);">(0 个已选)</span>
</div>
<div class="flex items-center space-x-2">
<select id="promptPoolMode" class="text-sm border rounded px-2 py-1 bg-white" style="border-color: var(--indigo-100);">
<option value="random">智能随机</option>
<option value="rotation">健康轮换</option>
</select>
<button id="toggleGeneratorPromptsBtn" class="text-xs px-2 py-1 border rounded hover:bg-gray-50">生成器提示词</button>
<button id="resetGeneratorPromptsBtn" class="text-xs px-2 py-1 border rounded hover:bg-gray-50">重置为默认</button>
</div>
</div>
<!-- 健康状态概览 -->
<div id="healthOverview" class="bg-white rounded-md p-3 mb-3 border border-indigo-200" style="border-color: var(--indigo-100);">
<div class="flex items-center justify-between mb-2">
<span class="text-sm font-medium text-gray-700">健康状态概览</span>
<button id="healthSettingsBtn" class="text-xs text-indigo-600 hover:text-indigo-800" style="color: var(--color-primary);">
<iconify-icon icon="carbon:settings" width="14"></iconify-icon> 设置
</button>
</div>
<div class="grid grid-cols-4 gap-3 text-center">
<div class="text-xs">
<div class="text-green-600 font-medium" id="healthyCount">0</div>
<div class="text-gray-500">健康</div>
</div>
<div class="text-xs">
<div class="text-yellow-600 font-medium" id="degradedCount">0</div>
<div class="text-gray-500">降级</div>
</div>
<div class="text-xs">
<div class="text-red-600 font-medium" id="deactivatedCount">0</div>
<div class="text-gray-500">失活</div>
</div>
<div class="text-xs">
<div class="text-indigo-600 font-medium" id="successRate" style="color: var(--color-primary);">0%</div>
<div class="text-gray-500">成功率</div>
</div>
</div>
</div>
<!-- 生成器元提示词(可选) -->
<div id="generatorPromptsPanel" class="hidden mb-3">
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
<div>
<label class="block text-xs text-gray-600 mb-1">生成器系统提示(可选)</label>
<textarea id="generatorSystemPrompt" rows="3" class="w-full text-sm border border-gray-300 rounded px-2 py-1" placeholder="自定义用于生成变体的系统提示(不填用默认)"></textarea>
</div>
<div>
<label class="block text-xs text-gray-600 mb-1">生成器用户提示(可选)</label>
<textarea id="generatorUserPrompt" rows="3" class="w-full text-sm border border-gray-300 rounded px-2 py-1" placeholder="自定义用于生成变体的用户提示(不填用默认)"></textarea>
</div>
</div>
</div>
<!-- 生成参数控制 -->
<div id="ppGenSection">
<!-- 生成参数控制 -->
<div class="grid grid-cols-1 md:grid-cols-5 gap-3 mb-3">
<div>
<label class="block text-xs text-gray-600 mb-1">生成数量</label>
<input type="number" id="variationCount" value="10" min="5" max="20" class="w-full text-sm border border-gray-300 rounded px-2 py-1">
</div>
<div>
<label class="block text-xs text-gray-600 mb-1">并发数</label>
<input type="number" id="generationConcurrency" value="1" min="1" max="10" class="w-full text-sm border border-gray-300 rounded px-2 py-1">
<p class="text-xs text-gray-500 mt-1">最多 10总生成量 = 生成数量 × 并发数</p>
</div>
<div>
<label class="block text-xs text-gray-600 mb-1">生成语言</label>
<input type="text" id="generatorLanguage" value="中文" placeholder="如中文、English、日本語" class="w-full text-sm border border-gray-300 rounded px-2 py-1">
<p class="text-xs text-gray-500 mt-1">用于替换生成器提示词中的 ${genlanguage}</p>
</div>
<div>
<label class="block text-xs text-gray-600 mb-1">相似度</label>
<select id="similarityControl" class="w-full text-sm border border-gray-300 rounded px-2 py-1">
<option value="0.2">差异很大</option>
<option value="0.4">中等差异</option>
<option value="0.6" selected>适度相似</option>
<option value="0.8">高度相似</option>
</select>
</div>
<div>
<label class="block text-xs text-gray-600 mb-1">AI模型</label>
<select id="generationModel" class="w-full text-sm border border-gray-300 rounded px-2 py-1">
<option value="">加载中...</option>
</select>
<p class="text-xs text-gray-500 mt-1">自动使用已配置的API密钥</p>
</div>
</div>
<!-- 生成控制按钮 -->
<div class="flex items-center justify-between">
<div class="flex items-center space-x-3">
<button id="generateVariationsBtn" class="px-4 py-2 text-white rounded-md transition-colors text-sm flex items-center disabled:opacity-50 disabled:cursor-not-allowed" style="background-color: var(--color-primary);">
<iconify-icon icon="carbon:ai-status" class="mr-2" width="16"></iconify-icon>
AI生成变体
</button>
<div id="generateStatus" class="text-xs text-gray-600 hidden">
<iconify-icon icon="carbon:loading" class="animate-spin mr-1" width="14"></iconify-icon>
正在生成...
</div>
</div>
<div class="flex items-center space-x-2">
<button id="clearPoolBtn" class="px-3 py-1.5 bg-gray-400 text-white rounded text-xs hover:bg-gray-500 transition-colors">
清空
</button>
<button id="importPoolBtn" class="px-3 py-1.5 bg-green-600 text-white rounded text-xs hover:bg-green-700 transition-colors">
导入
</button>
<button id="exportPoolBtn" class="px-3 py-1.5 bg-green-600 text-white rounded text-xs hover:bg-green-700 transition-colors">
导出
</button>
</div>
</div>
</div> <!-- /#ppGenSection -->
</div>
<!-- 提示词列表容器 -->
<div id="promptPoolList" class="space-y-2 max-h-96 overflow-y-auto border border-gray-200 rounded-lg p-3 bg-gray-50">
<div class="text-center text-gray-500 text-sm py-8">
<iconify-icon icon="carbon:ai-status-queued" class="text-gray-400 mb-2" width="32"></iconify-icon>
<p>暂无提示词</p>
<p class="text-xs mt-1">请先填写参考提示词,然后点击"AI生成变体"</p>
</div>
</div>
</div>
</div>
</div>
</div><!-- /.modern-card (Advanced) -->
<!-- ===================== -->
<!-- 卡片 4: 文件上传 -->
<!-- ===================== -->
<div class="modern-card p-4 md:p-6">
<!-- <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>
</div>
文件上传
</h2>
<label id="batchModeToggleWrapper" class="hidden flex items-center space-x-2 text-sm text-slate-600 bg-slate-100 px-3 py-1.5 rounded-full cursor-pointer hover:bg-slate-200 transition-colors">
<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 id="dropZone" class="upload-zone-modern p-8 text-center mb-6 group cursor-pointer">
<input type="file" id="pdfFileInput" accept=".pdf,.md,.txt,.docx,.pptx,.html,.htm,.epub,.yaml,.yml,.zip" class="hidden" multiple>
<input type="file" id="folderInput" class="hidden" webkitdirectory mozdirectory multiple>
<div class="mx-auto mb-4 w-20 h-20 flex items-center justify-center rounded-full transition-all duration-300" style="background-color: var(--indigo-50); color: var(--color-primary);">
<iconify-icon icon="mdi:upload" width="40"></iconify-icon>
</div>
<h3 class="text-lg font-semibold text-slate-700 mb-2">点击或拖拽文件到此处</h3>
<p class="text-sm text-slate-500 mb-6 max-w-md mx-auto">
支持 PDF, Markdown, Word, PowerPoint, HTML, EPUB 等常见文档格式
</p>
<div class="flex flex-wrap justify-center gap-3">
<button id="browseFilesBtn" class="px-5 py-2.5 bg-white border border-slate-200 text-slate-700 rounded-xl hover:text-indigo-600 transition-all flex items-center gap-2 font-medium shadow-sm" style="border-color: var(--color-border-medium); color: var(--slate-700);">
<iconify-icon icon="carbon:document-add" width="18"></iconify-icon>
选择文件
</button>
<button id="browseFolderBtn" class="px-5 py-2.5 bg-white border border-slate-200 text-slate-700 rounded-xl hover:text-indigo-600 transition-all flex items-center gap-2 font-medium shadow-sm" style="border-color: var(--color-border-medium); color: var(--slate-700);">
<iconify-icon icon="carbon:folder-add" width="18"></iconify-icon>
选择文件夹
</button>
</div>
</div>
<!-- 已选文件列表 -->
<div id="fileListContainer" class="hidden">
<div class="bg-slate-50 rounded-xl border border-slate-200 overflow-hidden">
<div class="px-4 py-3 border-b border-slate-200 flex justify-between items-center bg-slate-100/50">
<span class="text-sm font-semibold text-slate-700 flex items-center gap-2">
<iconify-icon icon="carbon:document-tasks" class="text-slate-500"></iconify-icon>
待处理文件
</span>
<button id="clearFilesBtn" class="text-xs text-red-500 hover:text-red-600 hover:bg-red-50 px-2 py-1 rounded transition-colors flex items-center gap-1">
<iconify-icon icon="carbon:trash-can" width="14"></iconify-icon>
清空
</button>
</div>
<div class="p-3">
<div id="fileFormatFilters" class="flex flex-wrap gap-2 mb-3"></div>
<div id="fileList" class="max-h-[300px] overflow-y-auto pr-1 space-y-2">
<!-- JS 填充 -->
</div>
</div>
</div>
</div>
<!-- 批量配置 (保持原有逻辑,仅微调样式) -->
<div id="batchModeConfig" class="hidden mt-4 border rounded-xl overflow-hidden" style="border-color: var(--indigo-100); background-color: var(--indigo-50);">
<div class="flex items-center justify-between px-4 py-3 border-b" style="border-color: var(--indigo-100);">
<span class="text-sm font-semibold" style="color: var(--indigo-700);">批量导出配置</span>
<button type="button" id="batchModeConfigToggle" class="text-xs hover:opacity-80 flex items-center space-x-1" style="color: var(--color-primary);">
<iconify-icon id="batchModeConfigToggleIcon" icon="carbon:chevron-down" width="14"></iconify-icon>
<span id="batchModeConfigToggleLabel">展开设置</span>
</button>
</div>
<div id="batchModeConfigBody" class="hidden px-4 py-4">
<div class="flex flex-col md:flex-row md:items-start md:space-x-6 space-y-4 md:space-y-0">
<div class="flex-1 min-w-0">
<label for="batchModeTemplate" class="block text-sm font-medium text-gray-700 mb-1">命名模板</label>
<input type="text" id="batchModeTemplate" class="w-full rounded-md border bg-white px-3 py-2 text-sm text-gray-800 focus:ring-2" placeholder="例如 {original_name}_{output_language}_{processing_time:YYYYMMDD-HHmmss}.{original_type}" style="border-color: var(--indigo-200); outline-color: var(--color-primary);">
<p class="text-xs text-gray-600 mt-2 leading-4">
可用变量:<span class="font-mono text-[11px]">{original_name}</span><span class="font-mono text-[11px]">{original_type}</span><span class="font-mono text-[11px]">{output_language}</span><span class="font-mono text-[11px]">{processing_time}</span> (支持 <span class="font-mono text-[11px]">{processing_time:YYYYMMDD-HHmmss}</span> 等格式)。
</p>
</div>
<div class="md:w-64">
<span class="block text-sm font-medium text-gray-700 mb-1">批量导出格式</span>
<div class="flex flex-wrap items-center gap-3 text-sm text-gray-700">
<label class="inline-flex items-center space-x-2">
<input type="checkbox" data-batch-format="original" class="rounded border-gray-300 focus:ring-indigo-500" checked style="color: var(--color-primary); accent-color: var(--color-primary);">
<span>原格式</span>
</label>
<label class="inline-flex items-center space-x-2">
<input type="checkbox" data-batch-format="markdown" class="rounded border-gray-300 focus:ring-indigo-500" checked style="color: var(--color-primary); accent-color: var(--color-primary);">
<span>Markdown</span>
</label>
<label class="inline-flex items-center space-x-2">
<input type="checkbox" data-batch-format="html" class="rounded border-gray-300 focus:ring-indigo-500" style="color: var(--color-primary); accent-color: var(--color-primary);">
<span>HTML</span>
</label>
<label class="inline-flex items-center space-x-2">
<input type="checkbox" data-batch-format="docx" class="rounded border-gray-300 focus:ring-indigo-500" style="color: var(--color-primary); accent-color: var(--color-primary);">
<span>DOCX</span>
</label>
<span class="hidden w-px h-4 md:block" style="background-color: var(--indigo-100);"></span>
<label class="inline-flex items-center space-x-2">
<input type="checkbox" id="batchModeZipToggle" data-batch-zip class="rounded border-gray-300 focus:ring-indigo-500" style="color: var(--color-primary); accent-color: var(--color-primary);">
<span>导出时打包为 ZIP</span>
</label>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 验证状态提示区域 -->
<div id="validationAlert" class="hidden mt-6 mb-4 p-3.5 rounded-lg border transition-all shadow-sm">
<div class="flex items-start gap-3">
<iconify-icon id="validationIcon" icon="carbon:warning" class="flex-shrink-0 mt-0.5" width="18"></iconify-icon>
<div class="flex-1 min-w-0">
<div class="flex items-center justify-between mb-1">
<h3 id="validationTitle" class="font-medium text-sm"></h3>
<button id="validationRefreshBtn" class="text-xs opacity-60 hover:opacity-100 transition-opacity" title="重新检查">
<iconify-icon icon="carbon:renew" width="16"></iconify-icon>
</button>
</div>
<p id="validationMessage" class="text-sm opacity-90"></p>
<div id="validationActions" class="mt-2.5 flex flex-wrap gap-2"></div>
</div>
</div>
</div>
<!-- 处理按钮 -->
<div class="flex justify-center mt-8 mb-12">
<button id="processBtn" class="btn-primary-large px-10 hidden py-4 text-lg font-semibold text-white rounded-2xl transition-all disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-3 hover:scale-[1.02] active:scale-[0.98]" style="background: var(--color-primary);">
<iconify-icon icon="carbon:rocket" width="24"></iconify-icon>
<span>开始处理</span>
</button>
<button id="onlyReadBtn" class="btn-primary-large px-10 py-4 text-lg font-semibold text-white rounded-2xl transition-all disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-3 hover:scale-[1.02] active:scale-[0.98]" style="background: var(--color-primary);">
<iconify-icon icon="carbon:rocket" width="24"></iconify-icon>
<span>仅阅读</span>
</button>
</div>
</div>
<!-- ===================== -->
<!-- 进度与结果区域 (恢复为单栏垂直排列) -->
<!-- ===================== -->
<div class="mt-12 space-y-6">
<!-- ===================== -->
<!-- 进度与结果区域 -->
<!-- ===================== -->
<!-- 处理进度显示区 -->
<div id="progressSection" class="modern-card p-4 md:p-6 hidden">
<div class="section-header">
<h2 class="section-title">
<div class="w-8 h-8 rounded-lg flex items-center justify-center mr-3" style="background-color: var(--indigo-100); color: var(--color-primary);">
<iconify-icon icon="carbon:progress-bar-round" width="20"></iconify-icon>
</div>
处理进度
</h2>
</div>
<div class="space-y-4">
<!-- 进度概览 -->
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2">
<span id="batchProgressText" class="text-sm font-semibold text-slate-700"></span>
<span id="concurrentProgressText" class="text-xs font-medium text-slate-500 bg-slate-100 px-2 py-1 rounded-full"></span>
</div>
<!-- 主进度条 -->
<div>
<div class="flex justify-between mb-2">
<span id="progressStep" class="text-sm font-medium text-slate-700">等待开始...</span>
<span id="progressPercentage" class="text-sm font-bold" style="color: var(--color-primary);">0%</span>
</div>
<div class="w-full bg-slate-100 rounded-full h-3 overflow-hidden">
<div id="progressBar" class="h-full rounded-full transition-all duration-300" style="width: 0%; background: var(--color-primary);"></div>
</div>
</div>
<!-- 处理日志 -->
<div id="progressLog" class="bg-slate-900 rounded-xl p-4 h-48 overflow-auto text-xs text-slate-300 font-mono leading-relaxed shadow-inner"></div>
</div>
</div>
<!-- 处理结果显示与下载区 -->
<div id="resultsSection" class="modern-card p-4 md:p-6 hidden">
<div class="section-header">
<h2 class="section-title">
<div class="w-8 h-8 rounded-lg bg-green-100 text-green-600 flex items-center justify-center mr-3">
<iconify-icon icon="carbon:result" width="20"></iconify-icon>
</div>
处理完成
</h2>
</div>
<div class="bg-green-50 border border-green-100 rounded-xl p-4 mb-6 flex items-start gap-3">
<iconify-icon icon="carbon:checkmark-filled" class="text-green-500 mt-0.5" width="20"></iconify-icon>
<div>
<h4 class="text-sm font-semibold text-green-800 mb-1">所有任务已完成</h4>
<p class="text-sm text-green-700">您可以下载包含所有结果的 ZIP 文件,或在历史记录中查看详情。</p>
</div>
</div>
<!-- 结果统计摘要 -->
<div id="resultsSummary" class="mb-6 text-sm text-slate-600">
<!-- Summary will be added here -->
</div>
<!-- 下载全部结果按钮 -->
<button id="downloadAllBtn" class="btn-primary-large px-6 py-3 text-white rounded-xl transition-all flex items-center justify-center w-full sm:w-auto gap-2 font-medium hover:scale-[1.02] active:scale-[0.98] bg-gradient-to-r from-green-500 to-emerald-600 shadow-green-500/20">
<iconify-icon icon="carbon:download" width="20"></iconify-icon>
<span>下载全部结果 (ZIP)</span>
</button>
</div>
</div>
</div><!-- /#mainAppContainer -->
</div><!-- /.flex-1.overflow-y-auto -->
</main><!-- /.app-main -->
</div>
<!-- ===================== -->
<!-- 通知系统浮层 -->
<!-- (用于显示操作反馈,如成功、错误、警告信息) -->
<!-- ===================== -->
<div id="notification-container" class="fixed top-4 right-4 z-50 flex flex-col items-end space-y-2 pointer-events-none max-w-lg"></div>
<!-- 悬浮历史记录按钮 -->
<button id="floatingHistoryBtn" class="floating-history-btn tiny-round-btn" title="查看历史记录">
<iconify-icon icon="carbon:time"></iconify-icon>
</button>
<!-- ===================== -->
<!-- 脚本引入区(顺序不能乱) -->
<!-- ===================== -->
<script src="https://unpkg.com/iconify-icon@1.0.7/dist/iconify-icon.min.js"></script>
<!-- GitHub Stars 统一获取模块 -->
<script src="js/utils/github-stars.js"></script>
<!-- 自动探测后端:若 /api/健康检查通过将切换为 backend也可用 ?mode=backend 强制 -->
<script>window.ENV_DEPLOYMENT_MODE = 'auto';</script>
<!-- 代理服务器地址统一配置(必须在其他 API 相关脚本之前加载) -->
<script src="js/config/proxy-config.js"></script>
<script src="js/api/api.js?v=2"></script>
<script src="js/storage/storage.js"></script>
<!-- 先初始化存储适配器(提供 isFrontendMode 标记) -->
<script src="js/storage/storage-adapter.js"></script>
<!-- 再加载术语库存储(会依据适配器模式决定是否探测后端) -->
<script src="js/storage/glossary-storage.js"></script>
<script src="https://gcore.jsdelivr.net/npm/mammoth@1.4.21/mammoth.browser.min.js"></script>
<script src="https://gcore.jsdelivr.net/npm/turndown@7.1.2/dist/turndown.min.js"></script>
<script src="js/ui/ui-helpers.js"></script>
<script src="js/ui/ui-notifications.js"></script>
<script src="js/ui/ui-model-search.js"></script>
<script src="js/ui/ui-model-panels.js"></script>
<script src="js/ui/ui-processing.js"></script>
<script src="js/ui/ui-key-manager-modal.js"></script>
<script src="js/ui/key-manager-ui.js"></script>
<!-- Embedding 向量搜索与重排模块 -->
<script src="js/boot/ensure-embedding.js"></script>
<script src="js/chatbot/agents/embedding-client.js"></script>
<script>
(function(){
try {
if (!window.EmbeddingClient || typeof window.EmbeddingClient.saveConfig !== 'function') {
var s = document.createElement('script');
s.src = 'js/chatbot/agents/embedding-client.js?v=' + Date.now();
s.async = true;
s.onload = function(){ console.log('[Boot] EmbeddingClient loaded via cache-bust'); };
s.onerror = function(){ console.error('[Boot] Failed to load EmbeddingClient'); };
document.head.appendChild(s);
}
} catch (e) {
console.warn('[Boot] EmbeddingClient ensure failed:', e.message);
}
})();
</script>
<script src="js/chatbot/agents/rerank-client.js"></script>
<script src="js/chatbot/agents/vector-store.js"></script>
<script src="js/chatbot/agents/semantic-vector-search.js"></script>
<!-- 高级搜索工具模块 -->
<script src="js/chatbot/agents/advanced-search-tools.js"></script>
<!-- ReAct 模块 v2.0 - 模块化重构版本 -->
<!-- 注意必须按此顺序加载engine.js 依赖其他所有模块 -->
<script src="js/chatbot/react/token-budget.js"></script>
<script src="js/chatbot/react/tool-registry.js"></script>
<script src="js/chatbot/react/json-parser.js"></script>
<script src="js/chatbot/react/system-prompt.js"></script>
<script src="js/chatbot/react/context-builder.js"></script>
<script src="js/chatbot/react/engine.js"></script>
<script src="js/chatbot/react/index.js"></script>
<script src="js/chatbot/core/llm-caller.js"></script>
<!-- Chatbot 配置管理模块 -->
<script src="js/chatbot/core/chatbot-config-manager.js?v=20251107c"></script>
<script src="js/chatbot/ui/chatbot-model-config-modal.js?v=20251107f"></script>
<script src="js/chatbot/ui/chatbot-floating-options.js"></script>
<script src="js/chatbot/ui/chatbot-tooltrace-ui.js"></script>
<!-- UI 模块化组件 (新增) -->
<script src="js/ui/ui_dom_elements.js"></script>
<script src="js/ui/ui_model_manager_core.js"></script>
<script src="js/ui/ui_model_ocr_config.js"></script>
<script src="js/ui/ui_embedding_config.js"></script>
<!-- UI 主文件 -->
<script src="js/ui/ui.js"></script>
<script src="js/ui/ocr-settings.js?v=2"></script> <!-- OCR 配置管理 -->
<!-- PDF.js 库(用于本地解析 PDF -->
<script src="https://gcore.jsdelivr.net/npm/pdfjs-dist@3.11.174/build/pdf.min.js"></script>
<script>
// 配置 PDF.js worker
if (typeof pdfjsLib !== 'undefined') {
pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://gcore.jsdelivr.net/npm/pdfjs-dist@3.11.174/build/pdf.worker.min.js';
}
</script>
<!-- OCR 处理模块 -->
<script src="js/process/ocr-adapters/base-adapter.js"></script> <!-- 基类 -->
<script src="js/process/ocr-adapters/mistral-adapter.js?v=2"></script>
<script src="js/process/ocr-adapters/mineru-adapter.js"></script>
<script src="js/process/ocr-adapters/doc2x-adapter.js"></script>
<script src="js/process/ocr-adapters/local-adapter.js"></script> <!-- 本地 PDF 解析 -->
<script src="js/process/ocr-manager.js"></script> <!-- OCR 管理器 -->
<script src="js/process/glossary-matcher.js"></script> <!-- 高效术语匹配器 -->
<!-- glossary-core.js 由 index.js 动态加载,避免重复 -->
<script src="js/process/glossary-config.js"></script> <!-- 术语库性能配置 -->
<script src="js/ui/glossary-progress.js"></script> <!-- 术语库进度条 -->
<script src="js/ui/glossary-editor-enhanced.js"></script> <!-- 增强版术语库编辑器 -->
<script src="js/ui/glossary-ui.js"></script>
<!-- 子块分割器(支持中英文标点分割),用于高亮与批注的精确定位 -->
<script src="js/processing/sub_block_segmenter.js"></script>
<!-- 修改为普通脚本标签,不使用模块 -->
<script src="js/process/index.js"></script>
<script src="js/process/prompt-pool-api.js"></script>
<script src="js/process/prompt-pool.js"></script>
<script src="js/process/mineru-structured-translation.js"></script> <!-- MinerU 结构化翻译 -->
<script src="js/ui/prompt-pool-ui.js"></script>
<script src="js/api/model-detector.js"></script>
<script src="js/app.js"></script>
<script src="js/processing/markdown_processor_enhanced.js"></script>
<!-- AST-based processor (新架构) -->
<script src="js/processing/annotation_plugin_ast.js"></script> <!-- 注释插件 -->
<script src="js/processing/markdown_processor_ast.js"></script>
<script src="js/processing/markdown_processor_integration.js"></script> <!-- 集成层 -->
<script src="js/processing/markdown_text_fix.js"></script>
<script src="js/processing/markdown_processor.js"></script>
<!-- AST 架构状态显示 -->
<script>
(function() {
if (window.MarkdownProcessorAST && window.MarkdownProcessor === window.MarkdownProcessorAST) {
console.log(
'%c🚀 Markdown 渲染引擎升级成功!%c\n' +
'当前版本: ' + window.MarkdownProcessorAST.version + '\n' +
'架构: AST (markdown-it)\n' +
'特性: 上下文感知 | 精准匹配 | 插件系统',
'color: #10b981; font-size: 14px; font-weight: bold; padding: 4px 0;',
'color: #64748b; font-size: 12px;'
);
}
})();
</script>
<!-- 文本自适应算法 (PDF 保留格式翻译) - 必须在 history 脚本之前加载 -->
<script src="js/utils/text-fitting.js"></script>
<script src="js/utils/text-fitting-integration.js"></script>
<!-- DOCX 导出 - MathML → OMML 转换器 -->
<script src="js/history/exporter/mathml2omml.browser.js"></script> <!-- 专业转换库(优先) -->
<script src="js/history/exporter/docx_mathml_converter_enhanced.js"></script> <!-- 增强转换器(备用) -->
<script src="js/history/exporter/history_exporter_docx.js"></script>
<script src="js/history/history_exporter.js"></script>
<script src="js/history/history.js"></script>
<script src="https://gcore.jsdelivr.net/npm/html2pdf.js@0.10.1/dist/html2pdf.bundle.min.js"></script>
<!-- ===================== -->
<!-- 历史记录面板 (模态框) -->
<div id="historyPanel" class="fixed inset-0 z-50 bg-slate-900/40 backdrop-blur-sm flex items-center justify-center hidden p-4 md:p-8">
<div class="bg-white rounded-2xl shadow-2xl w-full max-w-6xl max-h-[85vh] flex flex-col overflow-hidden border border-slate-100">
<div class="flex justify-between items-center p-5 border-b border-slate-100 bg-slate-50/50">
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-xl flex items-center justify-center" style="background-color: var(--indigo-100); color: var(--color-primary);">
<iconify-icon icon="carbon:time" width="24"></iconify-icon>
</div>
<div>
<h3 class="text-lg font-bold text-slate-800">历史记录</h3>
<p class="text-xs text-slate-500">查看和管理您处理过的文档</p>
</div>
</div>
<button id="closeHistoryPanel" class="p-2 text-slate-400 hover:text-slate-700 hover:bg-slate-100 rounded-lg transition-colors">
<iconify-icon icon="carbon:close" width="24"></iconify-icon>
</button>
</div>
<div class="flex flex-1 min-h-0">
<aside class="hidden md:flex w-64 shrink-0 border-r border-slate-100 bg-slate-50 p-4 flex-col" id="historySidebar">
<div class="flex items-center justify-between mb-4">
<span class="text-xs font-bold text-slate-500 uppercase tracking-wider">文件夹</span>
<button id="historyAddFolderBtn" class="hover:bg-slate-100 p-1.5 rounded-md transition-colors" title="新建文件夹" style="color: var(--color-primary);">
<iconify-icon icon="carbon:add" width="16"></iconify-icon>
</button>
</div>
<div class="flex-1 overflow-y-auto -mx-2 px-2" id="historyFolderList"></div>
</aside>
<div class="flex-1 flex flex-col min-h-0 p-5">
<!-- Mobile Folder Controls -->
<div class="md:hidden mb-4 flex gap-2">
<select id="historyFolderSelectMobile" class="flex-1 rounded-xl border-slate-200 text-sm py-2 pl-3 pr-8"></select>
<button id="historyAddFolderBtnMobile" class="p-2 border border-slate-200 rounded-xl" style="color: var(--color-primary);"><iconify-icon icon="carbon:add" width="20"></iconify-icon></button>
<button id="historyRenameFolderBtnMobile" class="p-2 border border-slate-200 rounded-xl text-slate-600"><iconify-icon icon="carbon:edit" width="20"></iconify-icon></button>
<button id="historyDeleteFolderBtnMobile" class="p-2 border border-slate-200 rounded-xl text-red-500"><iconify-icon icon="carbon:trash-can" width="20"></iconify-icon></button>
</div>
<div class="mb-4 relative">
<iconify-icon icon="carbon:search" class="absolute left-3.5 top-1/2 -translate-y-1/2 text-slate-400" width="20"></iconify-icon>
<input id="historySearchInput" type="search" placeholder="搜索文档标题、内容或 OCR 结果..."
class="w-full pl-11 pr-4 py-3 bg-slate-50 border-transparent rounded-xl focus:bg-white focus:ring-2 transition-all text-sm" style="border-color: var(--color-border-medium); outline-color: var(--color-primary);">
</div>
<div id="historyList" class="flex-1 overflow-y-auto space-y-3 pr-1"></div>
<div class="mt-5 pt-4 border-t border-slate-100">
<button id="clearHistoryBtn" class="w-full flex items-center justify-center gap-2 py-3 px-4 bg-red-50 text-red-600 font-medium rounded-xl hover:bg-red-100 transition-colors">
<iconify-icon icon="carbon:trash-can" width="20"></iconify-icon>
<span>清空所有历史记录</span>
</button>
</div>
</div>
</div>
</div>
</div>
<!-- 历史记录详情弹窗 (已废弃,详情在新页面打开) -->
<div id="historyDetailModal" class="fixed inset-0 z-[99999] bg-black bg-opacity-30 flex items-center justify-center hidden">
<div class="bg-white rounded-xl border-2 border-dashed border-gray-300 p-6 w-[90vw] max-w-2xl max-h-[90vh] overflow-y-auto relative">
<button id="closeHistoryDetail" class="absolute top-2 right-2 text-gray-400 hover:text-red-500"><iconify-icon icon="carbon:close" width="20"></iconify-icon></button>
<div id="historyDetailContent"></div>
</div>
</div>
<!-- 历史记录清空三步确认弹窗 -->
<div id="historyClearConfirmModal" class="fixed inset-0 z-[100000] bg-black/50 backdrop-blur-sm hidden items-center justify-center px-4">
<div class="bg-white rounded-2xl shadow-2xl max-w-md w-full overflow-hidden">
<div class="px-6 py-4 border-b border-gray-100 flex items-center justify-between">
<div class="flex items-center space-x-3">
<span class="inline-flex items-center justify-center w-8 h-8 rounded-full bg-red-100 text-red-600 font-semibold">!</span>
<div>
<h3 class="text-lg font-semibold text-gray-800">清空历史记录</h3>
<p class="text-xs text-gray-500">清除操作不可恢复,请谨慎确认</p>
</div>
</div>
<button id="historyClearCloseBtn" class="text-gray-400 hover:text-red-500 transition-colors">
<iconify-icon icon="carbon:close" width="22"></iconify-icon>
</button>
</div>
<div class="px-6 py-5 space-y-2">
<div id="historyClearStep1" class="space-y-3" aria-hidden="false">
<p id="historyClearStep1Message" class="text-sm text-gray-700 leading-relaxed">
即将永久删除所有历史记录(包括所有批次、译文、文件夹分配)。请确认是否继续。
</p>
<div class="flex justify-end space-x-3">
<button id="historyClearCancelBtn" class="px-3 py-2 text-sm rounded-md border border-gray-200 text-gray-500 hover:bg-gray-100">取消</button>
<button id="historyClearStep1Next" class="px-4 py-2 text-sm rounded-md bg-red-500 text-white hover:bg-red-600">继续</button>
</div>
</div>
<div id="historyClearStep2" class="space-y-2" hidden aria-hidden="true">
<p class="text-sm text-gray-700 leading-relaxed -mt-1">
请在下方输入 <span class="px-2 py-0.5 bg-red-50 text-red-600 rounded-md font-semibold">确定删除</span> 以确认操作。
</p>
<label class="sr-only" for="historyClearPhraseInput">输入 “确定删除” 以继续</label>
<input id="historyClearPhraseInput" type="text" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-red-300 focus:border-red-400" placeholder="请输入 确定删除" autocomplete="off" aria-describedby="historyClearPhraseHint">
<p id="historyClearPhraseHint" class="text-xs text-gray-500">必须准确输入 “确定删除” 才能继续。</p>
<div class="flex justify-between items-center">
<button id="historyClearStep2Back" class="text-sm text-gray-500 hover:text-gray-700">返回</button>
<button id="historyClearStep2Next" class="px-4 py-2 text-sm rounded-md bg-red-500 text-white opacity-60 cursor-not-allowed" disabled>确认短语</button>
</div>
</div>
<div id="historyClearStep3" class="space-y-3" hidden aria-hidden="true">
<div class="bg-red-50 border border-red-100 rounded-lg p-4 text-sm text-red-700">
<p class="font-semibold">最终确认:</p>
<p id="historyClearFinalMessage" class="mt-1 leading-relaxed">历史记录一旦清空,将无法恢复。请确保已备份需要的数据。</p>
</div>
<div class="flex justify-between items-center">
<button id="historyClearStep3Back" class="text-sm text-gray-500 hover:text-gray-700">返回</button>
<button id="historyClearExecute" class="px-4 py-2 text-sm rounded-md bg-red-600 text-white hover:bg-red-700">永久删除</button>
</div>
</div>
</div>
</div>
</div>
<!-- ===================== -->
<!-- 模型搜索弹窗 -->
<!-- ===================== -->
<div id="modelSearchOverlay" class="hidden fixed inset-0 z-[60] bg-slate-900/40 backdrop-blur-sm flex items-center justify-center p-4">
<div class="bg-white rounded-2xl shadow-2xl w-full max-w-2xl max-h-[80vh] flex flex-col overflow-hidden border border-slate-100">
<div class="flex items-center justify-between p-5 border-b border-slate-100">
<h3 id="modelSearchTitle" class="text-lg font-bold text-slate-800">选择模型</h3>
<button id="modelSearchCloseBtn" class="p-2 text-slate-400 hover:text-slate-700 hover:bg-slate-100 rounded-lg transition-colors">
<iconify-icon icon="carbon:close" width="20"></iconify-icon>
</button>
</div>
<div class="p-5 flex flex-col flex-1 min-h-0 gap-4">
<div class="relative shrink-0">
<iconify-icon icon="carbon:search" class="absolute left-3.5 top-1/2 -translate-y-1/2 text-slate-400" width="20"></iconify-icon>
<input id="modelSearchInput" type="search" class="w-full pl-11 pr-4 py-3 bg-slate-50 border-transparent rounded-xl focus:bg-white focus:ring-2 transition-all text-sm" placeholder="输入关键词搜索模型..." autocomplete="off" style="border-color: var(--color-border-medium); outline-color: var(--color-primary);">
</div>
<div class="flex-1 overflow-y-auto min-h-0 -mr-2 pr-2">
<div id="modelSearchList" class="space-y-2">
<!-- 动态填充模型列表 -->
</div>
<div id="modelSearchEmpty" class="hidden flex flex-col items-center justify-center py-12 text-slate-400">
<iconify-icon icon="carbon:search-locate" width="40" class="mb-2 opacity-50"></iconify-icon>
<p class="text-sm">未找到匹配的模型</p>
</div>
</div>
</div>
</div>
</div>
<!-- 模型与Key管理弹窗 -->
<!-- ===================== -->
<div id="modelKeyManagerModal" class="fixed inset-0 z-50 bg-slate-900/40 backdrop-blur-sm flex items-center justify-center hidden p-4 md:p-8">
<div class="bg-white rounded-2xl shadow-2xl w-full max-w-5xl max-h-[90vh] flex flex-col overflow-hidden border border-slate-100">
<!-- 弹窗头部 -->
<div class="flex justify-between items-center p-5 border-b border-slate-100 bg-slate-50/50">
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-xl bg-slate-200 text-slate-700 flex items-center justify-center">
<iconify-icon icon="carbon:settings-check" width="24"></iconify-icon>
</div>
<div>
<h3 class="text-lg font-bold text-slate-800">模型与密钥配置</h3>
<p class="text-xs text-slate-500">集中管理所有 AI 服务的连接信息</p>
</div>
</div>
<button id="closeModelKeyManager" class="p-2 text-slate-400 hover:text-slate-700 hover:bg-slate-100 rounded-lg transition-colors">
<iconify-icon icon="carbon:close" width="24"></iconify-icon>
</button>
</div>
<!-- 主体内容区 - 左右布局 -->
<div class="flex flex-1 overflow-hidden flex-col md:flex-row">
<!-- 左侧模型/源站点列表 -->
<div id="modelListColumn" class="w-full md:w-64 bg-slate-50 md:border-r border-slate-100 overflow-y-auto p-4 space-y-1">
<!-- 模型/源站点列表将由 JS 动态填充 -->
</div>
<!-- 右侧配置项与Key池管理 -->
<div class="flex-1 p-6 overflow-y-auto bg-white">
<!-- 当前选中模型/源站点的配置项 -->
<div id="modelConfigColumn" class="mb-8">
<!-- 配置项将由 JS 动态填充 -->
</div>
<!-- API Key 管理界面 (由 key-manager-ui.js 渲染) -->
<div id="keyManagerColumn" class="border-t border-slate-100 pt-6">
<!-- KeyManagerUI 实例将渲染到这里 -->
</div>
</div>
</div>
</div>
</div>
<!-- ===================== -->
<!-- 版权声明浮动卡片 -->
<!-- ===================== -->
<div id="copyrightModal" class="fixed inset-0 z-50 bg-black bg-opacity-30 flex items-center justify-center hidden px-4 py-8">
<div class="copyright-card-ani p-6 md:p-10 w-full max-w-6xl relative max-h-[90vh] flex flex-col">
<button id="closeCopyrightModal" class="absolute top-4 right-4 text-gray-400 hover:text-red-500 transition-colors z-10">
<iconify-icon icon="carbon:close" width="24"></iconify-icon>
</button>
<div class="text-center mb-5 md:mb-6">
<h2 class="text-2xl md:text-3xl font-extrabold tracking-tight mb-2">关于 Paper Burner X</h2>
</div>
<div class="text-gray-700 space-y-4 md:space-y-5 text-sm md:text-base font-normal overflow-y-auto pr-3 flex-1">
<div class="leading-relaxed text-left space-y-3">
<p class="text-sm md:text-base text-gray-700 font-normal">
这是一款<strong>开源的、在浏览器中即开即用的 AI 工作站</strong>,专为扫除海量的 PDF 文献、复杂的公式和跨语言的障碍。
</p>
<p class="text-sm md:text-base text-gray-700 font-normal">
它为需要进行精细、长文本阅读的研究人员和深度学习者设计,致力于将复杂的文档处理、翻译和分析流程整合到单一、流畅的体验中。
</p>
<div class="border-l-4 p-4 rounded-r-lg" style="background-color: var(--indigo-50); border-color: var(--color-primary);">
<p class="text-sm text-gray-700">
<strong style="color: var(--indigo-700);">GitHub:</strong> <a href="https://github.com/Feather-2/paper-burner" target="_blank" class="underline" style="color: var(--color-primary);">github.com/Feather-2/paper-burner</a><br>
<strong style="color: var(--indigo-700);">在线体验:</strong> <a href="https://paperburner.viwoplus.site/" target="_blank" class="underline" style="color: var(--color-primary);">paperburner.viwoplus.site</a>
</p>
</div>
</div>
<div>
<h3 class="text-base md:text-lg font-bold mb-3 flex items-center" style="color: var(--indigo-700);">
<iconify-icon icon="carbon:checkmark-outline" class="mr-2" width="20" style="color: var(--color-primary);"></iconify-icon>
目前实现了:
</h3>
<div class="space-y-4">
<div class="bg-gradient-to-r to-slate-50 p-4 rounded-lg border" style="from: var(--indigo-50); border-color: var(--indigo-100);">
<h4 class="text-base font-semibold text-gray-800 mb-2 flex items-center">
<iconify-icon icon="carbon:ai-status" class="mr-2" width="18" style="color: var(--color-primary);"></iconify-icon>
前端 Agent 驱动的智能检索
</h4>
<p class="text-sm text-gray-700 leading-relaxed">
我们在前端实现了一个 Agentic RAG 系统。通过赋予 AI 全局的文章结构和一系列工具(如 grep, vector search, fetch等等AI 能够自主决策、多步推理,并在长文本中实现复杂的分析和信息提取任务。
</p>
</div>
<div class="bg-gradient-to-r from-green-50 to-emerald-50 p-4 rounded-lg border border-green-100">
<h4 class="text-base font-semibold text-gray-800 mb-2 flex items-center">
<iconify-icon icon="carbon:flash" class="mr-2 text-green-600" width="18"></iconify-icon>
高性能批量处理
</h4>
<p class="text-sm text-gray-700 leading-relaxed">
支持多种文档格式PDF/DOCX/EPUB 等)和代码库的直接导入。利用并发 OCR 和翻译,并结合术语库(支持数万词条快速匹配),显著提升了文献处理效率。
</p>
</div>
<div class="bg-gradient-to-r from-purple-50 to-pink-50 p-4 rounded-lg border border-purple-100">
<h4 class="text-base font-semibold text-gray-800 mb-2 flex items-center">
<iconify-icon icon="carbon:settings-adjust" class="mr-2 text-purple-600" width="18"></iconify-icon>
高可扩展性与本地化
</h4>
<p class="text-sm text-gray-700 leading-relaxed">
目前所有数据均在浏览器本地,支持用户接入自定义 AI 模型端点,并提供了配套的 OCR Server 和 Docker 部署选项(开发中),让用户未来可以实现完全离线的本地化使用。
</p>
</div>
</div>
<p class="text-sm text-gray-600 italic mt-4 text-center">
希望这个工具能成为研究人员和知识工作者的得力助手,欢迎试用和提出宝贵意见!
</p>
</div>
<div>
<h3 class="text-base md:text-lg font-bold mb-3 flex items-center" style="color: var(--indigo-700);">
<iconify-icon icon="carbon:document" class="mr-2" width="20" style="color: var(--color-primary);"></iconify-icon>
具体介绍
</h3>
<div class="space-y-4">
<div>
<h4 class="text-base font-semibold text-gray-800 mb-2">一体化的文档处理引擎</h4>
<ul class="list-disc pl-5 text-sm text-gray-600 space-y-1.5 leading-relaxed">
<li><strong>广泛的格式支持:</strong>能够处理 PDF、DOCX、PPTX、EPUB、Markdown 甚至代码注释等多种格式,并支持导出为 DOCX、MD 等常用格式。</li>
<li><strong>智能导入与处理:</strong>不仅支持本地文件上传,更可一键从 GitHub 仓库或任意 URL 导入内容自动完成解析。PDF可以使用OCR (支持mineru/doc2x等) 与翻译引擎并实现保留原文格式翻译功能基于mineru目前优化中并会支持更多模型</li>
<li><strong>术语备择库:</strong>进行了性能优化,支持一次性导入数万条术语并进行快速匹配。</li>
<li>支持自定义模型端点可以支持检测、多key、快捷导出等机制使用灵活。</li>
</ul>
</div>
<div>
<h4 class="text-base font-semibold text-gray-800 mb-2">为深度阅读优化的交互体验</h4>
<ul class="list-disc pl-5 text-sm text-gray-600 space-y-1.5 leading-relaxed">
<li><strong>沉浸式对照阅读:</strong>提供智能对齐的段落级原译文对照、文档结构目录TOC、高亮与标注功能先进行无障碍的阅读再进行AI总结。</li>
<li><strong>增强学术内容展示:</strong>针对学术场景,特别优化了复杂公式的渲染。</li>
<li><strong>结构化信息提取:</strong>内置了"文献矩阵"等实用工具,能够将非结构化的论文内容,智能提取为清晰的结构化数据,方便进行横向对比和分析。</li>
</ul>
</div>
<div>
<h4 class="text-base font-semibold text-gray-800 mb-2">不止于问答:前端 Agent 驱动的智能分析</h4>
<p class="text-sm text-gray-600 leading-relaxed mb-2">
我们在纯前端环境中实现了一个长文本Agent。少量文本下将使用全量的策略而当提供长文本时候使用长文本Agent。
</p>
<ul class="list-disc pl-5 text-sm text-gray-600 space-y-1.5 leading-relaxed">
<li><strong>赋予 AI 全局视野:</strong>我们为 AI 构建了"分层意群/地图",让它在处理长文本时拥有对全文结构的整体认知。</li>
<li><strong>为 AI 配备工具箱:</strong>我们给予 AI 一系列工具,如精确匹配的 grep、向量搜索 vector search、内容抓取 fetch 等。AI 会根据你的问题,自主分析并决定调用哪种工具组合来寻找最佳答案。</li>
<li><strong>上述皆在纯前端实现,浏览器打开即用</strong></li>
</ul>
</div>
</div>
</div>
<div>
<h3 class="text-base md:text-lg font-bold mb-3 flex items-center" style="color: var(--indigo-700);">
<iconify-icon icon="carbon:road" class="mr-2" width="20" style="color: var(--color-primary);"></iconify-icon>
项目正在活跃地迭代
</h3>
<ul class="list-disc pl-5 text-sm text-gray-600 space-y-2 leading-relaxed">
<li><strong>完全本地化部署:</strong>正在开发Docker 部署方案,还提供了可自托管的 OCR Server最终目标是让用户可以完全在离线环境中使用全部功能。</li>
<li><strong>从单文档到多文档:</strong>下一个里程碑是将能力从分析单篇文献,扩展到处理多篇文献,并基于此开发能自动生成文献综述的综述 Agent成为真正的 AI 研究助理。</li>
</ul>
</div>
<div class="bg-gray-50 p-4 rounded-lg border border-gray-200">
<h3 class="text-base font-bold text-gray-800 mb-2 flex items-center">
<iconify-icon icon="carbon:license" class="mr-2 text-gray-600" width="18"></iconify-icon>
基于 AGPL 3.0 开源
</h3>
<p class="text-sm text-gray-600 leading-relaxed">
本项目基于 <a href="https://github.com/baoyudu/paper-burner" target="_blank" class="underline" style="color: var(--color-primary);">baoyudu/paper-burner</a>该项目是基于mistral ocr的pdf极简翻译工具并已在原始项目上进行了重构和各方面极多内容的扩充。为避免和Paper Burner原项目名称产生重复为示尊重和区分该分支项目改名Paper Burner X。
</p>
</div>
<div class="border-t-2 border-dashed pt-5 mt-5 text-center space-y-3" style="border-color: var(--indigo-200);">
<p class="text-sm md:text-base text-gray-600 font-normal">探索本分支的更多细节或贡献您的智慧:</p>
<a href="https://github.com/Feather-2/paper-burner" target="_blank" class="main-btn inline-flex items-center justify-center px-7 py-3 text-sm md:text-base font-bold shadow-md focus:outline-none focus:ring-2 focus:ring-offset-2" style="background-color: var(--color-primary); color: white;">
<iconify-icon icon="carbon:logo-github" class="mr-2" width="20"></iconify-icon>
访问新分支 (Feather-2/paper-burner)
</a>
<p class="text-xs text-gray-400 font-normal pt-1">
您的每一个建议对我们都至关重要!欢迎前往新分支的 <a href="https://github.com/Feather-2/paper-burner/issues" target="_blank" class="underline font-semibold" style="color: var(--color-primary);">Issue 区</a> 分享您的想法或参与讨论。
</p>
<p class="text-xs text-gray-400 italic">
致敬 <a href="https://github.com/baoyudu/paper-burner" target="_blank" class="underline" style="color: var(--color-primary);">Paper Burner</a> 开源项目Paper Burner X 在社区基础上持续扩展与迭代。
</p>
</div>
</div>
</div>
</div>
<script>
// App Shell 导航逻辑
document.addEventListener('DOMContentLoaded', () => {
const historyPanel = document.getElementById('historyPanel');
const modelKeyManagerModal = document.getElementById('modelKeyManagerModal');
// Desktop Sidebar Buttons
const sidebarSettingsBtn = document.getElementById('sidebarSettingsBtn');
if (sidebarSettingsBtn && modelKeyManagerModal) {
sidebarSettingsBtn.addEventListener('click', () => {
// 触发模型管理器的打开逻辑(与主界面的"设置"按钮保持一致)
const modelKeyManagerBtn = document.getElementById('modelKeyManagerBtn');
if (modelKeyManagerBtn) {
modelKeyManagerBtn.click();
} else {
// 后备方案:直接打开弹窗(但可能是空的)
modelKeyManagerModal.classList.remove('hidden');
}
});
}
// Mobile Sidebar Logic
const appSidebar = document.getElementById('appSidebar');
const sidebarOverlay = document.getElementById('sidebarOverlay');
const mobileMenuBtn = document.getElementById('mobileMenuBtn');
const sidebarCloseBtn = document.getElementById('sidebarCloseBtn');
function openSidebar() {
appSidebar?.classList.add('mobile-open');
sidebarOverlay?.classList.add('show');
}
function closeSidebar() {
appSidebar?.classList.remove('mobile-open');
sidebarOverlay?.classList.remove('show');
}
if (mobileMenuBtn) mobileMenuBtn.addEventListener('click', openSidebar);
if (sidebarCloseBtn) sidebarCloseBtn.addEventListener('click', closeSidebar);
if (sidebarOverlay) sidebarOverlay.addEventListener('click', closeSidebar);
// Desktop Sidebar Collapse Logic
const sidebarToggleBtn = document.getElementById('sidebarToggleBtn');
const sidebarToggleIcon = document.getElementById('sidebarToggleIcon');
function setSidebarState(collapsed) {
if (collapsed) {
appSidebar.classList.add('collapsed');
if (sidebarToggleIcon) sidebarToggleIcon.setAttribute('icon', 'carbon:side-panel-open');
} else {
appSidebar.classList.remove('collapsed');
if (sidebarToggleIcon) sidebarToggleIcon.setAttribute('icon', 'carbon:side-panel-close');
}
localStorage.setItem('pbx_sidebar_collapsed', collapsed);
}
// Init state from local storage
const isCollapsed = localStorage.getItem('pbx_sidebar_collapsed') === 'true';
// 初始加载时不启用动画,避免闪烁 (可选优化,这里暂不复杂化)
setSidebarState(isCollapsed);
if (sidebarToggleBtn) {
sidebarToggleBtn.addEventListener('click', () => {
const willCollapse = !appSidebar.classList.contains('collapsed');
setSidebarState(willCollapse);
});
}
});
// 一闪而过的提示文字淡入淡出动画 (用于"配置模型与Key管理"提示)
window.addEventListener('DOMContentLoaded', function() {
var tip = document.getElementById('flashConfigTip');
if (tip) {
tip.classList.add('show'); // 淡入
setTimeout(function() {
tip.classList.remove('show');
tip.classList.add('hide'); // 淡出
}, 1200); // 持续显示1.2秒
setTimeout(function() {
tip.style.display = 'none'; // 动画结束后隐藏元素
}, 1800); // 总共1.8秒后隐藏
}
});
// 版权声明弹窗逻辑
(function() {
var modal = document.getElementById('copyrightModal');
var showBtn = document.getElementById('showCopyrightModal');
var closeBtn = document.getElementById('closeCopyrightModal');
var alreadyShown = localStorage.getItem('paperBurnerBranchInfoShown');
function openModal() {
if (modal) modal.classList.remove('hidden');
}
function closeModal() {
if (modal) modal.classList.add('hidden');
}
if (showBtn) {
showBtn.addEventListener('click', openModal);
}
if (closeBtn) {
closeBtn.addEventListener('click', function() {
closeModal();
// 确保用户手动关闭后,标记为已显示,避免下次自动弹出
localStorage.setItem('paperBurnerBranchInfoShown', 'true');
});
}
// 点击遮罩关闭
if (modal) {
modal.addEventListener('click', function(e) {
if (e.target === modal) {
closeModal();
localStorage.setItem('paperBurnerBranchInfoShown', 'true');
}
});
}
// 首次访问自动展开"关于本分支"卡片 (如果 Landing Page 未显示)
// var landingPageIsActive = !localStorage.getItem('paperBurnerLandingPageShown'); // This logic is no longer needed here
// if (!alreadyShown && modal && !landingPageIsActive) { // This logic is no longer needed here
// openModal();
// }
})();
// Landing Page Logic Removed - main app is now shown by default // This line and related block will be removed by the next instruction.
// 获取 GitHub Stars 数量(使用统一模块)
(function() {
const starsElement = document.getElementById('githubStars');
if (!starsElement || !window.GitHubStars) return;
window.GitHubStars.getStars()
.then(result => {
starsElement.innerHTML = '<iconify-icon icon="carbon:star" width="10"></iconify-icon><span>' + result.formatted + ' stars</span>';
})
.catch(error => {
console.warn('[Index] Failed to fetch GitHub stars:', error);
// 失败时隐藏 stars 显示
starsElement.style.display = 'none';
});
})();
</script>
</body>
</html>