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
This commit is contained in:
2026-01-25 19:16:36 +08:00
parent 4d7d97ca19
commit 303dd78c54
332 changed files with 6204 additions and 617 deletions

View File

@@ -1,16 +1,16 @@
# AIA AI智能问答模块 - 当前状态与开发指南
> **文档版本:** v3.0
> **文档版本:** v3.1
> **创建日期:** 2026-01-14
> **维护者:** AIA模块开发团队
> **最后更新:** 2026-01-24 🎉 **V3.0版本发布 - Protocol Agent MVP完**
> **最后更新:** 2026-01-25 🎉 **V3.1版本发布 - Protocol Agent MVP完整交付**
> **重大里程碑:**
> - 🏆 通用流式响应服务OpenAI Compatible
> - 🎨 现代感UI100%还原原型图V11
> - 🚀 Ant Design X 深度集成
> - ✨ 12个智能体配置完成
> - 🆕 Prompt管理系统集成灰度预览、版本管理
> - 🎉 **Protocol Agent MVP完可复用Agent框架+5阶段对话流程**
> - 🎉 **Protocol Agent MVP完整交付(一键生成研究方案+Word导出**
---
@@ -42,9 +42,9 @@ AIAAI Intelligent Assistant模块提供覆盖临床研究全生命周期
### 当前状态
- **开发阶段:** **V3.0 Protocol Agent MVP完**
- **架构版本:** V3.0通用Agent框架 + Protocol Agent
- **完成度:** 75%MVP核心流程完成,待前后端联调
- **开发阶段:** 🎉 **V3.1 Protocol Agent MVP完整交付**
- **架构版本:** V3.1通用Agent框架 + Protocol Agent + 一键生成
- **完成度:** 90%MVP完整可用,待生产测试
### ✅ V2.1 新增功能2026-01-18
@@ -85,7 +85,40 @@ AIAAI Intelligent Assistant模块提供覆盖临床研究全生命周期
- [x] 前端三栏布局5阶段状态面板100%还原原型图
- [x] 5阶段流程科学问题→PICO→研究设计→样本量→观察指标
**待完成:** 前后端联调、一键生成、Word导出
---
### 🎉 V3.1 Protocol Agent 完整交付2026-01-25
**一键生成研究方案:**
- [x] 动态双面板布局ResizableSplitPane可拖拽调整
- [x] 视图切换(研究摘要 / 完整方案)
- [x] A4 纸张预览效果DocumentPanel
- [x] 流式生成 + Markdown 渲染 + 滚动跟随
- [x] 12章节完整临床研究方案结构
**Word 文档导出:**
- [x] Python 微服务pypandoc + Pandoc
- [x] Node.js API 端点(/export/docx
- [x] 前端一键下载
**用户体验优化:**
- [x] StatePanel 折叠/展开CollapsibleContent
- [x] 科学问题/PICO/样本量/观察指标完整显示
- [x] 延迟创建对话(避免空记录)
- [x] 对话标题自动更新
- [x] Prompt 工程优化(阶段约束、数据凝练放宽)
**Bug 修复:**
- [x] 滚动条显示问题flex min-height: 0
- [x] 模型阶段混乱问题Prompt 增强)
- [x] 数据类型错误toArray 辅助函数)
- [x] 顶部标题两行、欢迎语过大、列表编号错误
**代码统计:**
- 前端新增:~1,200行累计~3,300行
- 后端新增:~400行累计~4,700行
- Python新增~110行
- **总计:~8,500行**
---

View File

@@ -0,0 +1,50 @@
# **UI 布局深度分析Chat vs. Document 比例问题**
**核心冲突:**
* **Chat**: 主要是控制台,指令短,但历史记录长。
* **Document**: 是交付物,内容宽,需要沉浸式阅读。
* **Context Panel**: 是辅助信息,卡片式,不需要太宽。
## **1\. 为什么“固定比例”不是最优解?**
如果强行统一比例,两头都不讨好:
* **如果统一为 70% (Chat) : 30% (Right)**
* *问题*: 右侧只能放 PICO 卡片。一旦开始生成文档A4 纸被挤成“长条”,用户必须横向滚动或者字变得极小,根本没法阅读。
* **如果统一为 40% (Chat) : 60% (Right)**
* *问题*: 在前期的 PICO 收集阶段,右侧面板(只有几个卡片)会留出大片空白,显得界面空旷、重心失衡。
## **2\. 推荐方案:动态自适应布局 (Dynamic Split View)**
我们要根据 **“用户当前的注意力焦点”** 自动调整比例。
### **阶段 A要素收集期 (Focus on Chat)**
* **状态**AI 在问,用户在答。右侧只是辅助展示“已提取的 PICO”。
* **比例****Chat 65% : Context 35%**
* **理由**:此时用户的视线主要在聊天流上,右侧只是个“仪表盘”。
### **阶段 B方案生成期 (Focus on Document)**
* **状态**AI 在写长文,用户在审阅。聊天框只用来发简单的修改指令。
* **比例****Chat 35% : Document 65%**
* **理由**:此时右侧的 A4 纸是主角。A4 纸的最佳阅读宽度通常需要 800px+,否则排版会乱(尤其是表格)。
### **阶段 C终极自由 (User Control)**
* **功能**:在两栏中间加一个 **Drag Handle (拖拽手柄)**,允许用户自己拖动宽度。
* **记忆**:记住用户的最后设置。
## **3\. 视觉优化细节**
1. **平滑过渡**:当从阶段 A 切换到阶段 B 时,使用 CSS transition 让分界线平滑移动,而不是突变。
2. **折叠按钮**:允许用户完全折叠左侧 Chat进入 **“全屏沉浸阅读模式”** (100% Doc)。
## **4\. 结论**
**不要统一。**
请采用 **“模式驱动的默认比例 \+ 手动拖拽”** 的策略。
* 默认:**PICO 模式 (60/40)** \-\> **生成模式 (35/65)**

View File

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

View File

@@ -896,3 +896,4 @@ export interface SlashCommand {

View File

@@ -496,3 +496,4 @@ class StatisticsAgentOrchestrator extends BaseAgentOrchestrator<StatisticsContex
```

View File

@@ -1125,3 +1125,4 @@ export function createProtocolAgentDependencies(
```

View File

@@ -1,57 +1,80 @@
# 一键生成研究方案 - 开发计划
> **版本**: v1.1
> **版本**: v2.0
> **创建日期**: 2026-01-24
> **最后更新**: 2026-01-24
> **最后更新**: 2026-01-25
> **负责人**: AI Assistant
> **状态**: 开发
> **状态**: 开发
---
## 一、功能概述
### 目标
基于 Protocol Agent 收集的 5 个核心要素,一键生成完整的临床研究方案文档,支持在线编辑和 Word 导出。
基于 Protocol Agent 收集的 5 个核心要素,一键生成完整的临床研究方案文档,支持对话式修改和 Word 导出。
### 核心价值
- 将 5-10 小时的方案撰写工作缩短至 30 分钟
- AI 生成 + 人工编辑,保证专业性和个性化
- AI 生成 + 对话式修改 + Word 精修,保证专业性和个性化
- 输出符合伦理委员会审查要求的标准文档
### 技术策略No-Editor First无编辑器优先
**核心洞察**
> 用户最终会在 Word 中精修,我们的价值是 **"AI 生成高质量初稿"**,而不是 **"提供一个编辑器"**。
---
## 二、交互设计:两阶段渐进式生成
## 二、交互设计:对话流生成
### 第一阶段:对话框生成摘要
### 用户流程
```
用户点击"一键生成研究方案"
用户完成 5 阶段要素收集
AI 在对话框中流式输出研究方案摘要约500字
点击"一键生成研究方案"
用户确认摘要 → 进入第二阶段
用户不满意 → 在对话中继续调整要素
AI 在对话框中流式输出完整方案Markdown
用户对话修改:"把样本量部分改一下..."
AI 流式输出修改后的章节
用户满意 → 点击"导出 Word"
下载符合伦理委员会格式的 Word 文档
```
**摘要内容**
- 研究题目
- 研究目的(主要/次要)
- 研究设计概述
- 样本量结论
- 主要结局指标
### 第二阶段:方案编辑器生成完整方案
### 交互示意
```
用户点击"生成完整方案"
跳转到方案编辑器页面
流式生成完整研究方案5000-8000字
用户在线编辑 / AI协作润色
导出 Word 文档
┌─────────────────────────────────────────────────────────────┐
│ ← 返回 全流程研究方案制定 │
├────────────────────────────────────────┬────────────────────┤
│ │ 📋 研究方案状态 │
│ [AI] 根据您确认的信息,我已生成研究方案: │ │
│ ✅ 科学问题 │
│ # 1. 研究题目 │ ✅ PICO要素 │
糖尿病患者使用二甲双胍与格列美脲... │ ✅ 研究设计 │
│ │ ✅ 样本量 │
│ # 2. 研究背景 │ ✅ 观察指标 │
│ 2型糖尿病是全球性公共卫生问题... │ │
│ │ ──────────────── │
│ ... │ │
│ │ [📥 导出 Word] │
│ ┌────────────────────────────────┐ │ [🔄 重新生成] │
│ │ 📥 导出Word │ 🔄 修改本章节 │ │ │
│ └────────────────────────────────┘ │ │
│ │ │
│ [用户] 把样本量从200改成300并说明原因 │ │
│ │ │
│ [AI] 好的,我已修改样本量部分: │ │
│ ## 6. 样本量估算 │ │
│ 根据前期预实验数据...样本量调整为300例 │ │
│ │ │
├────────────────────────────────────────┴────────────────────┤
│ [输入消息...] [发送] │
└─────────────────────────────────────────────────────────────┘
```
---
@@ -79,229 +102,344 @@ AI 在对话框中流式输出研究方案摘要约500字
---
## 四、方案编辑器设计
## 四、技术方案
### 布局结构
### 架构概览
```
┌────────────────────────────────────────────────────────────────┐
← 返回 📄 研究方案编辑器 [自动保存✓] [导出Word] [发布]
├──────────┬─────────────────────────────────────┬───────────────┤
📑 大纲 📝 编辑区 🤖 AI助手
│ │ │ │
可点击Notion 风格分块编辑选中文本后:
│ 快速跳转 │ 支持 Markdown + 富文本 - /ai 润色 │
Slash 命令 (/)- /ai 扩写 │
│ 拖拽排序章节 - /ai 精简
└──────────┴────────────────────────────────────────────────────
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
前端 │ │ Node.js │ │ Python
│ ChatArea │ ──▶ │ Backend │ ──▶ │ Service │
│ (Pandoc)
└─────────────┘ └─────────────┘ └─────────────┘
│ 流式输出 │ 调用 LLM │ Markdown→Word
Markdown 生成方案格式转换
────────────────────────────────────────────────────
│ 用户下载 Word │
└─────────────────────────────────────────────────────┘
```
### 核心功能
| 功能 | 说明 | 优先级 |
|------|------|--------|
| **Slash 命令** | 输入 / 唤起菜单,支持 /ai 调用生成 | P0 |
| **分块编辑** | 每个章节独立编辑,支持拖拽排序 | P0 |
| **大纲导航** | 左侧目录,点击跳转 | P0 |
| **自动保存** | 每30秒 + 失焦时保存 | P0 |
| **导出Word** | Tiptap JSON → docx | P0 |
| **AI润色** | 选中文本,/ai polish 优化 | P1 |
| **AI扩写** | 选中章节,/ai expand 补充 | P1 |
| **Ghost Text** | AI 生成时显示幽灵文字预览 | P1 |
| **版本历史** | 查看修改记录,回滚 | P2 |
---
## 五、技术方案
### 技术选型Novel (Fork)
**选型结论**Fork [Novel](https://github.com/steven-tey/novel) 源码,而非 npm 包引入。
**选择 Novel 的理由**
| 因素 | Novel 优势 |
|------|-----------|
| **AI 原生** | 专为 AI 写作设计,已处理流式生成的 UX 细节Ghost Text、光标锁定 |
| **标准 Tiptap** | 直接暴露 Tiptap 配置,可插入交互组件(样本量计算器、引用卡片) |
| **可控性** | 源码在手100% 可定制 UI 和逻辑 |
| **对接成本** | 替换 useCompletion → useAIStream 即可对接现有后端 |
**Fork 策略**
### 技术栈
```
不要 npm install novel
将 novel/packages/core/src 复制到:
frontend-v2/src/shared/components/ProtocolEditor/
目录结构:
├── index.tsx # 主组件
├── extensions/ # Tiptap 扩展
│ ├── ai-autocomplete.ts # 替换为 useAIStream
│ ├── slash-command.tsx # 保留 Slash 菜单
│ ├── citation.ts # Phase 2: 文献引用
│ └── medical-table.ts # Phase 2: 复杂表格
├── components/ # UI 组件
│ ├── EditorContent.tsx
│ ├── SlashMenu.tsx
│ └── BubbleMenu.tsx
└── styles/ # 样式(处理 Tailwind 冲突)
└── editor.css
```
**Tailwind CSS 冲突处理**
```css
/* 方案CSS 命名空间隔离 */
.novel-editor-scope {
/* Novel 的 Tailwind 样式限制在此作用域 */
}
```
### 技术栈总览
```
前端编辑器Novel (Fork) - 基于 Tiptap/ProseMirror
├── 优点AI 原生、Notion 风格、源码可控
├── 核心Slash 命令、Ghost Text、拖拽排序
前端:复用现有 ChatArea + useAIStream
├── 增加"导出 Word"按钮
├── 增加"修改本章节"交互
AI 调用:复用现有 useAIStream Hook
├── 替换 Novel 的 useCompletion (Vercel AI SDK)
├── 对接 /api/v1/aia/protocol-agent/generate
后端 (Node.js)
├── POST /api/v1/aia/protocol-agent/generate/full # 生成完整方案
├── POST /api/v1/aia/protocol-agent/regenerate # 重新生成指定章节
├── POST /api/v1/aia/protocol-agent/export/docx # 导出 Word
文档导出docx.js 或 @tiptap-pro/extension-export-docx
数据存储PostgreSQL (protocol_generations 表)
后端 (Python)
├── Pandoc 集成
├── Reference Doc 模板控制样式
```
### 数据模型
```sql
-- 方案生成记录表
CREATE TABLE protocol_generations (
id UUID PRIMARY KEY,
conversation_id UUID REFERENCES conversations(id),
user_id UUID REFERENCES users(id),
-- 内容
summary TEXT, -- 摘要(第一阶段)
full_content JSONB, -- 完整方案Tiptap JSON标准格式
-- 状态
status VARCHAR(20), -- draft | generating | completed
version INT DEFAULT 1,
-- 元数据
word_file_url TEXT, -- 导出的Word文件URL
created_at TIMESTAMP,
updated_at TIMESTAMP
);
-- 方案生成记录表(复用现有 protocol_generations
ALTER TABLE protocol_schema.protocol_generations ADD COLUMN IF NOT EXISTS
sections JSONB; -- 分章节存储,支持局部修改
-- sections 结构示例
{
"title": "糖尿病患者使用二甲双胍...",
"background": "2型糖尿病是全球性...",
"objectives": "...",
"design": "...",
"subjects": "...",
"sample_size": "...",
"implementation": "...",
"endpoints": "...",
"data_management": "...",
"safety": "...",
"statistics": "...",
"ethics": "...",
"timeline": "...",
"references": "..."
}
```
### API 设计
```typescript
// 第一阶段:生成摘要(流式)
POST /api/v1/aia/protocol-agent/generate/summary
Request: { conversationId: string }
Response: SSE
// 第二阶段:生成完整方案(流式)
// 生成完整方案(流式)
POST /api/v1/aia/protocol-agent/generate/full
Request: { conversationId: string }
Response: SSE
Response: SSE Markdown
// 保存编辑
PUT /api/v1/aia/protocol-agent/generation/:id
Request: { content: TiptapJSON }
// 导出Word
POST /api/v1/aia/protocol-agent/generation/:id/export
Response: { downloadUrl: string }
// AI编辑润色/扩写)
POST /api/v1/aia/protocol-agent/generation/:id/ai-edit
// 重新生成指定章节(流式)
POST /api/v1/aia/protocol-agent/regenerate
Request: {
action: 'polish' | 'expand' | 'simplify',
selectedText: string,
context: string
conversationId: string,
section: 'sample_size' | 'background' | ...,
instruction: "把样本量从200改成300"
}
Response: SSE
Response: SSE
// 导出 Word
POST /api/v1/aia/protocol-agent/export/docx
Request: { conversationId: string }
Response: Binary (application/vnd.openxmlformats-officedocument.wordprocessingml.document)
// 获取当前方案内容
GET /api/v1/aia/protocol-agent/generation/:conversationId
Response: { sections: {...}, fullMarkdown: "...", version: 3 }
```
---
## 五、核心实现
### 5.1 Python 微服务Pandoc 转换器
**Dockerfile 增加依赖:**
```dockerfile
RUN apt-get update && apt-get install -y pandoc
RUN pip install pypandoc
```
**Service 代码:**
```python
# python-service/app/services/doc_service.py
import pypandoc
import os
def convert_md_to_docx(markdown_text: str, output_path: str):
# 使用参考文档控制样式(字体、字号、页眉)
reference_doc = os.path.join(
os.path.dirname(__file__),
'assets/protocol_template.docx'
)
pypandoc.convert_text(
markdown_text,
'docx',
format='markdown',
outputfile=output_path,
extra_args=[f'--reference-doc={reference_doc}']
)
return output_path
```
**API 端点:**
```python
# python-service/app/routers/doc_router.py
from fastapi import APIRouter, Response
from ..services.doc_service import convert_md_to_docx
import tempfile
router = APIRouter()
@router.post("/convert/docx")
async def convert_to_docx(request: dict):
markdown = request.get("content", "")
with tempfile.NamedTemporaryFile(suffix='.docx', delete=False) as f:
output_path = f.name
convert_md_to_docx(markdown, output_path)
with open(output_path, 'rb') as f:
content = f.read()
return Response(
content=content,
media_type="application/vnd.openxmlformats-officedocument.wordprocessingml.document"
)
```
### 5.2 Node.js 后端:导出 API
```typescript
// backend/src/modules/agent/protocol/controllers/ProtocolGenerateController.ts
export class ProtocolGenerateController {
// 生成完整方案
async generateFull(request: FastifyRequest, reply: FastifyReply) {
const { conversationId } = request.body as { conversationId: string };
// 获取 Protocol Context5 阶段数据)
const context = await this.contextService.getContext(conversationId);
// 构建生成 Prompt
const prompt = this.buildGeneratePrompt(context);
// 流式生成
const streamingService = createStreamingService(reply, {
onChunk: async (chunk) => {
// 保存到数据库
await this.saveChunk(conversationId, chunk);
}
});
await streamingService.streamGenerate([
{ role: 'system', content: this.getSystemPrompt() },
{ role: 'user', content: prompt }
]);
}
// 重新生成指定章节
async regenerateSection(request: FastifyRequest, reply: FastifyReply) {
const { conversationId, section, instruction } = request.body;
const context = await this.contextService.getContext(conversationId);
const currentGeneration = await this.getGeneration(conversationId);
const prompt = `
请根据以下指令修改"${section}"章节:
用户指令:${instruction}
当前章节内容:
${currentGeneration.sections[section]}
研究背景信息:
${JSON.stringify(context)}
请输出修改后的完整章节内容Markdown 格式):
`;
// 流式输出修改后的章节
const streamingService = createStreamingService(reply);
await streamingService.streamGenerate([
{ role: 'system', content: '你是临床研究方案撰写专家...' },
{ role: 'user', content: prompt }
]);
}
// 导出 Word
async exportDocx(request: FastifyRequest, reply: FastifyReply) {
const { conversationId } = request.body as { conversationId: string };
// 获取完整 Markdown
const generation = await this.getGeneration(conversationId);
const markdown = this.sectionsToMarkdown(generation.sections);
// 调用 Python 微服务转换
const response = await axios.post(
`${PYTHON_SERVICE_URL}/convert/docx`,
{ content: markdown },
{ responseType: 'arraybuffer' }
);
reply.header('Content-Type', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document');
reply.header('Content-Disposition', 'attachment; filename="research_protocol.docx"');
return reply.send(response.data);
}
}
```
### 5.3 前端ChatArea 增强
```tsx
// frontend-v2/src/modules/aia/protocol-agent/components/ChatArea.tsx
// 在消息气泡中增加导出按钮
const ProtocolMessageActions = ({ content, conversationId }) => {
const [exporting, setExporting] = useState(false);
const handleExport = async () => {
setExporting(true);
try {
const response = await fetch('/api/v1/aia/protocol-agent/export/docx', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ conversationId })
});
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = '研究方案.docx';
a.click();
} finally {
setExporting(false);
}
};
return (
<div className="protocol-message-actions">
<Button
icon={<DownloadOutlined />}
onClick={handleExport}
loading={exporting}
>
Word
</Button>
<Button icon={<EditOutlined />}>
</Button>
</div>
);
};
```
---
## 六、开发计划
### Phase 1Fork Novel + 对接后端3天)
**目标**3天内跑通"编辑器 + AI 流式生成"
### Phase 1MVP 核心功能2 天)
| 天数 | 任务 | 交付物 |
|------|------|--------|
| Day 1 | Fork Novel 源码 + 项目集成 | 编辑器基础渲染、Slash 菜单可用 |
| Day 2 | 替换 AI 调用 → useAIStream | /ai 命令可调用后端生成 |
| Day 3 | 摘要生成 API + 编辑器页面路由 | 完整的"对话→摘要→编辑器"流程 |
| Day 1 | Python 微服务集成 Pandoc | `/convert/docx` API 可用 |
| Day 1 | Node.js 导出 API | `/export/docx` 端点完成 |
| Day 2 | 前端导出按钮 | ChatArea 增加导出功能 |
| Day 2 | 完整方案生成 Prompt | 生成质量优化 |
**Phase 1 交付**
-Notion 风格编辑器可用
-/ai 命令可调用 Protocol Agent
-支持 Markdown 导出
-一键生成完整研究方案Markdown
-导出符合格式的 Word 文档
-基础对话式修改
### Phase 2完整方案生成 + Word 导出3天)
### Phase 2分章节修改2 天)
| 天数 | 任务 | 交付物 |
|------|------|--------|
| Day 4 | 完整方案生成 API流式 | 编辑器中流式显示完整方案 |
| Day 5 | 自动保存 + 版本管理 | 数据库存储、草稿恢复 |
| Day 6 | Word 导出功能 | docx 文件下载 |
| Day 3 | 分章节存储模型 | sections JSONB 字段 |
| Day 3 | 章节重新生成 API | `/regenerate` 端点 |
| Day 4 | 前端章节选择器 | 点击章节触发修改对话 |
| Day 4 | 版本历史 | 修改记录存储 |
**Phase 2 交付**
-完整方案流式生成
-自动保存
-Word 导出
-分章节存储和修改
-智能识别修改章节
-版本历史追踪
### Phase 3医疗特性增强4天)
### Phase 3体验优化1 天)
| 天数 | 任务 | 交付物 |
|------|------|--------|
| Day 7 | 集成 Tiptap Table 扩展 | 复杂表格支持(访视排期表) |
| Day 8 | 开发 CitationBlock | 文献引用组件(对接 PKB |
| Day 9 | AI 润色/扩写优化 | 选中文本 AI 编辑体验 |
| Day 10 | 测试 + UI 美化 | 完整功能测试 |
**Phase 3 交付**
- ✅ 复杂表格支持
- ✅ 文献引用功能
- ✅ AI 协作编辑完善
| Day 5 | Word 模板优化 | 符合伦理委员会格式 |
| Day 5 | UI 美化 | 导出进度、预览 |
| Day 5 | 测试与修复 | 完整功能测试 |
---
## 七、风险与依赖
## 七、与编辑器方案对比
| 风险 | 影响 | 缓解措施 |
|------|------|----------|
| Tailwind CSS 冲突 | 样式混乱 | CSS 命名空间隔离 |
| Novel 源码维护成本 | 后续升级困难 | 代码量小(~2000行),可独立维护 |
| LLM 生成质量不稳定 | 方案内容不专业 | 优化 Prompt + 人工模板 |
| 长文本生成超时 | 用户等待过久 | 分章节流式生成 |
| Word 导出格式问题 | 格式错乱 | 预设 Word 模板 |
| 维度 | 无编辑器方案(当前) | 编辑器方案(备选) |
|------|---------------------|-------------------|
| **开发周期** | 5 天 | 10 天 |
| **维护成本** | 低(复用现有组件) | 高Fork Novel |
| **格式控制** | ⭐⭐⭐⭐⭐ Pandoc 精确控制 | ⭐⭐⭐ 需自己处理 |
| **修改体验** | 对话式(稍慢) | 直接编辑(更快) |
| **用户习惯** | 符合Word 精修) | 需适应新界面 |
| **适用场景** | MVP / 快速验证 | 高频编辑场景 |
### 依赖项
### 何时考虑上编辑器?
- [x] Protocol Agent 5阶段数据收集已完成
- [x] StreamingService 流式输出(已完成)
- [x] useAIStream Hook已完成
- [ ] Novel 源码 Fork待执行
- [ ] docx.js 导出功能(待开发)
### 兜底方案
如果 Fork 的 Novel 代码难以维护:
- 可回退到 **Tiptap Headless**
- 用 Ant Design 重写 UI
- **数据模型 (Tiptap JSON) 完全兼容**,用户数据不丢失
如果用户反馈以下问题,再考虑引入编辑器:
1. "对话修改太慢,我想直接改某个字"
2. "我需要频繁调整段落顺序"
3. "我想同时看到多个章节并对比"
---
@@ -309,43 +447,69 @@ Response: SSE 流式输出
### 功能验收
- [ ] 点击"一键生成",对话框流式输出摘要
- [ ] 点击"生成完整方案",跳转编辑器并流式生成
- [ ] 编辑器支持 Slash 命令 (/)
- [ ] 编辑器支持章节拖拽排序
- [ ] 选中文本可调用 AI 润色/扩写
- [ ] 可导出标准格式的 Word 文档
- [ ] 支持复杂表格编辑
- [ ] 点击"一键生成",对话框流式输出完整方案
- [ ] 点击"导出 Word",下载格式正确的 docx 文件
- [ ] 输入"修改样本量"AI 只重新生成该章节
- [ ] Word 文档符合伦理委员会格式要求
### 性能指标
| 指标 | 目标 |
|------|------|
| 摘要生成时间 | < 30秒 |
| 完整方案生成时间 | < 3分钟 |
| 自动保存延迟 | < 1秒 |
| Word导出时间 | < 5秒 |
| 完整方案生成时间 | < 2 分钟 |
| Word 导出时间 | < 5 秒 |
| 章节修改响应时间 | < 30 秒 |
---
## 九、后续迭代
## 九、Word 模板设计
- **v1.1**: 方案模板库(不同研究类型
- **v1.2**: 多人协作编辑
- **v1.3**: 方案审核流程
- **v1.4**: 与伦理系统对接
### 格式要求(伦理委员会标准
```
字体:正文宋体小四,标题黑体三号
行距1.5 倍
页边距:上下 2.54cm,左右 3.17cm
页眉:研究方案 + 版本号
页脚:页码
```
### Reference Doc 制作
1. 在 Word 中创建符合格式的模板文档
2. 定义样式标题1、标题2、正文、表格等
3. 保存为 `protocol_template.docx`
4. 放置到 `python-service/app/services/assets/`
---
## 十、参考文档
## 十、后续迭代
- [Novel GitHub](https://github.com/steven-tey/novel)
- [Tiptap 官方文档](https://tiptap.dev/)
- [编辑器选型深度评估](./编辑器选型深度评估与落地建议.md)
- [Novel vs BlockNote 对比分析](./Novel_vs_BlockNote_深度对比分析.md)
### v1.1:编辑器增强(可选)
如果用户反馈需要更直接的编辑体验:
- 引入 Novel/Tiptap 编辑器
- 支持实时编辑 + AI 辅助
### v1.2:模板库
- 不同研究类型的方案模板
- RCT、队列研究、病例对照等
### v1.3:协作与审核
- 多人协作编辑
- 方案审核流程
- 与伦理系统对接
---
## 十一、参考文档
- [基于对话流的文档生成技术方案](./基于对话流的文档生成与导出技术方案.md)
- [编辑器选型深度评估](./编辑器选型深度评估与落地建议.md)(备选方案)
- [Novel vs BlockNote 对比分析](./Novel_vs_BlockNote_深度对比分析.md)(备选方案)
---
**文档更新记录**
- 2026-01-24 v1.0: 初始版本技术选型Tiptap/BlockNote
- 2026-01-24 v1.1: 技术选型改为 **Novel (Fork)**,更新开发计划
- 2026-01-24 v1.1: 技术选型改为 Novel (Fork)
- 2026-01-25 v2.0: **重大调整:采用无编辑器方案**,优先实现对话流生成 + Word 导出

View File

@@ -0,0 +1,349 @@
# 一键生成研究方案 - 开发计划 V2
> **版本**: 2.0
> **日期**: 2026-01-25
> **状态**: 规划中
> **前置依赖**: Pandoc Word 导出已完成 ✅
---
## 一、设计目标
### 1.1 核心理念
```
"关键要素就绪 → 一键生成方案"
```
**解耦设计**:方案生成功能**不依赖** AI 对话流程。无论用户通过 AI 对话收集要素,还是手动填写要素,只要必填项完成即可生成。
### 1.2 用户场景
| 场景 | 用户行为 | 系统响应 |
|------|---------|---------|
| **场景 A** | 用户通过 AI 对话完成 5 阶段 | 自动提示可生成,点击生成 |
| **场景 B** | 用户手动填写关键要素 | 校验通过后,直接点击生成 |
| **场景 C** | 用户对生成内容不满意 | 针对某章节"讨论与优化" |
### 1.3 必填要素校验
| 要素 | 是否必填 | 说明 |
|------|---------|------|
| 科学问题 | ✅ 必填 | 方案的核心目的 |
| PICO | ✅ 必填 | 研究框架基础 |
| 研究设计 | ✅ 必填 | 决定方案结构 |
| 观察指标 | ✅ 必填 | 结局评价依据 |
| 样本量 | ⚪ 可选 | 可后续补充 |
**校验规则**4/5 必填项完成 → 可生成基础方案
---
## 二、UI 架构设计
### 2.1 双阶段动态布局
```
┌─────────────────────────────────────────────────────────────┐
│ Protocol Agent Page │
├─────────────────────────────────────────────────────────────┤
│ │
│ 阶段 A: 要素收集期 │
│ ┌─────────────────────────┬───────────────────────────┐ │
│ │ ChatPanel │ ContextPanel │ │
│ │ 65% │ 35% │ │
│ │ │ ┌───────────────────┐ │ │
│ │ [AI 对话区域] │ │ 科学问题 [✓][✏️] │ │ │
│ │ │ │ PICO [✓][✏️] │ │ │
│ │ │ │ 研究设计 [✓][✏️] │ │ │
│ │ │ │ 样本量 [○] │ │ │
│ │ │ │ 观察指标 [✓][✏️] │ │ │
│ │ │ ├───────────────────┤ │ │
│ │ │ │ 进度: 80% (4/5) │ │ │
│ │ │ │ [✨ 一键生成方案] │ │ │
│ │ │ │ [📥 导出 Word] │ │ │
│ │ │ └───────────────────┘ │ │
│ └─────────────────────────┴───────────────────────────┘ │
│ ↕ 拖拽手柄 │
│ │
│ 阶段 B: 方案生成期 │
│ ┌───────────────────┬─────────────────────────────────┐ │
│ │ ChatPanel │ DocumentPanel │ │
│ │ 35% │ 65% │ │
│ │ │ ┌─────────────────────────┐ │ │
│ │ [正在撰写...] │ │ [复制] [导出 Word] │ │ │
│ │ 1. 研究背景 ✓ │ ├─────────────────────────┤ │ │
│ │ 2. 研究目的 ✓ │ │ │ │ │
│ │ 3. 研究设计 ⟳ │ │ ══ A4 文档预览 ══ │ │ │
│ │ │ │ │ │ │
│ │ [修改指令输入] │ │ 1. 研究背景 │ │ │
│ │ │ │ [讨论与优化] │ │ │
│ │ │ │ 内容... │ │ │
│ │ │ │ │ │ │
│ │ │ │ 2. 研究目的 │ │ │
│ │ │ │ [讨论与优化] │ │ │
│ │ │ │ 内容... │ │ │
│ │ │ │ │ │ │
│ └───────────────────┴─────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
```
### 2.2 视图切换机制
```typescript
type ViewMode = 'context' | 'document';
// 切换条件
const shouldSwitchToDocument = (event: string) => {
return event === 'GENERATION_STARTED';
};
// 布局比例
const LAYOUT_RATIO = {
context: { chat: 65, right: 35 },
document: { chat: 35, right: 65 },
};
```
### 2.3 视图切换按钮
顶部 Header 区域添加视图切换:
```
[视图: 关键要素] [视图: 完整方案]
```
---
## 三、功能模块详细设计
### 3.1 ContextPanel (要素面板) - 已有,需增强
**现有功能**
- ✅ 五阶段卡片展示
- ✅ 完成状态编辑按钮
- ✅ 导出 Word 按钮
**需新增**
1. **一键生成按钮位置调整**:移到面板顶部醒目位置
2. **生成条件提示**:显示 `已完成 4/5 必填项,可生成方案`
3. **手动添加要素入口**:未完成阶段也显示 [ 添加] 按钮
### 3.2 DocumentPanel (文档面板) - 新增
**核心功能**
- A4 纸张预览效果(宋体、首行缩进、分节标题)
- 流式输出 + 打字机光标
- 分章节"讨论与优化"按钮
- 顶部工具栏:复制、导出 Word
**章节结构**
```markdown
# 研究方案标题
## 1. 研究背景 (Background)
## 2. 研究目的 (Objectives)
## 3. 研究设计 (Study Design)
## 4. 研究对象 (Subjects)
## 5. 样本量估算 (Sample Size)
## 6. 研究实施步骤 (Implementation)
## 7. 观察指标 (Endpoints)
## 8. 数据管理与质量控制
## 9. 统计分析计划
## 10. 伦理与知情同意
## 11. 研究时间表
## 12. 参考文献
```
### 3.3 "讨论与优化" 功能
**交互流程**
1. 用户点击某章节的 [讨论与优化] 按钮
2. 左侧 Chat 自动切换到"章节讨论模式"
3. AI 理解上下文,针对该章节进行对话
4. 用户可发送修改建议
5. AI 重新生成该章节内容
6. 右侧文档实时更新
**技术实现**
```typescript
interface SectionContext {
sectionId: string; // 如 'background', 'objectives'
sectionTitle: string; // 如 '研究背景'
currentContent: string; // 当前内容
conversationMode: 'section'; // 章节讨论模式
}
```
### 3.4 动态布局组件
```typescript
// ResizableSplitPane.tsx
interface ResizableSplitPaneProps {
defaultRatio: number; // 默认比例 (0-100)
minRatio: number; // 最小比例
maxRatio: number; // 最大比例
onRatioChange: (ratio: number) => void;
leftPanel: React.ReactNode;
rightPanel: React.ReactNode;
}
```
---
## 四、开发计划 (分阶段)
### Phase 1: 核心功能 MVP (5天)
| 天数 | 任务 | 交付物 |
|------|------|--------|
| Day 1 | 动态布局组件 | `ResizableSplitPane` + 视图切换按钮 |
| Day 2 | DocumentPanel 基础 | A4 预览 + 流式渲染 + 打字机效果 |
| Day 3 | 生成 API 对接 | 分章节流式生成 + 前后端联调 |
| Day 4 | 生成按钮 & 校验 | 必填项校验 + 双位置生成按钮 |
| Day 5 | 集成测试 & Bug Fix | 端到端测试 |
**Phase 1 交付标准**
- [x] 用户可从 ContextPanel 点击生成
- [x] 方案在 DocumentPanel 流式展示
- [x] 布局可根据阶段自动切换
- [x] Word 导出功能正常
### Phase 2: "讨论与优化" (3天)
| 天数 | 任务 | 交付物 |
|------|------|--------|
| Day 6 | 章节按钮 UI | 每个章节显示 [讨论与优化] 按钮 |
| Day 7 | 章节对话模式 | Chat 切换到章节讨论模式 + API |
| Day 8 | 章节重新生成 | 单章节流式更新 + 文档同步 |
**Phase 2 交付标准**
- [x] 用户点击章节按钮,左侧进入讨论模式
- [x] AI 理解当前章节上下文
- [x] 重新生成的内容替换原章节
### Phase 3: 体验优化 (2天)
| 天数 | 任务 | 交付物 |
|------|------|--------|
| Day 9 | 手动添加要素 | 未完成阶段的 [ 添加] 入口 |
| Day 10 | 拖拽 & 记忆 | 拖拽手柄 + localStorage 记忆比例 |
---
## 五、技术要点
### 5.1 流式渲染方案
```typescript
// 使用现有 useAIStream hook
const { streamingContent, isStreaming } = useAIStream({
onChunk: (chunk) => {
// 实时更新 DocumentPanel
updateDocumentContent(chunk);
},
onComplete: (fullContent) => {
// 保存完整方案
saveProtocol(fullContent);
},
});
```
### 5.2 章节解析
```typescript
// 将流式 Markdown 解析为章节
const parseMarkdownSections = (markdown: string): Section[] => {
const sections: Section[] = [];
const regex = /^## (\d+)\. (.+)$/gm;
// ... 解析逻辑
return sections;
};
```
### 5.3 状态管理
```typescript
interface ProtocolGenerationState {
viewMode: 'context' | 'document';
layoutRatio: number;
generationStatus: 'idle' | 'generating' | 'completed';
currentSection: string | null; // 当前讨论的章节
sections: Section[];
canGenerate: boolean; // 基于必填项校验
}
```
---
## 六、文件结构
```
frontend-v2/src/modules/aia/protocol-agent/
├── components/
│ ├── ProtocolAgentPage.tsx # 主页面 (修改)
│ ├── ChatPanel.tsx # 聊天面板 (已有)
│ ├── StatePanel.tsx # 要素面板 (修改)
│ ├── DocumentPanel.tsx # 文档面板 (新增)
│ ├── ResizableSplitPane.tsx # 可拖拽分栏 (新增)
│ ├── SectionRenderer.tsx # 章节渲染器 (新增)
│ └── ViewSwitcher.tsx # 视图切换器 (新增)
├── hooks/
│ ├── useProtocolGeneration.ts # 方案生成 hook (新增)
│ └── useLayoutRatio.ts # 布局比例 hook (新增)
├── styles/
│ ├── protocol-agent.css # 主样式 (修改)
│ └── document-panel.css # 文档面板样式 (新增)
└── types/
└── index.ts # 类型定义 (修改)
```
---
## 七、风险与应对
| 风险 | 影响 | 应对措施 |
|------|------|---------|
| 长文档流式渲染性能 | 卡顿 | 虚拟滚动 + 分片渲染 |
| 章节解析复杂性 | 解析错误 | 规范化 Markdown 模板 |
| 布局切换体验 | 突兀 | CSS transition 平滑过渡 |
---
## 八、验收标准
### MVP (Phase 1)
- [ ] 4/5 必填项完成后,生成按钮可点击
- [ ] 点击生成后,自动切换到文档视图 (35:65)
- [ ] 文档以 A4 纸样式流式展示
- [ ] 生成完成后可导出 Word
### 完整版 (Phase 1+2+3)
- [ ] 每个章节有"讨论与优化"按钮
- [ ] 章节可单独重新生成
- [ ] 未完成阶段可手动添加要素
- [ ] 布局比例可拖拽调整并记忆
---
## 九、附录
### A. 参考原型
- `AIclinicalresearch/docs/03-业务模块/AIA-AI智能问答/00-系统设计/研究方案一键生成.html`
### B. 相关文档
- `UI_Layout_Ratio_Analysis.md` - 布局比例分析
- `基于对话流的文档生成与导出技术方案.md` - 技术方案
### C. 已完成依赖
- ✅ Pandoc 系统安装 (3.8.3)
- ✅ pypandoc Python 包
- ✅ Python `/api/convert/docx` 端点
- ✅ Node.js `ProtocolExportService`
- ✅ 前端导出按钮

View File

@@ -0,0 +1,110 @@
# **基于 Novel (Tiptap) 的 CRF 表单扩展开发指南**
**目标**: 在编辑器中实现 "填空"、"单选"、"日期选择" 等 CRF 控件,并支持导出 Word。
## **1\. 自定义 CRF 节点开发 (Tiptap Extensions)**
我们需要开发一组 **Node Extensions**,让编辑器理解表单元素。
### **1.1 填空输入框 (UnderlineInput)**
// extensions/underline-input.tsx
import { Node, mergeAttributes } from '@tiptap/core';
import { ReactNodeViewRenderer, NodeViewWrapper } from '@tiptap/react';
export const UnderlineInput \= Node.create({
name: 'underlineInput',
group: 'inline',
inline: true,
atom: true, // 作为一个整体,不可分割
addAttributes() {
return {
placeholder: { default: '请输入...' },
value: { default: '' },
}
},
parseHTML() {
return \[{ tag: 'span\[data-type="input"\]' }\]
},
renderHTML({ HTMLAttributes }) {
return \['span', mergeAttributes(HTMLAttributes, { 'data-type': 'input' })\]
},
// React 组件渲染
addNodeView() {
return ReactNodeViewRenderer(({ node, updateAttributes }) \=\> {
return (
\<NodeViewWrapper as="span" className="inline-block mx-1"\>
\<input
className="border-b border-black outline-none px-1 w-24 bg-transparent text-center focus:border-blue-500"
placeholder={node.attrs.placeholder}
value={node.attrs.value}
onChange={(e) \=\> updateAttributes({ value: e.target.value })}
/\>
\</NodeViewWrapper\>
)
})
},
});
### **1.2 单选组 (RadioGroup)**
// extensions/radio-group.tsx
// 类似逻辑,渲染一组 radio buttons
## **2\. AI 生成与解析策略**
如何让 AI 生成这些控件?我们约定一套 **"占位符语法"**。
* **Prompt**: "生成一个性别选择项,包含男、女。"
* **AI Output**: 性别: {{radio:男,女}}
* **前端解析 (Replacement Logic)**:
// 在 useAIStream 的 onStream 更新中
const parseContent \= (text) \=\> {
// 正则替换
if (text.includes('{{input}}')) {
editor.chain().focus().insertContent({ type: 'underlineInput' }).run();
}
// ...
};
## **3\. Word 导出逻辑 (docx.js)**
针对自定义节点的导出映射。
// utils/export-docx.ts
import { TextRun, UnderlineType } from "docx";
export const transformNode \= (node) \=\> {
switch (node.type) {
// 导出填空框 \-\> 带下划线的空格
case 'underlineInput':
return new TextRun({
text: node.attrs.value || " ", // 有值填值,无值填空格
underline: {
type: UnderlineType.SINGLE,
},
});
// 导出复选框 \-\> 特殊字符
case 'taskItem':
const isChecked \= node.attrs.checked;
return new TextRun({
text: isChecked ? "☑ " : "☐ ", // Unicode 字符
font: "Arial Unicode MS", // 确保字体支持
});
// ... 其他节点
}
};
## **4\. 总结**
Novel (Tiptap) 完全有能力承载 CRF 的需求。
虽然这需要一些 **"Extension 开发"** 的工作量,但相比自己从头写一个 Form Builder这是性价比最高的方案而且还能保持文档的流式阅读体验。

View File

@@ -0,0 +1,105 @@
# **基于对话流的文档生成与导出技术方案**
**核心策略**: No-Editor (无编辑器模式)
**目标**: 在 Chat 界面完成方案生成与修改,后端直接合成 Word 下载。
## **1\. 业务流程 (User Flow)**
sequenceDiagram
participant User
participant ChatUI
participant Agent (Node.js)
participant PandocSvc (Python)
User-\>\>ChatUI: "生成完整方案"
ChatUI-\>\>Agent: POST /generate
Agent--\>\>ChatUI: Stream Markdown ("\# 1\. 研究背景...")
User-\>\>ChatUI: "把样本量部分改一下..."
ChatUI-\>\>Agent: POST /regenerate
Agent--\>\>ChatUI: Stream Updated Markdown
User-\>\>ChatUI: 点击 \[📥 导出 Word\]
ChatUI-\>\>Agent: POST /export/docx { markdown }
Agent-\>\>PandocSvc: Convert(markdown, reference.docx)
PandocSvc--\>\>Agent: Buffer (Binary)
Agent--\>\>ChatUI: Blob (Download)
## **2\. 核心技术实现**
### **2.1 Python 微服务Pandoc 转换器**
利用你们现有的 Python 微服务,集成 pypandoc。
**Dockerfile 增加依赖:**
RUN apt-get update && apt-get install \-y pandoc
**Service 代码 (python-service/app/services/doc\_service.py):**
import pypandoc
import os
def convert\_md\_to\_docx(markdown\_text: str, output\_path: str):
\# 使用参考文档 (Reference Doc) 来控制样式(字体、字号、页眉)
reference\_doc \= os.path.join(os.path.dirname(\_\_file\_\_), 'assets/style\_template.docx')
pypandoc.convert\_text(
markdown\_text,
'docx',
format='markdown',
outputfile=output\_path,
extra\_args=\[f'--reference-doc={reference\_doc}'\]
)
### **2.2 Node.js 后端:导出 API**
// backend/src/modules/aia/controllers/exportController.ts
export const exportToWord \= async (req, reply) \=\> {
const { markdown } \= req.body;
// 1\. 调用 Python 微服务
const response \= await pythonService.post('/convert/docx', { content: markdown }, { responseType: 'arraybuffer' });
// 2\. 返回文件流
reply.header('Content-Type', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document');
reply.header('Content-Disposition', 'attachment; filename="protocol.docx"');
return reply.send(response.data);
};
### **2.3 前端Chat 组件增强**
在 AIStreamChat 的消息组件中,增加导出按钮。
// frontend-v2/src/shared/components/Chat/MessageBubble.tsx
const MessageBubble \= ({ content, role }) \=\> {
const handleDownload \= async () \=\> {
const blob \= await api.post('/aia/export/docx', { markdown: content }, { responseType: 'blob' });
saveAs(blob, '研究方案.docx');
};
return (
\<div className="message-bubble"\>
\<Markdown\>{content}\</Markdown\>
{role \=== 'assistant' && (
\<div className="toolbar"\>
\<Button icon={\<DownloadIcon /\>} onClick={handleDownload}\>
导出 Word
\</Button\>
\</div\>
)}
\</div\>
);
};
## **3\. 方案优势总结**
1. **格式完美**:通过 Pandoc 的 Reference Doc可以保证导出的 Word 完全符合医院的格式要求(如宋体小四、行间距等),这是前端编辑器很难做到的。
2. **开发极快**:不需要处理 Tiptap 的状态管理、协同冲突、光标位置等复杂问题。
3. **符合直觉**:用户习惯在 Word 里做最后的精修。
**结论:** 这是一个非常务实且高效的决策。我们可以先不上编辑器,把精力花在 **AI 生成内容的质量****Word 导出的样式** 上。

View File

@@ -201,3 +201,4 @@ export type AgentStage = 'topic' | 'design' | 'review' | 'data' | 'writing';

View File

@@ -291,3 +291,4 @@ POST /api/v1/aia/protocol-agent/generation/:id/export 导出Word
**预计交付时间**Week 51周后

View File

@@ -0,0 +1,352 @@
# Protocol Agent MVP 完整交付记录
> 日期2026-01-25
> 开发者AI Assistant
> 状态:🎉 **MVP 完整交付**
> 里程碑:**研究方案制定Agent + 一键生成研究方案 + Word导出**
---
## 📋 开发概述
在 2026-01-24 完成 Agent 框架基础后,今天完成了**一键生成研究方案**功能的全部开发工作,实现了从关键要素收集到完整研究方案输出的全流程,并解决了大量测试中发现的问题。
**这是一个重要的里程碑**Protocol Agent MVP 已完整可用!
---
## 🎯 今日完成的功能
### 一、一键生成研究方案(核心功能)
#### 1. 动态双面板布局
| 功能 | 实现 |
|------|------|
| **ResizableSplitPane** | 可拖拽调整左右面板宽度 |
| **ViewSwitcher** | "研究摘要" / "完整方案" 切换 |
| **DocumentPanel** | A4 纸张预览效果 |
| **动态比例** | 收集要素 65:35生成方案 35:65 |
#### 2. 研究方案生成
| 功能 | 实现 |
|------|------|
| **流式生成** | SSE 实时输出,打字机效果 |
| **Markdown渲染** | 自定义 MarkdownContent 组件 |
| **章节滚动** | 生成时自动滚动跟随 |
| **12章节结构** | 完整临床研究方案结构 |
#### 3. Word 文档导出
| 组件 | 技术 | 状态 |
|------|------|------|
| Python 微服务 | pypandoc + Pandoc | ✅ |
| Node.js API | `/export/docx` 端点 | ✅ |
| 前端下载 | Blob + download | ✅ |
---
### 二、用户体验优化(大量细节)
#### 滚动问题修复
- 🔧 添加 `min-height: 0` 到所有 flex 容器
- 🔧 自定义滚动条样式(更宽、更可见)
- 🔧 Firefox 兼容性处理
#### UI/UX 改进
| 问题 | 解决方案 |
|------|----------|
| 关键要素 → 研究摘要 | 重命名更准确 |
| 顶部标题两行 | CSS flex-direction: row !important |
| 欢迎语占满屏 | 精简内容 + 减少间距 |
| 列表编号1,1,1 | CSS list-style-type: decimal |
| 侧边栏按钮过长 | 紧凑设计 + 文字精简 |
#### StatePanel 优化
| 阶段 | 优化内容 |
|------|----------|
| 科学问题 | 可折叠显示,完整内容 |
| PICO | 四要素分行,可折叠 |
| 样本量 | 显示计算过程、参数、分组 |
| 观察指标 | 基线/暴露/结局/混杂完整展示 |
**CollapsibleContent 组件**
- 默认显示预览2-3行
- 渐变遮罩效果
- "展开全部"按钮
- 各阶段不同预览高度
---
### 三、Prompt 工程优化
#### 阶段指引 Prompt 增强
```typescript
// 新增 STAGE_ORDER 常量和 getStageInstructions 方法
// 明确当前阶段任务,严禁讨论已完成或未来阶段
```
**解决问题**:模型在"观察指标"阶段误讨论"样本量计算"
#### 数据凝练 Prompt 放宽
| 阶段 | 调整 |
|------|------|
| 科学问题 | 100-200字原50字 |
| PICO | 每项50-100字原20字 |
| 样本量 | 包含计算过程、参数、分组 |
| 观察指标 | 完整定义、测量方法、时间点 |
#### 方案生成 Prompt
- 移除 LLM 开场白("好的,作为一名资深..."
- 12章节结构化输出
- 基于关键要素动态生成标题
---
### 四、对话历史管理
#### 延迟创建模式
```
点击"新建对话" → 导航到 /new不创建记录
发送第一条消息 → 创建对话 + 发送消息
对话出现在历史列表中
```
**优势**:避免空对话污染历史列表(类似 ChatGPT
#### 标题自动更新
- 首条消息前20字符作为对话标题
- PATCH API 更新数据库
#### API 完善
| 端点 | 功能 |
|------|------|
| `GET /conversations?agentId=PROTOCOL_AGENT` | 过滤获取 |
| `PATCH /conversations/:id` | 更新标题 |
| `GET /messages/:conversationId` | 获取历史消息 |
---
### 五、Bug 修复
| 问题 | 原因 | 解决方案 |
|------|------|----------|
| TypeError: .map is not a function | 后端返回字符串而非数组 | `toArray()` 辅助函数 |
| 页面无法滚动 | flex 子元素未设 min-height | 添加 `min-height: 0` |
| Word 导出内容不全 | 传递的是模板而非生成内容 | 修改为传递实际内容 |
| "Table of Contents" 出现 | Pandoc --toc 参数 | 移除 toc 参数 |
| 模型讨论错误阶段 | Prompt 约束不足 | 增强阶段指引 |
---
## 📊 代码变更统计
### 新增/修改文件
| 类别 | 文件 | 变更 |
|------|------|------|
| **前端组件** | | |
| | ResizableSplitPane.tsx | 新增 ~150行 |
| | ViewSwitcher.tsx | 新增 ~50行 |
| | DocumentPanel.tsx | 新增 ~335行 |
| | MarkdownContent.tsx | 新增 ~200行 |
| | CollapsibleContentStageCard内 | 新增 ~100行 |
| **前端 Hooks** | | |
| | useProtocolGeneration.ts | 新增 ~261行 |
| | useProtocolConversations.ts | 修改 +60行 |
| **前端样式** | | |
| | protocol-agent.css | 修改 +800行共2500行 |
| **后端服务** | | |
| | ProtocolExportService.ts | 新增 ~100行 |
| | ProtocolAgentController.ts | 修改 +200行 |
| | ProtocolOrchestrator.ts | 修改 +100行 |
| **Python 微服务** | | |
| | doc_export_service.py | 新增 ~80行 |
| | main.py | 修改 +30行 |
### 代码总量
| 模块 | 今日新增 | 累计 |
|------|----------|------|
| 前端 | ~1,200行 | ~3,300行 |
| 后端 | ~400行 | ~4,700行 |
| Python | ~110行 | ~500行 |
| **总计** | **~1,700行** | **~8,500行** |
---
## 🧪 测试验证
### 功能测试
| 测试项 | 状态 |
|--------|------|
| 5阶段对话流程 | ✅ |
| 数据同步到状态面板 | ✅ |
| 样本量可选跳过 | ✅ |
| 一键生成研究方案 | ✅ |
| 生成时滚动跟随 | ✅ |
| Word 导出下载 | ✅ |
| 对话历史保存 | ✅ |
| 新建对话清空界面 | ✅ |
| 延迟创建(无空记录) | ✅ |
| StatePanel 折叠展开 | ✅ |
### 已知问题(已解决)
- ✅ 滚动条不显示
- ✅ 顶部标题两行
- ✅ 欢迎语空间过大
- ✅ 列表编号错误
- ✅ 模型阶段混乱
- ✅ 数据类型错误
- ✅ 空对话保存
---
## 🏗️ 架构亮点
### 1. 无编辑器方案MVP
```
对话中收集关键要素
一键生成 Markdown
A4 预览 + 讨论优化
Pandoc 转换 Word 导出
```
**优势**
- 开发速度快3天完成
- Word 格式完美
- 符合用户习惯
### 2. 动态布局适配
```
收集要素阶段Chat 65% : Context 35%
生成方案阶段Chat 35% : Document 65%
```
### 3. 延迟创建对话
- 点击新建不创建数据库记录
- 首条消息时才真正创建
- 避免空对话污染
---
## 📝 文件结构
```
frontend-v2/src/modules/aia/protocol-agent/
├── ProtocolAgentPage.tsx # 主页面362行
├── components/
│ ├── ChatArea.tsx # 聊天区域574行
│ ├── StatePanel.tsx # 状态面板198行
│ ├── StageCard.tsx # 阶段卡片450行含折叠
│ ├── DocumentPanel.tsx # 文档预览335行
│ ├── ResizableSplitPane.tsx # 可调整面板150行
│ ├── ViewSwitcher.tsx # 视图切换50行
│ ├── MarkdownContent.tsx # MD渲染200行
│ └── StageEditModal.tsx # 编辑弹窗
├── hooks/
│ ├── useProtocolContext.ts # 上下文管理
│ ├── useProtocolConversations.ts # 对话管理194行
│ └── useProtocolGeneration.ts # 生成管理261行
├── styles/
│ └── protocol-agent.css # 样式2500行
└── types.ts # 类型定义
backend/src/modules/agent/protocol/
├── controllers/
│ └── ProtocolAgentController.ts # 控制器778行
├── services/
│ ├── ProtocolOrchestrator.ts # 编排器475行
│ ├── ProtocolContextService.ts # 上下文服务320行
│ └── ProtocolExportService.ts # 导出服务100行
├── prompts/
│ └── protocolGenerationPrompts.ts # 生成Prompt
└── routes/
└── index.ts # 路由170行
extraction_service/
├── services/
│ └── doc_export_service.py # Word导出80行
└── main.py # FastAPI入口
```
---
## 🎉 里程碑达成
| 里程碑 | 状态 | 日期 |
|--------|------|------|
| M1: Agent框架 | ✅ | 2026-01-24 |
| M2: 5阶段流程 | ✅ | 2026-01-24 |
| M3: 前端UI | ✅ | 2026-01-24 |
| **M4: 一键生成** | ✅ | **2026-01-25** |
| **M5: Word导出** | ✅ | **2026-01-25** |
| **MVP 完整交付** | 🎉 | **2026-01-25** |
---
## 📋 后续计划
### Phase 2: 章节讨论模式(可选)
- [ ] "讨论与优化"按钮
- [ ] 单章节重新生成
- [ ] 修改建议实时预览
### Phase 3: 手动要素补充
- [ ] 右侧面板手动添加入口
- [ ] 支持跳过对话直接填写
- [ ] 拖拽比例记忆
### 长期优化
- [ ] EKB 知识库集成
- [ ] RAG 检索增强
- [ ] Prompt 持续调优
- [ ] 多语言支持
---
## 📚 相关文档
- [开发计划 V2](../04-开发计划/06-一键生成研究方案开发计划V2.md)
- [无编辑器技术方案](../04-开发计划/基于对话流的文档生成与导出技术方案.md)
- [UI布局比例分析](../00-系统设计/UI_Layout_Ratio_Analysis.md)
- [Protocol Agent 架构设计](../04-开发计划/04-Protocol_Agent开发计划/01-架构设计.md)
---
## 🏆 技术总结
1. **Pandoc 集成**:通过 Python 微服务实现高质量 Word 导出
2. **流式渲染**SSE + 自定义 Markdown 组件实现打字机效果
3. **延迟创建**:类 ChatGPT 的对话管理体验
4. **Prompt 工程**:阶段约束 + 数据凝练的精细化设计
5. **响应式布局**:动态比例适配不同工作阶段
---
**MVP 已完整交付!** 🎉
下一步:进入生产测试和用户反馈收集阶段。