skill_name: create-node description: 用于在 FlowGram demo-free-layout 中创建新的自定义节点,支持简单节点(自动表单)和复杂节点(自定义 UI) version: 1.0.0
本 SKILL 用于指导在 FlowGram 项目的 apps/demo-free-layout/src/nodes 目录下创建新的自定义工作流节点。
节点数据在保存时会存储到后端,基本结构如下:
{
id: 'node_xxxxx', // 节点 ID
type: 'node_type', // 节点类型
data: {
title: 'Node Title', // 节点标题
inputsValues: { ... }, // 节点表单字段的初始值(实际的值)
inputs: { ... }, // 节点表单的 JSON Schema(定义表单结构)
outputs: { ... }, // 节点输出的 JSON Schema(工作流执行时的输出)
// ... 其他自定义字段
}
}
data.inputsValues - 节点表单字段的初始值存储表单中各个字段的实际值,每个字段值包含 type 和 content 两个属性:
inputsValues: {
url: {
type: 'constant', // 常量类型
content: 'https://...', // 实际的值
},
prompt: {
type: 'template', // 模板类型(支持变量引用)
content: 'Hello {var}', // 可以引用变量
},
}
type 的可选值:
'constant':常量值,不支持变量引用'template':模板值,支持 {variableName} 语法引用变量'variable':变量引用data.inputs - 节点表单的 JSON Schema使用 JSON Schema 定义表单的结构,系统会根据这个 Schema 自动生成表单界面:
inputs: {
type: 'object',
required: ['url'], // 必填字段
properties: {
url: {
type: 'string',
},
timeout: {
type: 'number',
minimum: 0,
maximum: 60000,
},
prompt: {
type: 'string',
extra: {
formComponent: 'prompt-editor', // 指定自定义组件
},
},
},
}
data.outputs - 节点输出的 JSON Schema定义节点在工作流执行时的输出数据结构,供下游节点使用:
outputs: {
type: 'object',
properties: {
body: { type: 'string' },
statusCode: { type: 'number' },
headers: { type: 'object' },
},
}
inputs (JSON Schema) → 定义表单结构
inputsValues (实际值) → 存储表单数据
[节点执行]
outputs (JSON Schema) → 定义输出结构
在简单节点中,字段类型会自动匹配对应的表单组件:
| 字段类型 | extra.formComponent |
默认组件 |
|---|---|---|
string |
- | Input |
string |
'prompt-editor' |
PromptEditorWithVariables |
number |
- | InputNumber |
boolean |
- | Switch |
object |
- | JsonCodeEditor |
array |
- | JsonCodeEditor |
inputs Schema 自动生成表单index.ts 文件./templates/simple-node/index.ts文件结构:
{节点名}/
├── index.tsx # 节点注册配置
├── form-meta.tsx # 自定义表单渲染
├── types.tsx # TypeScript 类型定义
└── components/ # 自定义组件
└── *.tsx
模板位置:./templates/complex-node/
确定节点的核心信息:
database、webhook是否需要自定义 UI?
├─ 否 → 使用简单节点模式(复制 templates/simple-node/)
└─ 是 → 使用复杂节点模式(复制 templates/complex-node/)
# 复制模板
cp .claude/skills/create-node/templates/simple-node/index.ts \
apps/demo-free-layout/src/nodes/{节点名}/index.ts
# 修改模板中的 TODO 标记
# - {NODE_NAME} → 节点名(PascalCase)
# - {NODE_TYPE} → 节点类型枚举值
# - {node_name} → 节点名(kebab-case)
# - {node_type} → 节点类型(小写)
# 复制模板目录
cp -r .claude/skills/create-node/templates/complex-node \
apps/demo-free-layout/src/nodes/{节点名}
# 修改所有文件中的 TODO 标记
编辑 apps/demo-free-layout/src/nodes/constants.ts:
export enum WorkflowNodeType {
// ... 现有节点
{节点类型} = '{节点类型}',
}
编辑 apps/demo-free-layout/src/nodes/index.ts:
// 导入节点
export { {节点名}NodeRegistry } from './{节点名}';
// 添加到注册列表
export const nodeRegistries: FlowNodeRegistry[] = [
// ... 现有节点
{节点名}NodeRegistry,
];
在 apps/demo-free-layout/src/assets/ 目录下添加节点图标(SVG 或 JPG 格式):
apps/demo-free-layout/src/assets/icon-{节点名}.svg
# 启动开发服务器
rush dev:demo-free-layout
# 在浏览器中测试节点功能
从 @flowgram.ai/form-materials 导入:
import {
PromptEditorWithVariables, // 带变量的提示词编辑器
VariableSelector, // 变量选择器
JsonCodeEditor, // JSON 代码编辑器
CodeEditor, // 代码编辑器
DisplayOutputs, // 输出字段展示
DynamicValueInput, // 动态值输入
createInferInputsPlugin, // 输入推断插件
} from '@flowgram.ai/form-materials';
从 @douyinfe/semi-ui 导入:
import {
Input,
InputNumber,
Select,
Switch,
Button,
Divider,
} from '@douyinfe/semi-ui';
import { Field } from '@flowgram.ai/free-layout-editor';
import { FormItem, FormHeader, FormContent } from '../../form-components';
import { useNodeRenderContext } from '../../hooks';
// ✅ 好的做法:清晰的 Schema
inputs: {
type: 'object',
required: ['url', 'method'],
properties: {
url: {
type: 'string',
description: 'API endpoint URL',
},
method: {
type: 'string',
enum: ['GET', 'POST', 'PUT', 'DELETE'],
},
},
}
// ❌ 不好的做法:缺少约束
inputs: {
type: 'object',
properties: {
url: { type: 'string' },
method: { type: 'string' },
},
}
// ✅ 好的做法:使用 Field 绑定表单状态
<Field<string> name="api.url">
{({ field }) => (
<Input
value={field.value}
onChange={(value) => field.onChange(value)}
/>
)}
</Field>
// ❌ 不好的做法:手动管理状态
const [url, setUrl] = useState('');
<Input value={url} onChange={setUrl} />
export function CustomComponent() {
const { readonly } = useNodeRenderContext();
return (
<Input disabled={readonly} {...props} />
);
}
判断标准:
在 inputs Schema 中使用 formComponent: 'prompt-editor',并在 inputsValues 中使用 type: 'template'。
在 inputs Schema 的 required 数组中列出必填字段名。
inputsValues 和 inputs 必须一致吗?是的。inputsValues 中的字段必须在 inputs.properties 中有对应的定义。
支持 SVG、JPG、PNG 格式,推荐使用 SVG。
console.log(form.getValues())apps/demo-free-layout/src/nodes/llm/apps/demo-free-layout/src/nodes/http/apps/demo-free-layout/src/form-components/apps/demo-free-layout/src/nodes/default-form-meta.tsx.claude/skills/create-node/templates/simple-node/.claude/skills/create-node/templates/complex-node/# 启动开发服务器
rush dev:demo-free-layout
# 构建项目
rush build
# 类型检查
rush ts-check
# 代码检查
rush lint
创建新节点时,按照此检查清单执行:
constants.ts 中添加节点类型index.ts 中注册节点