用 React 19 做一个拖拽式算法编排器

智能体与编排 · 2025-09-14 · 5 分钟

画布状态与业务图状态各有职责:让连线真正成为可执行的数据流。

可视化编排的核心不是拖拽——那部分有现成的库。真正的难点是:画布上的图,和服务端真实的运行状态,如何不打架。

<figure> <a href="/images/editor.jpg" target="_blank" rel="noopener" title="点击查看大图"><img src="/images/editor-1440.webp" srcset="/images/editor-480.webp 480w, /images/editor-960.webp 960w, /images/editor-1440.webp 1440w" sizes="(max-width: 700px) calc(100vw - 40px), 760px" alt="算法编排器界面:左侧是组件库,中间是带端口的节点画布,右侧是节点配置表单,底部状态栏显示节点数、连线数与 DAG 校验结果" width="1800" height="1095" loading="lazy" decoding="async"></a> <figcaption>编排器的实际界面。<strong>左侧</strong>是组件库,按类别分组、可搜索;<strong>中间</strong>画布上每个节点都标着类型与版本(<code>input.source</code>、<code>cv_algorithm.open_vocabulary</code>……),两侧是输入/输出端口,连线即数据流;<strong>右侧</strong>是选中节点的配置表单;<strong>底部</strong>状态栏实时显示节点数、连线数与 DAG 校验结果。</figcaption> </figure>

两套状态,两种真相

画布状态是用户正在编辑的意图:节点、连线、参数。它是"未保存的草稿",可以任意修改、撤销、放弃。

运行状态是服务端的执行事实:每个节点实际在不在跑、上次执行结果、队列里有没有等待的任务。它来自轮询或推送,随时可能变化。

把这两者塞进同一个状态容器,会立刻出现经典问题:用户拖了一下节点,恰好服务端状态推送回来,把用户的编辑覆盖了。

分开管

· 草稿用本地状态管理,只在保存时提交;<br />· 运行态交给数据请求库管,带缓存与失效策略,只读不写;<br />· 界面上同一节点同时展示两者,但用不同的视觉层级——比如运行态用角标和描边,草稿态用节点本体。

38 种节点类型共享同一套节点外壳与选中/拖拽/连线交互,差异只在配置表单。表单按类型做映射,避免几十个几乎一样的组件。

一个容易忽略的细节

画布要能被键盘操作:Tab 遍历节点、方向键移动、Delete 删除、Escape 取消连线。拖拽在触屏上体验很差,提供一条"点击源节点 → 点击目标节点"的等价路径,移动端可用性提升明显。

复杂交互的可访问性不是加分项,它往往会让主流程本身变得更清楚。