Install any skill in seconds. Free to start, no credit card required.
Get Started Free →用于在 FlowGram demo-free-layout 中创建新的自定义节点,支持简单节点(自动表单)和复杂节点(自定义 UI)
.claude/skills/aiskillstore-create-node/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-03 | ✗→✓ | ▲ Improved | 36% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 125% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 31% | 0% |
| case-12 | ✗→✓ | ▲ Improved | 101% | 0% |
| case-13 | ✗→✓ | ▲ Improved | 131% | 0% |
本 SKILL 用于指导在 FlowGram 项目的 apps/demo-free-layout/src/nodes 目录下创建新的自定义工作流节点。
节点数据在保存时会存储到后端,基本结构如下:
typescript{ id: 'node_xxxxx', // 节点 ID type: 'node_type', // 节点类型 data: { title: 'Node Title', // 节点标题 inputsValues: { ... }, // 节点表单字段的初始值(实际的值) inputs: { ... }, // 节点表单的 JSON Schema(定义表单结构) outputs: { ... }, // 节点输出的 JSON Schema(工作流执行时的输出) // ... 其他自定义字段 } }
data.inputsValues - 节点表单字段的初始值存储表单中各个字段的实际值,每个字段值包含 type 和 content 两个属性:
typescriptinputsValues: { url: { type: 'constant', // 常量类型 content: 'https://...', // 实际的值 }, prompt: { type: 'template', // 模板类型(支持变量引用) content: 'Hello {var}', // 可以引用变量 }, }
type 的可选值:
'constant':常量值,不支持变量引用'template':模板值,支持 {variableName} 语法引用变量'variable':变量引用data.inputs - 节点表单的 JSON Schema使用 JSON Schema 定义表单的结构,系统会根据这个 Schema 自动生成表单界面:
typescriptinputs: { 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定义节点在工作流执行时的输出数据结构,供下游节点使用:
typescriptoutputs: { 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/)bash# 复制模板 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} → 节点类型(小写)
bash# 复制模板目录 cp -r .claude/skills/create-node/templates/complex-node \ apps/demo-free-layout/src/nodes/{节点名} # 修改所有文件中的 TODO 标记
编辑 apps/demo-free-layout/src/nodes/constants.ts:
typescriptexport enum WorkflowNodeType { // ... 现有节点 {节点类型} = '{节点类型}', }
编辑 apps/demo-free-layout/src/nodes/index.ts:
typescript// 导入节点 export { {节点名}NodeRegistry } from './{节点名}'; // 添加到注册列表 export const nodeRegistries: FlowNodeRegistry[] = [ // ... 现有节点 {节点名}NodeRegistry, ];
在 apps/demo-free-layout/src/assets/ 目录下添加节点图标(SVG 或 JPG 格式):
apps/demo-free-layout/src/assets/icon-{节点名}.svgbash# 启动开发服务器 rush dev:demo-free-layout # 在浏览器中测试节点功能
从 @flowgram.ai/form-materials 导入:
typescriptimport { PromptEditorWithVariables, // 带变量的提示词编辑器 VariableSelector, // 变量选择器 JsonCodeEditor, // JSON 代码编辑器 CodeEditor, // 代码编辑器 DisplayOutputs, // 输出字段展示 DynamicValueInput, // 动态值输入 createInferInputsPlugin, // 输入推断插件 } from '@flowgram.ai/form-materials';
从 @douyinfe/semi-ui 导入:
typescriptimport { Input, InputNumber, Select, Switch, Button, Divider, } from '@douyinfe/semi-ui';
typescriptimport { Field } from '@flowgram.ai/free-layout-editor'; import { FormItem, FormHeader, FormContent } from '../../form-components'; import { useNodeRenderContext } from '../../hooks';
typescript// ✅ 好的做法:清晰的 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' }, }, }
typescript// ✅ 好的做法:使用 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} />
typescriptexport 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/bash# 启动开发服务器 rush dev:demo-free-layout # 构建项目 rush build # 类型检查 rush ts-check # 代码检查 rush lint
创建新节点时,按照此检查清单执行:
constants.ts 中添加节点类型index.ts 中注册节点| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→fail | 12,577 | 21,917 | +74% | 1 | 1 | 0% | 2,615 | 6,274 | +140% | 0 | 0 | — |
case-02 | fail→fail | 26,992 | 29,018 | +8% | 1 | 1 | 0% | 6,063 | 8,004 | +32% | 0 | 0 | — |
case-03 | fail→pass | 23,823 | 11,920 | -50% | 1 | 1 | 0% | 3,936 | 5,354 | +36% | 0 | 0 | — |
case-04 | fail→fail | 20,016 | 20,403 | +2% | 1 | 1 | 0% | 2,897 | 6,247 | +116% | 0 | 0 | — |
case-05 | fail→fail | 18,616 | 19,664 | +6% | 1 | 1 | 0% | 3,204 | 6,622 | +107% | 0 | 0 | — |
case-06 | fail→fail | 14,653 | 18,434 | +26% | 1 | 1 | 0% | 2,642 | 5,183 | +96% | 0 | 0 | — |
case-07 | fail→pass | 9,503 | 4,282 | -55% | 1 | 1 | 0% | 1,650 | 3,712 | +125% | 0 | 0 | — |
case-08 | fail→pass | 15,287 | 14,102 | -8% | 1 | 1 | 0% | 3,544 | 4,637 | +31% | 0 | 0 | — |
case-09 | pass→pass | 9,256 | 4,731 | -49% | 1 | 1 | 0% | 1,577 | 3,826 | +143% | 0 | 0 | — |
case-10 | pass→pass | 14,113 | 9,519 | -33% | 1 | 1 | 0% | 1,497 | 3,735 | +149% | 0 | 0 | — |
case-11 | pass→pass | 13,356 | 3,182 | -76% | 1 | 1 | 0% | 1,342 | 3,517 | +162% | 0 | 0 | — |
case-12 | fail→pass | 15,031 | 7,647 | -49% | 1 | 1 | 0% | 1,664 | 3,340 | +101% | 0 | 0 | — |
case-13 | fail→pass | 17,188 | 14,472 | -16% | 1 | 1 | 0% | 2,098 | 4,849 | +131% | 0 | 0 | — |
case-14 | fail→pass | 8,565 | 7,581 | -11% | 1 | 1 | 0% | 1,315 | 3,314 | +152% | 0 | 0 | — |
case-15 | fail→pass | 21,961 | 4,920 | -78% | 1 | 1 | 0% | 2,793 | 3,811 | +36% | 0 | 0 | — |
case-16 | fail→pass | 19,534 | 8,513 | -56% | 1 | 1 | 0% | 2,274 | 4,479 | +97% | 0 | 0 | — |
case-17 | pass→pass | 13,433 | 2,404 | -82% | 1 | 1 | 0% | 1,436 | 3,300 | +130% | 0 | 0 | — |
case-18 | fail→pass | 13,092 | 7,653 | -42% | 1 | 1 | 0% | 1,316 | 3,303 | +151% | 0 | 0 | — |
case-19 | pass→pass | 11,996 | 9,335 | -22% | 1 | 1 | 0% | 1,210 | 3,695 | +205% | 0 | 0 | — |
case-20 | fail→pass | 14,014 | 9,635 | -31% | 1 | 1 | 0% | 1,659 | 3,769 | +127% | 0 | 0 | — |
case-21 | fail→pass | 9,954 | 6,611 | -34% | 1 | 1 | 0% | 853 | 3,132 | +267% | 0 | 0 | — |
case-22 | pass→pass | 16,086 | 4,067 | -75% | 1 | 1 | 0% | 2,126 | 3,579 | +68% | 0 | 0 | — |
case-23 | pass→pass | 7,882 | 10,610 | +35% | 1 | 1 | 0% | 1,273 | 3,889 | +205% | 0 | 0 | — |
case-24 | fail→pass | 19,852 | 14,869 | -25% | 1 | 1 | 0% | 2,616 | 4,779 | +83% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 24 cases were attempted. The headline lift of +50 percentage points is the difference between those two pass rates over the 24 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.