Files
AIclinicalresearch/docs/03-业务模块/ASL-AI智能文献/03-UI设计/全文解析与数据提取v4.html
HaHafeng 8eef9e0544 feat(asl): Complete Week 4 - Results display and Excel export with hybrid solution
Features:
- Backend statistics API (cloud-native Prisma aggregation)
- Results page with hybrid solution (AI consensus + human final decision)
- Excel export (frontend generation, zero disk write, cloud-native)
- PRISMA-style exclusion reason analysis with bar chart
- Batch selection and export (3 export methods)
- Fixed logic contradiction (inclusion does not show exclusion reason)
- Optimized table width (870px, no horizontal scroll)

Components:
- Backend: screeningController.ts - add getProjectStatistics API
- Frontend: ScreeningResults.tsx - complete results page (hybrid solution)
- Frontend: excelExport.ts - Excel export utility (40 columns full info)
- Frontend: ScreeningWorkbench.tsx - add navigation button
- Utils: get-test-projects.mjs - quick test tool

Architecture:
- Cloud-native: backend aggregation reduces network transfer
- Cloud-native: frontend Excel generation (zero file persistence)
- Reuse platform: global prisma instance, logger
- Performance: statistics API < 500ms, Excel export < 3s (1000 records)

Documentation:
- Update module status guide (add Week 4 features)
- Update task breakdown (mark Week 4 completed)
- Update API design spec (add statistics API)
- Update database design (add field usage notes)
- Create Week 4 development plan
- Create Week 4 completion report
- Create technical debt list

Test:
- End-to-end flow test passed
- All features verified
- Performance test passed
- Cloud-native compliance verified

Ref: Week 4 Development Plan
Scope: ASL Module MVP - Title Abstract Screening Results
Cloud-Native: Backend aggregation + Frontend Excel generation
2025-11-21 20:12:38 +08:00

361 lines
29 KiB
HTML
Raw Permalink 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">
<title>全文解析与数据提取模块原型 V4</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/heroicons/2.1.3/24/outline/css/heroicons.min.css" rel="stylesheet">
<style>
body { font-family: 'Inter', sans-serif; }
.sidebar { background-color: #f8fafc; transition: width 0.3s ease; }
.sidebar-link.active { background-color: #e0f2fe; color: #0c4a6e; font-weight: 600; }
.sidebar.collapsed { width: 5rem; }
.sidebar:not(.collapsed) .sidebar-text { display: inline; }
.sidebar.collapsed .sidebar-text, .sidebar.collapsed .logo-text, .sidebar.collapsed .nav-submenu { display: none; }
.modal-backdrop { background-color: rgba(0, 0, 0, 0.5); }
.progress-bar div { transition: width 0.5s ease-in-out; }
.main-content { transition: margin-left 0.3s ease; }
.sidebar.collapsed ~ .main-content { margin-left: 5rem; }
.sidebar:not(.collapsed) ~ .main-content { margin-left: 16rem; }
/* V4 Styles */
.workbench-queue { transition: width 0.3s ease; }
.workbench-queue.collapsed { width: 0rem; padding: 0; overflow: hidden; }
.workbench-queue.collapsed ~ .workbench-main { width: 100%; }
.workbench-queue-item.active { background-color: #e0f2fe; }
.pdf-highlight { background-color: #fef08a; transition: background-color 0.3s; }
.data-field:hover { background-color: #f0f9ff; }
.data-conflict { border: 2px solid #ef4444; }
.tooltip { visibility: hidden; opacity: 0; transition: opacity 0.2s; }
.has-tooltip:hover .tooltip { visibility: visible; opacity: 1; }
</style>
</head>
<body class="bg-gray-100 text-gray-800">
<!-- 主容器 -->
<div class="h-screen flex">
<!-- 模拟的左侧主导航 -->
<aside id="sidebar" class="sidebar w-64 border-r border-gray-200 p-4 flex-shrink-0 flex flex-col fixed h-full">
<div class="text-xl font-bold text-gray-800 mb-8 flex items-center space-x-2">
<svg class="h-8 w-8 text-sky-600" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M12 6.042A8.967 8.967 0 0 0 6 3.75c-1.052 0-2.062.18-3 .512v14.25A8.987 8.987 0 0 1 6 18c2.305 0 4.408.867 6 2.292m0-14.25a8.966 8.966 0 0 1 6-2.292c1.052 0-2.062.18-3 .512v14.25A8.987 8.987 0 0 0 18 18a8.967 8.967 0 0 0-6 2.292m0-14.25v14.25" /></svg>
<span class="logo-text">AI文献平台</span>
</div>
<nav class="flex-grow space-y-2">
<a href="#" class="sidebar-link group flex items-center px-3 py-2 text-sm font-medium rounded-md text-gray-700 space-x-3"><i class="h-5 w-5 hi-outline hi-home"></i><span class="sidebar-text">项目概览</span></a>
<a href="#" class="sidebar-link group flex items-center px-3 py-2 text-sm font-medium rounded-md text-gray-700 space-x-3"><i class="h-5 w-5 hi-outline hi-magnifying-glass"></i><span class="sidebar-text">1. 智能文献检索</span></a>
<a href="#" class="sidebar-link group flex items-center px-3 py-2 text-sm font-medium rounded-md text-gray-700 space-x-3"><i class="h-5 w-5 hi-outline hi-beaker"></i><span class="sidebar-text">2. AI辅助初筛</span></a>
<div id="nav-section-extraction">
<a href="#" id="nav-extraction-main" class="sidebar-link active group flex items-center px-3 py-2 text-sm font-medium rounded-md text-gray-700 space-x-3">
<i class="h-5 w-5 hi-outline hi-document-text"></i>
<span class="sidebar-text">3. 全文解析与数据提取</span>
</a>
<div class="nav-submenu pl-6 mt-1 space-y-1">
<a href="#" id="nav-library" class="sidebar-link active group flex items-center px-3 py-2 text-sm font-medium rounded-md text-gray-700"><span class="sidebar-text">文献库与模板</span></a>
<a href="#" id="nav-workbench" class="sidebar-link group flex items-center px-3 py-2 text-sm font-medium rounded-md text-gray-700"><span class="sidebar-text">审查台</span></a>
<a href="#" id="nav-summary" class="sidebar-link group flex items-center px-3 py-2 text-sm font-medium rounded-md text-gray-700"><span class="sidebar-text">数据汇总</span></a>
</div>
</div>
<a href="#" class="sidebar-link group flex items-center px-3 py-2 text-sm font-medium rounded-md text-gray-700 space-x-3"><i class="h-5 w-5 hi-outline hi-chart-pie"></i><span class="sidebar-text">4. 数据分析与报告</span></a>
</nav>
<div class="flex-shrink-0 mt-auto"><button id="sidebar-toggle" class="group flex items-center w-full px-3 py-2 text-sm font-medium rounded-md text-gray-700 hover:bg-gray-200 space-x-3"><i class="h-5 w-5 hi-outline hi-arrows-right-left"></i><span class="sidebar-text">收起</span></button></div>
</aside>
<!-- 主内容区 -->
<div class="main-content flex-grow flex flex-col w-full" style="margin-left: 16rem;">
<header class="bg-white shadow-sm flex-shrink-0 z-10 p-4 border-b"><h1 id="header-title" class="text-xl font-bold">全文解析与数据提取 / 文献库与模板</h1></header>
<div id="view-container" class="flex-grow p-6 overflow-auto">
<!-- 视图1: 文献库与模板 -->
<div id="library-view">
<!-- content from v3, unchanged -->
<div class="space-y-8"><div><h2 class="text-2xl font-bold mb-4">1. 数据提取与评价模板</h2><div class="bg-white p-6 rounded-lg shadow flex items-center justify-between"><p class="text-gray-600">为保证提取质量,请为本项目选择或创建一个模板。</p><div class="flex items-center space-x-4"><select id="template-selector" class="rounded-md border-gray-300 shadow-sm"><option value="">选择一个模板...</option><option value="meta_analysis">Meta分析标准模板</option><option value="drug_eval">药物评价模板</option></select><button class="text-sky-600 hover:text-sky-800 font-semibold">创建新模板</button></div></div></div><div><div class="flex justify-between items-center mb-4"><h2 class="text-2xl font-bold">2. 待提取文献库 (50篇)</h2><button id="launch-extraction-btn" disabled class="bg-sky-600 text-white font-bold py-2 px-6 rounded-lg shadow-md transition-all disabled:bg-gray-400 disabled:cursor-not-allowed">启动AI提取</button></div><div class="bg-white rounded-lg shadow overflow-hidden"><table class="min-w-full divide-y divide-gray-200"><thead class="bg-gray-50"><tr><th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">文献标题</th><th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">作者</th><th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">状态</th><th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">操作</th></tr></thead><tbody id="literature-library-body" class="bg-white divide-y divide-gray-200"></tbody></table></div></div></div>
</div>
<!-- 视图2: 三栏式审查台 (默认隐藏) -->
<div id="workbench-view" class="hidden h-full flex flex-col">
<div id="workbench-placeholder" class="hidden text-center p-10 bg-white rounded-lg shadow">
<h3 class="text-lg font-semibold text-gray-700">请选择一篇文献</h3>
<p class="text-gray-500 mt-2">请先从“文献库与模板”页面中选择一篇已完成的文献进入审查台。</p>
<button onclick="showView('library')" class="mt-4 bg-sky-600 text-white font-bold py-2 px-4 rounded-lg hover:bg-sky-700">返回文献库</button>
</div>
<div id="workbench-content" class="flex-grow flex space-x-4 overflow-hidden">
<!-- V4: New Workbench Queue -->
<aside id="workbench-queue" class="workbench-queue w-80 bg-white rounded-lg shadow flex-shrink-0 flex flex-col">
<div class="p-4 border-b flex justify-between items-center">
<h3 class="font-bold text-lg">工作队列</h3>
<button id="queue-toggle-btn" class="p-1 text-gray-500 hover:bg-gray-200 rounded">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5"><path stroke-linecap="round" stroke-linejoin="round" d="M15.75 19.5 8.25 12l7.5-7.5" /></svg>
</button>
</div>
<div class="p-2"><input type="search" placeholder="搜索文献..." class="w-full p-2 border rounded-md text-sm"></div>
<ul id="queue-list" class="overflow-y-auto flex-grow p-2 space-y-1"></ul>
</aside>
<!-- V4: Main Workbench Area (PDF + Form) -->
<div class="workbench-main flex-grow flex space-x-4">
<div class="w-1/2 bg-white rounded-lg shadow flex flex-col overflow-hidden">
<div class="p-4 border-b font-bold bg-gray-50">PDF原文阅读器</div>
<div id="pdf-viewer" class="overflow-y-auto p-6 text-gray-700 leading-relaxed"></div>
</div>
<div class="w-1/2 bg-white rounded-lg shadow flex flex-col overflow-hidden">
<div class="p-4 border-b font-bold bg-gray-50">结构化数据与评价模板</div>
<div id="data-template-form" class="overflow-y-auto p-6 space-y-6"></div>
</div>
</div>
</div>
</div>
<!-- 视图3: 数据汇总页 (默认隐藏) -->
<div id="summary-view" class="hidden">
<!-- content from v3, unchanged -->
<div class="bg-white rounded-lg shadow p-6"><div class="flex justify-between items-center mb-6"><h2 class="text-2xl font-bold">数据汇总</h2><div class="space-x-4"><button class="bg-gray-700 hover:bg-gray-800 text-white font-bold py-2 px-4 rounded-lg">↓ 导出为Excel</button><button class="bg-sky-600 hover:bg-sky-700 text-white font-bold py-2 px-4 rounded-lg">进入数据分析 →</button></div></div><div id="summary-stats" class="grid grid-cols-3 gap-6 mb-8 text-center"></div><div id="model-performance-summary" class="mb-8 bg-gray-50 p-4 rounded-lg"></div><input type="text" id="summary-search" placeholder="在数据中搜索..." class="w-full p-2 border rounded mb-4"><div class="overflow-x-auto"><table id="summary-table" class="min-w-full divide-y divide-gray-200"></table></div></div>
</div>
</div>
</div>
</div>
<!-- 任务状态面板 (模态框) -->
<div id="task-status-modal" class="hidden fixed inset-0 z-50 flex items-center justify-center">
<!-- content from v3, unchanged -->
<div class="modal-backdrop absolute inset-0"></div><div class="bg-white rounded-lg shadow-xl p-8 w-full max-w-2xl z-10"><h2 class="text-2xl font-bold text-center mb-4">AI提取进行中...</h2><div class="flex items-center space-x-4 mb-6"><div class="w-full bg-gray-200 rounded-full h-4 progress-bar"><div id="progress-bar-inner" class="bg-sky-500 h-4 rounded-full" style="width: 0%"></div></div><span id="progress-text" class="font-semibold">0 / 50</span></div><div id="live-counts" class="grid grid-cols-2 gap-x-8 gap-y-4 bg-gray-50 p-4 rounded-lg mb-4 text-sm"></div><p id="eta-text" class="text-center text-gray-500 mb-6">预计剩余时间: 计算中...</p><div class="flex justify-center"><button id="close-modal-btn" class="hidden bg-green-500 hover:bg-green-600 text-white font-bold py-3 px-6 rounded-lg">提取完成!返回文献库</button></div></div>
</div>
<script>
// --- STATE MANAGEMENT ---
let currentView = 'library';
let currentWorkbenchDocId = null;
let templateSelected = false;
let literatureData = [];
const TOTAL_DOCS = 50;
// --- DATA SIMULATION ---
function generateLiteratureData() {
for (let i = 1; i <= TOTAL_DOCS; i++) {
let status;
const rand = Math.random();
if (rand < 0.1) status = 'failed';
else if (rand < 0.3) status = 'fetching';
else status = 'ready';
literatureData.push({
id: i,
title: `${i}篇RCT研究: Drug-X对糖尿病的疗效`,
authors: 'Smith J, et al.',
status: status,
is_reviewed: false, // V4: Track review status
pdf_content: {
introduction: `研究背景II型糖尿病是全球性的健康问题。本研究旨在评估新型药物Drug-X的有效性。`,
methods: `研究设计:我们进行了一项多中心、双盲、随机对照试验(RCT)共纳入152名患者随机分为试验组(n=75)和安慰剂组(n=77)。主要终点为糖化血红蛋白变化。`,
results: `研究结果试验组的糖化血红蛋白平均降低了1.5%而安慰剂组为0.2% (p < 0.01)。`,
},
extracted_data: null
});
}
}
// --- INITIALIZATION ---
document.addEventListener('DOMContentLoaded', () => {
generateLiteratureData();
renderLibraryTable();
});
// --- VIEW & NAVIGATION MANAGEMENT ---
const views = { library: document.getElementById('library-view'), workbench: document.getElementById('workbench-view'), summary: document.getElementById('summary-view') };
const navLinks = { library: document.getElementById('nav-library'), workbench: document.getElementById('nav-workbench'), summary: document.getElementById('nav-summary') };
const headerTitle = document.getElementById('header-title');
function showView(view, docId = null) {
currentView = view;
Object.values(views).forEach(v => v.classList.add('hidden'));
Object.values(navLinks).forEach(l => l.classList.remove('active'));
views[view].classList.remove('hidden');
if(navLinks[view]) navLinks[view].classList.add('active');
const titles = { library: '文献库与模板', workbench: '审查台', summary: '数据汇总' };
headerTitle.textContent = '全文解析与数据提取 / ' + (titles[view] || '未知页面');
if (view === 'summary') renderSummaryPage();
if (view === 'workbench') {
const workbenchContent = document.getElementById('workbench-content');
const workbenchPlaceholder = document.getElementById('workbench-placeholder');
if (docId) {
currentWorkbenchDocId = docId;
workbenchContent.classList.remove('hidden');
workbenchPlaceholder.classList.add('hidden');
renderWorkbench(docId);
} else {
currentWorkbenchDocId = null;
workbenchContent.classList.add('hidden');
workbenchPlaceholder.classList.remove('hidden');
}
}
}
document.getElementById('nav-library').addEventListener('click', (e) => { e.preventDefault(); showView('library'); });
document.getElementById('nav-summary').addEventListener('click', (e) => { e.preventDefault(); showView('summary'); });
document.getElementById('nav-workbench').addEventListener('click', (e) => {
e.preventDefault();
showView('workbench', currentWorkbenchDocId);
});
// --- VIEW 1: LIBRARY & TEMPLATES (unchanged from V3) ---
const templateSelector = document.getElementById('template-selector');
const launchBtn = document.getElementById('launch-extraction-btn');
templateSelector.addEventListener('change', () => { templateSelected = templateSelector.value !== ''; updateLaunchButtonState(); });
function updateLaunchButtonState() { const readyDocs = literatureData.some(doc => doc.status === 'ready'); launchBtn.disabled = !(templateSelected && readyDocs); }
function renderLibraryTable() { const tbody = document.getElementById('literature-library-body'); tbody.innerHTML = literatureData.map(doc => { const statusMap = { fetching: `<span class="text-gray-500">正在获取全文...</span>`, ready: `<span class="text-green-600 font-semibold">准备就绪</span>`, failed: `<span class="text-red-600 font-semibold">获取失败</span>`, extracting: `<span class="text-blue-600">AI提取中...</span>`, completed: `<span class="text-purple-600 font-semibold">已完成</span>` }; let actionButton; switch(doc.status) { case 'failed': actionButton = `<button class="text-sky-600 hover:underline" onclick="handleUpload(${doc.id})">上传PDF</button>`; break; case 'completed': actionButton = `<button class="text-sky-600 hover:underline font-semibold" onclick="showView('workbench', ${doc.id})">进入审查台</button>`; break; case 'extracting': actionButton = `<span class="text-gray-400">提取中...</span>`; break; case 'ready': actionButton = `<span class="text-gray-400">等待提取</span>`; break; default: actionButton = `<span class="text-gray-400">获取中...</span>`; } return `<tr><td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">${doc.title}</td><td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${doc.authors}</td><td class="px-6 py-4 whitespace-nowrap text-sm">${statusMap[doc.status]}</td><td class="px-6 py-4 whitespace-nowrap text-sm">${actionButton}</td></tr>`; }).join(''); }
function handleUpload(docId) { alert(`模拟为文献ID ${docId} 上传PDF...`); const doc = literatureData.find(d => d.id === docId); doc.status = 'ready'; renderLibraryTable(); updateLaunchButtonState(); }
// --- TASK STATUS MODAL (unchanged from V3) ---
const taskModal = document.getElementById('task-status-modal');
launchBtn.addEventListener('click', () => { taskModal.classList.remove('hidden'); let processed = 0; const docsToProcess = literatureData.filter(d => d.status === 'ready'); const totalToProcess = docsToProcess.length; const progressBarInner = document.getElementById('progress-bar-inner'); const progressText = document.getElementById('progress-text'); const liveCounts = document.getElementById('live-counts'); const etaText = document.getElementById('eta-text'); const closeModalBtn = document.getElementById('close-modal-btn'); docsToProcess.forEach(d => d.status = 'extracting'); renderLibraryTable(); let counts = { pdf_ok: 0, pdf_fail: 0, deepseek_done: 0, qwen3_done: 0 }; const interval = setInterval(() => { if (processed < totalToProcess) { processed++; if (Math.random() > 0.05) counts.pdf_ok++; else counts.pdf_fail++; if (processed > 2 && Math.random() > 0.1) counts.deepseek_done++; if (processed > 4 && Math.random() > 0.2) counts.qwen3_done++; const percentage = (processed / totalToProcess) * 100; progressBarInner.style.width = `${percentage}%`; progressText.textContent = `${processed} / ${totalToProcess}`; liveCounts.innerHTML = `<div>PDF解析成功: <span class="font-bold">${counts.pdf_ok}</span></div><div>PDF解析失败: <span class="font-bold text-red-500">${counts.pdf_fail}</span></div><div>DeepSeek 提取完成: <span class="font-bold">${counts.deepseek_done}</span></div><div>Qwen3 提取完成: <span class="font-bold">${counts.qwen3_done}</span></div>`; etaText.textContent = `预计剩余时间: 约 ${Math.round((totalToProcess - processed) * 0.5)}`; } else { clearInterval(interval); etaText.textContent = '所有任务已加入后台队列处理完成!'; closeModalBtn.classList.remove('hidden'); docsToProcess.forEach(d => { d.status = 'completed'; d.extracted_data = { study_design: { deepseek: 'RCT', qwen3: 'RCT', source: '我们进行了一项多中心、双盲、随机对照试验(RCT)' }, sample_size: { deepseek: '150', qwen3: '152', source: '共纳入152名患者' }, intervention_group_n: { deepseek: '75', qwen3: '75', source: '随机分为试验组(n=75)和安慰剂组(n=77)' }, rob_randomization: { user_judgement: null, evidence: '' }, rob_blinding: { user_judgement: null, evidence: '' }, }; }); renderLibraryTable(); } }, 500); });
document.getElementById('close-modal-btn').addEventListener('click', () => { taskModal.classList.add('hidden'); });
// --- VIEW 2: WORKBENCH (V4 Refactor) ---
function renderWorkbench(docId) {
const doc = literatureData.find(d => d.id === docId);
if (!doc) return;
renderWorkbenchQueue();
renderPdfViewer(doc);
renderDataForm(doc);
addWorkbenchInteractivity();
}
function renderWorkbenchQueue() {
const queueList = document.getElementById('queue-list');
const completedDocs = literatureData.filter(d => d.status === 'completed');
queueList.innerHTML = completedDocs.map(doc => {
const isActive = doc.id === currentWorkbenchDocId;
const statusIcon = doc.is_reviewed
? `<span class="text-green-500" title="已审查">✓</span>`
: `<span class="text-gray-400" title="待审查">○</span>`;
return `<li class="workbench-queue-item p-3 rounded-md cursor-pointer hover:bg-gray-100 ${isActive ? 'active' : ''}" onclick="showView('workbench', ${doc.id})">
<div class="flex justify-between items-start">
<p class="text-sm font-medium text-gray-800 truncate pr-2">${doc.title}</p>
${statusIcon}
</div>
<p class="text-xs text-gray-500">${doc.authors}</p>
</li>`;
}).join('');
}
function renderPdfViewer(doc) {
const pdfViewer = document.getElementById('pdf-viewer');
pdfViewer.innerHTML = Object.entries(doc.pdf_content).map(([key, text]) =>
`<p class="mb-4"><strong class="capitalize">${key}:</strong> <span data-section="${key}">${text}</span></p>`
).join('');
}
function renderDataForm(doc) {
const formContainer = document.getElementById('data-template-form');
const data = doc.extracted_data;
if (!data) {
formContainer.innerHTML = `<div class="text-center p-8"><h3 class="font-bold text-lg">数据加载失败</h3><p class="text-gray-600 mt-2">该文献的数据不存在或尚未提取。</p></div>`;
return;
}
formContainer.innerHTML = `
<div class="space-y-4">
<h3 class="text-lg font-bold border-b pb-2">数据提取</h3>
${renderDataField('study_design', '研究设计', data.study_design)}
${renderDataField('sample_size', '总样本量', data.sample_size)}
${renderDataField('intervention_group_n', '干预组样本量', data.intervention_group_n)}
</div>
<div class="space-y-4 mt-8">
<h3 class="text-lg font-bold border-b pb-2">批判性评价 (Cochrane RoB 2)</h3>
${renderAppraisalField('rob_randomization', '随机过程产生的偏倚')}
${renderAppraisalField('rob_blinding', '致盲产生的偏倚')}
</div>
<div class="mt-8 pt-4 border-t">
<button onclick="markAsReviewed(${doc.id})" class="w-full bg-green-600 text-white font-bold py-3 rounded-lg hover:bg-green-700">标记为已审查</button>
</div>
`;
}
function renderDataField(id, label, data) {
const isConflict = data.deepseek !== data.qwen3;
return `
<div class="data-field rounded-lg border p-4 ${isConflict ? 'data-conflict' : ''}" data-source="${data.source}">
<label class="font-semibold text-gray-700 block mb-3">${label} ${isConflict ? '<span class="text-red-500 font-bold text-xs ml-2">待仲裁</span>' : ''}</label>
<div class="grid grid-cols-2 gap-4 mb-3 text-sm">
<div class="bg-gray-50 p-2 rounded"><p class="font-bold text-gray-500">DeepSeek:</p><p class="text-gray-800">${data.deepseek}</p></div>
<div class="bg-gray-50 p-2 rounded"><p class="font-bold text-gray-500">Qwen3:</p><p class="text-gray-800">${data.qwen3}</p></div>
</div>
<div>
<label class="text-sm font-medium">最终值:</label>
<input type="text" value="${isConflict ? '' : data.deepseek}" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm p-2">
</div>
</div>
`;
}
function renderAppraisalField(id, label) { return `<div class="rounded-lg border p-4"><label class="font-semibold text-gray-700 block mb-3">${label}</label><div class="flex space-x-4"><label class="flex items-center"><input type="radio" name="${id}" class="mr-1"> 低风险</label><label class="flex items-center"><input type="radio" name="${id}" class="mr-1"> 有些顾虑</label><label class="flex items-center"><input type="radio" name="${id}" class="mr-1"> 高风险</label></div><button class="mt-3 text-xs text-sky-600 hover:underline">🔗 链接证据</button></div>`; }
function addWorkbenchInteractivity() {
document.querySelectorAll('.data-field').forEach(field => {
field.addEventListener('mouseover', () => {
const sourceText = field.dataset.source;
highlightPdfText(sourceText, true);
});
field.addEventListener('mouseout', () => {
highlightPdfText(null, false);
});
});
}
function highlightPdfText(textToHighlight, shouldHighlight) {
const pdfViewer = document.getElementById('pdf-viewer');
let content = pdfViewer.innerHTML.replace(/<mark class="pdf-highlight">/g, '').replace(/<\/mark>/g, '');
if (shouldHighlight && textToHighlight) {
content = content.replace(textToHighlight, `<mark class="pdf-highlight">${textToHighlight}</mark>`);
}
pdfViewer.innerHTML = content;
if (shouldHighlight) {
const mark = pdfViewer.querySelector('mark');
if (mark) mark.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
}
function markAsReviewed(docId) {
const doc = literatureData.find(d => d.id === docId);
if(doc) doc.is_reviewed = true;
renderWorkbenchQueue();
}
document.getElementById('queue-toggle-btn').addEventListener('click', () => {
document.getElementById('workbench-queue').classList.toggle('collapsed');
});
// --- VIEW 3: SUMMARY (unchanged from V3) ---
function renderSummaryPage() {
const completedDocs = literatureData.filter(d => d.status === 'completed');
document.getElementById('summary-stats').innerHTML = `<div class="bg-gray-50 p-4 rounded-lg"><div class="text-3xl font-bold">${TOTAL_DOCS}</div><div class="text-gray-500">总计文献</div></div><div class="bg-green-50 p-4 rounded-lg"><div class="text-3xl font-bold text-green-600">${completedDocs.length}</div><div class="text-gray-500">已提取文献</div></div><div class="bg-purple-50 p-4 rounded-lg"><div class="text-3xl font-bold text-purple-600">${completedDocs.length * 5}</div><div class="text-gray-500">已提取数据点</div></div>`;
const deepseekAccuracy = "92.5%"; const qwen3Accuracy = "88.0%";
document.getElementById('model-performance-summary').innerHTML = `<h3 class="font-bold text-lg mb-2">模型表现评估</h3><div class="flex space-x-8 text-center"><div><div class="text-2xl font-bold text-sky-700">${deepseekAccuracy}</div><div class="text-sm text-gray-500">DeepSeek 正确率</div></div><div><div class="text-2xl font-bold text-teal-700">${qwen3Accuracy}</div><div class="text-sm text-gray-500">Qwen3 正确率</div></div></div>`;
const summaryTable = document.getElementById('summary-table');
const tableHead = `<thead><tr class="bg-gray-50"><th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">文献</th><th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">研究设计</th><th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">总样本量</th><th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">干预组样本量</th><th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">随机化偏倚</th></tr></thead>`;
const tableBody = `<tbody>${completedDocs.map(doc => `
<tr class="hover:bg-gray-50">
<td class="px-4 py-2 text-sm font-medium">${doc.title}</td>
<td class="px-4 py-2 text-sm has-tooltip relative"><span>${doc.extracted_data.study_design.deepseek}</span><div class="tooltip absolute z-10 -mt-16 w-64 bg-gray-800 text-white text-xs rounded py-1 px-2">${doc.extracted_data.study_design.source}</div></td>
<td class="px-4 py-2 text-sm has-tooltip relative"><span>${doc.extracted_data.sample_size.qwen3}</span><div class="tooltip absolute z-10 -mt-16 w-64 bg-gray-800 text-white text-xs rounded py-1 px-2">${doc.extracted_data.sample_size.source}</div></td>
<td class="px-4 py-2 text-sm">...</td>
<td class="px-4 py-2 text-sm">...</td>
</tr>`).join('')}</tbody>`;
summaryTable.innerHTML = tableHead + tableBody;
}
// --- GLOBAL INTERACTIVITY ---
document.getElementById('sidebar-toggle').addEventListener('click', () => {
document.getElementById('sidebar').classList.toggle('collapsed');
});
</script>
</body>
</html>