Files
AIclinicalresearch/docs/03-业务模块/AIA-AI智能问答/00-系统设计/研究方案一键生成.html
HaHafeng 303dd78c54 feat(aia): Protocol Agent MVP complete with one-click generation and Word export
- Add one-click research protocol generation with streaming output

- Implement Word document export via Pandoc integration

- Add dynamic dual-panel layout with resizable split pane

- Implement collapsible content for StatePanel stages

- Add conversation history management with title auto-update

- Fix scroll behavior, markdown rendering, and UI layout issues

- Simplify conversation creation logic for reliability
2026-01-25 19:16:36 +08:00

277 lines
16 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Protocol Agent V3.0 - 生成全流程</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/lucide@latest"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&family=Noto+Sans+SC:wght@300;400;500;700&family=Noto+Serif+SC:wght@400;700&display=swap');
body { font-family: 'Inter', 'Noto Sans SC', sans-serif; }
/* 聊天气泡 */
.chat-bubble-ai { background-color: #F3F4F6; border-radius: 12px 12px 12px 2px; }
.chat-bubble-user { background-color: #4F46E5; color: white; border-radius: 12px 12px 2px 12px; }
/* A4 纸张效果 (预览模式) */
.a4-paper {
width: 100%;
max-width: 210mm;
min-height: 297mm;
padding: 20mm;
background: white;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
margin: 0 auto;
font-family: 'Noto Serif SC', serif; /* 宋体风格 */
color: #1F2937;
line-height: 1.8;
font-size: 15px;
}
/* 打字机光标 */
.typing-cursor::after { content: '❘'; animation: blink 1s infinite; color: #4F46E5; }
@keyframes blink { 50% { opacity: 0; } }
/* 右侧面板切换动画 */
.panel-transition { transition: opacity 0.3s ease, transform 0.3s ease; }
.panel-hidden { opacity: 0; transform: translateX(20px); pointer-events: none; position: absolute; top:0; left:0; right:0; }
.panel-visible { opacity: 1; transform: translateX(0); position: relative; }
</style>
</head>
<body class="bg-gray-100 h-screen flex flex-col overflow-hidden">
<!-- Header -->
<header class="bg-white border-b border-gray-200 h-14 flex items-center justify-between px-4 shadow-sm z-20 shrink-0">
<div class="flex items-center gap-3">
<div class="w-8 h-8 bg-indigo-600 rounded-lg flex items-center justify-center text-white font-bold">
<i data-lucide="bot" class="w-5 h-5"></i>
</div>
<div>
<h1 class="font-bold text-gray-800 text-sm">Protocol Agent</h1>
<div class="flex items-center gap-1.5">
<span class="w-2 h-2 bg-green-500 rounded-full"></span>
<span class="text-[10px] text-gray-500">PICO Ready</span>
</div>
</div>
</div>
<!-- Tab Switcher (用于演示) -->
<div class="flex bg-gray-100 p-1 rounded-lg">
<button id="btn-view-context" class="px-3 py-1 text-xs font-medium bg-white shadow-sm rounded text-gray-700">视图: 关键要素</button>
<button id="btn-view-doc" class="px-3 py-1 text-xs font-medium text-gray-500 hover:text-gray-700">视图: 完整方案</button>
</div>
</header>
<main class="flex-1 flex overflow-hidden">
<!-- Left: Chat (35%) -->
<section class="w-[400px] flex flex-col bg-white border-r border-gray-200 z-10 shadow-lg">
<div id="chat-box" class="flex-1 overflow-y-auto p-4 space-y-6 bg-slate-50">
<!-- History -->
<div class="flex gap-3">
<div class="w-8 h-8 rounded-full bg-indigo-100 flex items-center justify-center shrink-0"><i data-lucide="bot" class="w-4 h-4 text-indigo-600"></i></div>
<div class="chat-bubble-ai p-3 text-sm text-gray-700 shadow-sm">
样本量计算已完成 (N=386)。至此,您的研究方案 <strong>5 个关键要素</strong> 已全部收集完毕。
</div>
</div>
<!-- 核心:生成按钮 Action Card -->
<div class="flex justify-center" id="action-area">
<div class="bg-white border-2 border-indigo-100 rounded-xl p-4 shadow-md w-full max-w-sm hover:border-indigo-300 transition-all cursor-default">
<div class="flex items-center gap-3 mb-3">
<div class="bg-indigo-600 text-white p-2 rounded-lg"><i data-lucide="sparkles" class="w-5 h-5"></i></div>
<div>
<h3 class="font-bold text-gray-800 text-sm">要素已就绪</h3>
<p class="text-xs text-gray-500">基于已确认的 PICO 与样本量</p>
</div>
</div>
<button onclick="startGeneration()" class="w-full bg-indigo-600 hover:bg-indigo-700 text-white text-sm font-bold py-2.5 rounded-lg shadow-sm flex items-center justify-center gap-2 transition-transform active:scale-95">
开始撰写完整方案
<i data-lucide="arrow-right" class="w-4 h-4"></i>
</button>
</div>
</div>
<!-- 生成中的状态 (初始隐藏) -->
<div id="generating-msg" class="hidden flex gap-3 animate-fade-in">
<div class="w-8 h-8 rounded-full bg-indigo-100 flex items-center justify-center shrink-0"><i data-lucide="pen-tool" class="w-4 h-4 text-indigo-600 animate-pulse"></i></div>
<div class="chat-bubble-ai p-3 text-sm text-gray-700 shadow-sm w-full">
<p class="font-bold text-indigo-600 mb-2 text-xs">正在撰写...</p>
<div class="space-y-2">
<div class="flex items-center justify-between text-xs">
<span class="text-gray-600">1. 研究背景</span>
<i data-lucide="check-circle" class="w-3 h-3 text-green-500"></i>
</div>
<div class="flex items-center justify-between text-xs">
<span class="text-gray-600">2. 研究目的</span>
<i data-lucide="check-circle" class="w-3 h-3 text-green-500"></i>
</div>
<div class="flex items-center justify-between text-xs">
<span class="text-indigo-600 font-medium">3. 研究设计</span>
<i data-lucide="loader-2" class="w-3 h-3 text-indigo-500 animate-spin"></i>
</div>
</div>
</div>
</div>
</div>
<!-- Input -->
<div class="p-3 border-t border-gray-200 bg-white">
<div class="relative">
<input type="text" placeholder="对生成结果不满意?输入修改指令..." class="w-full pl-4 pr-10 py-3 bg-gray-50 border border-gray-200 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500">
<button class="absolute right-2 top-1/2 -translate-y-1/2 p-1.5 text-indigo-600"><i data-lucide="send" class="w-4 h-4"></i></button>
</div>
</div>
</section>
<!-- Right: Dual Panel (Context vs Document) -->
<section class="flex-1 bg-slate-100 relative overflow-hidden">
<!-- Panel 1: Context View (结构化数据) -->
<div id="panel-context" class="absolute inset-0 p-8 overflow-y-auto panel-visible panel-transition">
<div class="max-w-3xl mx-auto">
<div class="flex items-center justify-between mb-6">
<h2 class="text-lg font-bold text-gray-800 flex items-center gap-2">
<i data-lucide="database" class="w-5 h-5 text-gray-500"></i>
关键要素 (Context Data)
</h2>
<span class="text-xs bg-white border border-gray-200 px-3 py-1 rounded-full text-gray-500">数据源: Postgres JSONB</span>
</div>
<div class="grid grid-cols-1 gap-4">
<!-- PICO Card -->
<div class="bg-white p-5 rounded-xl border border-gray-200 shadow-sm">
<div class="text-xs font-bold text-gray-400 uppercase mb-3">PICO 模型</div>
<div class="space-y-3">
<div class="flex"><span class="w-8 font-bold text-blue-600">P</span><span class="text-sm text-gray-700">≥65岁原发性高血压患者</span></div>
<div class="flex"><span class="w-8 font-bold text-indigo-600">I</span><span class="text-sm text-gray-700">阿司匹林肠溶片 100mg/d</span></div>
<div class="flex"><span class="w-8 font-bold text-orange-600">C</span><span class="text-sm text-gray-700">安慰剂</span></div>
<div class="flex"><span class="w-8 font-bold text-green-600">O</span><span class="text-sm text-gray-700">5年内缺血性脑卒中发生率</span></div>
</div>
</div>
<!-- Sample Size Card -->
<div class="bg-white p-5 rounded-xl border border-gray-200 shadow-sm flex items-center justify-between">
<div>
<div class="text-xs font-bold text-gray-400 uppercase mb-1">样本量估算</div>
<div class="text-sm text-gray-600">Alpha=0.05, Power=0.8</div>
</div>
<div class="text-2xl font-bold text-indigo-600 font-mono">N=386</div>
</div>
</div>
<div class="mt-8 text-center text-sm text-gray-400">
<i data-lucide="arrow-left" class="w-4 h-4 inline mr-1"></i>
点击左侧 "开始撰写" 将基于以上数据生成文档
</div>
</div>
</div>
<!-- Panel 2: Document View (A4 Preview) -->
<div id="panel-doc" class="absolute inset-0 p-8 overflow-y-auto panel-hidden panel-transition bg-gray-200/50">
<!-- Toolbar -->
<div class="absolute top-4 right-8 flex gap-2 z-10">
<button class="bg-white hover:bg-gray-50 text-gray-700 px-3 py-1.5 rounded border border-gray-300 text-xs font-medium shadow-sm flex items-center gap-1">
<i data-lucide="copy" class="w-3 h-3"></i> 复制
</button>
<button class="bg-indigo-600 hover:bg-indigo-700 text-white px-3 py-1.5 rounded border border-transparent text-xs font-medium shadow-sm flex items-center gap-1">
<i data-lucide="download" class="w-3 h-3"></i> 导出 Word
</button>
</div>
<!-- Paper -->
<div class="a4-paper">
<h1 class="text-2xl font-bold text-center mb-10 pt-4">阿司匹林预防老年高血压患者缺血性脑卒中的<br>前瞻性、随机、双盲对照研究方案</h1>
<div class="space-y-6">
<div>
<h2 class="text-lg font-bold mb-2 border-b border-gray-800 pb-1">1. 研究背景 (Background)</h2>
<p class="text-justify indent-8">
脑卒中是全球范围内导致死亡和长期残疾的主要原因之一。流行病学数据显示在65岁以上的老年人群中高血压是缺血性脑卒中最重要的独立危险因素...
</p>
</div>
<div>
<h2 class="text-lg font-bold mb-2 border-b border-gray-800 pb-1">2. 研究目的 (Objectives)</h2>
<p class="text-justify indent-8">
<strong>主要目的:</strong> 评价每日口服 100mg 阿司匹林肠溶片对比安慰剂,在降低 65 岁及以上原发性高血压患者 5 年内缺血性脑卒中发生率方面的有效性。
</p>
<p class="text-justify indent-8 mt-2">
<strong>次要目的:</strong> 评估阿司匹林组与安慰剂组在全因死亡率、出血性脑卒中发生率及主要出血事件(如消化道出血)方面的差异。
</p>
</div>
<div>
<h2 class="text-lg font-bold mb-2 border-b border-gray-800 pb-1">3. 研究设计 (Study Design)</h2>
<p class="text-justify indent-8 typing-cursor">
本研究采用多中心、随机、双盲、安慰剂对照设计 (RCT)。入组后的受试者将通过中央随机系统以 1:1 的比例分配至试验组(阿司匹林)或对照组(安慰剂)。
</p>
</div>
</div>
</div>
<div class="h-20"></div> <!-- Bottom Spacer -->
</div>
</section>
</main>
<script>
lucide.createIcons();
const panelContext = document.getElementById('panel-context');
const panelDoc = document.getElementById('panel-doc');
const btnViewContext = document.getElementById('btn-view-context');
const btnViewDoc = document.getElementById('btn-view-doc');
const actionArea = document.getElementById('action-area');
const generatingMsg = document.getElementById('generating-msg');
const chatBox = document.getElementById('chat-box');
function switchTab(view) {
if (view === 'context') {
panelContext.classList.remove('panel-hidden');
panelContext.classList.add('panel-visible');
panelDoc.classList.remove('panel-visible');
panelDoc.classList.add('panel-hidden');
btnViewContext.classList.add('bg-white', 'shadow-sm', 'text-gray-700');
btnViewContext.classList.remove('text-gray-500');
btnViewDoc.classList.remove('bg-white', 'shadow-sm', 'text-gray-700');
btnViewDoc.classList.add('text-gray-500');
} else {
panelContext.classList.remove('panel-visible');
panelContext.classList.add('panel-hidden');
panelDoc.classList.remove('panel-hidden');
panelDoc.classList.add('panel-visible');
btnViewDoc.classList.add('bg-white', 'shadow-sm', 'text-gray-700');
btnViewDoc.classList.remove('text-gray-500');
btnViewContext.classList.remove('bg-white', 'shadow-sm', 'text-gray-700');
btnViewContext.classList.add('text-gray-500');
}
}
function startGeneration() {
// 1. 切换到文档视图
switchTab('doc');
// 2. Chat UI 更新
actionArea.style.display = 'none'; // 隐藏按钮
generatingMsg.classList.remove('hidden'); // 显示进度条
// 滚动到底部
chatBox.scrollTo({ top: chatBox.scrollHeight, behavior: 'smooth' });
}
// 绑定 Tab 点击事件
btnViewContext.onclick = () => switchTab('context');
btnViewDoc.onclick = () => switchTab('doc');
</script>
</body>
</html>