# 一键生成研究方案 - 开发计划 > **版本**: v2.0 > **创建日期**: 2026-01-24 > **最后更新**: 2026-01-25 > **负责人**: AI Assistant > **状态**: 开发中 --- ## 一、功能概述 ### 目标 基于 Protocol Agent 收集的 5 个核心要素,一键生成完整的临床研究方案文档,支持对话式修改和 Word 导出。 ### 核心价值 - 将 5-10 小时的方案撰写工作缩短至 30 分钟 - AI 生成 + 对话式修改 + Word 精修,保证专业性和个性化 - 输出符合伦理委员会审查要求的标准文档 ### 技术策略:No-Editor First(无编辑器优先) **核心洞察**: > 用户最终会在 Word 中精修,我们的价值是 **"AI 生成高质量初稿"**,而不是 **"提供一个编辑器"**。 --- ## 二、交互设计:对话流生成 ### 用户流程 ``` 用户完成 5 阶段要素收集 ↓ 点击"一键生成研究方案" ↓ AI 在对话框中流式输出完整方案(Markdown) ↓ 用户对话修改:"把样本量部分改一下..." ↓ AI 流式输出修改后的章节 ↓ 用户满意 → 点击"导出 Word" ↓ 下载符合伦理委员会格式的 Word 文档 ``` ### 交互示意 ``` ┌─────────────────────────────────────────────────────────────┐ │ ← 返回 全流程研究方案制定 │ ├────────────────────────────────────────┬────────────────────┤ │ │ 📋 研究方案状态 │ │ [AI] 根据您确认的信息,我已生成研究方案: │ │ │ │ ✅ 科学问题 │ │ # 1. 研究题目 │ ✅ PICO要素 │ │ 糖尿病患者使用二甲双胍与格列美脲... │ ✅ 研究设计 │ │ │ ✅ 样本量 │ │ # 2. 研究背景 │ ✅ 观察指标 │ │ 2型糖尿病是全球性公共卫生问题... │ │ │ │ ──────────────── │ │ ... │ │ │ │ [📥 导出 Word] │ │ ┌────────────────────────────────┐ │ [🔄 重新生成] │ │ │ 📥 导出Word │ 🔄 修改本章节 │ │ │ │ └────────────────────────────────┘ │ │ │ │ │ │ [用户] 把样本量从200改成300,并说明原因 │ │ │ │ │ │ [AI] 好的,我已修改样本量部分: │ │ │ ## 6. 样本量估算 │ │ │ 根据前期预实验数据...样本量调整为300例 │ │ │ │ │ ├────────────────────────────────────────┴────────────────────┤ │ [输入消息...] [发送] │ └─────────────────────────────────────────────────────────────┘ ``` --- ## 三、研究方案结构 ```markdown # 临床研究方案 ## 1. 研究题目 ## 2. 研究背景与立题依据 ## 3. 研究目的 ## 4. 研究设计 ## 5. 研究对象(纳入/排除标准) ## 6. 样本量估算 ## 7. 研究实施步骤与技术路线 ## 8. 观察指标 ## 9. 数据管理与质量控制 ## 10. 安全性评价 ## 11. 统计分析计划 ## 12. 伦理与知情同意 ## 13. 研究时间表 ## 14. 参考文献 ``` --- ## 四、技术方案 ### 架构概览 ``` ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ 前端 │ │ Node.js │ │ Python │ │ ChatArea │ ──▶ │ Backend │ ──▶ │ Service │ │ │ │ │ │ (Pandoc) │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ │ │ 流式输出 │ 调用 LLM │ Markdown→Word │ Markdown │ 生成方案 │ 格式转换 ▼ ▼ ▼ ┌─────────────────────────────────────────────────────┐ │ 用户下载 Word │ └─────────────────────────────────────────────────────┘ ``` ### 技术栈 ``` 前端:复用现有 ChatArea + useAIStream ├── 增加"导出 Word"按钮 ├── 增加"修改本章节"交互 │ 后端 (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 │ 后端 (Python): ├── Pandoc 集成 ├── Reference Doc 模板控制样式 ``` ### 数据模型 ```sql -- 方案生成记录表(复用现有 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/full Request: { conversationId: string } Response: SSE 流式输出完整 Markdown // 重新生成指定章节(流式) POST /api/v1/aia/protocol-agent/regenerate Request: { conversationId: string, section: 'sample_size' | 'background' | ..., instruction: "把样本量从200改成300" } 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 Context(5 阶段数据) 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 (