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

@@ -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这是性价比最高的方案而且还能保持文档的流式阅读体验。