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,90 @@
# **Protocol Agent 降本增效开发指南:从 MVP 到最终形态**
核心原则: "Think Big, Start Small" (大处着眼,小处着手)。
当前状态: 感到 PRD 工作量过大,需要削减范围,聚焦核心价值。
目标: 将 V3 架构拆解为可落地的 MVP优先保证核心流程跑通暂缓非关键特性的开发。
## **1\. 心理减负:我们到底在做什么?**
其实 Protocol Agent V3 的核心代码并没有 PRD 看起来那么吓人。剥去那些高大上的名词,它的本质其实很简单:
1. **Orchestrator** ≈ 一个 switch/case 语句,根据当前步骤加载不同的 Prompt。
2. **Context** ≈ 往数据库存一个 JSON 对象。
3. **Action Card** ≈ 给前端发一个特定格式的 JSON让它渲染个按钮。
4. **Backend CoT** ≈ 把一大段 Prompt 拆成 3 段小 Prompt 拼起来。
**结论:** 技术本身不难(都是 CRUD 和字符串拼接),难的是逻辑梳理。只要逻辑理顺了,代码量其实可控。
## **2\. 战术削减MVP 阶段可以“砍”掉什么?(Phase 1 Scope)**
为了在第一阶段快速上线并验证价值,建议**暂时砍掉**以下 50% 的非核心功能:
| 功能模块 | PRD 定义 (V3) | MVP 策略 (Phase 1\) | 理由 (为什么砍掉?) |
| :---- | :---- | :---- | :---- |
| **🧠 大脑 (Brain)** | 复杂的意图识别 Router | **线性流转** | 用户只要完成 PICO就自动跳到样本量。不需要 AI 猜意图,强制按顺序来。 |
| **💾 记忆 (Memory)** | 复杂的增量提取算法 | **全量覆盖** | 不要做复杂的 diff 提取。每次对话结束后,直接让 AI 提取一次 PICO 覆盖存入 DB 即可。 |
| **📚 知识 (Knowledge)** | EKB V2 混合检索 | **Mock 数据 / 暂缓** | 第一版先用通用大模型的能力,暂不接自建知识库。或者仅接入最简单的关键词检索。 |
| **🛠️ 手脚 (Hands)** | 通用的 Action Card 协议 | **硬编码** | 不要写通用的 Deep Link 协议。直接硬编码if (stage \== 'SAMPLE') return { type: 'sample\_card' }。 |
| **⚙️ 配置 (Ops)** | 可视化 SOP 编排后台 | **JSON 文件** | 不要开发 Admin UI。直接在代码里写一个 const SOP\_CONFIG \= \[...\] JSON 对象。 |
| **🛡️ 风控 (Reflexion)** | 复杂的规则引擎 | **Prompt 校验** | 不写代码规则。直接在 System Prompt 里加一句:“请检查样本量是否过小”。 |
**MVP 核心交付物:**
1. 一个能聊天的界面。
2. 右侧能显示 PICO 表格(数据存 DB
3. 能弹出一个样本量计算器,算完能把数填回右侧表格。
(这就足够震撼了!)
## **3\. 分阶段实施路线图 (Step-by-Step)**
我们把原本宏大的 V3 计划,重新拆解为三个“舒适区”内的小目标。
### **✅ Phase 1: 核心链路打通 (2周)**
**目标:** "跑通 Happy Path"。用户能进来,能聊,能算,能存。
1. **数据库**:建 ProtocolContext 表 (JSONB)。
2. **后端**:写一个简单的 Orchestrator只支持 SCIENTIFIC\_QUESTION \-\> PICO \-\> SAMPLE\_SIZE 三个阶段的线性切换。
3. **前端**
* 左侧聊天。
* 右侧面板(只读,显示 Context JSON
* 一个写死的“样本量计算”卡片。
4. **验证**:用户说完 PICO右侧面板能变点击卡片能算数算完数右侧面板能更新。**(这就够了!)**
### **⏩ Phase 2: 配置化与逻辑增强 (2周)**
**目标:** "把硬编码变成配置"。
1. **配置化**:把 Phase 1 里写死在代码里的 Prompt 和 SOP提取到 prompt\_templates 表中。
2. **管理后台**:做一个极其简陋的 Admin 页面(哪怕是纯文本框编辑 JSON用来改 Prompt。
3. **知识库接入**:此时再把 EKB V2 接入进来,增强“选题评价”环节的专业度。
### **🚀 Phase 3: 平台化与反思 (2周)**
**目标:** "更聪明,更通用"。
1. **Reflexion**:加上 reflexionGuard开始对用户算出的结果指指点点“你这个样本量太小了”
2. **通用化**:重构代码,提取 BaseOrchestrator准备开发统计 Agent。
## **4\. 给开发团队的具体建议 (Actionable Advice)**
1. **不要过度设计 (YAGNI)**
* 不要一开始就写 interface IAgent不要写复杂的 AbstractFactory。
* 就写一个 ProtocolService.ts里面的逻辑哪怕有点面条代码也没关系先跑通。
2. **利用现有资源**
* **前端**:你们已经有了 Ant Design X聊天界面和卡片组件几乎是现成的。
* **后端**:你们已经有了 pg-boss异步提取任务直接套用以前的代码。
* **工具**:样本量计算器已经是现成的 Web 页面,只要加一行代码 window.parent.postMessage 就能和 Chat 通信(如果是 iframe 嵌入)或通过 URL 回调。
3. **关于“力不从心”**
* 这种感觉通常来自于\*\*“不确定性”\*\*。
* **解决办法**:把 PRD 里的所有“待定”、“可能”、“未来支持”全部划掉。只做确定的、具体的、最简单的功能。
## **5\. 总结**
**您不需要现在就造出完美的 AIA-OS。**
现在的任务只是:
用 Postgres 存一个 JSON让 LLM 根据这个 JSON 说话,再让一个网页计算器改一下这个 JSON。
仅此而已。把这个做出来就是巨大的成功。其他的可视化编排、混合检索、多Agent协同都是在这个核心循环跑通之后自然生长出来的枝叶。
**建议:** 拿着这份“减负版”计划,跟团队说:“我们先不看那份几十页的 PRD 了,我们先花两周时间,把这三个核心功能(聊天、状态同步、工具调用)做出来。”

View File

@@ -0,0 +1,158 @@
# **产品需求文档 (PRD): 研究方案制定智能体 (Protocol Agent) & AIA 基础平台**
文档版本: v1.0
状态: Final Draft
日期: 2026-01-20
项目代号: AIA-Protocol-V3
核心理念: Plan-Execute-Reflexion (规划-执行-反思) | Backend-Driven SOP (后端驱动) | Deep Link Action (深度链接)
## **1\. 项目背景与目标 (Background & Goals)**
### **1.1 背景**
当前 AIA 模块包含 7 个独立运行的 AI 问答工具科学问题、PICO、选题评价等。这种“散点式”的交互导致用户体验割裂上下文Context无法流转且缺乏对复杂逻辑如样本量计算的深度支持。
### **1.2 核心目标**
1. **统一入口 (Unified Entry)**:将 7 个模块整合为一个 **Protocol Agent**,通过自然语言对话引导用户完成全流程。
2. **长程记忆 (Long-term Memory)**:利用结构化数据 (JSONB) 维护一份“活的”研究方案,突破 LLM Context 限制。
3. **专业深度 (Professional Depth)**:引入 **自建知识库 (EKB)****后端配置化思维链 (CoT)**,确保输出符合医学逻辑。
4. **平台化基石 (Platform Foundation)**:沉淀一套通用的 Agent 开发框架 (AIA-OS),为未来的统计、清洗 Agent 打下地基。
## **2\. 产品核心理念:仿生架构 (The Bionic Architecture)**
产品设计严格遵循 **"Brain-Memory-Knowledge-Hands"** 四位一体架构:
| 组件 | 对应系统模块 | 核心职责 |
| :---- | :---- | :---- |
| **🧠 大脑 (Brain)** | **Orchestrator Engine** | 负责意图识别 (Router)、流程规划 (Plan)、思维链执行 (SOP) 和结果反思 (Reflexion)。 |
| **💾 记忆 (Memory)** | **ProtocolContext (DB)** | 存储结构化的方案数据PICO、N值、指标实现跨会话、跨工具的状态同步。 |
| **📚 知识 (Knowledge)** | **EKB (Self-built RAG)** | 基于 Postgres 的混合检索系统,提供 RCT 案例、指南、评分标准等外部知识。 |
| **🛠️ 手脚 (Hands)** | **Deep Link Action** | 通过 **Action Card** 协议唤起现有的 Web 工具(样本量计算器、清洗工具),并回写结果。 |
## **3\. 用户流程 (User Journey)**
### **3.1 典型交互路径**
1. **意图识别**:用户输入“我想做一个阿司匹林的研究”。
* *Brain*: 识别为 SCIENTIFIC\_QUESTION 阶段,加载对应 SOP。
2. **多轮对话 (Plan)**AI 引导用户完善 PICO 信息。
* *Memory*: 实时提取 P, I, C, O 并存入数据库。
* *UI*: 右侧 **状态面板 (State Panel)** 实时点亮已确认的字段。
3. **工具执行 (Execute)**:用户确认 PICO 后AI 判断需要计算样本量。
* *Brain*: 生成 Action Card。
* *UI*: 聊天窗显示“🚀 前往计算样本量”卡片。
* *Hands*: 用户点击跳转 \-\> 在工具页完成计算 \-\> 点击“同步”。
4. **反思校验 (Reflexion)**:数据回写后。
* *Brain*: 触发后端校验规则(如 N \< 20
* *UI*: AI 发送消息:“⚠️ 样本量过小,建议重新调整 Alpha 值。”
5. **阶段流转**:校验通过后,自动进入下一阶段(观察指标设计)。
## **4\. 功能需求详情 (Functional Requirements)**
### **4.1 核心交互界面 (Unified Interface)**
#### **F1. 流式对话窗口 (Chat Interface)**
* **支持格式**Markdown, Latex, **XML Thinking Block** (深度思考折叠区)。
* **Action Card 渲染**:识别后端返回的 type: action\_card JSON渲染为操作卡片。
* **Reflexion 反馈**识别系统级通知System Message以特殊样式如紫色边框展示 AI 的反思结果。
#### **F2. 实时状态面板 (Protocol State Panel)**
* **位置**:屏幕右侧(桌面端)/ 抽屉式(移动端)。
* **数据源**:实时订阅 ProtocolContext.data。
* **展示内容**
* 科学问题 (Text)
* PICO (Table)
* 样本量 (Highlight Number)
* 核心指标 (Tags)
* **交互**:支持用户**手动编辑**面板内容,编辑结果优先级高于 AI 提取。
### **4.2 编排引擎 (Orchestrator Backend)**
#### **F3. 动态 SOP 编排 (Backend CoT)**
* **机制**:从数据库 prompt\_templates.chain\_config 加载 SOP 步骤。
* **执行**:将 SOP 编译为 System Prompt强制 AI 输出 XML 思考标签。
* **配置项**:支持在 Admin 后台配置 Step 顺序、指令内容、输出 Key。
#### **F4. 反思卫士 (Reflexion Guard)**
* **触发时机**:工具回写数据后、阶段切换前。
* **校验逻辑**
* **硬规则**N \< 10 (Fail), Power \< 0.8 (Warning)。
* **软规则**Prompt-based check ("检查对照组设置是否符合伦理?")。
### **4.3 知识库集成 (EKB Integration)**
#### **F5. 混合检索 (Hybrid Search)**
* **场景**:选题评价、指标设计。
* **逻辑**
* SQL 过滤WHERE study\_design-\>\>'sample\_size' \> 500。
* Vector 检索ORDER BY embedding \<=\> query。
* Rerank调用重排序模型取 Top 3。
### **4.4 工具集成 (Deep Link)**
#### **F6. 通用回写协议 (Universal Sync API)**
* **API**: POST /api/v1/aia/context/:id/sync
* **Payload**: { "agent\_type": "PROTOCOL", "key": "sample\_size", "value": {...} }
* **工具端改造**:现有样本量计算器需支持解析 URL 参数 (?ctx\_id=...) 并在计算完成后调用回写 API。
## **5\. 后端配置与运营 (LLM Ops)**
为了支持“后端配置化”,需要构建一个简易的运营后台。
### **5.1 配置管理**
* **全局配置**Base Persona, Memory Schema。
* **阶段配置**7 个阶段的 Prompt, SOP (JSON Array), Reflexion Rules。
### **5.2 可观测性 (Trace)**
* **会话回放**:查看每一次 Router 决策、CoT 思考过程、Tool 调用结果。
* **Bad Case 修正**:基于 Trace ID 快速定位问题,调整 Prompt 后一键重测。
## **6\. 平台化扩展策略 (Platform Strategy)**
本项目的交付物不仅仅是一个 Agent而是一套 **AIA-OS 规范**
### **6.1 通用能力抽象**
* **Context Interface**:定义 IContext 接口,未来支持 StatsContext (统计)、CleaningContext (清洗)。
* **Action Card Protocol**action\_card JSON 结构在所有 Agent 中通用。
* **Orchestrator Core**:状态机引擎与业务逻辑解耦,未来只需编写不同的 Config 即可生成新 Agent。
## **7\. 技术架构 (Technical Architecture)**
* **前端**React 19 \+ Ant Design X \+ Tailwind CSS
* **后端**Node.js (Fastify)
* **语言**TypeScript (全栈)
* **数据库**PostgreSQL 15+ (pgvector 插件)
* **ORM**Prisma (Schema-first)
* **异步队列**pg-boss (处理耗时任务如 PDF 解析、结构化提取)
* **模型层**OpenAI Compatible API (DeepSeek/Qwen)
## **8\. 实施路线图 (Roadmap)**
### **Phase 1: 核心骨架 (Week 1\)**
* \[后端\] 建立 protocol\_contexts 和 prompt\_templates (含 CoT 字段) 表。
* \[后端\] 实现 ProtocolOrchestrator 基础状态机。
* \[前端\] 搭建 Chat \+ State Panel 布局。
### **Phase 2: 智能增强 (Week 2\)**
* \[后端\] 实现动态 CoT 组装与 XML 解析。
* \[后端\] 集成 EKB V2 混合检索服务。
* \[后端\] 实现异步 PICO 提取 Worker。
### **Phase 3: 闭环打通 (Week 3\)**
* \[前端\] 开发 Action Card 组件。
* \[工具\] 改造样本量计算器,支持 Deep Link 回写。
* \[后端\] 实现 Reflexion Guard 校验逻辑。
* \[Ops\] 上线简易配置后台 (Admin UI)。

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>

View File

@@ -0,0 +1,442 @@
<!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>