From ff74e96cbf2dc0be22189799333a04daee66102c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 9 Nov 2025 13:59:20 +0000 Subject: [PATCH] Add comprehensive Flowgram migration guide with full implementation examples Co-authored-by: whyour <22700758+whyour@users.noreply.github.com> --- FLOWGRAM_MIGRATION_GUIDE.md | 692 ++++++++++++++++++++++++++++++++++++ 1 file changed, 692 insertions(+) create mode 100644 FLOWGRAM_MIGRATION_GUIDE.md diff --git a/FLOWGRAM_MIGRATION_GUIDE.md b/FLOWGRAM_MIGRATION_GUIDE.md new file mode 100644 index 00000000..35e4f9ea --- /dev/null +++ b/FLOWGRAM_MIGRATION_GUIDE.md @@ -0,0 +1,692 @@ +# Flowgram Migration Guide - 从 React Flow 迁移到 Flowgram.ai + +## 概述 (Overview) + +本文档提供从 React Flow 迁移到 Flowgram.ai 的完整指南。 + +参考 Demo: https://github.com/bytedance/flowgram.ai/tree/main/apps/demo-free-layout + +## 1. 依赖安装 (Dependencies Installation) + +### 已添加的依赖 (Dependencies Added) + +```json +{ + "@flowgram.ai/core": "^1.0.2", + "@flowgram.ai/free-layout-editor": "^1.0.2", + "@flowgram.ai/reactive": "^1.0.2", + "@flowgram.ai/free-snap-plugin": "^1.0.2", + "@flowgram.ai/free-lines-plugin": "^1.0.2", + "@flowgram.ai/free-node-panel-plugin": "^1.0.2", + "@flowgram.ai/minimap-plugin": "^1.0.2", + "@flowgram.ai/free-container-plugin": "^1.0.2", + "@flowgram.ai/free-group-plugin": "^1.0.2", + "@flowgram.ai/form-materials": "^1.0.2", + "@flowgram.ai/panel-manager-plugin": "^1.0.2", + "@flowgram.ai/free-stack-plugin": "^1.0.2", + "@flowgram.ai/runtime-interface": "^1.0.2", + "@flowgram.ai/runtime-js": "^1.0.2", + "nanoid": "^5.0.9" +} +``` + +### 安装命令 (Installation Command) + +```bash +cd /home/runner/work/qinglong/qinglong +npm install --legacy-peer-deps +# or +pnpm install +``` + +## 2. 数据结构适配 (Data Structure Adaptation) + +### 当前 React Flow 格式 + +```typescript +{ + nodes: [ + { + id: "trigger-1", + type: "trigger", + position: { x: 100, y: 100 }, + data: { + label: "Webhook Trigger", + triggerType: "webhook", + config: {} + } + } + ], + edges: [ + { + id: "e1", + source: "trigger-1", + target: "condition-1" + } + ] +} +``` + +### Flowgram 格式 + +```typescript +{ + nodes: [ + { + id: "trigger_1", + type: "trigger", + meta: { + position: { x: 100, y: 100 } + }, + data: { + title: "Webhook Trigger", + triggerType: "webhook", + config: {}, + // Flowgram 使用 inputs/outputs schema + outputs: { + type: "object", + properties: { + data: { type: "object" } + } + } + } + } + ], + edges: [ + { + sourceNodeID: "trigger_1", + targetNodeID: "condition_1" + } + ] +} +``` + +## 3. 节点注册系统 (Node Registry System) + +### 节点注册接口 (Node Registry Interface) + +```typescript +// src/pages/scenario/flowgram/types.ts +import { FlowNodeRegistry as FlowgramRegistry } from '@flowgram.ai/free-layout-editor'; + +export type FlowNodeRegistry = FlowgramRegistry; + +export interface NodeData { + title: string; + inputs?: any; + outputs?: any; + inputsValues?: any; + [key: string]: any; +} +``` + +### 创建节点注册 (Create Node Registries) + +#### Start 节点 (src/pages/scenario/flowgram/nodes/start.tsx) + +```typescript +import { FlowNodeRegistry } from '../types'; + +export const StartNodeRegistry: FlowNodeRegistry = { + type: 'start', + meta: { + category: 'basic', + label: '开始', + description: '工作流开始节点', + }, + data: { + title: '开始', + outputs: { + type: 'object', + properties: { + triggerData: { + type: 'object', + description: '触发数据', + }, + }, + }, + }, + formMeta: { + properties: { + title: { + type: 'string', + title: '标题', + }, + }, + }, +}; +``` + +#### Trigger 节点 (src/pages/scenario/flowgram/nodes/trigger.tsx) + +```typescript +export const TriggerNodeRegistry: FlowNodeRegistry = { + type: 'trigger', + meta: { + category: 'triggers', + label: '触发器', + description: '各种触发器类型', + }, + data: { + title: '触发器', + triggerType: 'webhook', + outputs: { + type: 'object', + properties: { + data: { type: 'object' }, + }, + }, + }, + formMeta: { + properties: { + title: { + type: 'string', + title: '标题', + }, + triggerType: { + type: 'string', + title: '触发类型', + enum: ['time', 'webhook', 'variable', 'task_status', 'system_event'], + enumNames: ['时间触发', 'Webhook', '变量监听', '任务状态', '系统事件'], + }, + config: { + type: 'object', + title: '配置', + properties: { + schedule: { + type: 'string', + title: 'Cron 表达式', + }, + filePath: { + type: 'string', + title: '文件路径', + }, + }, + }, + }, + }, +}; +``` + +#### Condition 节点 + +```typescript +export const ConditionNodeRegistry: FlowNodeRegistry = { + type: 'condition', + meta: { + category: 'logic', + label: '条件', + description: '条件判断节点', + }, + data: { + title: '条件', + conditions: [], + outputs: { + type: 'object', + properties: { + result: { type: 'boolean' }, + }, + }, + }, + formMeta: { + properties: { + title: { type: 'string', title: '标题' }, + operator: { + type: 'string', + title: '操作符', + enum: ['equals', 'not_equals', 'greater_than', 'less_than', 'contains', 'not_contains'], + enumNames: ['等于', '不等于', '大于', '小于', '包含', '不包含'], + }, + field: { type: 'string', title: '字段名' }, + value: { type: 'string', title: '比较值' }, + }, + }, +}; +``` + +#### Action 节点 + +```typescript +export const ActionNodeRegistry: FlowNodeRegistry = { + type: 'action', + meta: { + category: 'actions', + label: '动作', + description: '执行动作', + }, + data: { + title: '动作', + actionType: 'run_task', + }, + formMeta: { + properties: { + title: { type: 'string', title: '标题' }, + actionType: { + type: 'string', + title: '动作类型', + enum: ['run_task', 'set_variable', 'execute_command', 'send_notification'], + enumNames: ['运行任务', '设置变量', '执行命令', '发送通知'], + }, + cronId: { type: 'number', title: '任务 ID' }, + name: { type: 'string', title: '变量名' }, + value: { type: 'string', title: '变量值' }, + command: { type: 'string', title: '命令' }, + message: { type: 'string', title: '消息' }, + }, + }, +}; +``` + +#### Control 节点 + +```typescript +export const ControlNodeRegistry: FlowNodeRegistry = { + type: 'control', + meta: { + category: 'control', + label: '控制流', + description: '控制流节点', + }, + data: { + title: '控制流', + controlType: 'delay', + }, + formMeta: { + properties: { + title: { type: 'string', title: '标题' }, + controlType: { + type: 'string', + title: '控制类型', + enum: ['delay', 'retry', 'circuit_breaker'], + enumNames: ['延迟', '重试', '熔断器'], + }, + delaySeconds: { type: 'number', title: '延迟秒数' }, + maxRetries: { type: 'number', title: '最大重试次数' }, + retryDelay: { type: 'number', title: '重试延迟' }, + backoffMultiplier: { type: 'number', title: '退避倍数' }, + failureThreshold: { type: 'number', title: '失败阈值' }, + }, + }, +}; +``` + +#### Logic Gate 节点 + +```typescript +export const LogicGateNodeRegistry: FlowNodeRegistry = { + type: 'logic_gate', + meta: { + category: 'logic', + label: '逻辑门', + description: 'AND/OR 逻辑', + }, + data: { + title: '逻辑门', + gateType: 'AND', + }, + formMeta: { + properties: { + title: { type: 'string', title: '标题' }, + gateType: { + type: 'string', + title: '逻辑类型', + enum: ['AND', 'OR'], + enumNames: ['AND', 'OR'], + }, + }, + }, +}; +``` + +#### End 节点 + +```typescript +export const EndNodeRegistry: FlowNodeRegistry = { + type: 'end', + meta: { + category: 'basic', + label: '结束', + description: '工作流结束节点', + }, + data: { + title: '结束', + inputs: { + type: 'object', + properties: { + result: { type: 'any' }, + }, + }, + }, + formMeta: { + properties: { + title: { type: 'string', title: '标题' }, + }, + }, +}; +``` + +## 4. 编辑器组件 (Editor Component) + +### 主编辑器 (src/pages/scenario/flowgram/Editor.tsx) + +```typescript +import React from 'react'; +import { EditorRenderer, FreeLayoutEditorProvider } from '@flowgram.ai/free-layout-editor'; +import '@flowgram.ai/free-layout-editor/index.css'; +import { useEditorProps } from './hooks/useEditorProps'; +import { nodeRegistries } from './nodes'; + +interface EditorProps { + initialData: any; + onSave: (data: any) => void; +} + +export const FlowgramEditor: React.FC = ({ initialData, onSave }) => { + const editorProps = useEditorProps(initialData, nodeRegistries, onSave); + + return ( +
+ + + +
+ ); +}; +``` + +### 编辑器配置 (src/pages/scenario/flowgram/hooks/useEditorProps.tsx) + +```typescript +import { useMemo } from 'react'; +import { FreeLayoutProps } from '@flowgram.ai/free-layout-editor'; +import { createMinimapPlugin } from '@flowgram.ai/minimap-plugin'; +import { createFreeSnapPlugin } from '@flowgram.ai/free-snap-plugin'; +import { createFreeNodePanelPlugin } from '@flowgram.ai/free-node-panel-plugin'; +import { createFreeLinesPlugin } from '@flowgram.ai/free-lines-plugin'; +import { FlowNodeRegistry } from '../types'; + +export function useEditorProps( + initialData: any, + nodeRegistries: FlowNodeRegistry[], + onSave: (data: any) => void +): FreeLayoutProps { + return useMemo( + () => ({ + background: true, + readonly: false, + twoWayConnection: true, + initialData, + nodeRegistries, + + // 节点数据转换 + fromNodeJSON(node, json) { + return json; + }, + + toNodeJSON(node, json) { + return json; + }, + + // 连线颜色 + lineColor: { + default: '#4d53e8', + drawing: '#5DD6E3', + hovered: '#37d0ff', + selected: '#37d0ff', + error: 'red', + }, + + // 连线规则 + canAddLine(ctx, fromPort, toPort) { + if (fromPort.node === toPort.node) return false; + return !fromPort.node.lines.allInputNodes.includes(toPort.node); + }, + + // 内容变化回调 + onContentChange: (ctx, event) => { + if (ctx.document.disposed) return; + const data = ctx.document.toJSON(); + onSave(data); + }, + + // 插件 + plugins: () => [ + createMinimapPlugin({}), + createFreeSnapPlugin({}), + createFreeNodePanelPlugin({}), + createFreeLinesPlugin({}), + ], + }), + [initialData, nodeRegistries, onSave] + ); +} +``` + +## 5. 模态框集成 (Modal Integration) + +### 替换 visualWorkflowModal.tsx + +```typescript +// src/pages/scenario/flowgramWorkflowModal.tsx +import React, { useState, useEffect } from 'react'; +import { Modal } from 'antd'; +import { FlowgramEditor } from './flowgram/Editor'; +import intl from 'react-intl-universal'; + +interface FlowgramWorkflowModalProps { + visible: boolean; + scenario?: any; + onOk: (scenario: any) => void; + onCancel: () => void; +} + +export const FlowgramWorkflowModal: React.FC = ({ + visible, + scenario, + onOk, + onCancel, +}) => { + const [workflowData, setWorkflowData] = useState(null); + const [name, setName] = useState(''); + + useEffect(() => { + if (scenario) { + setName(scenario.name || ''); + setWorkflowData(scenario.workflowGraph || getInitialData()); + } else { + setName(''); + setWorkflowData(getInitialData()); + } + }, [scenario, visible]); + + const getInitialData = () => ({ + nodes: [ + { + id: 'start_0', + type: 'start', + meta: { position: { x: 100, y: 300 } }, + data: { title: '开始' }, + }, + { + id: 'end_0', + type: 'end', + meta: { position: { x: 800, y: 300 } }, + data: { title: '结束' }, + }, + ], + edges: [], + }); + + const handleSave = (data: any) => { + setWorkflowData(data); + }; + + const handleOk = () => { + onOk({ + ...scenario, + name, + workflowGraph: workflowData, + }); + }; + + return ( + +
+ setName(e.target.value)} + style={{ width: '100%', padding: '8px' }} + /> +
+ {workflowData && ( + + )} +
+ ); +}; +``` + +## 6. 在主页面中使用 (Usage in Main Page) + +### 更新 index.tsx + +```typescript +// src/pages/scenario/index.tsx +import { FlowgramWorkflowModal } from './flowgramWorkflowModal'; + +// 替换原来的 +// import { VisualWorkflowModal } from './visualWorkflowModal'; + +// 使用 + setModalVisible(false)} +/> +``` + +## 7. 后端兼容性 (Backend Compatibility) + +### 数据转换工具 (Data Conversion) + +```typescript +// src/pages/scenario/flowgram/utils/dataConverter.ts + +/** + * 将 Flowgram 格式转换为后端格式 + */ +export function convertFlowgramToBackend(flowgramData: any) { + return { + nodes: flowgramData.nodes.map((node: any) => ({ + id: node.id, + type: node.type, + position: node.meta?.position || { x: 0, y: 0 }, + data: node.data, + })), + edges: flowgramData.edges.map((edge: any) => ({ + id: `${edge.sourceNodeID}-${edge.targetNodeID}`, + source: edge.sourceNodeID, + target: edge.targetNodeID, + sourcePort: edge.sourcePortID, + })), + }; +} + +/** + * 将后端格式转换为 Flowgram 格式 + */ +export function convertBackendToFlowgram(backendData: any) { + return { + nodes: backendData.nodes.map((node: any) => ({ + id: node.id, + type: node.type, + meta: { + position: node.position, + }, + data: node.data, + })), + edges: backendData.edges.map((edge: any) => ({ + sourceNodeID: edge.source, + targetNodeID: edge.target, + sourcePortID: edge.sourcePort, + })), + }; +} +``` + +## 8. CSS 样式 (Styles) + +### 导入 Flowgram 样式 + +```typescript +// src/pages/scenario/flowgram/Editor.tsx +import '@flowgram.ai/free-layout-editor/index.css'; + +// 自定义样式 +const customStyles = ` + .flowgram-editor { + width: 100%; + height: 600px; + border: 1px solid #d9d9d9; + border-radius: 4px; + } +`; +``` + +## 9. 测试清单 (Testing Checklist) + +- [ ] 依赖安装成功 +- [ ] 节点可以正常创建和拖拽 +- [ ] 节点可以连接 +- [ ] 节点配置面板正常显示 +- [ ] 保存功能正常 +- [ ] 与后端图执行引擎兼容 +- [ ] 可以编辑现有场景 +- [ ] 所有节点类型都可用 + +## 10. 故障排查 (Troubleshooting) + +### 常见问题 + +**Q: Flowgram 插件包无法安装** +A: 使用 `npm install --legacy-peer-deps` 或 `pnpm install` + +**Q: TypeScript 类型错误** +A: 添加类型声明文件或使用 `// @ts-ignore` + +**Q: 样式不显示** +A: 确保导入了 `@flowgram.ai/free-layout-editor/index.css` + +**Q: 节点不显示** +A: 检查节点注册是否正确,formMeta 是否完整 + +## 11. 参考资源 (Resources) + +- Flowgram 官方 Demo: https://github.com/bytedance/flowgram.ai/tree/main/apps/demo-free-layout +- Flowgram 文档: https://flowgram.ai/ +- 当前实现: src/pages/scenario/visualWorkflowModal.tsx (React Flow 版本) + +## 总结 (Summary) + +这个迁移涉及: +1. 替换依赖包(React Flow -> Flowgram) +2. 重写节点注册系统 +3. 重新实现编辑器组件 +4. 适配数据格式 +5. 保持后端兼容性 + +预计工作量:8-12 小时 +难度:中等 +风险:中等(主要是 Flowgram 文档不完整)