前端画布:ReactFlow 怎么把拖拽变成 flowData
30 秒导读: 前面四章(01–04)讲的是"一份 flowData JSON 被后端怎么表示、怎么执行"。这份 JSON 从哪来?答案是前端画布:用户在一张 ReactFlow(一个 React 流程图库)画布上拖节点、拉连线、填参数,这些鼠标操作被实时序列化成
{ nodes, edges, viewport },存进数据库的flowData字段。本章讲这条链的前端一端,闭合"图即程序"的另一半。
1. 这是什么(先建直觉)
一句话定义: 前端画布是 Flowise 的"可视化编程界面"——把 LLM 应用的每个组件画成一个盒子(节点),盒子之间连线表示数据流,整张图就是程序本身。
它在整条链里的位置。 后端章节反复出现一个词:flowData。它其实是一段 JSON 字符串,形如:
{
"nodes": [ { "id": "llmAgentflow_0", "type": "agentflowNode", "position": {...}, "data": {...} } ],
"edges": [ { "id": "...", "source": "startAgentflow_0", "target": "llmAgentflow_0", ... } ],
"viewport": { "x": 0, "y": 0, "zoom": 1 }
}
- 前端:用户拖拽 → 生成/修改
nodes、edges→ 序列化成上面这段 JSON → 存库。 - 后端(01–04 章):读这段 JSON → 拓扑排序 → 构建成 LangChain 流水线 / AgentFlow V2 解释器 → 执行。
所以画布这一层的核心职责只有两件事:可视化编辑(让人操作图)和序列化(把图变成 JSON)。执行细节一律回指 02 / 03。
用起来什么样。 打开 /agentcanvas 路由,看到一块无限画布:左上角有个"+"抽屉列出所有可用节点,把节点拖进画布,拖节点右侧的小圆点(handle)到另一个节点左侧,就连成一条边;双击节点弹出参数表单填值;Cmd/Ctrl+S 保存。这一整套就是本章要拆开的东西。
2. 两套前端并存(这是最容易懵的地方)
Flowise 仓库里同时存在两套画布代码,服务不同产品线。先分清,否则读源码会串。
| 维度 | 老画布 packages/ui | 新画布 @flowiseai/agentflow(packages/agentflow) |
|---|---|---|
| 形态 | 一个完整 React 应用(Vite + Redux) | 一个可被别的 app 引入的独立 npm 包(库) |
| 语言 | JavaScript(.jsx) | TypeScript(.tsx) |
| 服务对象 | Chatflow(经典引擎,02)+ 老版 Multi-Agent | AgentFlow V2(03) |
| 状态管理 | 全局 Redux store(store/) | 包内自带 Context + reducer(infrastructure/store) |
| 画布入口 | views/canvas/index.jsx | Agentflow.tsx |
| ReactFlow 节点类型 | customNode / stickyNote / buttonedge | agentflowNode / iteration / stickyNote / agentflowEdge |
为什么要抽出一个独立包? 老画布是"应用的一部分",只能在 Flowise 自己的 UI 里跑。新的 @flowiseai/agentflow 被设计成可嵌入的库——别的 React 应用可以 import { Agentflow } from '@flowiseai/agentflow',传入 apiBaseUrl、token、components 就得到一块 AgentFlow V2 画布。这解释了它为什么要有严格的分层架构(见 §3)和 provider 注入设计。
依据:老画布
packages/ui/src/views/canvas/index.jsx:58(nodeTypes = { customNode, stickyNote });新包packages/agentflow/src/Agentflow.tsx:402(export const Agentflow = forwardRef(...),带apiBaseUrl/token/components的注释示例见Agentflow.tsx:386-400)。
2.1 老画布长什么样(速览,不深入)
老画布 Canvas 是一个巨型函数组件,把所有东西塞在一起:ReactFlow 实例、Redux dispatch、API 调用、拖放回调都在 index.jsx 里。看两个代表性片段建立印象就够了——
- 拖放建节点:
onDrop(views/canvas/index.jsx:274)读dataTransfer里的节点 JSON,用initNode初始化后concat进nodes。 - 保存:
handleSaveFlow(views/canvas/index.jsx:211)调reactFlowInstance.toObject()拿到{ nodes, edges, viewport },JSON.stringify后作为flowData发给createNewChatflow/updateChatflowAPI。
老画布的模式和新包思路一致(拖放→改 state→序列化→存库),但代码是"一坨"。下面把篇幅留给结构清晰、更有教学价值的新包。
3. agentflow 包的四层架构
新包 @flowiseai/agentflow 采用领域驱动的四层架构,核心规则一句话:依赖只能向下流。先看这张图(从上到下是依赖方向,上层能用下层,下层绝不反向 import):
┌───────────────────────────────────────────────┐
│ 根文件 (Public Face) │