Files
AIclinicalresearch/docs/03-业务模块/AIA-AI智能问答/00-系统设计/研究方案原型图0119.html
HaHafeng 96290d2f76 feat(aia): Implement Protocol Agent MVP with reusable Agent framework
Sprint 1-3 Completed (Backend + Frontend):

Backend (Sprint 1-2):
- Implement 5-layer Agent framework (Query->Planner->Executor->Tools->Reflection)
- Create agent_schema with 6 tables (agent_definitions, stages, prompts, sessions, traces, reflexion_rules)
- Create protocol_schema with 2 tables (protocol_contexts, protocol_generations)
- Implement Protocol Agent core services (Orchestrator, ContextService, PromptBuilder)
- Integrate LLM service adapter (DeepSeek/Qwen/GPT-5/Claude)
- 6 API endpoints with full authentication
- 10/10 API tests passed

Frontend (Sprint 3):
- Add Protocol Agent entry in AgentHub (indigo theme card)
- Implement ProtocolAgentPage with 3-column layout
- Collapsible sidebar (Gemini style, 48px <-> 280px)
- StatePanel with 5 stage cards (scientific_question, pico, study_design, sample_size, endpoints)
- ChatArea with sync button and action cards integration
- 100% prototype design restoration (608 lines CSS)
- Detailed endpoints structure: baseline, exposure, outcomes, confounders

Features:
- 5-stage dialogue flow for research protocol design
- Conversation-driven interaction with sync-to-protocol button
- Real-time context state management
- One-click protocol generation button (UI ready, backend pending)

Database:
- agent_schema: 6 tables for reusable Agent framework
- protocol_schema: 2 tables for Protocol Agent
- Seed data: 1 agent + 5 stages + 9 prompts + 4 reflexion rules

Code Stats:
- Backend: 13 files, 4338 lines
- Frontend: 14 files, 2071 lines
- Total: 27 files, 6409 lines

Status: MVP core functionality completed, pending frontend-backend integration testing

Next: Sprint 4 - One-click protocol generation + Word export
2026-01-24 17:29:24 +08:00

442 lines
23 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 V2.0 - 产品原型</title>
<script src="https://cdn.tailwindcss.com"></script>
<!-- 引入 Lucide 图标 -->
<script src="https://unpkg.com/lucide@latest"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Noto+Sans+SC:wght@300;400;500;700&display=swap');
body { font-family: 'Inter', 'Noto Sans SC', sans-serif; }
/* 模拟 Ant Design X 的 ultramodern 风格 */
.chat-bubble-ai {
background-color: #F3F4F6;
border-top-left-radius: 2px;
border-top-right-radius: 12px;
border-bottom-right-radius: 12px;
border-bottom-left-radius: 12px;
}
.chat-bubble-user {
background-color: #4F46E5; /* Indigo-600 */
color: white;
border-top-left-radius: 12px;
border-top-right-radius: 2px;
border-bottom-right-radius: 12px;
border-bottom-left-radius: 12px;
}
.reflexion-border {
border-left: 4px solid #9333EA; /* Purple-600 */
background: #F3E8FF; /* Purple-100 */
}
/* 状态面板动画 */
.flash-update {
animation: flash 1.5s ease-out;
}
@keyframes flash {
0% { background-color: #dbeafe; } /* blue-100 */
100% { background-color: transparent; }
}
/* 滚动条美化 */
::-webkit-scrollbar { width: 6px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 3px; }
::-webkit-scrollbar-thumb:hover { background: #94a3b8; }
</style>
</head>
<body class="bg-gray-100 h-screen flex flex-col overflow-hidden">
<!-- Top Navigation -->
<header class="bg-white border-b border-gray-200 h-14 flex items-center justify-between px-4 shadow-sm z-10">
<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 shadow-lg shadow-indigo-200">
<i data-lucide="bot" class="w-5 h-5"></i>
</div>
<div>
<h1 class="font-bold text-gray-800 text-sm">研究方案制定 Agent</h1>
<div class="flex items-center gap-1.5">
<span class="w-2 h-2 bg-green-500 rounded-full animate-pulse"></span>
<span class="text-xs text-gray-500">Orchestrator Online</span>
</div>
</div>
</div>
<div class="flex items-center gap-4">
<div class="hidden md:flex items-center gap-2 px-3 py-1.5 bg-gray-50 rounded-full border border-gray-200">
<span class="text-xs font-medium text-gray-500">当前阶段:</span>
<span class="text-xs font-bold text-indigo-600 bg-indigo-50 px-2 py-0.5 rounded border border-indigo-100">Step 5: 样本量计算</span>
</div>
<button class="p-2 hover:bg-gray-100 rounded-full text-gray-600">
<i data-lucide="settings" class="w-5 h-5"></i>
</button>
</div>
</header>
<!-- Main Workspace -->
<main class="flex-1 flex overflow-hidden">
<!-- Left: Chat Interface (70%) -->
<section class="flex-1 flex flex-col bg-white relative">
<!-- Chat History -->
<div id="chat-container" class="flex-1 overflow-y-auto p-4 md:p-6 space-y-6 pb-20">
<!-- Welcome Message -->
<div class="flex gap-4">
<div class="w-8 h-8 rounded-full bg-indigo-100 flex items-center justify-center shrink-0">
<i data-lucide="sparkles" class="w-4 h-4 text-indigo-600"></i>
</div>
<div class="space-y-1 max-w-[80%]">
<div class="text-xs text-gray-400 ml-1">Protocol Agent • 10:00 AM</div>
<div class="chat-bubble-ai p-4 text-sm text-gray-800 shadow-sm">
<p>您好!我是您的研究方案制定助手。</p>
<p class="mt-2">我已经记录了您关于<strong>“阿司匹林预防老年高血压患者中风”</strong>的科学问题和 PICO 信息。</p>
<p class="mt-2">根据当前的 RCT 设计,我们需要进行<strong>样本量计算</strong>以支撑伦理审查。您准备好开始了吗?</p>
</div>
</div>
</div>
<!-- User Message -->
<div class="flex gap-4 flex-row-reverse">
<div class="w-8 h-8 rounded-full bg-gray-200 flex items-center justify-center shrink-0">
<i data-lucide="user" class="w-4 h-4 text-gray-500"></i>
</div>
<div class="space-y-1 max-w-[80%]">
<div class="text-xs text-gray-400 text-right mr-1">User • 10:02 AM</div>
<div class="chat-bubble-user p-4 text-sm shadow-sm">
好的,帮我计算一下样本量。
</div>
</div>
</div>
<!-- AI Plan & Action Card (Deep Link) -->
<div class="flex gap-4">
<div class="w-8 h-8 rounded-full bg-indigo-100 flex items-center justify-center shrink-0">
<i data-lucide="brain-circuit" class="w-4 h-4 text-indigo-600"></i>
</div>
<div class="space-y-1 max-w-[80%]">
<div class="text-xs text-gray-400 ml-1">Protocol Agent • 10:02 AM</div>
<!-- The Action Card Component -->
<div class="border border-indigo-200 bg-indigo-50/50 rounded-xl p-4 shadow-sm w-[380px]">
<div class="flex items-start gap-3 mb-3">
<div class="bg-indigo-100 p-2 rounded-lg">
<i data-lucide="calculator" class="w-5 h-5 text-indigo-600"></i>
</div>
<div>
<h3 class="font-bold text-gray-900 text-sm">建议执行:样本量计算</h3>
<p class="text-xs text-gray-500 mt-0.5">基于双侧两样本均数比较 (RCT)</p>
</div>
</div>
<!-- Prefilled Parameters Preview -->
<div class="bg-white rounded border border-indigo-100 p-2 mb-3 space-y-1">
<div class="flex justify-between text-xs">
<span class="text-gray-500">Alpha (α):</span>
<span class="font-mono font-medium">0.05</span>
</div>
<div class="flex justify-between text-xs">
<span class="text-gray-500">Power (1-β):</span>
<span class="font-mono font-medium">0.80</span>
</div>
<div class="flex justify-between text-xs">
<span class="text-gray-500">Effect Size:</span>
<span class="font-mono font-medium">预估 0.15 vs 0.10</span>
</div>
</div>
<button onclick="openToolModal()" class="w-full bg-indigo-600 hover:bg-indigo-700 text-white text-sm font-medium py-2 rounded-lg transition flex items-center justify-center gap-2 shadow-sm shadow-indigo-200">
<span>🚀 前往计算工具</span>
<i data-lucide="external-link" class="w-3 h-3"></i>
</button>
<p class="text-[10px] text-center text-gray-400 mt-2">点击将打开工具面板,参数已自动预填</p>
</div>
</div>
</div>
<!-- Dynamic Content Will Be Appended Here -->
</div>
<!-- Input Area -->
<div class="p-4 border-t border-gray-100 bg-white absolute bottom-0 w-full">
<div class="relative">
<input type="text" placeholder="输入您的指令..." class="w-full pl-4 pr-12 py-3 bg-gray-50 border border-gray-200 rounded-xl focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:bg-white transition text-sm">
<button class="absolute right-2 top-1/2 -translate-y-1/2 p-1.5 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700">
<i data-lucide="send" class="w-4 h-4"></i>
</button>
</div>
</div>
</section>
<!-- Right: Protocol State Panel (30%) -->
<aside class="w-[350px] bg-gray-50 border-l border-gray-200 flex flex-col hidden md:flex">
<div class="p-4 border-b border-gray-200 bg-white flex justify-between items-center">
<h2 class="font-bold text-gray-800 text-sm flex items-center gap-2">
<i data-lucide="file-text" class="w-4 h-4 text-gray-500"></i>
方案状态 (Context)
</h2>
<span class="text-[10px] bg-green-100 text-green-700 px-2 py-0.5 rounded-full font-medium">Synced</span>
</div>
<div class="flex-1 overflow-y-auto p-4 space-y-4">
<!-- Section 1: Scientific Question -->
<div class="bg-white p-3 rounded-lg border border-gray-200 shadow-sm">
<h3 class="text-xs font-bold text-gray-400 uppercase mb-2 tracking-wider">01 科学问题</h3>
<p class="text-xs text-gray-700 leading-relaxed font-medium">
阿司匹林预防老年高血压患者中风的疗效与安全性研究
</p>
</div>
<!-- Section 2: PICO -->
<div class="bg-white p-3 rounded-lg border border-gray-200 shadow-sm">
<div class="flex justify-between items-center mb-2">
<h3 class="text-xs font-bold text-gray-400 uppercase tracking-wider">02 PICO</h3>
<button class="text-[10px] text-indigo-600 hover:underline">编辑</button>
</div>
<div class="space-y-2">
<div class="flex gap-2">
<span class="bg-blue-100 text-blue-700 px-1.5 py-0.5 rounded text-[10px] font-bold w-6 text-center">P</span>
<span class="text-xs text-gray-600">≥65岁原发性高血压患者</span>
</div>
<div class="flex gap-2">
<span class="bg-indigo-100 text-indigo-700 px-1.5 py-0.5 rounded text-[10px] font-bold w-6 text-center">I</span>
<span class="text-xs text-gray-600">阿司匹林肠溶片 100mg/d</span>
</div>
<div class="flex gap-2">
<span class="bg-orange-100 text-orange-700 px-1.5 py-0.5 rounded text-[10px] font-bold w-6 text-center">C</span>
<span class="text-xs text-gray-600">安慰剂</span>
</div>
<div class="flex gap-2">
<span class="bg-green-100 text-green-700 px-1.5 py-0.5 rounded text-[10px] font-bold w-6 text-center">O</span>
<span class="text-xs text-gray-600">5年内缺血性脑卒中发生率</span>
</div>
</div>
</div>
<!-- Section 3: Study Design -->
<div class="bg-white p-3 rounded-lg border border-gray-200 shadow-sm">
<h3 class="text-xs font-bold text-gray-400 uppercase mb-2 tracking-wider">03 研究设计</h3>
<div class="flex flex-wrap gap-2">
<span class="text-[10px] border border-gray-200 px-2 py-1 rounded bg-gray-50 text-gray-600">RCT</span>
<span class="text-[10px] border border-gray-200 px-2 py-1 rounded bg-gray-50 text-gray-600">双盲</span>
<span class="text-[10px] border border-gray-200 px-2 py-1 rounded bg-gray-50 text-gray-600">多中心</span>
</div>
</div>
<!-- Section 4: Sample Size (The Active One) -->
<div id="state-sample-size" class="bg-white p-3 rounded-lg border-2 border-indigo-100 shadow-sm transition-colors duration-500">
<h3 class="text-xs font-bold text-indigo-600 uppercase mb-2 tracking-wider flex justify-between">
04 样本量
<i data-lucide="loader-2" class="w-3 h-3 animate-spin"></i>
</h3>
<div class="flex items-center justify-between">
<span class="text-xs text-gray-500">总样本量 (N)</span>
<span id="n-value-display" class="text-lg font-bold text-gray-300 font-mono">待计算...</span>
</div>
</div>
</div>
</aside>
</main>
<!-- Modal: Simulated External Tool (Deep Link Destination) -->
<div id="tool-modal" class="fixed inset-0 bg-black/50 hidden z-50 flex items-center justify-center backdrop-blur-sm opacity-0 transition-opacity duration-300">
<div class="bg-white rounded-xl shadow-2xl w-[600px] overflow-hidden transform scale-95 transition-transform duration-300" id="tool-modal-content">
<!-- Tool Header -->
<div class="bg-gray-800 text-white p-4 flex justify-between items-center">
<div class="flex items-center gap-2">
<i data-lucide="calculator" class="w-5 h-5 text-indigo-400"></i>
<span class="font-bold">统计工具箱 v3.0 | 样本量计算器</span>
</div>
<button onclick="closeToolModal()" class="text-gray-400 hover:text-white">
<i data-lucide="x" class="w-5 h-5"></i>
</button>
</div>
<!-- Tool Body (Simulated Form) -->
<div class="p-6 bg-gray-50">
<div class="bg-blue-50 border border-blue-200 text-blue-800 text-xs p-3 rounded mb-4 flex gap-2">
<i data-lucide="info" class="w-4 h-4 shrink-0"></i>
已从 Protocol Agent 自动填充参数。修改参数后请点击“重新计算”。
</div>
<div class="grid grid-cols-2 gap-4 mb-4">
<div>
<label class="block text-xs font-bold text-gray-500 mb-1">检验类型</label>
<select class="w-full text-sm border-gray-300 rounded p-2 border">
<option>双侧两样本均数比较</option>
<option>卡方检验</option>
</select>
</div>
<div>
<label class="block text-xs font-bold text-gray-500 mb-1">检验水准 (α)</label>
<input type="number" value="0.05" class="w-full text-sm border-gray-300 rounded p-2 border">
</div>
<div>
<label class="block text-xs font-bold text-gray-500 mb-1">把握度 (1-β)</label>
<input type="number" value="0.80" class="w-full text-sm border-gray-300 rounded p-2 border">
</div>
<div>
<label class="block text-xs font-bold text-gray-500 mb-1">脱落率 (%)</label>
<input type="number" value="10" class="w-full text-sm border-gray-300 rounded p-2 border">
</div>
</div>
<!-- Effect Size Params -->
<div class="bg-white p-3 rounded border border-gray-200 mb-4">
<h4 class="text-xs font-bold text-gray-800 mb-2">效应量参数 (Effect Size)</h4>
<div class="flex gap-4">
<div class="flex-1">
<label class="block text-[10px] text-gray-400">实验组预估发生率</label>
<input type="number" value="0.10" class="w-full font-mono text-sm border-gray-300 rounded p-1 border">
</div>
<div class="flex-1">
<label class="block text-[10px] text-gray-400">对照组预估发生率</label>
<input type="number" value="0.15" class="w-full font-mono text-sm border-gray-300 rounded p-1 border">
</div>
</div>
</div>
<!-- Result Area -->
<div class="flex items-center justify-between bg-gray-100 p-4 rounded-lg mb-6">
<span class="text-sm font-bold text-gray-600">计算结果 (N):</span>
<span id="tool-result" class="text-2xl font-bold text-indigo-600 font-mono">---</span>
</div>
<!-- Actions -->
<div class="flex justify-end gap-3">
<button onclick="calculate()" class="px-4 py-2 bg-white border border-gray-300 text-gray-700 text-sm font-medium rounded hover:bg-gray-50">
试算
</button>
<button id="btn-sync" onclick="syncAndClose()" disabled class="px-4 py-2 bg-indigo-600 text-white text-sm font-medium rounded hover:bg-indigo-700 disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-2">
<i data-lucide="refresh-cw" class="w-4 h-4"></i>
确认并同步到方案
</button>
</div>
</div>
</div>
</div>
<script>
// 初始化 Lucide 图标
lucide.createIcons();
// Modal 控制逻辑
const modal = document.getElementById('tool-modal');
const modalContent = document.getElementById('tool-modal-content');
const btnSync = document.getElementById('btn-sync');
const toolResult = document.getElementById('tool-result');
function openToolModal() {
modal.classList.remove('hidden');
// 简单的淡入动画
setTimeout(() => {
modal.classList.remove('opacity-0');
modalContent.classList.remove('scale-95');
}, 10);
}
function closeToolModal() {
modal.classList.add('opacity-0');
modalContent.classList.add('scale-95');
setTimeout(() => {
modal.classList.add('hidden');
}, 300);
}
// 模拟计算过程
function calculate() {
toolResult.innerText = "Calculing...";
setTimeout(() => {
toolResult.innerText = "386";
btnSync.disabled = false;
btnSync.classList.add('animate-pulse');
}, 600);
}
// 核心:同步回写逻辑 (Reflexion Loop)
function syncAndClose() {
closeToolModal();
// 1. 更新右侧状态面板 (State Panel Update)
const stateCard = document.getElementById('state-sample-size');
const nValueDisplay = document.getElementById('n-value-display');
// 视觉反馈:闪烁一下
stateCard.classList.add('flash-update');
stateCard.classList.remove('border-indigo-100');
stateCard.classList.add('border-green-500'); // 变成绿色边框
// 移除 Loading 图标
stateCard.querySelector('i').remove();
// 更新数值
setTimeout(() => {
nValueDisplay.innerText = "N = 386";
nValueDisplay.classList.remove('text-gray-300');
nValueDisplay.classList.add('text-indigo-600');
}, 500);
// 2. 更新聊天界面 (Chat Update with Reflexion)
const chatContainer = document.getElementById('chat-container');
// 插入系统消息
const systemMsgHTML = `
<div class="flex justify-center my-4">
<span class="text-[10px] bg-gray-100 text-gray-500 px-2 py-1 rounded-full border border-gray-200">
<i data-lucide="check-circle" class="w-3 h-3 inline mr-1 text-green-500"></i>
系统消息: 用户已同步样本量结果 N=386
</span>
</div>
`;
chatContainer.insertAdjacentHTML('beforeend', systemMsgHTML);
// 模拟 AI 思考 (Reflexion)
setTimeout(() => {
const aiReflexionHTML = `
<div class="flex gap-4 animate-fade-in-up">
<div class="w-8 h-8 rounded-full bg-purple-100 flex items-center justify-center shrink-0 border border-purple-200">
<i data-lucide="check-check" class="w-4 h-4 text-purple-600"></i>
</div>
<div class="space-y-1 max-w-[80%]">
<div class="text-xs text-purple-500 ml-1 font-bold">Reflexion Guard • 刚刚</div>
<div class="chat-bubble-ai p-4 text-sm text-gray-800 shadow-sm reflexion-border">
<p class="font-bold text-purple-700 text-xs mb-1 mb-2 flex items-center gap-1">
<i data-lucide="shield-check" class="w-3 h-3"></i>
质量校验通过
</p>
<p>我已校验您的计算结果:</p>
<ul class="list-disc list-inside mt-1 text-gray-600 text-xs space-y-1">
<li>N=386 满足 RCT 设计对统计效能 (Power=0.8) 的要求。</li>
<li>参数设置符合心血管预防研究的常规标准。</li>
</ul>
<p class="mt-3">数值已同步到方案中。接下来,建议我们进行<strong>第五步CRF (病例报告表) 设计</strong>。</p>
</div>
</div>
</div>
`;
chatContainer.insertAdjacentHTML('beforeend', aiReflexionHTML);
lucide.createIcons(); // 重新渲染新插入的图标
// 自动滚动到底部
chatContainer.scrollTop = chatContainer.scrollHeight;
}, 1000);
}
// 点击遮罩关闭
modal.addEventListener('click', (e) => {
if (e.target === modal) closeToolModal();
});
</script>
</body>
</html>