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
This commit is contained in:
2026-01-24 17:29:24 +08:00
parent 61cdc97eeb
commit 96290d2f76
345 changed files with 13945 additions and 47 deletions

View File

@@ -0,0 +1,381 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AIA Protocol Agent Ops - V3.0</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/lucide@latest"></script>
<!-- 引入 Alpine.js 用于简单的状态管理 -->
<script src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></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&family=JetBrains+Mono:wght@400;500&display=swap');
body { font-family: 'Inter', 'Noto Sans SC', sans-serif; }
.font-mono { font-family: 'JetBrains Mono', monospace; }
/* 滚动条 */
::-webkit-scrollbar { width: 6px; height: 6px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 3px; }
::-webkit-scrollbar-thumb:hover { background: #94a3b8; }
[x-cloak] { display: none !important; }
</style>
</head>
<body class="bg-slate-50 h-screen flex flex-col text-slate-800" x-data="{
currentTab: 'global-persona', // global-persona, global-memory, stage-prompt, stage-cot, stage-reflexion
currentStage: 'SAMPLE_SIZE_CALC'
}">
<!-- 1. 顶部栏 (Global Status) -->
<header class="bg-white border-b border-slate-200 h-16 px-6 flex items-center justify-between shadow-sm z-20 shrink-0">
<div class="flex items-center gap-3">
<div class="w-9 h-9 bg-indigo-600 rounded-lg flex items-center justify-center text-white shadow-md">
<i data-lucide="cpu" class="w-5 h-5"></i>
</div>
<div>
<h1 class="font-bold text-lg text-slate-800">Protocol Agent Ops</h1>
<div class="flex items-center gap-2 text-[10px] text-slate-500 uppercase tracking-wider font-medium">
<span>Env: Production</span>
<span class="w-1 h-1 bg-slate-300 rounded-full"></span>
<span>Orchestrator V3.0</span>
</div>
</div>
</div>
<div class="flex items-center gap-4">
<div class="flex items-center gap-2 px-3 py-1.5 bg-slate-100 rounded text-xs text-slate-600">
<span class="w-2 h-2 bg-green-500 rounded-full"></span>
API Status: Healthy
</div>
<button class="bg-indigo-600 hover:bg-indigo-700 text-white px-4 py-2 rounded-lg text-sm font-medium transition flex items-center gap-2 shadow-sm">
<i data-lucide="save" class="w-4 h-4"></i>
发布变更
</button>
</div>
</header>
<div class="flex flex-1 overflow-hidden">
<!-- 2. 左侧导航 (Global -> Stages) -->
<aside class="w-64 bg-white border-r border-slate-200 flex flex-col overflow-y-auto shrink-0">
<!-- Global Config Section -->
<div class="p-4">
<div class="text-xs font-bold text-slate-400 uppercase tracking-wider mb-3 px-2">Level 1: 全局设定</div>
<nav class="space-y-1">
<button @click="currentTab = 'global-persona'" :class="currentTab === 'global-persona' ? 'bg-indigo-50 text-indigo-700' : 'text-slate-700 hover:bg-slate-50'" class="w-full text-left block px-3 py-2 text-sm rounded-lg flex items-center gap-3 transition">
<i data-lucide="settings" class="w-4 h-4" :class="currentTab === 'global-persona' ? 'text-indigo-600' : 'text-slate-400'"></i>
全局人设 (Persona)
</button>
<button @click="currentTab = 'global-memory'" :class="currentTab === 'global-memory' ? 'bg-indigo-50 text-indigo-700' : 'text-slate-700 hover:bg-slate-50'" class="w-full text-left block px-3 py-2 text-sm rounded-lg flex items-center gap-3 transition">
<i data-lucide="database" class="w-4 h-4" :class="currentTab === 'global-memory' ? 'text-indigo-600' : 'text-slate-400'"></i>
记忆与知识库
</button>
</nav>
</div>
<!-- Stages Config Section -->
<div class="p-4 pt-0">
<div class="text-xs font-bold text-slate-400 uppercase tracking-wider mb-3 px-2 flex justify-between items-center">
Level 2: 阶段配置
<span class="bg-indigo-100 text-indigo-700 px-1.5 rounded text-[10px]">7</span>
</div>
<nav class="space-y-1">
<template x-for="(stage, idx) in ['科学问题梳理', 'PICO 分析', '选题评价', '观察指标设计', '样本量计算', 'CRF 设计', '方案撰写']">
<button
@click="currentStage = stage; currentTab = 'stage-cot'"
:class="currentStage === stage ? 'bg-indigo-50 text-indigo-700 border border-indigo-100 shadow-sm' : 'text-slate-600 hover:bg-slate-50'"
class="w-full text-left px-3 py-2 text-sm rounded-lg flex items-center gap-3 transition relative group"
>
<span
class="w-5 text-center text-xs font-mono"
:class="currentStage === stage ? 'text-indigo-500' : 'text-slate-400'"
x-text="'0' + (idx + 1)"
></span>
<span x-text="stage" :class="currentStage === stage ? 'font-medium' : ''"></span>
<span x-show="currentStage === stage" class="absolute right-3 w-1.5 h-1.5 bg-indigo-500 rounded-full"></span>
</button>
</template>
</nav>
</div>
</aside>
<!-- 3. 主内容区 -->
<main class="flex-1 flex flex-col bg-slate-50/50 overflow-hidden relative">
<!-- Global & Memory Tabs (Only show when Global is selected) -->
<div x-show="currentTab.startsWith('global')" class="px-8 py-6 border-b border-slate-200 bg-white">
<h2 class="text-xl font-bold text-slate-800 mb-1 flex items-center gap-2">
<i data-lucide="globe" class="w-5 h-5 text-indigo-600"></i>
全局配置中心
</h2>
<p class="text-sm text-slate-500">定义 Protocol Agent 的底层逻辑、记忆结构和通用知识库挂载。</p>
</div>
<!-- Stage Header (Only show when Stage is selected) -->
<div x-show="currentTab.startsWith('stage')" class="px-8 py-6 border-b border-slate-200 bg-white flex justify-between items-start">
<div>
<h2 class="text-xl font-bold text-slate-800 mb-1 flex items-center gap-2">
<span x-text="currentStage"></span>
<span class="text-xs bg-slate-100 text-slate-500 px-2 py-0.5 rounded font-mono font-normal">ID: SAMPLE_SIZE_CALC</span>
</h2>
<p class="text-sm text-slate-500">配置该阶段的任务指令、思维链逻辑和反思规则。</p>
</div>
<!-- Logic Layer Tabs -->
<div class="flex bg-slate-100 p-1 rounded-lg">
<button @click="currentTab = 'stage-prompt'" :class="currentTab === 'stage-prompt' ? 'bg-white text-indigo-600 shadow-sm' : 'text-slate-600 hover:text-slate-900'" class="px-4 py-1.5 text-sm font-medium rounded-md transition">Prompt (指令)</button>
<button @click="currentTab = 'stage-cot'" :class="currentTab === 'stage-cot' ? 'bg-white text-indigo-600 shadow-sm' : 'text-slate-600 hover:text-slate-900'" class="px-4 py-1.5 text-sm font-medium rounded-md transition flex items-center gap-2"><i data-lucide="git-merge" class="w-3 h-3"></i>思维链 (CoT)</button>
<button @click="currentTab = 'stage-reflexion'" :class="currentTab === 'stage-reflexion' ? 'bg-white text-indigo-600 shadow-sm' : 'text-slate-600 hover:text-slate-900'" class="px-4 py-1.5 text-sm font-medium rounded-md transition flex items-center gap-2"><i data-lucide="shield-check" class="w-3 h-3"></i>反思卫士</button>
</div>
</div>
<!-- Scrollable Content Area -->
<div class="flex-1 overflow-y-auto p-8">
<div class="max-w-4xl mx-auto space-y-6">
<!-- ========================================== -->
<!-- 1. 全局人设 (Global Persona) -->
<!-- ========================================== -->
<div x-show="currentTab === 'global-persona'" x-transition:enter="transition ease-out duration-200" x-transition:enter-start="opacity-0 translate-y-2">
<div class="bg-white border border-slate-200 rounded-xl p-6 shadow-sm mb-6">
<h3 class="font-bold text-slate-700 mb-4">Base System Prompt (基座人设)</h3>
<textarea class="w-full h-48 text-sm border border-slate-200 rounded-lg p-4 bg-slate-50 focus:bg-white focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500 outline-none resize-none font-mono leading-relaxed">
你是一个严谨的临床研究方法学专家助手 (Protocol Agent)。
你的核心目标是辅助用户制定一份科学、合规、可执行的临床研究方案。
核心原则:
1. 循证医学:所有建议必须基于可靠的医学证据。
2. 逻辑严密:在回答前,必须先进行逻辑推演 (Chain of Thought)。
3. 状态感知:始终基于 Context 中已确认的信息PICO等进行回答不要重复索要已知信息。
4. 语气风格:专业、客观、理性,避免过度热情的营销式语气。
</textarea>
<div class="flex justify-end mt-4">
<button class="text-sm text-indigo-600 hover:underline">查看历史版本 (v1.2)</button>
</div>
</div>
</div>
<!-- ========================================== -->
<!-- 2. 记忆与知识库 (Global Memory & Knowledge)-->
<!-- ========================================== -->
<div x-show="currentTab === 'global-memory'" x-transition:enter="transition ease-out duration-200" x-transition:enter-start="opacity-0 translate-y-2">
<!-- Memory Schema -->
<div class="bg-white border border-slate-200 rounded-xl p-6 shadow-sm mb-6">
<h3 class="font-bold text-slate-700 mb-4 flex items-center gap-2">
<i data-lucide="hard-drive" class="w-4 h-4 text-indigo-500"></i>
记忆结构定义 (Memory Schema)
</h3>
<div class="bg-slate-900 rounded-lg p-4 font-mono text-xs text-slate-300 overflow-x-auto">
<pre>{
"scientific_question": "string",
"pico": {
"p": "string (Patient)",
"i": "string (Intervention)",
"c": "string (Comparison)",
"o": "string (Outcome)"
},
"sample_size": {
"n_total": "number",
"power": "number",
"alpha": "number"
},
...
}</pre>
</div>
<p class="text-xs text-slate-500 mt-2">此 Schema 定义了 Agent 可以“记住”的结构化数据字段。修改此配置需同步更新后端 Prisma Schema。</p>
</div>
<!-- Knowledge Base -->
<div class="bg-white border border-slate-200 rounded-xl p-6 shadow-sm">
<h3 class="font-bold text-slate-700 mb-4 flex items-center gap-2">
<i data-lucide="library" class="w-4 h-4 text-green-500"></i>
知识库挂载 (EKB Mounting)
</h3>
<div class="space-y-3">
<div class="flex items-center justify-between p-3 border border-slate-200 rounded-lg bg-slate-50">
<div class="flex items-center gap-3">
<div class="w-8 h-8 bg-white border border-slate-200 rounded flex items-center justify-center">
<i data-lucide="book" class="w-4 h-4 text-indigo-500"></i>
</div>
<div>
<div class="text-sm font-medium text-slate-800">Clinical Guidelines (临床指南库)</div>
<div class="text-xs text-slate-500">包含 2020-2025 全球核心指南 | 12,400 篇</div>
</div>
</div>
<div class="flex items-center gap-2">
<span class="text-xs bg-green-100 text-green-700 px-2 py-0.5 rounded">Active</span>
<button class="text-slate-400 hover:text-slate-600"><i data-lucide="more-horizontal" class="w-4 h-4"></i></button>
</div>
</div>
<div class="flex items-center justify-between p-3 border border-slate-200 rounded-lg bg-slate-50">
<div class="flex items-center gap-3">
<div class="w-8 h-8 bg-white border border-slate-200 rounded flex items-center justify-center">
<i data-lucide="file-text" class="w-4 h-4 text-indigo-500"></i>
</div>
<div>
<div class="text-sm font-medium text-slate-800">RCT Protocols (RCT 方案库)</div>
<div class="text-xs text-slate-500">高质量 RCT 注册方案 | 5,300 篇</div>
</div>
</div>
<div class="flex items-center gap-2">
<span class="text-xs bg-green-100 text-green-700 px-2 py-0.5 rounded">Active</span>
<button class="text-slate-400 hover:text-slate-600"><i data-lucide="more-horizontal" class="w-4 h-4"></i></button>
</div>
</div>
</div>
</div>
</div>
<!-- ========================================== -->
<!-- 3. 阶段 Prompt (Stage Prompt) -->
<!-- ========================================== -->
<div x-show="currentTab === 'stage-prompt'" x-transition:enter="transition ease-out duration-200" x-transition:enter-start="opacity-0 translate-y-2">
<div class="bg-white border border-slate-200 rounded-xl p-6 shadow-sm">
<div class="flex justify-between items-center mb-4">
<h3 class="font-bold text-slate-700">Task Instruction (阶段任务指令)</h3>
<div class="text-xs text-slate-500">这将覆盖到 System Prompt 的 [Stage Instruction] 部分</div>
</div>
<textarea class="w-full h-64 text-sm border border-slate-200 rounded-lg p-4 bg-slate-50 focus:bg-white focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500 outline-none resize-none font-mono leading-relaxed">
当前任务阶段:【样本量计算】
你的目标是帮助用户确定合适的研究样本量。
请遵循以下逻辑:
1. 识别研究设计类型RCT、队列、病例对照
2. 询问或确认计算所需的统计学参数Alpha, Power, Effect Size, 组间比例)。
- 如果用户不知道,给出该领域的常用默认值(如 Alpha=0.05, Power=0.8)。
3. 一旦参数齐全,请生成 Action Card 引导用户调用计算器。
4. 计算完成后,对结果进行解读(是否过大/过小,是否符合伦理)。
注意:严禁自己口算样本量,必须调用工具。
</textarea>
</div>
</div>
<!-- ========================================== -->
<!-- 4. 思维链编排 (Stage CoT) -->
<!-- ========================================== -->
<div x-show="currentTab === 'stage-cot'" x-transition:enter="transition ease-out duration-200" x-transition:enter-start="opacity-0 translate-y-2">
<div class="flex items-center justify-between mb-4">
<h3 class="font-bold text-slate-700">思维链 SOP 可视化编排</h3>
<button class="text-xs bg-slate-100 hover:bg-slate-200 text-slate-600 px-3 py-1.5 rounded transition">查看 JSON 源码</button>
</div>
<!-- Step List -->
<div class="space-y-4">
<!-- Step 1 -->
<div class="bg-white border border-slate-200 rounded-xl p-5 shadow-sm hover:border-indigo-300 transition-colors group relative">
<div class="absolute left-0 top-0 bottom-0 w-1.5 bg-slate-300 rounded-l-xl group-hover:bg-indigo-500 transition-colors"></div>
<div class="flex gap-4">
<div class="shrink-0 flex flex-col items-center gap-2 pt-1">
<span class="w-6 h-6 rounded-full bg-slate-100 text-slate-500 text-xs font-bold flex items-center justify-center border border-slate-200">1</span>
</div>
<div class="flex-1 space-y-3">
<div class="flex justify-between items-center">
<input type="text" value="Check Parameters (参数完整性检查)" class="font-bold text-slate-800 bg-transparent border-none p-0 focus:ring-0 w-full text-sm">
<div class="flex items-center gap-2">
<div class="text-[10px] bg-slate-100 text-slate-500 px-2 py-0.5 rounded font-mono">Output: &lt;completeness_check&gt;</div>
<i data-lucide="trash-2" class="w-4 h-4 text-slate-300 hover:text-red-500 cursor-pointer"></i>
</div>
</div>
<textarea class="w-full text-xs border border-slate-200 rounded-lg p-2 bg-slate-50 focus:bg-white outline-none resize-none h-16">检查用户输入的 Context 中是否包含样本量计算所需的全部参数Alpha, Power, 预期效应量)。</textarea>
</div>
</div>
</div>
<!-- Step 2 -->
<div class="bg-white border border-slate-200 rounded-xl p-5 shadow-sm hover:border-indigo-300 transition-colors group relative">
<div class="absolute left-0 top-0 bottom-0 w-1.5 bg-slate-300 rounded-l-xl group-hover:bg-indigo-500 transition-colors"></div>
<div class="flex gap-4">
<div class="shrink-0 flex flex-col items-center gap-2 pt-1">
<span class="w-6 h-6 rounded-full bg-slate-100 text-slate-500 text-xs font-bold flex items-center justify-center border border-slate-200">2</span>
</div>
<div class="flex-1 space-y-3">
<div class="flex justify-between items-center">
<input type="text" value="Match Methodology (方法学匹配)" class="font-bold text-slate-800 bg-transparent border-none p-0 focus:ring-0 w-full text-sm">
<div class="flex items-center gap-2">
<div class="text-[10px] bg-slate-100 text-slate-500 px-2 py-0.5 rounded font-mono">Output: &lt;method_match&gt;</div>
<i data-lucide="trash-2" class="w-4 h-4 text-slate-300 hover:text-red-500 cursor-pointer"></i>
</div>
</div>
<textarea class="w-full text-xs border border-slate-200 rounded-lg p-2 bg-slate-50 focus:bg-white outline-none resize-none h-16">基于 PICO 中的 Study Design (RCT/Cohort),推荐最合适的样本量计算公式。若无法确定,推荐通用公式。</textarea>
</div>
</div>
</div>
<!-- Add Step -->
<button class="w-full py-3 border-2 border-dashed border-slate-300 rounded-xl text-slate-500 text-sm font-medium hover:border-indigo-500 hover:text-indigo-600 hover:bg-indigo-50 transition flex items-center justify-center gap-2">
<i data-lucide="plus" class="w-4 h-4"></i>
添加思考步骤
</button>
</div>
</div>
<!-- ========================================== -->
<!-- 5. 反思卫士 (Stage Reflexion) -->
<!-- ========================================== -->
<div x-show="currentTab === 'stage-reflexion'" x-transition:enter="transition ease-out duration-200" x-transition:enter-start="opacity-0 translate-y-2">
<div class="bg-blue-50 border border-blue-200 rounded-lg p-4 mb-6 flex gap-3">
<i data-lucide="shield-check" class="w-5 h-5 text-blue-600 shrink-0 mt-0.5"></i>
<div class="text-sm text-blue-800">
<p class="font-bold mb-1">Reflexion Guard (反思卫士)</p>
<p>当工具执行完毕并回写数据时后端会自动运行这些规则。如果校验失败Agent 将拒绝推进流程并报警。</p>
</div>
</div>
<div class="bg-white border border-slate-200 rounded-lg overflow-hidden shadow-sm">
<div class="px-4 py-3 bg-slate-50 border-b border-slate-200 flex justify-between items-center">
<h3 class="text-sm font-bold text-slate-700">规则列表 (Rule-based)</h3>
<button class="text-xs text-indigo-600 hover:underline">+ 添加规则</button>
</div>
<table class="w-full text-sm text-left">
<thead class="bg-slate-50 text-slate-500 border-b border-slate-200">
<tr>
<th class="px-4 py-3 font-medium">校验字段</th>
<th class="px-4 py-3 font-medium">逻辑</th>
<th class="px-4 py-3 font-medium">阈值</th>
<th class="px-4 py-3 font-medium">报警消息</th>
<th class="px-4 py-3 text-right">操作</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-100">
<tr>
<td class="px-4 py-3 font-mono text-indigo-600">sample_size.n</td>
<td class="px-4 py-3 text-slate-600">&lt;</td>
<td class="px-4 py-3 font-mono">10</td>
<td class="px-4 py-3 text-slate-600">样本量过小,无法通过伦理审查</td>
<td class="px-4 py-3 text-right text-slate-400 hover:text-indigo-600 cursor-pointer"><i data-lucide="edit-2" class="w-4 h-4 inline"></i></td>
</tr>
<tr>
<td class="px-4 py-3 font-mono text-indigo-600">sample_size.n</td>
<td class="px-4 py-3 text-slate-600">&gt;</td>
<td class="px-4 py-3 font-mono">100000</td>
<td class="px-4 py-3 text-slate-600">样本量过大,请确认可行性</td>
<td class="px-4 py-3 text-right text-slate-400 hover:text-indigo-600 cursor-pointer"><i data-lucide="edit-2" class="w-4 h-4 inline"></i></td>
</tr>
</tbody>
</table>
</div>
<div class="mt-6 bg-white border border-slate-200 rounded-lg p-6 shadow-sm">
<h3 class="text-sm font-bold text-slate-700 mb-3">AI 软校验 (Prompt-based)</h3>
<textarea class="w-full text-xs border border-slate-200 rounded-lg p-3 bg-slate-50 focus:bg-white outline-none resize-none h-20" placeholder="在此输入给 AI 的反思指令...">请结合用户的研究病种(如罕见病),判断该样本量是否合理。如果是罕见病,样本量小是可以接受的。</textarea>
</div>
</div>
</div>
</div>
</main>
</div>
<script>
lucide.createIcons();
</script>
</body>
</html>