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:
110
docs/03-业务模块/AIA-AI智能问答/04-开发计划/Novel_CRF_Extension_Guide.md
Normal file
110
docs/03-业务模块/AIA-AI智能问答/04-开发计划/Novel_CRF_Extension_Guide.md
Normal 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,这是性价比最高的方案,而且还能保持文档的流式阅读体验。
|
||||
Reference in New Issue
Block a user