feat(rvw,asl): RVW V3.0 smart review + ASL deep research history + stability
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
This commit is contained in:
541
docs/03-业务模块/ASL-AI智能文献/03-UI设计/v6.html
Normal file
541
docs/03-业务模块/ASL-AI智能文献/03-UI设计/v6.html
Normal file
@@ -0,0 +1,541 @@
|
||||
<!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>
|
||||
@@ -1,30 +1,25 @@
|
||||
# RVW稿件审查模块 - 当前状态与开发指南
|
||||
|
||||
> **文档版本:** v5.1
|
||||
> **文档版本:** v6.0
|
||||
> **创建日期:** 2026-01-07
|
||||
> **最后更新:** 2026-02-18
|
||||
> **最后更新:** 2026-03-07
|
||||
> **维护者:** 开发团队
|
||||
> **当前状态:** 🚀 **V2.0 "数据侦探" Week 3 完成(统计验证扩展+用户体验优化)**
|
||||
> **当前状态:** 🚀 **V3.0 "智能审稿增强" 完成(LLM数据核查 + 临床评估 + 稳定性增强)**
|
||||
> **文档目的:** 快速了解RVW模块状态,为新AI助手提供上下文
|
||||
>
|
||||
> **🎉 V2.0 进展(2026-02-18 Week 3):**
|
||||
> - ✅ **负号归一化**:防止 float() 崩溃,覆盖 6 种负号变体
|
||||
> - ✅ **T 检验验证增强**:智能样本量提取 + subrow 精确高亮
|
||||
> - ✅ **SE 三角验证增强**:多行单元格 subrow 支持
|
||||
> - ✅ **CI vs P 值验证增强**:subrow 支持 + 灵活 P 值解析
|
||||
> - ✅ **前端翻译映射**:新增 6 种 IssueType 中文翻译
|
||||
> - ✅ **文件格式提示**:PDF/.doc 上传时提示无法数据验证
|
||||
> **🎉 V3.0 进展(2026-03-07):**
|
||||
> - ✅ **LLM 数据核查**:DataForensicsSkill 增加 LLM 验证通道,规则验证兜底,独立 60s 超时
|
||||
> - ✅ **临床专业评估**:新增 ClinicalAssessmentSkill,基于 FINER 标准的研究选题系统评估
|
||||
> - ✅ **PromptService 集成**:RVW_DATA_VALIDATION + RVW_CLINICAL 两个 Prompt 可在运营管理端配置
|
||||
> - ✅ **稳定性增强**:SkillExecutor 使用 Promise.allSettled 实现并行故障隔离
|
||||
> - ✅ **部分完成支持**:新增 `partial_completed` 状态 + `errorDetails` 字段,部分模块失败仍展示成功结果
|
||||
> - ✅ **前端 4 Tab 报告**:稿约规范性 / 方法学 / 数据验证 / 临床评估,Word 导出全覆盖
|
||||
>
|
||||
> **🎉 V2.0 进展(Week 1-2):**
|
||||
> - ✅ **L1 算术验证器**:行列加总、百分比验证(Day 3)
|
||||
> - ✅ **L2 统计验证器**:CI↔P 值一致性、卡方检验逆向验证(Day 6)
|
||||
> - ✅ **L2.5 一致性取证**:SE三角验证、SD>Mean检查(Day 6 终审提权)
|
||||
> - ✅ **Word 文档解析**:python-docx 表格提取 + 特殊符号提取(Day 2)
|
||||
> - ✅ **Skills 核心框架**:types、registry、executor、profile、context(Day 7)
|
||||
> - ✅ **DataForensicsSkill**:OSS 集成、依赖注入、优雅降级(Day 8)
|
||||
> - ✅ **EditorialSkill + MethodologySkill**:封装现有服务(Day 9)
|
||||
> - ✅ **ReviewWorker 改造**:集成 SkillExecutor,支持 V1/V2 切换(Day 10)
|
||||
> - ✅ **前端数据验证 Tab**:ForensicsReport 组件、精确单元格高亮(Week 3)
|
||||
> **V2.0 进展回顾:**
|
||||
> - ✅ L1 算术验证 + L2 统计验证 + L2.5 一致性取证
|
||||
> - ✅ Skills 核心框架(types, registry, executor, profile)
|
||||
> - ✅ DataForensicsSkill + EditorialSkill + MethodologySkill
|
||||
> - ✅ ReviewWorker 改造 + 前端数据验证 Tab
|
||||
|
||||
---
|
||||
|
||||
@@ -39,13 +34,13 @@
|
||||
| **商业价值** | ⭐⭐⭐⭐⭐ 极高 |
|
||||
| **独立性** | ⭐⭐⭐⭐⭐ 极高(用户群完全不同) |
|
||||
| **目标用户** | 期刊初审编辑 |
|
||||
| **开发状态** | ✅ **核心功能100%完成,支持Word导出,已集成到 frontend-v2** |
|
||||
| **开发状态** | ✅ **V3.0 完成:4维审查(规范性+方法学+数据验证+临床评估)+ 稳定性增强 + Word导出** |
|
||||
|
||||
### 核心目标
|
||||
|
||||
> 打造一个**"开箱即用"**的智能审稿工具。编辑上传稿件,系统自动运行双重检查(规范性+方法学),输出可供参考的审稿报告。
|
||||
> 打造一个**"开箱即用"**的智能审稿工具。编辑上传稿件,系统自动运行多维检查(规范性+方法学+数据验证+临床评估),输出可供参考的审稿报告。
|
||||
>
|
||||
> **核心指标**:上传到出报告 < 2分钟;规范性问题检出率 > 80%
|
||||
> **核心指标**:上传到出报告 < 3分钟(4模块并行);规范性问题检出率 > 80%
|
||||
|
||||
### 功能规格
|
||||
|
||||
@@ -60,7 +55,19 @@
|
||||
- 统计学方法描述评估(5个检查点)
|
||||
- 统计分析评估(6个检查点)
|
||||
|
||||
3. **综合评分 + PICO提取**
|
||||
3. **数据验证**(V2.0 规则 + V3.0 LLM 双通道)
|
||||
- L1 算术验证:行列加总、百分比计算
|
||||
- L2 统计验证:CI↔P 一致性、T检验、卡方检验
|
||||
- L2.5 一致性取证:SE三角验证、SD>Mean检查
|
||||
- LLM 智能核查:批量表格发给 LLM 核查(独立 60s 超时,失败降级为纯规则验证)
|
||||
|
||||
4. **临床专业评估**(V3.0 新增)
|
||||
- 基于 FINER 标准(可行性/创新性/伦理性/相关性)
|
||||
- 研究问题明确性评估(PICO 完整性)
|
||||
- 创新性 + 临床价值 + 科学性 + 可行性多维分析
|
||||
- Prompt 可在运营管理端配置(RVW_CLINICAL)
|
||||
|
||||
5. **综合评分 + PICO提取**
|
||||
- 规范性分数(0-100)
|
||||
- 方法学状态(🔴错误 🟡存疑 🟢通过)
|
||||
- PICO结构化提取(P/I/C/O)
|
||||
@@ -95,14 +102,15 @@ backend/src/modules/rvw/
|
||||
│ ├── reviewService.ts # 核心服务(任务创建、执行)
|
||||
│ ├── editorialService.ts # 稿约规范性评估
|
||||
│ ├── methodologyService.ts # 方法学评估
|
||||
│ ├── clinicalService.ts # 🆕 V3.0 临床专业评估服务
|
||||
│ └── utils.ts # 工具函数
|
||||
├── workers/
|
||||
│ └── reviewWorker.ts # pg-boss异步任务处理(V2.0 Skills集成)
|
||||
├── skills/ # 🆕 V2.0 Skills 架构
|
||||
│ ├── core/ # 核心框架(types, registry, executor等)
|
||||
│ ├── library/ # Skill 实现(Forensics, Editorial, Methodology)
|
||||
│ └── reviewWorker.ts # pg-boss异步任务处理(V2.0 Skills集成 + V3.0 partial_completed)
|
||||
├── skills/ # V2.0 Skills 架构
|
||||
│ ├── core/ # 核心框架(types, registry, executor[allSettled]等)
|
||||
│ ├── library/ # Skill 实现(Forensics[+LLM], Editorial, Methodology, 🆕Clinical)
|
||||
│ └── index.ts # 模块入口
|
||||
├── types/index.ts # TypeScript类型定义
|
||||
├── types/index.ts # TypeScript类型定义(含 partial_completed 状态)
|
||||
└── __tests__/ # API测试脚本
|
||||
|
||||
前端(✅ 已完成):
|
||||
@@ -116,11 +124,13 @@ frontend-v2/src/modules/rvw/
|
||||
└── components/
|
||||
├── Header.tsx # 页头(上传按钮)
|
||||
├── Sidebar.tsx # 侧边栏导航
|
||||
├── TaskTable.tsx # 任务列表表格
|
||||
├── TaskDetail.tsx # 任务详情(进度条+报告+Word导出)
|
||||
├── TaskTable.tsx # 任务列表表格(支持 partial_completed 状态)
|
||||
├── TaskDetail.tsx # 任务详情(进度条+报告+Word导出+部分完成警告)
|
||||
├── EditorialReport.tsx # 稿约规范性报告
|
||||
├── MethodologyReport.tsx # 方法学评估报告
|
||||
├── AgentModal.tsx # 智能体选择弹窗
|
||||
├── ForensicsReport.tsx # 数据验证报告(含 LLM 核查结果)
|
||||
├── ClinicalReport.tsx # 🆕 V3.0 临床专业评估报告
|
||||
├── AgentModal.tsx # 智能体选择弹窗(4 个维度)
|
||||
└── ScoreRing.tsx # 评分环组件
|
||||
|
||||
旧版本(保留兼容):
|
||||
@@ -129,13 +139,17 @@ backend/src/legacy/
|
||||
├── controllers/reviewController.ts
|
||||
└── services/reviewService.ts
|
||||
|
||||
Prompt(保持不变):
|
||||
Prompt(文件 + PromptService 数据库双通道):
|
||||
backend/prompts/
|
||||
├── review_editorial_system.txt # 稿约评估(266行)
|
||||
└── review_methodology_system.txt # 方法学评估(257行)
|
||||
数据库 prompt_templates 表(运营管理端可配置):
|
||||
├── RVW_DATA_VALIDATION # 🆕 V3.0 数据验证 LLM 核查 Prompt
|
||||
└── RVW_CLINICAL # 🆕 V3.0 临床专业评估 Prompt
|
||||
|
||||
数据库(✅ 已完成):
|
||||
- ReviewTask表已添加新字段:selectedAgents, editorialScore, methodologyScore, methodologyStatus, picoExtract, isArchived, archivedAt
|
||||
- 🆕 V3.0 新增 error_details JSONB 字段(Skill 级失败详情,支持 partial_completed 状态)
|
||||
- ✅ Schema已迁移到 rvw_schema(2026-01-10完成)
|
||||
```
|
||||
|
||||
@@ -211,11 +225,12 @@ backend/prompts/
|
||||
|
||||
| 能力 | 位置 | 用途 |
|
||||
|------|------|------|
|
||||
| **LLM网关** | `@/common/llm/LLMFactory` | AI评估 |
|
||||
| **LLM网关** | `@/common/llm/LLMFactory` | AI评估(稿约/方法学/数据核查/临床评估) |
|
||||
| **PromptService** | `@/common/prompt` | 🆕 V3.0 Prompt 管理(灰度预览、运营端配置) |
|
||||
| **文档处理** | `ExtractionClient` | Word/PDF文本提取 |
|
||||
| **存储** | `@/common/storage` | 文件存储 |
|
||||
| **存储** | `@/common/storage` | 文件存储(OSS/本地) |
|
||||
| **日志** | `@/common/logging` | 结构化日志 |
|
||||
| **任务队列** | `jobQueue` | 异步任务处理 |
|
||||
| **任务队列** | `jobQueue` | 异步任务处理(pg-boss) |
|
||||
|
||||
### LLM模型
|
||||
|
||||
@@ -326,11 +341,13 @@ Content-Type: multipart/form-data
|
||||
|
||||
### 对新AI助手
|
||||
|
||||
1. ✅ **核心功能已完成**:前后端已迁移到新架构,可正常使用
|
||||
1. ✅ **V3.0 已完成**:4 维审查 + 稳定性增强 + partial_completed 支持
|
||||
2. ✅ **已集成到 frontend-v2**:通过顶部导航栏"预审稿"进入
|
||||
3. ✅ **v2 API 已就绪**:/api/v2/rvw/* 路由可用
|
||||
4. ✅ **遵循云原生规范**:使用 logger 服务替代 console.log
|
||||
5. ⚠️ **保留旧API**:v1路由保持兼容,支持旧前端
|
||||
5. ✅ **PromptService 集成**:RVW_DATA_VALIDATION + RVW_CLINICAL Prompt 可在运营管理端配置
|
||||
6. ✅ **并行故障隔离**:SkillExecutor 使用 Promise.allSettled,单个 Skill 崩溃不影响其他
|
||||
7. ⚠️ **保留旧API**:v1路由保持兼容,支持旧前端
|
||||
|
||||
### 已完成改造
|
||||
|
||||
@@ -414,9 +431,25 @@ Content-Type: multipart/form-data
|
||||
- ✅ 前端翻译映射更新(6 种新 IssueType)
|
||||
- ✅ 文件格式提示(Header、ReportDetail、TaskDetail)
|
||||
|
||||
### 后续版本(V2.1+)
|
||||
### 🚀 V3.0 "智能审稿增强" 开发进度(2026-03-07)
|
||||
|
||||
- [ ] Week 4 功能测试和 Bug 修复
|
||||
| 任务 | 状态 | 说明 |
|
||||
|------|------|------|
|
||||
| LLM 数据核查通道 | ✅ 已完成 | DataForensicsSkill 增加 LLM 验证,独立 60s 超时,规则验证兜底 |
|
||||
| RVW_DATA_VALIDATION Prompt | ✅ 已完成 | PromptService 集成,运营管理端可配置 |
|
||||
| 临床专业评估 Skill | ✅ 已完成 | ClinicalAssessmentSkill,基于 FINER 标准 |
|
||||
| RVW_CLINICAL Prompt | ✅ 已完成 | PromptService 集成,运营管理端可配置 |
|
||||
| SkillExecutor Promise.allSettled | ✅ 已完成 | 并行 Skill 故障隔离,单个崩溃不影响其他 |
|
||||
| partial_completed 状态 | ✅ 已完成 | 新增任务状态 + error_details JSONB 字段 |
|
||||
| reviewWorker 写入 errorDetails | ✅ 已完成 | 记录每个失败/超时 Skill 的名称和原因 |
|
||||
| 前端 4 Tab 报告 | ✅ 已完成 | 稿约规范性/方法学/数据验证/临床评估 |
|
||||
| 前端 partial_completed UI | ✅ 已完成 | 琥珀色警告横幅 + 列表"部分完成"标签 |
|
||||
| Word 导出覆盖临床评估 | ✅ 已完成 | 导出报告包含临床专业评估章节 |
|
||||
|
||||
### 后续版本(V3.1+)
|
||||
|
||||
- [ ] 全面移除评分机制(只列问题,不打分)
|
||||
- [ ] 单模块重试机制(partial_completed → 重试失败模块)
|
||||
- [ ] ANOVA 验证(多组比较)
|
||||
- [ ] 配对 T 检验验证
|
||||
- [ ] 非参数检验(Mann-Whitney、Wilcoxon)
|
||||
@@ -429,7 +462,7 @@ Content-Type: multipart/form-data
|
||||
|
||||
---
|
||||
|
||||
**文档版本:** v5.1
|
||||
**最后更新:** 2026-02-18
|
||||
**当前状态:** 🚀 V2.0 "数据侦探" Week 3 完成,Skills 架构 + 统计验证 + 用户体验优化
|
||||
**下一步:** Week 4 功能测试和 Bug 修复
|
||||
**文档版本:** v6.0
|
||||
**最后更新:** 2026-03-07
|
||||
**当前状态:** 🚀 V3.0 "智能审稿增强" 完成(LLM数据核查 + 临床评估 + 稳定性增强)
|
||||
**下一步:** V3.1 移除评分机制 + 单模块重试
|
||||
|
||||
@@ -15,19 +15,32 @@
|
||||
|
||||
| # | 变更内容 | 迁移文件 | 优先级 | 备注 |
|
||||
|---|---------|---------|--------|------|
|
||||
| — | *暂无* | | | |
|
||||
| DB-1 | modules 表 seed 新增 ASL_SR 模块(系统综述项目) | `backend/scripts/seed-modules.js` | 高 | 部署后需执行 `node scripts/seed-modules.js`,并在运营管理端为目标用户/租户开通 |
|
||||
| DB-2 | prompt_templates 表新增 RVW_DATA_VALIDATION + RVW_CLINICAL 两个 Prompt | `backend/scripts/migrate-rvw-prompts.ts` | 高 | 部署后需执行 `npx tsx scripts/migrate-rvw-prompts.ts`,运营管理端可配置修改 |
|
||||
| DB-3 | ReviewTask 表新增 `error_details` JSONB 字段(存储 Skill 级失败详情) | `prisma/migrations/20260307_add_error_details_to_review_task/migration.sql` | 高 | 支持 partial_completed 状态,记录每个失败/超时 Skill 的名称和原因 |
|
||||
|
||||
### 后端变更 (Node.js)
|
||||
|
||||
| # | 变更内容 | 涉及文件 | 需要操作 | 备注 |
|
||||
|---|---------|---------|---------|------|
|
||||
| — | *暂无* | | | |
|
||||
| BE-1 | Deep Research V2.0 新增历史列表 + 删除接口 + getTask 鉴权修复 | `deepResearchController.ts`, `routes/index.ts` | 重新构建镜像 | GET /research/v2/tasks, DELETE /research/tasks/:taskId, getTask 增加 userId 校验 |
|
||||
| BE-2 | SR 相关路由(projects/literatures/screening/fulltext-screening/extraction/charting/meta-analysis)增加 `requireModule('ASL_SR')` 中间件 | `asl/routes/index.ts`, `extraction/routes/index.ts`, `charting/routes/index.ts`, `meta-analysis/routes/index.ts` | 重新构建镜像 | 需先完成 DB-1 seed,否则无 ASL_SR 模块会 403 |
|
||||
| BE-3 | Unifuncs DeepSearch API 从 S2 升级至 S3(新增 `language: "zh"`) | `unifuncsSseClient.ts`, `unifuncsAsyncClient.ts` | 重新构建镜像 | 通过 `UNIFUNCS_MODEL` 环境变量控制,默认 s3,设为 s2 可降级 |
|
||||
| BE-4 | RVW 数据验证增加 LLM 核查通道(DataForensicsSkill 增强) | `DataForensicsSkill.ts`, `prompt.fallbacks.ts` | 重新构建镜像 | 规则验证完成后批量调用 LLM 核查所有表格,失败时降级为纯规则验证 |
|
||||
| BE-5 | RVW 新增临床专业评估维度(ClinicalAssessmentSkill) | `clinicalService.ts`(新), `ClinicalAssessmentSkill.ts`(新), `library/index.ts`, `profile.ts`, `utils.ts`, `reviewWorker.ts`, `reviewService.ts` | 重新构建镜像 | 新增 clinical Agent + Skill,存储在 contextData.clinicalReview |
|
||||
| BE-6 | RVW 稳定性增强:SkillExecutor Promise.allSettled + partial_completed 状态 + errorDetails | `executor.ts`, `reviewWorker.ts`, `reviewService.ts`, `reviewController.ts`, `types/index.ts` | 重新构建镜像 | 并行 Skill 故障隔离,部分模块失败时仍返回成功模块结果,新增 `partial_completed` 任务状态 |
|
||||
| BE-7 | DataForensicsSkill LLM 核查增加独立 60s 超时 | `DataForensicsSkill.ts` | 重新构建镜像 | LLM 核查超时不阻塞整体 Skill,graceful 降级为纯规则验证 |
|
||||
|
||||
### 前端变更
|
||||
|
||||
| # | 变更内容 | 涉及文件 | 需要操作 | 备注 |
|
||||
|---|---------|---------|---------|------|
|
||||
| — | *暂无* | | | |
|
||||
| FE-1 | ASL 左侧导航栏重构为互斥手风琴(Deep Research 历史记录 + SR 工具导航) | `ASLLayout.tsx`, `asl-sidebar.css`(新建), `DeepResearchPage.tsx`, `asl/index.tsx` | 重新构建镜像 | Panel A: 智能文献检索历史;Panel B: 系统综述项目(5 工具) |
|
||||
| FE-2 | Deep Research 历史记录功能(API 客户端 + 类型定义 + URL 任务恢复) | `asl/api/index.ts`, `types/deepResearch.ts`, `DeepResearchPage.tsx`, `asl/index.tsx` | 重新构建镜像 | 新增 listDeepResearchTasks / deleteDeepResearchTask API;新增 /research/deep/:taskId 路由 |
|
||||
| FE-3 | Panel B SR 工具导航权限控制(hasModule('ASL_SR')) | `ASLLayout.tsx`, `asl-sidebar.css` | 重新构建镜像 | 未开通时显示"请联系管理员开通";已开通显示 5 个 SR 工具导航项 |
|
||||
| FE-4 | RVW 数据验证报告增加 LLM 核查结果展示 | `ForensicsReport.tsx`, `rvw/types/index.ts` | 重新构建镜像 | 总览展示完整 LLM 报告,每个表格卡片展开后显示对应 AI 核查结果(Markdown) |
|
||||
| FE-5 | RVW 新增临床专业评估 Tab + Agent 选择项 | `ClinicalReport.tsx`(新), `AgentModal.tsx`, `TaskDetail.tsx`, `rvw/types/index.ts` | 重新构建镜像 | 共 4 个 Tab:稿约规范性/方法学/数据验证/临床评估;Word 导出包含临床评估章节 |
|
||||
| FE-6 | RVW 前端支持 partial_completed 状态(部分完成) | `TaskDetail.tsx`, `TaskTable.tsx`, `rvw/types/index.ts` | 重新构建镜像 | 琥珀色警告横幅展示失败模块详情,列表页显示"部分完成"标签,支持查看已完成模块的报告 |
|
||||
|
||||
### Python 微服务变更
|
||||
|
||||
@@ -45,7 +58,7 @@
|
||||
|
||||
| # | 变更内容 | 服务 | 变量名 | 备注 |
|
||||
|---|---------|------|--------|------|
|
||||
| — | *暂无* | | | |
|
||||
| ENV-1 | Unifuncs 模型版本控制(可选,不配置则默认 s3) | nodejs-backend | `UNIFUNCS_MODEL=s3` | 降级时改为 `s2`,无需重新部署代码 |
|
||||
|
||||
### 基础设施变更
|
||||
|
||||
|
||||
197
docs/05-部署文档/0305部署/01-部署完成总结.md
Normal file
197
docs/05-部署文档/0305部署/01-部署完成总结.md
Normal file
@@ -0,0 +1,197 @@
|
||||
# 2026年3月5日部署完成总结
|
||||
|
||||
> **部署日期**:2026-03-05
|
||||
> **部署范围**:数据库数据更新(1项) + Node.js后端 + 前端Nginx + R统计引擎待部署
|
||||
> **部署状态**:✅ 后端/前端已完成
|
||||
> **文档日期**:2026-03-05
|
||||
|
||||
---
|
||||
|
||||
## 部署成果一览
|
||||
|
||||
### 服务版本对比
|
||||
|
||||
| 服务 | 部署前 | 部署后 | 变更类型 |
|
||||
|------|--------|--------|---------|
|
||||
| PostgreSQL(RDS) | 86 表 | 86 表(数据更新) | modules 表 seed 更新 |
|
||||
| Node.js后端 | v2.4 | **v2.6** | 登录踢人 + 权限体系 + SSA双通道 |
|
||||
| 前端Nginx | v2.0 | **v2.3** | UI优化 + 权限适配 + 批量导入重构 |
|
||||
|
||||
### 内网地址变更
|
||||
|
||||
| 服务 | 部署前地址 | 部署后地址 | 状态 |
|
||||
|------|-----------|-----------|------|
|
||||
| Node.js后端 | `172.17.197.32:3001` | `172.17.197.36:3001` | ✅ 已变更 |
|
||||
| 前端Nginx | `172.17.197.32:80` | `172.17.173.104:80` | ✅ 已变更 |
|
||||
|
||||
---
|
||||
|
||||
## 一、数据库更新
|
||||
|
||||
### 1.1 DB-1:modules 表数据更新
|
||||
|
||||
通过 `node scripts/seed-modules.js` 连接 RDS 外网执行(upsert 幂等操作):
|
||||
|
||||
| 操作 | 模块代码 | 名称 | 说明 |
|
||||
|------|---------|------|------|
|
||||
| 新增 | `RM` | 研究管理 | 排序 9 |
|
||||
| 新增 | `AIA_PROTOCOL` | 全流程研究方案制定 | 排序 100 |
|
||||
| 更新 | `IIT` | CRA质控 | 原名 IIT Manager |
|
||||
|
||||
执行结果:modules 表共 10 个模块,全部上线 ✅
|
||||
|
||||
### 1.2 未执行项
|
||||
|
||||
| 项目 | 原因 |
|
||||
|------|------|
|
||||
| DB-2:RVW Prompt 更新 | 用户指定不执行 |
|
||||
| DB-3:SSA 双通道表结构 | 待后续部署 |
|
||||
|
||||
---
|
||||
|
||||
## 二、Node.js后端更新(v2.4 → v2.6)
|
||||
|
||||
### 2.1 主要变更(9 项)
|
||||
|
||||
| 类别 | 变更内容 |
|
||||
|------|---------|
|
||||
| 登录安全 | 同一手机号登录踢人机制(JWT tokenVersion + 缓存校验) |
|
||||
| 权限体系 | `/me/modules` API 尊重 user_modules 精细化配置 |
|
||||
| 权限体系 | 用户模块配置校验放宽(模块代码存在即可,不限租户订阅) |
|
||||
| 权限体系 | user_modules 独立生效(如 AIA_PROTOCOL 可单独配给用户) |
|
||||
| 模块名称 | getModuleName 补充 RM、AIA_PROTOCOL、IIT→CRA质控 |
|
||||
| RVW | 稿约 Prompt 源文件期刊名称修正 |
|
||||
| Seed | 内部租户补充 RM、AIA_PROTOCOL 模块 |
|
||||
| SSA | 双通道架构:Agent 模式 4 服务 + ChatHandler 分流 |
|
||||
| 用户管理 | 批量导入增加 autoInheritModules + 模块校验 |
|
||||
|
||||
### 2.2 镜像信息
|
||||
|
||||
| 项目 | 值 |
|
||||
|------|---|
|
||||
| ACR 仓库 | `backend-service` |
|
||||
| 镜像版本 | v2.4 → **v2.6** |
|
||||
| Digest | `sha256:17dc3b3b6171bad891b0d366a22e1b52d79db7fc9caccedf816a7feab4cea449` |
|
||||
| 内网地址 | `http://172.17.197.36:3001` |
|
||||
|
||||
---
|
||||
|
||||
## 三、前端Nginx更新(v2.0 → v2.3)
|
||||
|
||||
### 3.1 主要变更(11 项)
|
||||
|
||||
| 类别 | 变更内容 |
|
||||
|------|---------|
|
||||
| ASL | 隐藏数据源/年限/篇数 + 去掉研究方案生成/文献管理 + 默认进入智能文献检索 |
|
||||
| AIA | 删除「已接入DeepSeek」和搜索框 + Protocol Agent 按权限动态显示 |
|
||||
| AIA | 数据评价与预处理/智能统计分析链接修正 |
|
||||
| 首页 | 重定向到 `/ai-qa`,不再显示模块卡片首页 |
|
||||
| PKB | 创建知识库时隐藏科室选择,默认 General |
|
||||
| 安全 | 被踢出时提示「账号已在其他设备登录」 |
|
||||
| 运营端 | 用户模块权限弹窗显示所有模块(含未订阅标注) |
|
||||
| 运营端 | 批量导入用户重构为 4 步流程 + 自动继承租户模块 |
|
||||
|
||||
### 3.2 镜像信息
|
||||
|
||||
| 项目 | 值 |
|
||||
|------|---|
|
||||
| ACR 仓库 | `ai-clinical_frontend-nginx` |
|
||||
| 镜像版本 | v2.0 → **v2.3** |
|
||||
| Digest | `sha256:db031053d8ac50d8f2ce39a8406534743d974a5506b7d4af5a944dd145ce8589` |
|
||||
| 内网地址 | `http://172.17.173.104:80` |
|
||||
|
||||
---
|
||||
|
||||
## 四、环境变量联动更新
|
||||
|
||||
| 服务 | 环境变量 | 旧值 | 新值 |
|
||||
|------|---------|------|------|
|
||||
| frontend-nginx-service | `BACKEND_SERVICE_HOST` | `172.17.197.32` | `172.17.197.36` |
|
||||
|
||||
> CLB 负载均衡器由阿里云自动更新,无需手动操作。
|
||||
|
||||
---
|
||||
|
||||
## 五、待后续部署项
|
||||
|
||||
| 项目 | 说明 |
|
||||
|------|------|
|
||||
| DB-3 | SSA 双通道表结构(ssa_sessions + ssa_agent_executions) |
|
||||
| R-1 | R 统计引擎新增 execute-code 端点(需重构 R 镜像) |
|
||||
|
||||
---
|
||||
|
||||
## 六、当前系统配置速查
|
||||
|
||||
### 服务内网地址
|
||||
|
||||
```
|
||||
R统计引擎: http://172.17.173.101:8080 (不变)
|
||||
Python: http://172.17.173.102:8000 (不变)
|
||||
后端: http://172.17.197.36:3001 (更新)
|
||||
前端: http://172.17.173.104:80 (更新)
|
||||
```
|
||||
|
||||
### ACR 镜像版本
|
||||
|
||||
| 仓库 | 版本 |
|
||||
|------|-----|
|
||||
| `ssa-r-statistics` | v1.0.1 |
|
||||
| `python-extraction` | v1.2 |
|
||||
| `backend-service` | **v2.6** |
|
||||
| `ai-clinical_frontend-nginx` | **v2.3** |
|
||||
|
||||
### 公网访问
|
||||
|
||||
```
|
||||
CLB: http://8.140.53.236/
|
||||
域名: https://iit.xunzhengyixue.com/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
## 七、二次热修部署(同日)
|
||||
|
||||
### 7.1 触发原因
|
||||
|
||||
- SSA 智能统计分析上传文件报错:`The column execution_mode does not exist in the current database`
|
||||
- 前端/后端其他 bug 修复(基于测试反馈)
|
||||
|
||||
### 7.2 数据库热修
|
||||
|
||||
| 操作 | 内容 | 状态 |
|
||||
|------|------|------|
|
||||
| ALTER TABLE | `ssa_sessions` 新增 `execution_mode` 列(默认 `'qper'`) | ✅ |
|
||||
| CREATE TABLE | `ssa_agent_executions` 表 + 索引 + 外键 | ✅ |
|
||||
| INSERT | Prisma 迁移记录同步注册 | ✅ |
|
||||
|
||||
### 7.3 镜像重构与部署
|
||||
|
||||
| 服务 | 版本变化 | 镜像 Digest | IP 变化 |
|
||||
|------|---------|-------------|---------|
|
||||
| Node.js 后端 | v2.6 → **v2.7** | `sha256:bfb7d3e6ce39...` | `172.17.197.36` → `172.17.197.37` |
|
||||
| 前端 Nginx | v2.3 → **v2.4** | `sha256:03fab06fb1a2...` | `172.17.173.104` → `172.17.173.105` |
|
||||
|
||||
### 7.4 最终系统配置
|
||||
|
||||
```
|
||||
后端: http://172.17.197.37:3001
|
||||
前端: http://172.17.173.105:80
|
||||
R统计引擎: http://172.17.173.101:8080
|
||||
Python: http://172.17.173.102:8000
|
||||
```
|
||||
|
||||
| ACR 仓库 | 最终版本 |
|
||||
|----------|---------|
|
||||
| `backend-service` | **v2.7** |
|
||||
| `ai-clinical_frontend-nginx` | **v2.4** |
|
||||
| `ssa-r-statistics` | v1.0.1 |
|
||||
| `python-extraction` | v1.2 |
|
||||
|
||||
---
|
||||
|
||||
> **文档版本**:v1.1
|
||||
> **最后更新**:2026-03-05
|
||||
> **维护人员**:开发团队
|
||||
Reference in New Issue
Block a user