Files
AIclinicalresearch/docs/03-业务模块/ASL-AI智能文献/00-系统设计/证据整合V2.0/工具3 全文提取产品原型图V2.html
HaHafeng dc6b292308 docs(asl): Complete Tool 3 extraction workbench V2.0 development plan (v1.5)
ASL Tool 3 Development Plan:
- Architecture blueprint v1.5 (6 rounds of architecture review, 13 red lines)
- M1/M2/M3 sprint checklists (Skeleton Pipeline / HITL Workbench / Dynamic Template Engine)
- Code patterns cookbook (9 chapters: Fan-out, Prompt engineering, ACL, SSE dual-track, etc.)
- Key patterns: Fan-out with Last Child Wins, Optimistic Locking, teamConcurrency throttling
- PKB ACL integration (anti-corruption layer), MinerU Cache-Aside, NOTIFY/LISTEN cross-pod SSE
- Data consistency snapshot for long-running extraction tasks

Platform capability:
- Add distributed Fan-out task pattern development guide (7 patterns + 10 anti-patterns)
- Add system-level async architecture risk analysis blueprint
- Add PDF table extraction engine design and usage guide (MinerU integration)
- Add table extraction source code (TableExtractionManager + MinerU engine)

Documentation updates:
- Update ASL module status with Tool 3 V2.0 plan readiness
- Update system status document (v6.2) with latest milestones
- Add V2.0 product requirements, prototypes, and data dictionary specs
- Add architecture review documents (4 rounds of review feedback)
- Add test PDF files for extraction validation

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-23 22:49:16 +08:00

646 lines
42 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN" class="scroll-smooth">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>工具 3全文智能提取工作台 V1.1</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<script>
tailwind.config = {
theme: {
extend: {
colors: { primary: '#1677ff', primaryHover: '#4096ff', bgBase: '#f0f2f5', panelBg: '#ffffff' },
animation: { 'pulse-fast': 'pulse 1.5s cubic-bezier(0.4, 0, 0.6, 1) infinite', }
}
}
}
</script>
<style>
::-webkit-scrollbar { width: 6px; height: 6px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 4px; }
::-webkit-scrollbar-thumb:hover { background: #94a3b8; }
.drawer-slide-in { transform: translateX(100%); transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); }
.drawer-open { transform: translateX(0); }
@keyframes fadeIn { from { opacity: 0; transform: translateY(15px); } to { opacity: 1; transform: translateY(0); } }
.animate-fade-in { animation: fadeIn 0.4s ease-out forwards; }
/* 模态框动画 */
.modal-fade-in { opacity: 0; transform: scale(0.95); transition: all 0.2s ease-out; }
.modal-open { opacity: 1; transform: scale(1); }
/* 步骤条样式 */
.step-item { position: relative; flex: 1; text-align: center; }
.step-item::after { content: ''; position: absolute; top: 12px; left: 50%; width: 100%; height: 2px; background-color: #e2e8f0; z-index: 0; }
.step-item:last-child::after { display: none; }
.step-circle { width: 26px; height: 26px; border-radius: 50%; background-color: #e2e8f0; color: #64748b; font-size: 12px; font-weight: bold; display: flex; align-items: center; justify-content: center; margin: 0 auto 8px; position: relative; z-index: 10; border: 2px solid #fff; }
.step-item.active .step-circle { background-color: #1677ff; color: #fff; box-shadow: 0 0 0 3px rgba(22, 119, 255, 0.2); }
.step-item.active ~ .step-item::after { background-color: #e2e8f0; }
.step-item.completed .step-circle { background-color: #1677ff; color: #fff; }
.step-item.completed::after { background-color: #1677ff; }
.log-container::-webkit-scrollbar-thumb { background: #475569; }
</style>
</head>
<body class="bg-bgBase text-gray-800 font-sans h-screen flex overflow-hidden">
<!-- 侧边导航 (仅作上下文展示,不可点) -->
<aside class="w-64 bg-slate-900 text-white flex flex-col h-full flex-shrink-0 shadow-xl z-20">
<div class="h-16 flex items-center px-6 border-b border-slate-800">
<i class="fa-solid fa-notes-medical text-blue-400 text-xl mr-3"></i>
<span class="text-lg font-bold tracking-wide">AI Clinical</span>
</div>
<div class="p-4 text-xs font-semibold text-slate-500 uppercase tracking-wider">循证医学工具箱</div>
<nav class="flex-1 px-3 space-y-1">
<div class="w-full flex items-center px-3 py-2.5 text-slate-500 opacity-50"><i class="fa-solid fa-magnifying-glass-chart w-6"></i><span class="ml-2">1: 智能文献检索</span></div>
<div class="w-full flex items-center px-3 py-2.5 text-slate-500 opacity-50"><i class="fa-solid fa-filter w-6"></i><span class="ml-2">2: 标题摘要初筛</span></div>
<div class="my-2 border-t border-slate-800"></div>
<div class="w-full flex items-center px-3 py-2.5 bg-blue-600/20 text-blue-400 rounded-lg"><i class="fa-solid fa-file-pdf w-6"></i><span class="ml-2 font-medium">3: 全文智能提取</span></div>
<div class="w-full flex items-center px-3 py-2.5 text-slate-500 opacity-50"><i class="fa-solid fa-diagram-project w-6"></i><span class="ml-2">4: SR 图表生成器</span></div>
<div class="w-full flex items-center px-3 py-2.5 text-slate-500 opacity-50"><i class="fa-solid fa-chart-line w-6"></i><span class="ml-2">5: Meta 分析引擎</span></div>
</nav>
</aside>
<!-- 右侧工作区 -->
<main class="flex-1 flex flex-col h-full relative">
<header class="h-16 bg-panelBg shadow-sm flex items-center justify-between px-6 z-10 flex-shrink-0">
<h1 class="text-lg font-semibold text-gray-800">工具 3全文复筛与智能提取工作台</h1>
<div id="export-action" class="hidden">
<button class="px-4 py-2 bg-green-600 text-white rounded text-sm hover:bg-green-700 transition-colors shadow flex items-center" onclick="alert('已导出标准科研 Excel 宽表!')">
<i class="fa-solid fa-file-excel mr-2"></i> 下载结构化提取结果 (Excel)
</button>
</div>
</header>
<!-- 全局 Toast -->
<div id="global-toast" class="fixed top-20 left-1/2 transform -translate-x-1/2 bg-gray-800 text-white px-5 py-2.5 rounded shadow-lg z-50 flex items-center transition-all duration-300 opacity-0 -translate-y-4 pointer-events-none">
<i class="fa-solid fa-circle-info mr-2 text-blue-400"></i><span id="toast-msg" class="text-sm font-medium">提示信息</span>
</div>
<!-- 流程步骤条 -->
<div class="bg-white border-b border-gray-200 px-10 py-4 flex-shrink-0">
<div class="flex justify-between max-w-4xl mx-auto">
<div class="step-item active" id="step-indicator-1">
<div class="step-circle">1</div>
<div class="text-xs font-medium mt-1 text-gray-800">配置模板与上传</div>
</div>
<div class="step-item" id="step-indicator-2">
<div class="step-circle">2</div>
<div class="text-xs font-medium mt-1 text-gray-500">机器解析与提取</div>
</div>
<div class="step-item" id="step-indicator-3">
<div class="step-circle">3</div>
<div class="text-xs font-medium mt-1 text-gray-500">人机比对与核准</div>
</div>
</div>
</div>
<div class="flex-1 overflow-y-auto p-6 bg-bgBase flex flex-col items-center">
<!-- ================= VIEW 1: 配置与上传 ================= -->
<div id="view-setup" class="w-full max-w-6xl animate-fade-in space-y-6">
<div class="grid grid-cols-5 gap-6">
<!-- 左侧:模板配置 (占3列更宽敞以展示模板结构) -->
<div class="col-span-3 bg-white p-6 rounded-xl shadow-sm border border-gray-200 flex flex-col h-full">
<h2 class="text-base font-bold text-gray-800 mb-4 flex items-center justify-between">
<span><i class="fa-solid fa-layer-group text-primary mr-2"></i>步骤 1配置提取模板 (Schema)</span>
<span class="text-xs bg-blue-50 text-blue-600 px-2 py-1 rounded border border-blue-200 font-normal">动态模板引擎</span>
</h2>
<!-- 1. 选择基座 -->
<div class="mb-5">
<label class="block text-xs font-semibold text-gray-500 mb-1.5 uppercase tracking-wider">选择系统通用基座</label>
<select id="base-template-select" class="w-full text-sm border-gray-300 rounded-lg py-2.5 px-3 border bg-gray-50 focus:ring-primary focus:border-primary font-medium text-gray-700 outline-none transition-colors" onchange="changeTemplate()">
<option value="RCT">模板 A: 标准 RCT 提取与质量评价 (推荐)</option>
<option value="Cohort">模板 B: 观察性研究提取与 NOS 评价</option>
</select>
<!-- 动态展示基座包含的字段 -->
<div class="mt-3 p-3 bg-slate-50 border border-slate-200 rounded-md">
<div class="text-xs text-gray-500 mb-2 flex items-center">
<i class="fa-solid fa-lock mr-1.5 text-gray-400"></i> 该基座自动包含以下标准化字段 (不可删改)
</div>
<div id="base-fields-container" class="flex flex-wrap gap-1.5">
<!-- 通过 JS 渲染 -->
</div>
</div>
</div>
<!-- 2. 自定义字段管理 -->
<div class="border-t border-gray-100 pt-5 flex-1 flex flex-col">
<div class="flex justify-between items-center mb-3">
<div>
<label class="block text-xs font-semibold text-gray-700 uppercase tracking-wider">用户自定义插槽 (Custom Fields)</label>
<p class="text-[10px] text-gray-400 mt-0.5">针对您的特定临床问题,添加专属的提取变量</p>
</div>
<button class="text-xs bg-blue-50 text-primary border border-blue-200 hover:bg-blue-100 px-3 py-1.5 rounded transition-colors flex items-center shadow-sm" onclick="openFieldModal()">
<i class="fa-solid fa-plus mr-1.5"></i>添加自定义字段
</button>
</div>
<!-- 自定义字段列表容器 -->
<div id="custom-fields-list" class="space-y-3 flex-1 overflow-y-auto pr-1">
<!-- 通过 JS 动态渲染 -->
</div>
</div>
</div>
<!-- 右侧PDF 上传 (占2列) -->
<div class="col-span-2 bg-white p-6 rounded-xl shadow-sm border border-gray-200 flex flex-col">
<h2 class="text-base font-bold text-gray-800 mb-4 flex items-center"><i class="fa-solid fa-file-pdf text-red-500 mr-2"></i>步骤 2上传文献 (PDF)</h2>
<div class="flex-1 border-2 border-dashed border-gray-300 rounded-lg bg-gray-50 hover:bg-gray-100 transition-colors flex flex-col items-center justify-center p-6 cursor-pointer relative" id="upload-area" onclick="simulateUpload()">
<i class="fa-solid fa-cloud-arrow-up text-4xl text-gray-400 mb-3"></i>
<p class="text-sm font-medium text-gray-700">点击或将 PDF 文件拖拽至此处</p>
<p class="text-xs text-gray-500 mt-1">支持批量上传,单文件最大 50MB</p>
<div class="absolute bottom-4 text-[10px] text-gray-400 bg-white px-2 py-1 rounded shadow-sm border border-gray-200">
💡 提示:上传后将自动应用左侧配置的模板进行提取
</div>
</div>
<!-- 模拟已上传的文件列表 (初始隐藏) -->
<div id="file-list" class="hidden flex-1 flex-col space-y-2 mt-2 overflow-y-auto">
<div class="flex items-center justify-between p-2.5 bg-gray-50 border border-gray-200 rounded-md">
<div class="flex items-center overflow-hidden"><i class="fa-solid fa-file-pdf text-red-500 mr-2 text-lg"></i><span class="text-sm text-gray-700 truncate w-40">Gandhi_2018_NEJM.pdf</span></div>
<span class="text-xs text-green-600"><i class="fa-solid fa-check"></i> 1.2MB</span>
</div>
<div class="flex items-center justify-between p-2.5 bg-gray-50 border border-gray-200 rounded-md">
<div class="flex items-center overflow-hidden"><i class="fa-solid fa-file-pdf text-red-500 mr-2 text-lg"></i><span class="text-sm text-gray-700 truncate w-40">Hellmann_2019_Lancet.pdf</span></div>
<span class="text-xs text-green-600"><i class="fa-solid fa-check"></i> 3.5MB</span>
</div>
<div class="flex items-center justify-between p-2.5 bg-gray-50 border border-gray-200 rounded-md">
<div class="flex items-center overflow-hidden"><i class="fa-solid fa-file-pdf text-red-500 mr-2 text-lg"></i><span class="text-sm text-gray-700 truncate w-40">Socinski_2018_JCO.pdf</span></div>
<span class="text-xs text-green-600"><i class="fa-solid fa-check"></i> 2.1MB</span>
</div>
</div>
</div>
</div>
<div class="mt-6 flex justify-end">
<button id="btn-start" class="bg-primary hover:bg-primaryHover text-white px-8 py-3 rounded-lg font-medium shadow-md transition-all flex items-center opacity-50 cursor-not-allowed" disabled onclick="startProcessing()">
<i class="fa-solid fa-rocket mr-2"></i> 确认模板并开始批量提取
</button>
</div>
</div>
<!-- ================= VIEW 2: 机器处理流 (Processing) ================= -->
<div id="view-processing" class="w-full max-w-4xl hidden mt-10">
<div class="bg-white rounded-xl shadow-lg border border-gray-200 overflow-hidden">
<div class="p-8 text-center border-b border-gray-100">
<div class="w-20 h-20 mx-auto relative mb-4">
<div class="absolute inset-0 border-4 border-blue-100 rounded-full"></div>
<div class="absolute inset-0 border-4 border-primary rounded-full border-t-transparent animate-spin"></div>
<div class="absolute inset-0 flex items-center justify-center"><i class="fa-solid fa-robot text-primary text-2xl"></i></div>
</div>
<h2 class="text-xl font-bold text-gray-800">机器静默提取中...</h2>
<p class="text-sm text-gray-500 mt-2">任务已进入 pg-boss 队列,利用 MinerU 与 DeepSeek-V3 联合榨取数据</p>
<div class="w-full max-w-md mx-auto bg-gray-100 rounded-full h-2 mt-6 overflow-hidden">
<div class="bg-primary h-2 rounded-full w-1/3 relative transition-all duration-1000" id="progress-bar"></div>
</div>
</div>
<!-- 模拟终端日志 -->
<div class="bg-slate-900 p-6 h-64 overflow-y-auto log-container font-mono text-sm space-y-2" id="process-logs">
<div class="text-slate-400">>> Initializing extraction pipeline...</div>
</div>
</div>
</div>
<!-- ================= VIEW 3: 提取工作台 (Workbench) ================= -->
<div id="view-workbench" class="w-full max-w-6xl hidden animate-fade-in">
<div class="bg-blue-50 border border-blue-100 p-3 rounded-lg mb-4 text-sm text-gray-700 flex justify-between items-center shadow-sm">
<div class="flex items-center">
<i class="fa-solid fa-circle-check text-blue-500 mr-2 text-lg"></i>
<span>机器提取完毕!共提取 <strong>3</strong> 篇文献。请点击“复核提单”进行人机协同验对,标记为 <strong class="text-green-600">Approved</strong> 的数据才允许导出。</span>
</div>
</div>
<div class="bg-white rounded-lg shadow-sm border border-gray-200 overflow-hidden">
<table class="w-full text-left text-sm text-gray-600">
<thead class="bg-gray-50 text-gray-700 text-xs uppercase border-b border-gray-200">
<tr>
<th class="px-5 py-4 font-semibold">Study ID / 标题</th>
<th class="px-5 py-4 font-semibold w-40">机器解析流</th>
<th class="px-5 py-4 font-semibold w-32">复核状态</th>
<th class="px-5 py-4 font-semibold w-24 text-center">操作</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-100" id="workbench-tbody">
<!-- 行 1 -->
<tr class="hover:bg-blue-50/30">
<td class="px-5 py-4">
<div class="font-bold text-gray-800">Gandhi 2018</div>
<div class="text-xs text-primary hover:underline cursor-pointer mt-1 truncate w-96" onclick="openDrawer()">Pembrolizumab plus Chemotherapy in Metastatic NonSmall-Cell Lung Cancer</div>
</td>
<td class="px-5 py-4">
<div class="text-[10px] text-green-600 mb-1"><i class="fa-solid fa-check mr-1"></i>MinerU 表格还原</div>
<div class="text-[10px] text-blue-600"><i class="fa-solid fa-robot mr-1"></i>DeepSeek 榨取</div>
</td>
<td class="px-5 py-4" id="status-1"><span class="text-xs bg-orange-50 text-orange-600 px-2 py-1 rounded border border-orange-200 flex items-center w-max"><span class="w-1.5 h-1.5 rounded-full bg-orange-500 mr-1.5 animate-pulse"></span>待核对</span></td>
<td class="px-5 py-4 text-center"><button class="bg-primary text-white text-xs px-3 py-1.5 rounded hover:bg-primaryHover shadow-sm" onclick="openDrawer()">复核提单</button></td>
</tr>
<!-- 行 2 -->
<tr class="hover:bg-blue-50/30">
<td class="px-5 py-4">
<div class="font-bold text-gray-800">Hellmann 2019</div>
<div class="text-xs text-gray-500 mt-1 truncate w-96">Nivolumab plus Ipilimumab in Advanced NonSmall-Cell Lung Cancer</div>
</td>
<td class="px-5 py-4">
<div class="text-[10px] text-green-600 mb-1"><i class="fa-solid fa-check mr-1"></i>MinerU 表格还原</div>
<div class="text-[10px] text-blue-600"><i class="fa-solid fa-robot mr-1"></i>DeepSeek 榨取</div>
</td>
<td class="px-5 py-4"><span class="text-xs bg-orange-50 text-orange-600 px-2 py-1 rounded border border-orange-200 flex items-center w-max"><span class="w-1.5 h-1.5 rounded-full bg-orange-500 mr-1.5 animate-pulse"></span>待核对</span></td>
<td class="px-5 py-4 text-center"><button class="bg-primary text-white text-xs px-3 py-1.5 rounded hover:bg-primaryHover shadow-sm" onclick="showToast('原型仅演示第一篇的复核')">复核提单</button></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</main>
<!-- ================= 添加/编辑自定义字段 Modal ================= -->
<div id="field-modal-backdrop" class="fixed inset-0 bg-slate-900/50 z-50 hidden transition-opacity" onclick="closeFieldModal()"></div>
<div id="field-modal" class="fixed inset-0 z-50 hidden items-center justify-center pointer-events-none">
<div class="bg-white rounded-xl shadow-2xl w-[500px] flex flex-col pointer-events-auto modal-fade-in" id="field-modal-content">
<div class="px-6 py-4 border-b border-gray-200 flex justify-between items-center bg-slate-50 rounded-t-xl">
<h2 class="text-base font-bold text-gray-800" id="field-modal-title">添加自定义提取字段</h2>
<button class="text-gray-400 hover:text-gray-800" onclick="closeFieldModal()"><i class="fa-solid fa-xmark text-lg"></i></button>
</div>
<div class="p-6 space-y-4">
<input type="hidden" id="field-id">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">字段名称 <span class="text-red-500">*</span></label>
<input type="text" id="field-name" placeholder="例如:糖尿病史比例 (%)" class="w-full p-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-primary focus:border-primary outline-none">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">期望数据类型 <span class="text-red-500">*</span></label>
<select id="field-type" class="w-full p-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-primary focus:border-primary outline-none bg-white">
<option value="String">文本 (String)</option>
<option value="Number">具体数值 (Number)</option>
<option value="Percentage">百分比 (Percentage)</option>
<option value="Boolean">是/否 (Boolean)</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">AI 提取指令 (Prompt) <span class="text-red-500">*</span></label>
<p class="text-[10px] text-gray-500 mb-2">告诉大模型应该去哪里找、怎么找这个数据。</p>
<textarea id="field-prompt" rows="3" placeholder="例如:请在基线特征表 (Table 1) 中寻找合并有 Type 2 Diabetes 的患者比例或人数。" class="w-full p-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-primary focus:border-primary outline-none resize-none text-sm"></textarea>
</div>
</div>
<div class="px-6 py-4 border-t border-gray-200 bg-gray-50 flex justify-end gap-3 rounded-b-xl">
<button class="px-4 py-2 text-sm text-gray-600 border border-gray-300 rounded bg-white hover:bg-gray-100" onclick="closeFieldModal()">取消</button>
<button class="px-5 py-2 text-sm text-white bg-primary rounded shadow-sm hover:bg-primaryHover" onclick="saveCustomField()">保存字段</button>
</div>
</div>
</div>
<!-- ================= 核心:右侧智能提单抽屉 ================= -->
<div id="drawer-backdrop" class="fixed inset-0 bg-slate-900/50 z-40 hidden transition-opacity" onclick="closeDrawer()"></div>
<div id="extraction-drawer" class="fixed top-0 right-0 h-full w-[700px] bg-white shadow-2xl z-50 drawer-slide-in flex flex-col">
<!-- 保持之前精美的抽屉设计 -->
<div class="px-6 py-4 border-b border-gray-200 flex justify-between items-center bg-slate-50 shrink-0">
<div class="pr-8">
<div class="flex items-center space-x-2 mb-1.5">
<span id="drawer-status-badge" class="text-xs bg-orange-100 text-orange-600 px-2 py-0.5 rounded border border-orange-200 font-medium"><span class="w-1.5 h-1.5 inline-block rounded-full bg-orange-500 mr-1 animate-pulse"></span>Pending Review (待复核)</span>
<span class="text-[10px] text-gray-400 bg-white border px-1.5 py-0.5 rounded"><i class="fa-solid fa-robot text-blue-500 mr-1"></i>基于所选模板提取</span>
</div>
<h2 class="text-base font-bold text-gray-800 leading-tight">Pembrolizumab plus Chemotherapy in Metastatic NonSmall-Cell Lung Cancer</h2>
</div>
<button class="text-gray-400 hover:text-gray-800 p-2 border border-gray-200 rounded bg-white shadow-sm" onclick="closeDrawer()"><i class="fa-solid fa-xmark"></i></button>
</div>
<div class="bg-slate-800 p-2.5 flex justify-between items-center shrink-0 px-6">
<span class="text-xs text-gray-300"><i class="fa-solid fa-shield-halved text-green-400 mr-1.5"></i>已强制开启 Quote 原文溯源护栏,解决 AI 幻觉。</span>
<button class="bg-gray-700 border border-gray-600 text-white hover:bg-gray-600 px-3 py-1 rounded text-xs transition-colors shadow-sm" onclick="alert('利用浏览器原生功能,将在新标签页打开 OSS 中的 PDF 进行比对')">
查看源 PDF <i class="fa-solid fa-arrow-up-right-from-square ml-1 text-[10px]"></i>
</button>
</div>
<div class="flex-1 overflow-y-auto p-6 space-y-6 bg-slate-50">
<!-- 模块 2: 基线特征 (+ 自定义字段) -->
<div class="bg-white rounded-lg shadow-sm border border-gray-200 relative overflow-hidden">
<div class="absolute left-0 top-0 bottom-0 w-1 bg-blue-500"></div>
<div class="p-4 pl-5">
<h3 class="text-sm font-bold text-gray-800 mb-4 flex items-center border-b border-gray-100 pb-2"><i class="fa-solid fa-users text-blue-500 mr-2"></i>模块 2基线特征 (Table 1 Baseline)</h3>
<div class="space-y-4">
<div class="grid grid-cols-2 gap-4">
<div>
<label class="text-xs text-gray-500 mb-1 block">实验组人数 (Intervention_N)</label>
<input type="text" value="410" class="w-full p-2 border border-blue-300 bg-blue-50 text-primary font-bold rounded focus:ring-1 focus:ring-primary outline-none font-mono">
</div>
<div>
<label class="text-xs text-gray-500 mb-1 block">对照组人数 (Control_N)</label>
<input type="text" value="206" class="w-full p-2 border border-gray-300 rounded focus:ring-1 focus:ring-primary outline-none font-mono">
</div>
</div>
<div class="bg-slate-50 border border-slate-200 p-2.5 rounded-md relative mt-1">
<span class="absolute -top-2 left-2 bg-slate-200 text-slate-500 text-[9px] px-1 rounded uppercase font-bold tracking-wider">AI Quote</span>
<p class="text-xs text-slate-600 italic m-0 font-serif border-l-2 border-slate-400 pl-2 mt-1">"...A total of <span class="bg-yellow-200 font-bold px-1 rounded">410</span> patients were assigned to pembrolizumab, and <span class="bg-yellow-200 font-bold px-1 rounded">206</span> to placebo..."</p>
</div>
<!-- 动态展示自定义字段提取结果 -->
<div id="drawer-custom-fields" class="pt-3 border-t border-dashed border-gray-200">
<!-- 示例 -->
<label class="text-xs text-gray-700 font-bold mb-1 flex items-center">
糖尿病史比例 (%)
<span class="ml-2 text-[9px] bg-blue-100 text-blue-600 border border-blue-200 px-1 rounded uppercase tracking-wider"><i class="fa-solid fa-bolt text-yellow-500 mr-0.5"></i> Custom Slot</span>
</label>
<input type="text" value="22.4%" class="w-1/2 p-2 border border-blue-300 bg-blue-50 text-primary font-bold rounded outline-none font-mono">
<div class="bg-slate-50 border border-slate-200 p-2.5 rounded-md relative mt-2">
<span class="absolute -top-2 left-2 bg-slate-200 text-slate-500 text-[9px] px-1 rounded uppercase font-bold tracking-wider">AI Quote</span>
<p class="text-xs text-slate-600 italic m-0 font-serif border-l-2 border-slate-400 pl-2 mt-1">"Table 1: Medical history of Type 2 Diabetes Mellitus - Pembrolizumab group: 92 (<span class="bg-yellow-200 font-bold px-1 rounded">22.4%</span>)."</p>
</div>
</div>
</div>
</div>
</div>
<!-- 模块 4: 结局指标 -->
<div class="bg-white rounded-lg shadow-sm border border-gray-200 relative overflow-hidden">
<div class="absolute left-0 top-0 bottom-0 w-1 bg-purple-500"></div>
<div class="p-4 pl-5">
<div class="flex justify-between items-center border-b border-gray-100 pb-2 mb-4">
<h3 class="text-sm font-bold text-gray-800 flex items-center"><i class="fa-solid fa-chart-line text-purple-500 mr-2"></i>模块 4结局指标 (Outcomes)</h3>
<div class="bg-purple-50 text-purple-700 text-[10px] px-2 py-0.5 rounded font-medium border border-purple-200">自动检测</div>
</div>
<div class="grid grid-cols-3 gap-3 mb-3">
<div>
<label class="text-xs text-gray-500 block mb-1">HR 值 (OS)</label>
<input type="text" value="0.49" class="w-full p-2 border border-purple-300 rounded font-bold text-purple-700 bg-white outline-none text-center shadow-inner">
</div>
<div>
<label class="text-xs text-gray-500 block mb-1">95% CI 下限</label>
<input type="text" value="0.38" class="w-full p-2 border border-gray-300 rounded font-mono text-center text-sm outline-none">
</div>
<div>
<label class="text-xs text-gray-500 block mb-1">95% CI 上限</label>
<input type="text" value="0.64" class="w-full p-2 border border-gray-300 rounded font-mono text-center text-sm outline-none">
</div>
</div>
</div>
</div>
</div>
<div class="p-4 border-t border-gray-200 bg-white flex justify-between items-center shrink-0">
<span class="text-xs text-gray-400">请确保所有包含 Quote 的数值已核验</span>
<div class="space-x-3">
<button class="px-5 py-2 text-sm font-medium text-gray-600 bg-white border border-gray-300 rounded-md hover:bg-gray-50" onclick="closeDrawer()">取消</button>
<button class="px-5 py-2 text-sm font-medium text-white bg-green-600 rounded-md hover:bg-green-700 shadow flex items-center" onclick="approveAndClose()">
<i class="fa-solid fa-check-double mr-2"></i> 核准保存
</button>
</div>
</div>
</div>
<!-- 脚本交互逻辑 -->
<script>
function showToast(msg) {
const toast = document.getElementById('global-toast');
document.getElementById('toast-msg').innerText = msg;
toast.classList.remove('opacity-0', '-translate-y-4', 'pointer-events-none');
setTimeout(() => toast.classList.add('opacity-0', '-translate-y-4', 'pointer-events-none'), 3000);
}
// --- 模板引擎数据模型 ---
const baseTemplates = {
'RCT': ['研究标识 (Study_ID)', '试验注册号 (NCT)', '研究类型 (Design)', '干预组人数 (N)', '对照组人数 (N)', '年龄 (Age)', '性别 (Gender)', 'RoB 2.0 偏倚评估', '核心结局指标 (HR/Events)'],
'Cohort': ['研究标识 (Study_ID)', '暴露组人数 (N)', '非暴露组人数 (N)', '随访人年 (Person-years)', '基线匹配方法 (PSM)', 'NOS 偏倚评分', '相对危险度 (RR/OR)']
};
let customFields = [
{ id: 1, name: '糖尿病史比例 (%)', type: 'Percentage', prompt: '请在基线表中寻找合并有 Type 2 Diabetes 的患者比例' }
];
let editingFieldId = null;
// 初始化渲染
window.onload = function() {
renderBaseFields();
renderCustomFields();
};
// 渲染基础字段标签
function renderBaseFields() {
const select = document.getElementById('base-template-select');
const container = document.getElementById('base-fields-container');
const fields = baseTemplates[select.value];
container.innerHTML = fields.map(field =>
`<span class="inline-block text-[11px] bg-white border border-slate-200 text-slate-600 px-2 py-1 rounded shadow-sm">
<i class="fa-solid fa-lock text-slate-300 mr-1"></i> ${field}
</span>`
).join('');
}
// 切换基座模板
function changeTemplate() {
renderBaseFields();
showToast('基座模板已切换,解析核心规则已更新');
}
// 渲染自定义字段列表
function renderCustomFields() {
const list = document.getElementById('custom-fields-list');
if (customFields.length === 0) {
list.innerHTML = `<div class="text-center py-4 text-xs text-gray-400 border border-dashed border-gray-200 rounded">暂无自定义字段AI 将仅提取系统基座数据</div>`;
return;
}
list.innerHTML = customFields.map(field => `
<div class="bg-white border border-blue-100 shadow-sm p-3 rounded-lg flex items-start group hover:border-blue-300 transition-colors">
<div class="flex-1">
<div class="flex items-center mb-1.5">
<span class="text-sm font-bold text-gray-800 mr-3">${field.name}</span>
<span class="text-[10px] bg-blue-50 text-blue-600 px-1.5 py-0.5 rounded border border-blue-200 font-mono">Type: ${field.type}</span>
</div>
<div class="text-xs text-gray-500 bg-gray-50 border border-gray-100 p-2 rounded font-serif italic relative">
<span class="absolute -top-2 left-2 bg-gray-50 px-1 text-[9px] text-gray-400 not-italic">AI Prompt</span>
"${field.prompt}"
</div>
</div>
<div class="flex flex-col space-y-2 ml-3 opacity-0 group-hover:opacity-100 transition-opacity">
<button class="text-gray-400 hover:text-primary transition-colors" onclick="openFieldModal(${field.id})" title="编辑"><i class="fa-solid fa-pen-to-square"></i></button>
<button class="text-gray-400 hover:text-red-500 transition-colors" onclick="deleteField(${field.id})" title="删除"><i class="fa-solid fa-trash-can"></i></button>
</div>
</div>
`).join('');
}
// --- 模态框控制 ---
const modal = document.getElementById('field-modal');
const modalBackdrop = document.getElementById('field-modal-backdrop');
const modalContent = document.getElementById('field-modal-content');
function openFieldModal(id = null) {
editingFieldId = id;
if (id) {
const field = customFields.find(f => f.id === id);
document.getElementById('field-modal-title').innerText = '编辑自定义提取字段';
document.getElementById('field-name').value = field.name;
document.getElementById('field-type').value = field.type;
document.getElementById('field-prompt').value = field.prompt;
} else {
document.getElementById('field-modal-title').innerText = '添加自定义提取字段';
document.getElementById('field-name').value = '';
document.getElementById('field-type').value = 'String';
document.getElementById('field-prompt').value = '';
}
modal.classList.remove('hidden');
modal.classList.add('flex');
modalBackdrop.classList.remove('hidden');
setTimeout(() => modalContent.classList.add('modal-open'), 10);
}
function closeFieldModal() {
modalContent.classList.remove('modal-open');
setTimeout(() => {
modal.classList.add('hidden');
modal.classList.remove('flex');
modalBackdrop.classList.add('hidden');
}, 200);
}
function saveCustomField() {
const name = document.getElementById('field-name').value.trim();
const type = document.getElementById('field-type').value;
const prompt = document.getElementById('field-prompt').value.trim();
if (!name || !prompt) {
alert('请填写完整的字段名称和 AI 提取指令!');
return;
}
if (editingFieldId) {
const field = customFields.find(f => f.id === editingFieldId);
field.name = name;
field.type = type;
field.prompt = prompt;
showToast('字段修改成功');
} else {
const newId = customFields.length > 0 ? Math.max(...customFields.map(f => f.id)) + 1 : 1;
customFields.push({ id: newId, name, type, prompt });
showToast('成功添加自定义提取字段');
}
renderCustomFields();
closeFieldModal();
}
function deleteField(id) {
if (confirm('确定要删除这个自定义提取字段吗?')) {
customFields = customFields.filter(f => f.id !== id);
renderCustomFields();
showToast('字段已删除');
}
}
// --- 步骤 1: 模拟上传文件 ---
function simulateUpload() {
const uploadArea = document.getElementById('upload-area');
const fileList = document.getElementById('file-list');
const btnStart = document.getElementById('btn-start');
uploadArea.classList.add('hidden');
fileList.classList.remove('hidden');
fileList.classList.add('flex');
btnStart.classList.remove('opacity-50', 'cursor-not-allowed');
btnStart.classList.add('hover:shadow-lg');
btnStart.disabled = false;
}
// --- 步骤 2: 开始批量提取 ---
function startProcessing() {
document.getElementById('step-indicator-1').classList.replace('active', 'completed');
document.getElementById('step-indicator-2').classList.add('active');
document.getElementById('view-setup').classList.add('hidden');
document.getElementById('view-processing').classList.remove('hidden');
const logs = document.getElementById('process-logs');
const pBar = document.getElementById('progress-bar');
// 组装最终 Schema 的提示
const baseSchema = document.getElementById('base-template-select').value;
const customCount = customFields.length;
const events = [
{ delay: 500, text: `<span class="text-blue-400">[MinerU]</span> Extracting tables from Gandhi_2018_NEJM.pdf...`, progress: '20%' },
{ delay: 800, text: `<span class="text-green-400">[MinerU]</span> Table extraction success.`, progress: '30%' },
{ delay: 600, text: `<span class="text-purple-400">[DeepSeek]</span> Building Dynamic Schema: [Base: ${baseSchema}] + [Custom Fields: ${customCount}]...`, progress: '50%' },
{ delay: 1000, text: `<span class="text-yellow-400">[System]</span> 1/3 Documents processed.`, progress: '60%' },
{ delay: 500, text: `<span class="text-blue-400">[MinerU]</span> Parsing remaining documents...`, progress: '80%' },
{ delay: 1200, text: `<span class="text-green-500 font-bold">[Success] All documents successfully extracted according to custom schema!</span>`, progress: '100%' },
{ delay: 800, type: 'finish' }
];
let cumDelay = 0;
events.forEach(e => {
cumDelay += e.delay;
setTimeout(() => {
if(e.type === 'finish') {
finishProcessing();
return;
}
pBar.style.width = e.progress;
const div = document.createElement('div');
div.innerHTML = `>> ${e.text}`;
logs.appendChild(div);
logs.scrollTop = logs.scrollHeight;
}, cumDelay);
});
}
// --- 步骤 3: 进入工作台 ---
function finishProcessing() {
document.getElementById('step-indicator-2').classList.replace('active', 'completed');
document.getElementById('step-indicator-3').classList.add('active');
document.getElementById('view-processing').classList.add('hidden');
document.getElementById('view-workbench').classList.remove('hidden');
}
// --- 步骤 4: 抽屉操作 ---
const drawer = document.getElementById('extraction-drawer');
const backdrop = document.getElementById('drawer-backdrop');
function openDrawer() {
backdrop.classList.remove('hidden');
void drawer.offsetWidth;
drawer.classList.add('drawer-open');
}
function closeDrawer() {
drawer.classList.remove('drawer-open');
setTimeout(() => backdrop.classList.add('hidden'), 300);
}
function approveAndClose() {
closeDrawer();
showToast('提取数据已核准 (Approved) 存入数据库');
const statusCell = document.getElementById('status-1');
statusCell.innerHTML = `<span class="text-xs bg-green-50 text-green-600 px-2 py-1 rounded border border-green-200 flex items-center w-max"><i class="fa-solid fa-check-double mr-1"></i>Approved</span>`;
document.getElementById('export-action').classList.remove('hidden');
}
</script>
</body>
</html>