RVW module (V3.0 Smart Review Enhancement): - Add LLM data validation via PromptService (RVW_DATA_VALIDATION) - Add ClinicalAssessmentSkill with FINER-based evaluation (RVW_CLINICAL) - Remove all numeric scores from UI (editorial, methodology, overall) - Implement partial_completed status with Promise.allSettled - Add error_details JSON field to ReviewTask for granular failure info - Fix overallStatus logic: warning status now counts as success - Restructure ForensicsReport: per-table LLM results, remove top-level block - Refactor ClinicalReport: structured collapsible sections - Increase all skill timeouts to 300s for long manuscripts (20+ pages) - Increase DataForensics LLM timeout to 180s, pg-boss to 15min - Executor default fallback timeout 30s -> 60s ASL module: - Add deep research history with sidebar accordion UI - Implement waterfall flow for historical task display - Upgrade Unifuncs DeepSearch API from S2 to S3 with fallback - Add ASL_SR module seed for admin configurability - Fix new search button inconsistency Docs: - Update RVW module status to V3.0 - Update deployment changelist - Add 0305 deployment summary DB Migration: - Add error_details JSONB column to rvw_schema.review_tasks Tested: All 4 review modules verified, partial completion working Made-with: Cursor
541 lines
37 KiB
HTML
541 lines
37 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>AI智能文献平台 v6.0 (互斥手风琴版)</title>
|
||
<!-- Tailwind CSS -->
|
||
<script src="https://cdn.tailwindcss.com"></script>
|
||
<!-- Lucide Icons -->
|
||
<script src="https://unpkg.com/lucide@latest"></script>
|
||
<style>
|
||
.fade-in { animation: fadeIn 0.3s ease-out forwards; }
|
||
.slide-in-from-bottom { animation: slideInFromBottom 0.3s ease-out forwards; }
|
||
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
|
||
@keyframes slideInFromBottom { from { transform: translateY(1rem); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
|
||
|
||
/* 隐藏滚动条但保留滚动功能 */
|
||
.no-scrollbar::-webkit-scrollbar { display: none; }
|
||
.no-scrollbar { -ms-overflow-style: none; scrollbar-width: none; }
|
||
|
||
/* * 核心黑科技:利用 CSS Grid 实现完美的柔性折叠动画
|
||
* grid-rows-[0fr] 会将高度压缩到 0
|
||
* grid-rows-[1fr] 会让高度填满 flex 分配的空间
|
||
*/
|
||
.accordion-grid {
|
||
transition: grid-template-rows 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
||
}
|
||
.accordion-content {
|
||
overflow: hidden;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body class="bg-slate-50 font-sans text-slate-900 overflow-hidden h-screen flex flex-col">
|
||
|
||
<!-- 全局顶部导航 -->
|
||
<header class="h-14 bg-slate-900 text-slate-300 flex items-center justify-between px-6 shadow-md z-30 shrink-0">
|
||
<div class="flex items-center space-x-8">
|
||
<div class="flex items-center space-x-2 text-white font-bold tracking-wide">
|
||
<div class="bg-blue-600 p-1 rounded"><i data-lucide="activity" class="w-4 h-4 text-white"></i></div>
|
||
<span>医学科研 AI 平台</span>
|
||
</div>
|
||
<nav class="flex space-x-1">
|
||
<a href="#" class="px-4 py-2 text-sm font-medium hover:text-white transition rounded-md">AI 问答</a>
|
||
<a href="#" class="px-4 py-2 text-sm font-medium text-white bg-slate-800 rounded-md shadow-inner flex items-center">
|
||
<i data-lucide="book-open" class="w-4 h-4 mr-2 text-blue-400"></i> AI 文献
|
||
</a>
|
||
<a href="#" class="px-4 py-2 text-sm font-medium hover:text-white transition rounded-md">数据清洗</a>
|
||
<a href="#" class="px-4 py-2 text-sm font-medium hover:text-white transition rounded-md">统计分析</a>
|
||
</nav>
|
||
</div>
|
||
<div class="flex items-center space-x-4">
|
||
<button class="text-slate-400 hover:text-white"><i data-lucide="bell" class="w-5 h-5"></i></button>
|
||
<div class="w-8 h-8 bg-blue-600 rounded-full flex items-center justify-center text-white font-bold text-sm">Dr</div>
|
||
</div>
|
||
</header>
|
||
|
||
<div class="flex-1 flex overflow-hidden">
|
||
|
||
<!-- ============================================== -->
|
||
<!-- 核心重构 V6: 互斥手风琴侧边栏 (Mutually Exclusive Accordion) -->
|
||
<!-- ============================================== -->
|
||
<div class="w-72 bg-white border-r border-slate-200 flex flex-col z-20 shrink-0 shadow-sm relative">
|
||
|
||
<!-- 【面板 A】:智能文献检索 -->
|
||
<div id="panel-search-wrapper" class="flex flex-col transition-all duration-400">
|
||
<!-- Header A -->
|
||
<button onclick="togglePanel('SEARCH')" class="flex items-center justify-between px-5 py-4 hover:bg-slate-50 transition-colors w-full group border-b border-transparent" id="header-search">
|
||
<div class="flex items-center text-slate-800 font-bold text-[14px] transition-colors" id="title-search">
|
||
<i data-lucide="sparkles" class="w-4 h-4 mr-2.5 text-indigo-500" id="icon-search"></i>
|
||
智能文献检索
|
||
</div>
|
||
<i data-lucide="chevron-down" id="chevron-search" class="w-4 h-4 text-slate-400 transition-transform duration-400"></i>
|
||
</button>
|
||
|
||
<!-- Content A (利用 Grid 实现流畅折叠) -->
|
||
<div id="grid-search" class="grid accordion-grid grid-rows-[1fr]">
|
||
<div class="accordion-content flex flex-col min-h-0 bg-white">
|
||
<div class="p-4 pb-2 pt-0 shrink-0">
|
||
<button onclick="createNewSearch()" class="w-full bg-indigo-600 text-white hover:bg-indigo-700 py-2.5 rounded-lg flex items-center justify-center text-sm font-semibold transition-colors shadow-sm shadow-indigo-200">
|
||
<i data-lucide="plus" class="w-4 h-4 mr-1.5"></i> 新建智能检索
|
||
</button>
|
||
</div>
|
||
<div class="px-4 py-2 flex items-center justify-between text-xs font-bold text-slate-400 uppercase tracking-wider shrink-0">
|
||
<span>最近检索历史</span>
|
||
<i data-lucide="history" class="w-3.5 h-3.5"></i>
|
||
</div>
|
||
<!-- List A -->
|
||
<div class="flex-1 overflow-y-auto px-3 pb-3 space-y-1 no-scrollbar" id="search-list-container">
|
||
<!-- 由 JS 渲染 -->
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 始终存在的分割线 -->
|
||
<div class="h-px bg-slate-200 shrink-0 z-10 shadow-sm"></div>
|
||
|
||
<!-- 【面板 B】:系统综述项目 -->
|
||
<div id="panel-project-wrapper" class="flex flex-col transition-all duration-400">
|
||
<!-- Header B -->
|
||
<button onclick="togglePanel('PROJECT')" class="flex items-center justify-between px-5 py-4 hover:bg-slate-50 transition-colors w-full group bg-slate-50/50" id="header-project">
|
||
<div class="flex items-center text-slate-700 font-bold text-[14px] transition-colors" id="title-project">
|
||
<i data-lucide="folder-kanban" class="w-4 h-4 mr-2.5 text-slate-500" id="icon-project"></i>
|
||
系统综述项目 (SR)
|
||
</div>
|
||
<i data-lucide="chevron-up" id="chevron-project" class="w-4 h-4 text-slate-400 transition-transform duration-400"></i>
|
||
</button>
|
||
|
||
<!-- Content B -->
|
||
<div id="grid-project" class="grid accordion-grid grid-rows-[0fr]">
|
||
<div class="accordion-content flex flex-col min-h-0 bg-slate-50">
|
||
<div class="px-4 py-2 flex items-center justify-between text-xs font-bold text-slate-400 uppercase tracking-wider shrink-0 mt-1">
|
||
<span>我的工作区</span>
|
||
<i data-lucide="layout-grid" class="w-3.5 h-3.5"></i>
|
||
</div>
|
||
<!-- List B -->
|
||
<div class="flex-1 overflow-y-auto px-3 pb-2 space-y-1 no-scrollbar" id="project-list-container">
|
||
<!-- 由 JS 渲染项目列表 -->
|
||
</div>
|
||
<!-- 弱化设计的新建项目按钮 -->
|
||
<div class="px-4 pb-4 shrink-0">
|
||
<button onclick="createNewProject()" class="w-full border border-dashed border-slate-300 text-slate-500 hover:text-blue-600 hover:border-blue-300 hover:bg-blue-50 py-2 rounded-lg flex items-center justify-center text-[13px] font-medium transition-colors bg-white">
|
||
<i data-lucide="plus" class="w-3.5 h-3.5 mr-1.5"></i> 创建新 SR 项目
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 侧边栏底部弹簧垫片,把剩余空间挤上去 -->
|
||
<div class="flex-1 bg-slate-50"></div>
|
||
|
||
<!-- 底部配额信息 (固定在最底) -->
|
||
<div class="p-4 border-t border-slate-200 bg-white shrink-0">
|
||
<div class="text-[10px] text-slate-500 mb-1.5 font-medium flex justify-between uppercase tracking-wider">
|
||
<span>DeepSearch 配额</span>
|
||
<span>45k/100k</span>
|
||
</div>
|
||
<div class="w-full bg-slate-100 rounded-full h-1.5 overflow-hidden">
|
||
<div class="bg-indigo-400 h-full rounded-full" style="width: 45%"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 主内容区 -->
|
||
<div class="flex-1 flex flex-col overflow-hidden bg-slate-50 relative" id="main-content-container">
|
||
<!-- 由 JS 渲染核心界面 -->
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
// ==========================================
|
||
// 1. 数据模型与状态 (Data Models & State)
|
||
// ==========================================
|
||
|
||
let searchHistories = [
|
||
{ id: 1, title: 'SGLT2 抑制剂对糖尿病的心血管影响', date: '今天', hasResult: true },
|
||
{ id: 2, title: 'PD-1 联合化疗在非小细胞肺癌中的疗效', date: '昨天', hasResult: true },
|
||
{ id: 3, title: 'Aspirin 一级预防最新 RCT', date: '前天', hasResult: true },
|
||
{ id: 4, title: '阿兹海默症的最新靶向治疗方案综述', date: '本周', hasResult: true },
|
||
{ id: 5, title: '肠道微生态与抑郁症发病机制的相关性研究', date: '本周', hasResult: true },
|
||
{ id: 6, title: 'COVID-19 长新冠症状的系统性回顾', date: '更早', hasResult: true },
|
||
{ id: 7, title: '间歇性禁食对代谢综合征的影响', date: '更早', hasResult: true },
|
||
{ id: 8, title: 'CAR-T 细胞疗法在实体瘤中的进展', date: '更早', hasResult: true }
|
||
];
|
||
|
||
let srProjects = [
|
||
{ id: 1, title: 'SGLT2 抑制剂 Meta 分析', phase: 4, status: '进行中', count: 42, tiabConflictsResolved: true, metaRun: false },
|
||
{ id: 2, title: '二甲双胍对认知功能的系统综述', phase: 2, status: '初筛中', count: 856, tiabConflictsResolved: false, metaRun: false },
|
||
{ id: 3, title: 'CAR-T 治疗严重不良事件汇总', phase: 6, status: '已闭环', count: 18, tiabConflictsResolved: true, metaRun: true }
|
||
];
|
||
|
||
const PHASES = [
|
||
{ id: 1, name: '检索', icon: 'search' },
|
||
{ id: 2, name: '初筛', icon: 'file-text' },
|
||
{ id: 3, name: '复筛', icon: 'file-text' },
|
||
{ id: 4, name: '提取/RoB', icon: 'database' },
|
||
{ id: 5, name: 'Meta', icon: 'activity' },
|
||
{ id: 6, name: '报告', icon: 'file-output' }
|
||
];
|
||
|
||
// 核心全局状态 V6.0
|
||
const state = {
|
||
expandedPanel: 'SEARCH', // 核心变量:控制当前哪个抽屉打开 ('SEARCH' 或 'PROJECT')
|
||
activeContext: 'SEARCH', // 核心变量:控制右侧主屏显示什么内容
|
||
currentSearchId: 1,
|
||
currentProjectId: null,
|
||
};
|
||
|
||
// ==========================================
|
||
// 2. 状态变更函数 (Actions)
|
||
// ==========================================
|
||
|
||
// ⭐ 手风琴核心逻辑
|
||
function togglePanel(panelName) {
|
||
if (state.expandedPanel !== panelName) {
|
||
state.expandedPanel = panelName;
|
||
|
||
// 联动:当用户点开某个抽屉时,如果右侧内容不属于该抽屉,自动切换右侧内容
|
||
if (panelName === 'SEARCH' && state.activeContext !== 'SEARCH') {
|
||
state.activeContext = 'SEARCH';
|
||
if (!state.currentSearchId && searchHistories.length > 0) state.currentSearchId = searchHistories[0].id;
|
||
} else if (panelName === 'PROJECT' && state.activeContext !== 'PROJECT') {
|
||
state.activeContext = 'PROJECT';
|
||
if (!state.currentProjectId && srProjects.length > 0) state.currentProjectId = srProjects[0].id;
|
||
}
|
||
|
||
renderApp();
|
||
}
|
||
}
|
||
|
||
function selectSearch(id) {
|
||
state.activeContext = 'SEARCH';
|
||
state.currentSearchId = id;
|
||
if (state.expandedPanel !== 'SEARCH') state.expandedPanel = 'SEARCH';
|
||
renderApp();
|
||
}
|
||
|
||
function selectProject(id) {
|
||
state.activeContext = 'PROJECT';
|
||
state.currentProjectId = id;
|
||
if (state.expandedPanel !== 'PROJECT') state.expandedPanel = 'PROJECT';
|
||
renderApp();
|
||
}
|
||
|
||
function createNewSearch() {
|
||
state.activeContext = 'SEARCH';
|
||
state.currentSearchId = 'new';
|
||
renderApp();
|
||
}
|
||
|
||
function createNewProject() {
|
||
state.activeContext = 'PROJECT';
|
||
state.currentProjectId = 'new';
|
||
renderApp();
|
||
}
|
||
|
||
// ⭐ 转化逻辑:从左上角直接飞入左下角
|
||
function convertToProject() {
|
||
const sourceSearch = searchHistories.find(s => s.id === state.currentSearchId);
|
||
const newProject = {
|
||
id: Date.now(),
|
||
title: `${sourceSearch ? sourceSearch.title : '未命名'} (AI 转化)`,
|
||
phase: 2,
|
||
status: '初筛中',
|
||
count: 1250,
|
||
tiabConflictsResolved: false,
|
||
metaRun: false
|
||
};
|
||
srProjects.unshift(newProject);
|
||
|
||
// 切换上下文并强制展开下方抽屉,收起上方抽屉
|
||
state.activeContext = 'PROJECT';
|
||
state.currentProjectId = newProject.id;
|
||
state.expandedPanel = 'PROJECT';
|
||
renderApp();
|
||
}
|
||
|
||
// 项目流程控制函数 (同前)
|
||
function resolveProjectConflicts(projectId) {
|
||
const p = srProjects.find(p => p.id === projectId);
|
||
if(p) p.tiabConflictsResolved = true;
|
||
renderApp();
|
||
}
|
||
function advanceProjectPhase(projectId, targetPhase) {
|
||
const p = srProjects.find(p => p.id === projectId);
|
||
if(p) { p.phase = targetPhase; p.status = targetPhase === 6 ? '已闭环' : '进行中'; }
|
||
renderApp();
|
||
}
|
||
function runProjectMeta(projectId) {
|
||
const p = srProjects.find(p => p.id === projectId);
|
||
if(p) p.metaRun = true;
|
||
renderApp();
|
||
}
|
||
|
||
// ==========================================
|
||
// 3. 视图渲染逻辑 (Views)
|
||
// ==========================================
|
||
|
||
function renderSidebar() {
|
||
const isSearchExpanded = state.expandedPanel === 'SEARCH';
|
||
|
||
// 1. 处理 CSS Grid 动画折叠
|
||
const gridSearch = document.getElementById('grid-search');
|
||
const gridProject = document.getElementById('grid-project');
|
||
const panelSearchWrap = document.getElementById('panel-search-wrapper');
|
||
const panelProjectWrap = document.getElementById('panel-project-wrapper');
|
||
|
||
if (isSearchExpanded) {
|
||
// 展开上方,收起下方
|
||
gridSearch.classList.replace('grid-rows-[0fr]', 'grid-rows-[1fr]');
|
||
gridProject.classList.replace('grid-rows-[1fr]', 'grid-rows-[0fr]');
|
||
panelSearchWrap.classList.replace('shrink-0', 'flex-1');
|
||
panelSearchWrap.classList.replace('min-h-0', 'min-h-0'); // keep
|
||
panelProjectWrap.classList.replace('flex-1', 'shrink-0');
|
||
} else {
|
||
// 收起上方,展开下方
|
||
gridSearch.classList.replace('grid-rows-[1fr]', 'grid-rows-[0fr]');
|
||
gridProject.classList.replace('grid-rows-[0fr]', 'grid-rows-[1fr]');
|
||
panelSearchWrap.classList.replace('flex-1', 'shrink-0');
|
||
panelProjectWrap.classList.replace('shrink-0', 'flex-1');
|
||
panelProjectWrap.classList.replace('min-h-0', 'min-h-0'); // keep
|
||
}
|
||
|
||
// 2. 处理 Header 的图标旋转与高亮
|
||
const chevSearch = document.getElementById('chevron-search');
|
||
const chevProject = document.getElementById('chevron-project');
|
||
const titleSearch = document.getElementById('title-search');
|
||
const titleProject = document.getElementById('title-project');
|
||
const iconSearch = document.getElementById('icon-search');
|
||
const iconProject = document.getElementById('icon-project');
|
||
|
||
if (isSearchExpanded) {
|
||
chevSearch.classList.add('rotate-180');
|
||
chevProject.classList.remove('rotate-180');
|
||
titleSearch.classList.add('text-indigo-700'); titleSearch.classList.remove('text-slate-800');
|
||
iconSearch.classList.replace('text-slate-400', 'text-indigo-500');
|
||
titleProject.classList.replace('text-blue-700', 'text-slate-700');
|
||
iconProject.classList.replace('text-blue-500', 'text-slate-500');
|
||
} else {
|
||
chevSearch.classList.remove('rotate-180');
|
||
chevProject.classList.add('rotate-180');
|
||
titleSearch.classList.remove('text-indigo-700'); titleSearch.classList.add('text-slate-800');
|
||
iconSearch.classList.replace('text-indigo-500', 'text-slate-400');
|
||
titleProject.classList.replace('text-slate-700', 'text-blue-700');
|
||
iconProject.classList.replace('text-slate-500', 'text-blue-500');
|
||
}
|
||
|
||
// 3. 渲染列表内部项目
|
||
const searchContainer = document.getElementById('search-list-container');
|
||
searchContainer.innerHTML = searchHistories.map(h => {
|
||
const isActive = (state.activeContext === 'SEARCH' && state.currentSearchId === h.id);
|
||
return `
|
||
<div onclick="selectSearch(${h.id})" class="cursor-pointer group flex items-start p-2.5 rounded-lg transition-colors ${isActive ? 'bg-indigo-50 border border-indigo-100 shadow-sm' : 'hover:bg-slate-100 border border-transparent'}">
|
||
<i data-lucide="message-square" class="w-4 h-4 mt-0.5 mr-2.5 shrink-0 ${isActive ? 'text-indigo-600' : 'text-slate-400 group-hover:text-slate-600'}"></i>
|
||
<div class="flex-1 overflow-hidden">
|
||
<h4 class="text-sm font-medium truncate ${isActive ? 'text-indigo-900' : 'text-slate-700'}">${h.title}</h4>
|
||
<span class="text-[10px] text-slate-400 mt-0.5 block">${h.date}</span>
|
||
</div>
|
||
</div>
|
||
`;
|
||
}).join('');
|
||
|
||
const projectContainer = document.getElementById('project-list-container');
|
||
projectContainer.innerHTML = srProjects.map(p => {
|
||
const isActive = (state.activeContext === 'PROJECT' && state.currentProjectId === p.id);
|
||
const statusColor = p.status === '已闭环' ? 'emerald' : p.status === '初筛中' ? 'amber' : 'blue';
|
||
return `
|
||
<div onclick="selectProject(${p.id})" class="cursor-pointer group flex items-start p-2.5 mb-1.5 rounded-lg transition-colors ${isActive ? 'bg-white border border-blue-200 shadow-sm ring-1 ring-blue-50' : 'hover:bg-white border border-transparent'}">
|
||
<i data-lucide="folder" class="w-4 h-4 mt-0.5 mr-2.5 shrink-0 ${isActive ? 'text-blue-600' : 'text-slate-400 group-hover:text-blue-500'}"></i>
|
||
<div class="flex-1 overflow-hidden">
|
||
<h4 class="text-[13px] font-bold truncate ${isActive ? 'text-blue-900' : 'text-slate-600'}">${p.title}</h4>
|
||
<div class="flex items-center mt-1.5 space-x-2">
|
||
<span class="text-[10px] font-bold text-${statusColor}-700 bg-${statusColor}-100 px-1.5 rounded">${p.status}</span>
|
||
<span class="text-[10px] font-mono text-slate-500">P${p.phase}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
}).join('');
|
||
}
|
||
|
||
// 主内容区渲染 (与 V5 逻辑一致)
|
||
function renderMainContent() {
|
||
const container = document.getElementById('main-content-container');
|
||
|
||
if (state.activeContext === 'SEARCH') {
|
||
if (state.currentSearchId === 'new') {
|
||
container.innerHTML = `
|
||
<div class="flex-1 flex flex-col items-center justify-center p-8 fade-in h-full bg-white">
|
||
<div class="w-16 h-16 bg-indigo-50 border border-indigo-100 rounded-2xl flex items-center justify-center mb-6 shadow-sm">
|
||
<i data-lucide="sparkles" class="w-8 h-8 text-indigo-500"></i>
|
||
</div>
|
||
<h2 class="text-2xl font-bold text-slate-800 mb-2">探索医学前沿</h2>
|
||
<p class="text-slate-500 mb-8 max-w-md text-center text-sm">输入您的研究问题或 PICOS 关键词,DeepSearch 将为您生成高质量综述报告与文献库。</p>
|
||
|
||
<div class="w-full max-w-2xl bg-white p-2 rounded-xl border border-slate-300 shadow-sm flex items-center focus-within:ring-2 ring-indigo-500/50 transition-all focus-within:border-indigo-500">
|
||
<input type="text" class="flex-1 outline-none px-4 py-3 text-slate-700 placeholder-slate-400 bg-transparent" placeholder="例如:SGLT2 抑制剂对 2 型糖尿病的心血管结局影响...">
|
||
<button class="bg-indigo-600 hover:bg-indigo-700 text-white p-3 rounded-lg shadow-sm transition">
|
||
<i data-lucide="arrow-up" class="w-5 h-5"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
`;
|
||
} else {
|
||
const searchInfo = searchHistories.find(s => s.id === state.currentSearchId) || searchHistories[0];
|
||
container.innerHTML = `
|
||
<div class="flex flex-col h-full fade-in bg-white">
|
||
<header class="h-14 bg-white border-b border-slate-100 flex items-center px-6 shrink-0">
|
||
<h1 class="text-sm font-bold text-slate-700 truncate">${searchInfo.title}</h1>
|
||
<span class="ml-3 text-[10px] font-medium text-slate-500 bg-slate-100 px-2 py-1 rounded-md border border-slate-200 flex items-center"><i data-lucide="layers" class="w-3 h-3 mr-1"></i>1250 篇</span>
|
||
</header>
|
||
|
||
<div class="flex-1 overflow-y-auto p-6 md:p-8 space-y-8 pb-32">
|
||
<div class="max-w-4xl mx-auto flex space-x-4">
|
||
<div class="w-8 h-8 rounded-full bg-slate-200 flex items-center justify-center shrink-0 mt-1"><span class="text-slate-600 text-xs font-bold">Dr</span></div>
|
||
<div class="flex-1"><div class="inline-block bg-slate-100 px-5 py-3.5 rounded-2xl rounded-tl-none text-slate-800 text-sm shadow-sm">帮我查一下相关文献,要求是近5年的英文随机对照试验(RCT)。</div></div>
|
||
</div>
|
||
<div class="max-w-4xl mx-auto flex space-x-4">
|
||
<div class="w-8 h-8 rounded-full bg-indigo-600 flex items-center justify-center shrink-0 mt-1 shadow-md shadow-indigo-200"><i data-lucide="bot" class="w-4 h-4 text-white"></i></div>
|
||
<div class="flex-1">
|
||
<div class="bg-white border border-slate-200 rounded-2xl p-6 shadow-sm">
|
||
<h4 class="font-bold text-slate-800 flex items-center mb-3">
|
||
<i data-lucide="check-circle-2" class="w-5 h-5 text-emerald-500 mr-2"></i> 智能检索完成:获取有效文献 1,250 篇
|
||
</h4>
|
||
<p class="text-sm text-slate-600 leading-relaxed mb-5">
|
||
基于您的查询,我从 PubMed 和 Cochrane Library 汇总了最新高质量研究。主要发现包括:目标干预不仅能有效改善主要结局指标,还在多个大型试验中显著降低了不良事件风险。
|
||
</p>
|
||
<div class="mt-4 p-5 bg-gradient-to-r from-blue-50 to-indigo-50 border border-blue-100 rounded-xl flex items-center justify-between">
|
||
<div>
|
||
<h5 class="font-bold text-blue-900 text-sm flex items-center"><i data-lucide="workflow" class="w-4 h-4 mr-1.5 text-blue-600"></i> 需要进行严格的系统综述吗?</h5>
|
||
<p class="text-xs text-blue-700 mt-1.5 opacity-80">将这 1,250 篇文献转入流水线,即可启动自动化双盲初筛。</p>
|
||
</div>
|
||
<button onclick="convertToProject()" class="bg-blue-600 hover:bg-blue-700 text-white text-sm font-medium py-2 px-5 rounded-lg shadow-sm shadow-blue-200 transition flex items-center shrink-0">
|
||
转入 SR 项目流水线 <i data-lucide="arrow-right" class="w-4 h-4 ml-1.5"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="absolute bottom-0 left-0 right-0 bg-gradient-to-t from-white via-white to-transparent p-6 pb-8">
|
||
<div class="max-w-4xl mx-auto bg-white p-1.5 rounded-xl border border-slate-300 shadow-lg shadow-slate-200/50 flex items-center">
|
||
<input type="text" class="flex-1 outline-none px-4 py-2 text-sm text-slate-700 bg-transparent" placeholder="追问检索结果,或修改条件...">
|
||
<button class="bg-indigo-600 text-white p-2.5 rounded-lg shadow-sm"><i data-lucide="arrow-up" class="w-4 h-4"></i></button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
}
|
||
} else if (state.activeContext === 'PROJECT') {
|
||
if (state.currentProjectId === 'new') {
|
||
container.innerHTML = `
|
||
<div class="flex-1 flex flex-col items-center justify-center p-8 fade-in h-full bg-slate-50">
|
||
<div class="w-16 h-16 bg-blue-100 rounded-2xl flex items-center justify-center mb-6 shadow-sm border border-blue-200">
|
||
<i data-lucide="folder-plus" class="w-8 h-8 text-blue-600"></i>
|
||
</div>
|
||
<h2 class="text-2xl font-bold text-slate-800 mb-2">创建系统综述项目 (SR)</h2>
|
||
<p class="text-slate-500 mb-8 max-w-md text-center text-sm">建立严谨的瀑布流科研管线。支持通过 DeepSearch 抓取或本地导入文献库。</p>
|
||
|
||
<div class="w-full max-w-lg bg-white p-7 rounded-2xl border border-slate-200 shadow-sm space-y-5">
|
||
<div>
|
||
<label class="block text-sm font-bold text-slate-700 mb-2">项目名称</label>
|
||
<input type="text" class="w-full border border-slate-300 rounded-lg px-4 py-2.5 text-sm focus:ring-2 focus:ring-blue-500/50 focus:border-blue-500 outline-none transition-all bg-slate-50 hover:bg-white" placeholder="例如:某某药物的疗效与安全性 Meta 分析">
|
||
</div>
|
||
<div class="pt-4 flex justify-end space-x-3 border-t border-slate-100">
|
||
<button class="px-5 py-2.5 text-sm font-medium text-slate-600 hover:bg-slate-100 rounded-lg transition-colors">取消</button>
|
||
<button class="px-5 py-2.5 text-sm font-bold text-white bg-blue-600 hover:bg-blue-700 rounded-lg shadow-sm shadow-blue-200 transition-colors">创建并配置方案</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
} else {
|
||
const project = srProjects.find(p => p.id === state.currentProjectId) || srProjects[0];
|
||
container.innerHTML = `
|
||
<div class="flex flex-col h-full fade-in bg-slate-50/50">
|
||
<header class="h-14 bg-white border-b border-slate-200 flex items-center justify-between px-6 shadow-sm shrink-0">
|
||
<div class="flex items-center">
|
||
<div class="bg-blue-100 p-1 rounded mr-3"><i data-lucide="folder-kanban" class="w-4 h-4 text-blue-600"></i></div>
|
||
<h1 class="text-sm font-bold text-slate-800">${project.title}</h1>
|
||
</div>
|
||
<span class="text-[11px] font-bold text-slate-500 bg-slate-100 px-2.5 py-1 rounded-md border border-slate-200 tracking-wide uppercase">Workspace</span>
|
||
</header>
|
||
|
||
<!-- 瀑布流步进器 Stepper -->
|
||
<div class="bg-white border-b border-slate-200 px-8 py-3 shadow-sm shrink-0 overflow-x-auto">
|
||
<div class="flex items-center justify-between min-w-max">
|
||
${PHASES.map((phase, index) => {
|
||
const isActive = project.phase === phase.id;
|
||
const isPast = project.phase > phase.id;
|
||
const circleClasses = isActive ? 'border-blue-600 bg-blue-50 text-blue-600 shadow-sm ring-2 ring-blue-100'
|
||
: isPast ? 'border-emerald-500 bg-emerald-500 text-white'
|
||
: 'border-slate-300 bg-slate-50 text-slate-400';
|
||
const textClasses = isActive ? 'text-blue-800 font-bold' : isPast ? 'text-slate-700' : 'text-slate-400 font-medium';
|
||
const lineClasses = isPast ? 'bg-emerald-500' : 'bg-slate-200';
|
||
|
||
return `
|
||
<div class="flex items-center cursor-pointer group" onclick="advanceProjectPhase(${project.id}, ${phase.id})">
|
||
<div class="flex items-center justify-center w-7 h-7 rounded-full border-2 transition-all ${circleClasses}">
|
||
${isPast ? `<i data-lucide="check" class="w-3.5 h-3.5"></i>` : `<i data-lucide="${phase.icon}" class="w-3 h-3"></i>`}
|
||
</div>
|
||
<span class="ml-2.5 text-[13px] transition-colors ${textClasses} group-hover:text-blue-600">${phase.name}</span>
|
||
${index < PHASES.length - 1 ? `<div class="w-8 h-[2px] mx-4 transition-colors ${lineClasses}"></div>` : ''}
|
||
</div>
|
||
`;
|
||
}).join('')}
|
||
</div>
|
||
</div>
|
||
|
||
<main class="flex-1 overflow-y-auto p-8" id="sr-step-content">
|
||
${renderProjectPhaseContent(project)}
|
||
</main>
|
||
</div>
|
||
`;
|
||
}
|
||
}
|
||
}
|
||
|
||
function renderProjectPhaseContent(project) {
|
||
const SectionHeader = (title, desc) => `
|
||
<div class="mb-6">
|
||
<h2 class="text-xl font-bold text-slate-800">${title}</h2>
|
||
<p class="text-sm text-slate-500 mt-1.5">${desc}</p>
|
||
</div>
|
||
`;
|
||
|
||
switch(project.phase) {
|
||
case 1: return `<div class="max-w-4xl mx-auto fade-in">${SectionHeader("Step 1: 选题与检索数据源", "准备阶段...")}<div class="bg-white p-10 rounded-2xl border border-slate-200 text-center shadow-sm"><button onclick="advanceProjectPhase(${project.id}, 2)" class="bg-blue-600 text-white px-5 py-2.5 rounded-lg text-sm font-medium hover:bg-blue-700 shadow-sm">模拟导入完成,进入初筛</button></div></div>`;
|
||
case 2:
|
||
let conflictUI = !project.tiabConflictsResolved
|
||
? `<div class="bg-amber-50 border border-amber-200 rounded-xl p-6 mb-6"><h4 class="font-bold text-amber-800 text-sm flex items-center mb-2"><i data-lucide="alert-triangle" class="w-4 h-4 mr-2"></i> 必须解决冲突</h4><div class="bg-white rounded-lg border border-amber-100 p-4 flex justify-between items-center shadow-sm"><span class="text-sm font-medium text-slate-700">冲突记录: Example Paper 2024</span><button onclick="resolveProjectConflicts(${project.id})" class="text-xs font-bold bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700 shadow-sm">一键解决</button></div></div>`
|
||
: `<div class="bg-emerald-50 border border-emerald-200 rounded-xl p-6 mb-6 flex items-center"><i data-lucide="check-circle" class="w-6 h-6 text-emerald-500 mr-3"></i><div><h4 class="font-bold text-emerald-800 text-sm">初筛已完成</h4></div></div>`;
|
||
let nxtBtn = !project.tiabConflictsResolved
|
||
? `<button disabled class="px-6 py-2.5 rounded-lg bg-slate-200 text-slate-400 text-sm font-medium cursor-not-allowed">进入复筛</button>`
|
||
: `<button onclick="advanceProjectPhase(${project.id}, 3)" class="bg-blue-600 text-white px-6 py-2.5 rounded-lg text-sm font-bold shadow-sm hover:bg-blue-700">进入复筛</button>`;
|
||
return `<div class="max-w-4xl mx-auto fade-in">${SectionHeader("Step 2: 标题摘要初筛", "双盲初筛流水线。")}${conflictUI}<div class="flex justify-end">${nxtBtn}</div></div>`;
|
||
case 3:
|
||
case 4:
|
||
case 5:
|
||
let btnText = project.phase === 3 ? '进入数据提取' : project.phase === 4 ? '进入Meta分析' : '生成最终报告';
|
||
return `<div class="max-w-4xl mx-auto fade-in">${SectionHeader(`Step ${project.phase}: ${PHASES[project.phase-1].name}`, "专业操作区。")}<div class="bg-white p-16 text-center rounded-2xl border border-slate-200 mb-6 shadow-sm"><i data-lucide="${PHASES[project.phase-1].icon}" class="w-12 h-12 text-slate-300 mx-auto mb-4"></i><p class="text-slate-500 text-sm font-medium">这里是 ${PHASES[project.phase-1].name} 的详细工作台区域。</p></div><div class="flex justify-end"><button onclick="advanceProjectPhase(${project.id}, ${project.phase + 1})" class="bg-slate-800 text-white px-6 py-2.5 rounded-lg text-sm font-bold flex items-center shadow-sm hover:bg-slate-700">${btnText} <i data-lucide="arrow-right" class="w-4 h-4 ml-2"></i></button></div></div>`;
|
||
case 6:
|
||
return `<div class="max-w-4xl mx-auto fade-in">${SectionHeader("Step 6: 报告生成", "项目成果。")}<div class="bg-white border border-slate-200 rounded-2xl p-12 flex flex-col items-center text-center shadow-sm"><div class="bg-emerald-50 p-4 rounded-full border border-emerald-100 mb-4"><i data-lucide="check-circle" class="w-12 h-12 text-emerald-500"></i></div><h2 class="text-2xl font-black text-slate-800">流程闭环完成</h2><button class="mt-8 bg-blue-600 text-white px-6 py-3 rounded-xl shadow-md text-sm font-bold flex items-center hover:bg-blue-700 transition"><i data-lucide="download" class="w-4 h-4 mr-2"></i> 下载 PRISMA & Word 报告</button></div></div>`;
|
||
}
|
||
}
|
||
|
||
// ==========================================
|
||
// 4. 初始化引擎
|
||
// ==========================================
|
||
function renderApp() {
|
||
renderSidebar();
|
||
renderMainContent();
|
||
lucide.createIcons();
|
||
}
|
||
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
renderApp();
|
||
});
|
||
</script>
|
||
</body>
|
||
</html> |