wechen-agent-editor-v1
v1.3.10
Published
Wechen Agent:可执行的 AI 流程编排编辑器(React Flow),节点即模块、边即执行顺序;Monaco + Ant Design,面向 npm
Maintainers
Readme
wechen-agent-editor-v1
面向宿主应用的 Agent 流程编排编辑器 React 组件:左侧可拖入的组件库、中间 @xyflow/react 画布、右侧基于 antd 与 Monaco 的配置区。图上的 节点 表示功能模块,边 表示执行顺序;持久化的 flow 可由你的运行时或执行引擎消费。
包名:以 package.json 的 name 为准(wechen-agent-editor-v1)。安装与 import 均使用该名称。
当前发布版本:见根目录 package.json 的 version(变更见 CHANGELOG.md)。发版前自检见文末 维护者:发版。
宿主集成必读:本包
dependencies为空,运行时能力全部来自 peerDependencies。除npm install wechen-agent-editor-v1外,须在业务工程根目录一并安装react/react-dom/antd/@xyflow/react/dagre/monaco-editor/@monaco-editor/react(见下文 安装)。缺任一项会导致画布空白、连线失效、自动排版失败或 Monaco 无法加载。
文档导航:能力地图 · 架构说明 · 私有化部署 · 性能预算 · 图操作门控 ref · 自动布局 ref · ref 矩阵 · 集成阶梯 L0–L3 · 工程基线 · 2026 全库审计 · Playground ?tab=overview
快速开始
60 秒最小集成(L0)
只需 onSave 与 antd ConfigProvider,并保证父容器有高度(例如 height: 100% 链或 100vh)。未传 initialFlow 时为空画布(与编辑器默认一致)。
安装(在宿主项目根目录执行,勿只装本包):
npm install wechen-agent-editor-v1 react react-dom antd @xyflow/react dagre monaco-editor @monaco-editor/reactMonaco 还需在应用入口配置 worker(见 Monaco Worker)。
import { ConfigProvider } from "antd";
import {
WechenAgentEditor,
getDefaultComponentLibraryGroups,
} from "wechen-agent-editor-v1";
// 若生产构建后缺少 Flow 全局样式,再增加:import "wechen-agent-editor-v1/style.css";
export function AgentPage() {
return (
<ConfigProvider>
<div style={{ height: "100vh" }}>
<WechenAgentEditor
libraryGroups={getDefaultComponentLibraryGroups()}
onSave={(s) => console.log("flow", s.flow)}
/>
</div>
</ConfigProvider>
);
}常用配置一条对象(L1)
标题、初始流、只读、以及在默认组件库上按条目打补丁,可用 buildWechenAgentEditorProps 一次展开为 WechenAgentEditor 的 props(类型见包内导出)。
import { ConfigProvider } from "antd";
import {
WechenAgentEditor,
buildWechenAgentEditorProps,
cloneWechenAgentFlow,
COMPONENT_LIBRARY_GROUP_APP,
defaultInitialFlow,
} from "wechen-agent-editor-v1";
const editorProps = buildWechenAgentEditorProps({
title: "我的流程",
initialFlow: cloneWechenAgentFlow(defaultInitialFlow),
readOnly: false,
library: {
preset: "default",
itemPatches: [
{ groupKey: COMPONENT_LIBRARY_GROUP_APP, itemKey: "alert", patch: { subtitle: "自定义副标题" } },
],
},
onSave: (s) => console.log("flow", s.flow),
});
export function AgentPage() {
return (
<ConfigProvider>
<div style={{ height: "100vh" }}>
<WechenAgentEditor {...editorProps} />
</div>
</ConfigProvider>
);
}高级扩展(自定义画布节点、右侧配置面板、组件库列表映射、顶栏按钮等)仍使用完整 WechenAgentEditorProps;说明见下文「行为与扩展(摘要)」与 src/index.ts 导出。
后端工作流 JSON(存库 / 执行 / 回显)
编辑器内部持久化形态为 WechenAgentFlowJson(React Flow 的 nodes / edges + 业务 data)。若后端希望 扁平 DTO(flowId、flowName、nodes[].type + config、edges[].source/target 等),请使用:
exportBackendWorkflowJson(snapshot | flow, meta)→WechenBackendWorkflowJson(根字段schemaVersion当前为"1",与业务字段version区分)importBackendWorkflowToFlow(dto)→WechenAgentFlowJson,再作为initialFlow或ref.replaceFlow回显
边 id(删边 / 命令式 API):
onSave/getSnapshot/flowJson:edges[].id为画布稳定 id,ref.deleteEdge(edgeId)/getSelectedEdgeId()应使用该 id。- 后端 DTO:
exportBackendWorkflowJson会写出edges[].id(与画布一致);importBackendWorkflowToFlow优先 使用 DTO 中的id,省略时按e-${source}-${target}-${index}合成。仅持久化 DTO 时,回灌后删边请用 回灌后的flow.edges[].id,勿沿用导出前的 id(除非 DTO 已含id且已往返)。 - 推荐:需要完整画布状态时优先存
snapshot.flow或flowJson;仅存 DTO 时务必带上edges[].id字段。
有损字段:导出会去掉 RF 运行期字段与部分边样式;导入会按边 id 分配 sourceHandle/targetHandle。详见类型 WechenBackendWorkflowJson 的 JSDoc。
连线布局:
flowJson/getSnapshot:在edge.data中持久化wechenSourceAttachSide/wechenTargetAttachSide与wechenPathOptions(offset、borderRadius),回显时 优先使用存盘布局,与编辑态一致。- 后端 DTO:
edges[].layout可选携带上述字段(exportBackendWorkflowJson/importBackendWorkflowToFlow);若需 完整 画布状态(含全部edge.data),请优先持久化flowJson而非仅 DTO 拓扑。
节点类型映射(画布 ↔ DTO):
| 画布 | DTO nodes[].type | 说明 |
|------|-------------------|------|
| pill | start | 入口 |
| mainAgent | agent | 主 Agent |
| card(默认) | task | 通用任务 |
| card + data.backendNodeType | condition / api / script | 扩展类型;导入后写回 backendNodeType |
画布类型与 DTO 解耦:导出时 config.canvasNodeType 始终为画布 node.type(可为 MAIN、SUB 等任意非空字符串,或内置 pill / mainAgent / card);config.libraryGroupKey、config.libraryItemKey 为组件库拖入元数据。导入时优先用 canvasNodeType 恢复 node.type(任意非空值),后端 nodes[].type(agent / task / start 等)仍按上表映射。保存 onSave / flowJson 直接保留 nodes[].type(与 items[].type 一致)。
| 字段 | 含义 |
|------|------|
| 画布 node.type | React Flow 节点类型;拖入时 仅 由 items[].type(必填,任意非空字符串)决定 |
| DTO nodes[].type | 后端执行类型(agent / task / start …) |
| config.canvasNodeType | 往返时还原画布 node.type |
| data.libraryGroupKey / libraryItemKey | 库分组 catalog id / 库项 id(非 props.type) |
自定义节点若需稳定 DTO 类型,请在 node.data 设置 backendNodeType(取值与 WechenBackendWorkflowNodeType 一致)。
import {
WechenAgentEditor,
exportBackendWorkflowJson,
importBackendWorkflowToFlow,
} from "wechen-agent-editor-v1";
<WechenAgentEditor
onSave={(s) => {
const dto = exportBackendWorkflowJson(s, {
flowId: "agent_flow_001",
workflowVersion: "1.0",
});
void fetch("/api/flows", {
method: "POST",
body: JSON.stringify(dto),
headers: { "Content-Type": "application/json" },
});
}}
/>;
// 回显:从接口取 dto 后
const flow = importBackendWorkflowToFlow(dtoFromApi);
// <WechenAgentEditor initialFlow={flow} />本仓库 playground 提供对照:
pnpm run dev:文档站(侧栏能力 Tab + 仓库文档?tab=repo-docs+ 分栏独立滚动;默认?tab=overview),见 playground/README.md。pnpm run dev:minimal:仅最小宿主集成(对照上文「60 秒最小集成」)。
编辑器与 Agent 运行时
本包是 编排 UI + 序列化 + ref API,不包含 Agent / LLM 执行引擎。推荐分层:
WechenAgentEditor(编排) → onSave / exportBackendWorkflowJson → 宿主持久化 → 你们的运行时调度onTestRun/ref.testRun():仅触发宿主回调,具体执行由宿主实现。validateWechenAgentFlow(flow):纯函数图校验(默认不阻断保存);可在onBeforeSave内返回false拦截。- ref 增删改查:
getSnapshot/getFlow/getInitialFlow/getNode/getEdge/replaceFlow/setInitialFlow/patchNodeData(node.data) /patchNode/setNodePosition(坐标与壳层) /setTitle/removeNode/duplicateNode/removeEdge/deleteEdge/ 路径与布局 ref 等(见WechenAgentEditorRef、Playground?tab=ref-api)。 - 连线路径(自由折线):选中边后浮动条「添加拐点」;仅 中间拐点 可拖拽(任意角度线段),首尾随节点桩固定;水平/垂直线段 可整条平移(鼠标移到线段上,光标变为 ↔ 或 ↕);
Delete在顶点选中时删点。normalizeWechenAgentFlowJson会清除无效path并回到auto。 - 选中浮动操作条:单选节点时显示 删除 / 复制;单选边时显示 删除、添加/删除拐点、手动边 恢复自动路径。与边上 路径顶点手柄(几何编辑)分工;浮动条锚点在路径外侧,避免压住连线与节点。
readOnly/readingMode/ 多选时不显示。可选renderSelectionToolbar扩展。 - 布局相关 ref(
readOnly/readingMode下均为 no-op):完整说明 → docs/AUTO-LAYOUT-REF.md
| 方法 | 作用 |
|------|------|
| relayoutEdges() | 重算连线,不移动节点,保留手调路径中间拐点 |
| autoLayoutNodes(options?) | 低层节点排版(默认一体化);默认布线但 不清 手调拐点 |
| autoLayout(options?) | 顶栏推荐:无参 = node+edge(清手调路径)+fitView;only 白名单 |
| fitView() | 仅适配视口 |
editorRef.current?.autoLayout();
editorRef.current?.autoLayout({ only: ["node"] });
editorRef.current?.autoLayout({ only: ["edge"] });
editorRef.current?.autoLayoutNodes({ direction: "TB" });
editorRef.current?.relayoutEdges();- 回显后连线布局:
getSnapshot/flowJson保存锚边与路径选项;replaceFlow优先恢复存盘布局。 - 路径避让 / 一体化排版:见 AUTO-LAYOUT-REF.md。
import { validateWechenAgentFlow } from "wechen-agent-editor-v1";
<WechenAgentEditor
onBeforeSave={(snap) => {
const { valid, issues } = validateWechenAgentFlow(snap.flow);
if (!valid) {
console.warn(issues);
return false;
}
}}
onSave={(s) => { /* POST exportBackendWorkflowJson(s, meta) */ }}
/>安装
本包为 纯组件库:package.json 中 dependencies 为空,不会把 React Flow、Dagre、Monaco 等打进你的 node_modules 树里作为本包的私有副本。宿主必须在应用根声明下列 peer,并与本包解析到同一份运行时(尤其 @xyflow/react)。
推荐:一条命令装齐
npm install wechen-agent-editor-v1 react react-dom antd @xyflow/react dagre monaco-editor @monaco-editor/react使用 pnpm / yarn 时同样在业务包中声明上述依赖,勿仅在 monorepo 子包里安装而根目录缺失 peer。
仅安装本包时常见现象
| 缺失 peer | 现象 |
|-----------|------|
| react / react-dom | 无法渲染 |
| antd | 壳层/配置区样式或组件报错 |
| @xyflow/react | 画布不显示、连线/拖线无响应、Handle 失效 |
| dagre | autoLayout / autoLayoutNodes 报错或无效 |
| monaco-editor + @monaco-editor/react | 代码编辑区空白(另需 Monaco Worker) |
Peer 依赖清单(须在宿主 package.json 中声明)
版本须 ≥ 本包 peerDependencies 下限,并与业务栈锁定策略一致(下表为开发本仓库时使用的参考版本):
| 包 | 下限(peer) | 用途 |
|----|-------------|------|
| react、react-dom | ≥18 | UI 运行时 |
| antd | ≥5 | 编辑器壳层、配置区、顶栏 |
| @xyflow/react | ≥12 | 画布(节点/边/连线) |
| dagre | ≥0.8.5 | 节点自动排版(autoLayoutNodes) |
| monaco-editor | ≥0.45 | 代码编辑内核 |
| @monaco-editor/react | ≥4.6 | React 封装 |
自 1.2.3 起 @xyflow/react、dagre 由 peer 提供(不再随本包内嵌)。自 1.0.x 起 react / antd / monaco-* 即为 peer。升级自内嵌 Flow 的旧版时,请务必在宿主根目录 新增 @xyflow/react 与 dagre 依赖。
自 1.3.2 起推荐用 onFlowOperation 统一图操作策略(替代仅覆盖删除的 onBeforeRemove*);peer 清单无变化,从 1.3.0 升级通常只需 bump 版本并回归构建与画布烟测。
不要再依赖本包 tarball 内自带的 @xyflow/react(历史 bundledDependencies 已移除)。宿主与编辑器 必须 npm ls @xyflow/react 仅一棵有效树。
安装后建议自检:
npm ls @xyflow/react
npm ls react理想情况为各包 仅一棵 依赖树、无重复主版本。若出现多份,请在打包配置中对 @xyflow/react(及必要时 react)配置 resolve.alias / dedupe,或统一锁定版本后重装 node_modules。
安装失败(ERESOLVE)
npm 7+ 会校验整棵依赖树的 peer。冲突常来自宿主已有包,未必与本包声明矛盾。优先:升级冲突链上的上游(如 Pro 组件、ahooks、Umi 插件)使 peer 与 React 18 一致。临时可 npm install wechen-agent-editor-v1 --legacy-peer-deps 或在宿主 .npmrc 使用 legacy-peer-deps=true(上线前务必用 npm run build 验证,并计划去掉该放宽)。
Umi / MFSU
一般可直接使用。若遇预编译或模块解析异常,可在配置里将 wechen-agent-editor-v1、@xyflow/react、monaco-editor、@monaco-editor/react 加入 mfsu.exclude(见 Umi MFSU),删除 .umi 后重启;或用 mfsu: false 做对比排查。
依赖示例(版本请按团队锁定,须满足本包 peerDependencies):
{
"dependencies": {
"@monaco-editor/react": "^4.6.0",
"@xyflow/react": "^12.3.6",
"antd": "^5.22.0",
"dagre": "^0.8.5",
"monaco-editor": "^0.45.0",
"react": "^18.3.0",
"react-dom": "^18.3.0",
"wechen-agent-editor-v1": "^1.3.6"
}
}集成排错
白屏优先:打开控制台过滤 [wechen-agent-editor],对照 docs/WHITE-SCREEN-RISK-REGISTER.md 中的 ID/code;Playground 手测 ?tab=legacy-host-api-flow(JSON 文本域)与 ?tab=custom-config-panel(配置区边界)。
| 现象 | 可能原因 | 处理 |
|------|----------|------|
| 代码编辑区空白 / 控制台 Monaco worker 报错 | 未配置 Monaco worker | 按 README「Monaco Worker」在应用入口引导 worker |
| worker-javascript.js MIME text/html / importScripts 失败 | 开发服务器对 worker 返回 HTML 404 | 配置 MonacoEnvironment;Umi 见 docs/UMI-MONACO-WORKER.md |
| 选中边后白屏、Rendered more hooks than during the previous render | 旧版边组件在 return null 后调用 hook | 升级含边组件 Hooks 修复的版本(见 CHANGELOG) |
| 有边即白屏、reading 'offset' | 旧 flow 无 wechenPathOptions 或几何未就绪 | 升级含 legacy 边容错版本;控制台搜 [wechen-agent-editor](如 EDGE_PATH_OPTIONS_NULL、EDGE_GEOMETRY_UNAVAILABLE) |
| 画布区域 Alert「渲染异常」、其余页面正常 | 画布子树未捕获异常 | 看控制台 CANVAS_RENDER_CRASH 堆栈;修复宿主 renderFlowNode 或数据 |
| 配置区 Alert「配置区渲染异常」、画布仍可用 | renderConfigPanel 抛错 | 看 CONFIG_PANEL_RENDER_CRASH;修复宿主配置面板实现 |
| 画布不可见或高度为 0 | 父级无 定高 或 flex 链缺 minHeight: 0 | 使用「嵌入 flex 布局」示例;或传编辑器 minHeight |
| 连线异常、Handle 无响应、样式错乱 | 未装 peer 或 @xyflow/react 双实例 | 按上文 一条命令 装齐 peer;npm ls @xyflow/react 仅一棵;打包器 alias/dedupe |
| autoLayout 报错 | 未安装 dagre | 在宿主 dependencies 添加 dagre |
| ref.deleteEdge(id) 无效 | 持久化载体无稳定 edges[].id | 优先存 snapshot.flow / flowJson;仅存 DTO 时导出须含 edges[].id,回灌后用 新 id |
| 只读仍出现浮动条或可删节点 | readOnly 未传给编辑器根组件 | <WechenAgentEditor readOnly />;勿仅 Form disabled |
| 存库后连线形状与编辑时不一致 | 仅存拓扑 DTO、缺 layout | 使用 flowJson 或 DTO edges[].layout(见「连线布局」) |
Playground ?tab=overview 提供术语表与持久化对比;工程清单见 docs/ENGINEERING-BASELINE.md。
package.json的repository/homepage/bugs若为占位 URL,表示尚未绑定公开仓库;私有 fork 可忽略,发版前请改为真实地址。
使用
入口与样式
import { WechenAgentEditor, type WechenAgentSnapshot } from "wechen-agent-editor-v1";发布产物中 dist/index.js 会包含对 ./style.css 的引用,正常情况下图样式会随主入口进入打包结果。若生产环境仍看不到 Flow 全局样式,可增加:
import "wechen-agent-editor-v1/style.css";antd 5 为 CSS-in-JS,请用 ConfigProvider 包裹应用(与官方用法一致)。
嵌入 flex 布局
在 列方向 flex 页面中,推荐与 playground 相同的外层结构:父级定高(或 100vh),编辑器所在子项使用 flex: 1、minHeight: 0,编辑器根节点会 flex: 1 并 height/width: 100% 填满该区域。
<div style={{ height: "100vh", display: "flex", flexDirection: "column", minHeight: 0 }}>
<YourToolbar />
<div style={{ flex: 1, minHeight: 0, display: "flex", flexDirection: "column" }}>
<WechenAgentEditor {...props} />
</div>
</div>行为说明:自 1.1.x 起,画布区域 不再 默认施加 min-height: 400px;若需要最小可视高度,请对编辑器传入 minHeight prop,或在页面级容器设置 min-height。
骨架 CSS 类名(自定义样式)
加载 wechen-agent-editor-v1/style.css 后,可用下列稳定类名覆盖布局分区样式(与根 className 合并,不会替换默认类):
| 类名 | 区域 |
| --- | --- |
| wechen-agent-editor | 根布局 |
| wechen-agent-editor--reading | 阅读模式根修饰 |
| wechen-agent-editor__workspace | 工作区(顶栏 + 主体) |
| wechen-agent-editor__header | 顶栏 |
| wechen-agent-editor__body | 主体行(左栏 + 画布 + 右栏) |
| wechen-agent-editor__library-sider | 左侧组件库 Sider |
| wechen-agent-editor__library-panel | 组件库面板内容 |
| wechen-agent-editor__main | 中间主内容 |
| wechen-agent-editor__canvas / wechen-agent-canvas | 画布壳(二者同元素,后者为兼容别名) |
| wechen-agent-editor__config-sider | 右侧配置 Sider |
| wechen-agent-editor__config-panel | 配置面板内容 |
| wechen-agent-node-shell / wechen-agent-node-shell--selected | 内置节点外壳 / 选中态 |
| wechen-agent-node-shell--mainAgent / --card / --pill | 节点类型修饰 |
| wechen-agent-edge / wechen-agent-edge--selected | 默认连线 / 选中态 |
自定义选中高亮(颜色写在 CSS 变量中,勿依赖行内 style):
| CSS 变量 | 默认 | 说明 |
| --- | --- | --- |
| --wechen-selection-color | #52c41a | 节点选中边框、默认边选中描边 |
| --wechen-selection-border-width | 2px | 节点选中边框宽度 |
| --wechen-selection-shadow | 绿晕阴影 | 节点选中阴影 |
| --wechen-node-border-color | #d9d9d9 | 未选中节点边框 |
| --wechen-edge-stroke | #b1b1b7 | 未选中边描边 |
| --wechen-edge-stroke-selected-width | 2 | 选中边线宽 |
| --wechen-zone-strip-border-color | rgba(22, 119, 255, 0.55) | 四边感应条 hover 描边色 |
| --wechen-zone-strip-background | rgba(22, 119, 255, 0.06) | 四边感应条 hover 背景色 |
| --wechen-zone-strip-border-width | 2px | 感应条 hover 描边宽度 |
四边感应条稳定类名:wechen-agent-zone-strip(基类)、wechen-agent-zone-strip--top / --right / --bottom / --left(边向)、wechen-agent-zone-strip--active(当前边 hover)。宿主可直接写选择器覆盖,或与上表变量一并设置在 .wechen-agent-editor 上。
/* 例:自定义顶栏背景 */
.wechen-agent-editor__header {
background: #fafafa !important;
}
/* 例:选中高亮改为品牌蓝 */
.my-app .wechen-agent-editor {
--wechen-selection-color: #1677ff;
}
/* 例:四边感应条 hover 色 */
.my-app .wechen-agent-editor {
--wechen-zone-strip-border-color: #1677ff;
--wechen-zone-strip-background: rgba(22, 119, 255, 0.12);
}国际化(壳层 vs 宿主自定义)
编辑器是 编排基座:画布节点由 renderFlowNode、右侧内容区由 renderConfigPanel / renderNodeConfig 自定义时,业务文案由宿主自行 i18n。包内 locale / localeMessages 覆盖 壳层(顶栏按钮、左右栏分区标题、组件库搜索/空状态、画布缩放控件等;经 React Flow ariaLabelConfig 注入)。
语言契约
| 条件 | 编辑器显示语言 |
| --- | --- |
| 未传 locale 与 localeMessages | 内置中文(zh-CN / DEFAULT_ZH_CN),与 antd ConfigProvider、浏览器语言 无关 |
| locale="en-US"(无 localeMessages) | 内置英文(DEFAULT_EN_US) |
| locale + localeMessages | 先解析 locale 底包,再 浅合并 localeMessages 覆盖同 key |
| 仅传 localeMessages(无 locale) | legacy:仅以 pack 为准;缺失 key 显示 key 字符串(与旧版一致) |
| 存在 localeTranslate | 最高优先级(便于对接 i18next) |
语言包 key 为默认中文(与界面中文一致),例如 { "保存": "Save", "配置": "Settings" }。代码中可用 LOCALE_KEYS / FALLBACK_LOCALE_KEYS 常量。
import {
WechenAgentEditor,
LOCALE_KEYS,
registerWechenAgentEditorLocale,
exportWechenAgentEditorLocaleBundle,
mergeWechenAgentEditorLocaleMessages,
} from "wechen-agent-editor-v1";
// 中文(默认)
<WechenAgentEditor />;
// 英文:一行 prop,无需维护整包 JSON
<WechenAgentEditor locale="en-US" />;
// 注册自定义语言后切换
registerWechenAgentEditorLocale("ja-JP", jaPartialMessages);
<WechenAgentEditor locale="ja-JP" />;
// legacy:整包 localeMessages(key 为中文)
const enPack = mergeWechenAgentEditorLocaleMessages({
[LOCALE_KEYS.HEADER_SAVE]: "Save",
});
<WechenAgentEditor localeMessages={enPack} />;
// ref:读取当前语言包或命令式切换(受控 locale prop 优先)
editorRef.current?.getLocaleMessages();
editorRef.current?.setLocale("en-US");renderConfigPanel / renderNodeConfig / renderSelectionToolbar 的 ctx.locale 含 t、locale(id)、messages(与 getLocaleMessages() 一致)。
// antd 与编辑器语言独立配置
<ConfigProvider locale={antdLocale}>
<WechenAgentEditor locale="en-US" />
</ConfigProvider>exportWechenAgentEditorLocaleBundle('en-US'):导出内置英文整包(作翻译模板)。exportWechenAgentEditorShellLocaleBundle(locale)/exportWechenAgentEditorFallbackLocaleBundle(locale):按 locale 导出子集。registerWechenAgentEditorLocale/listWechenAgentEditorLocaleIds:扩展语言列表。configSiderTitle:可完全覆盖右侧栏外框标题。validateLocaleMessagesCompleteness:检查大 JSON 是否缺 key(开发期console.warn)。
完整演示见 Playground ?tab=i18n(locale prop、注册表、ref getLocaleMessages、legacy pack)。
嵌入尺寸、readOnly 与 readingMode
根组件默认 height / width 为 100%,flex: 1、minHeight: 0(便于 flex 父级);可通过 className / style 透传;可选 minHeight。请保证父级链路上有明确高度(见上文 flex 示例),否则中间画布可能无法撑满。
readOnly 须挂在 <WechenAgentEditor readOnly /> 根组件上;仅对右侧 Form 设 disabled 不会 禁止画布改图、浮动条或组件库拖入。
| | 三栏 UI | 画布选中 | 右侧配置 | 浮动条 / 改图 |
|---|:---:|:---:|:---:|:---:|
| 默认 | ✓ | ✓ | 可编辑 | ✓ |
| readOnly | ✓ | ✓(查看) | disabled | ✗(无浮动条;ref 改图 no-op) |
| readingMode | 仅画布 | ✗ | 无右栏 | ✗ |
readOnly:禁止对图的增删改(组件库拖入、连线、删除键、拖动节点、选中浮动条、ref.removeNode/patchNodeData等),仍可使用平移/缩放查看,可点选节点/边 在右侧只读查看配置。readOnly与readingMode会出现在renderConfigPanel/renderNodeConfig的入参中。readingMode:仅展示中间流程画布(隐藏顶栏与左右栏),并禁用选中与一切改图,适合文档内嵌、大屏预览等纯展示场景(隐含只读)。根节点会附加wechen-agent-editor--reading。
删除守卫(onBeforeRemoveNode / onBeforeRemoveEdge)— @deprecated
新集成请用 下文 图操作事件(
onFlowOperation)。本 API 仅覆盖删除;与onFlowOperation同时传入时 事件优先。迁移见 docs/CAPABILITY-MAP.md。
在可编辑模式下,可通过回调 按节点/边粒度 决定是否允许删除。同步返回 false 时取消该次删除(Backspace 与 ref.removeNode / ref.removeEdge 均生效)。readOnly / readingMode 下不调用回调。删除节点时 级联移除 的关联边 不会 逐条触发 onBeforeRemoveEdge。
<WechenAgentEditor
onBeforeRemoveNode={(node) => {
// 例:禁止删除入口节点,或 data.deletable === false 的节点
if (node.id === "start_node") return false;
if (node.data?.deletable === false) return false;
return true;
}}
onBeforeRemoveEdge={(edge) => edge.data?.locked !== true}
/>L1 工厂同样支持:buildWechenAgentEditorProps({ onBeforeRemoveNode: ... })。
图操作事件(onFlowOperation)
在可编辑模式下,删/复制/新增/连线/patch/路径 等改图操作在应用前会派发 WechenFlowOperationEvent。宿主在 onFlowOperation 中按 event.kind 与 event.node / event.edge 同步返回 false 即可取消(键盘、浮动条、ref 一致)。onFlowOperation 对 node.remove / edge.remove 优先于 legacy 删除回调。readOnly / readingMode 下不调用。
完整 kind / source 说明表、载荷字段与反模式 → docs/FLOW-OPERATION-REF.md(SSOT)。登记类型:WechenFlowOperationKind(src/flow/flowOperationEvents.ts 中 WECHEN_FLOW_OPERATION_KINDS)。
拒绝 node.remove 时 原子保留 节点及因该次删除会连带移除的边(级联边 不 再派发 edge.remove);显式删边仍走 edge.remove。
| kind | 含义(摘要) |
|--------|----------------|
| node.remove | 删节点(键盘 / 浮动条 / ref) |
| node.duplicate | 复制节点 |
| node.add | 新增节点(拖库 / ref) |
| node.patch | 补丁 data(配置区)或壳层 position/type 等(ref) |
| node.move | 拖节点结束落位(拒绝则回滚坐标) |
| edge.remove | 显式删边 |
| edge.add | 新建连线 |
| edge.reconnect | 拖拽改端点 |
| edge.patch | 补丁边 |
| edge.path.reset | 单条边恢复自动路径 |
| edge.path.insertVertex / edge.path.removeVertex | 折线路径插/删拐点 |
<WechenAgentEditor
onFlowOperation={(event) => {
if (event.node?.type === "pill") {
if (event.kind === "node.remove" || event.kind === "node.duplicate") {
return false;
}
}
return true;
}}
/>Playground:?tab=flow-operation-policy。L1:buildWechenAgentEditorProps({ onFlowOperation: ... })。
顶底感应区连线(默认,上进下出)
内置 pill / card / mainAgent 在未使用 renderFlowNode、且未启用下文 legacy flowHandles 时,采用 顶 / 底 周界窄条:输入池 = 顶边(target)、输出池 = 底边(source),符合 Agent 自上而下 阅读;多条入/出边沿顶/底边按邻居 水平坐标 分列槽位;整条感应条均可拖拽拉出连线(十字光标),几何锚点仍在 条带中心 stub。新建连接桩:in-new-top-{nodeId}(顶)、out-new-bottom-{nodeId}(底)。透明 Handle、无固定圆点桩 UI。新边 sourceHandle / targetHandle 仍为 out-${edgeId} / in-${edgeId};normalizeWechenAgentFlowJson 归一化旧桩 id。新建连线写入 edge.data.wechenSourceAttachSide / wechenTargetAttachSide(默认 底→顶)。旧图 left/right 锚边在加载/同步时迁移为 top/bottom。ConnectionMode.Strict、isValidConnection(禁自环)。默认边 平滑阶梯 + 终点箭头(箭头在 target 端)。与 autoLayoutNodes / relayoutEdges、底边扇出 共干 路径配合。
周界四向感应的 完整方案说明(交互、角区、数据字段、代码映射) 见仓库 openspec/changes/node-perimeter-sensing-scheme/design.md。
组件库 legacy 接线桩(flowHandles,可选)
已弃用优先:编排默认以 四边感应区 为准。libraryGroups 中每项仍可设置 flowHandles 以兼容旧版「声明桩位」:FlowHandleSpec[] 或 mode: 'even' 自动均分。拖入后写入 data.flowHandles;card / mainAgent 在 flowHandles 展开非空 时仍走声明桩渲染。libraryGroups 为必传 prop(可用 getDefaultComponentLibraryGroups() 或自建分组)。group.key 仅为分组 catalog id(如 "main" / "MAIN"),不 决定节点类型。items[].type(必填) 为拖入后的画布 node.type,可为任意非空字符串(如宿主业务类型 MAIN / SUB,或内置 mainAgent / card / pill);items[].key 为库项 id,写入 data.libraryItemKey,与 type 是两码事。同组可配置不同 type。拖入后另写入 data.libraryGroupKey。
items[].defaultData(可选):拖入或 ref.addNodeFromLibrary 时 浅合并 进 node.data(在工厂基础字段之后;libraryGroupKey / libraryItemKey 始终由包写入,不会被 defaultData 覆盖)。嵌套对象整键替换(与 patchNode 深度合并 不同)。flowHandles 仅来自 item.flowHandles,写在 defaultData 内的 flowHandles 会被忽略。内置 mainAgent 仍有包内默认(如 baseModel),可用 defaultData 覆盖同名键。
const libraryGroups = [
{
key: "MAIN",
title: "主Agent",
items: [
{
key: "planner",
title: "Planner",
type: "MAIN",
defaultData: { strategy: "Plan-Execute" },
},
{ key: "supervisor", title: "Supervisor", type: "MAIN" },
],
},
{
key: "SUB",
title: "应用Agent",
items: [
{
key: "alert",
title: "告警分析",
type: "SUB",
defaultData: { skill: "alert-analysis" },
},
],
},
];非内置 type 使用与 card 相同的通用节点外壳与四边感应区;缺 item.type 时拖放会提示且不会创建节点。宿主可在加载配置后调用 validateComponentLibraryGroups(libraryGroups) 预检缺 type 的条目。
组件库条目 UI(renderComponentLibraryItem)
左侧列表 默认无「+」按钮,添节点主路径为 拖拽 到画布。若需行尾点击添加,在条目上设 showAddButton: true(调用与拖放相同的 addNodeToCanvas)。
宿主可通过 renderComponentLibraryItem 自定义 每一行 的呈现与 trailing 操作(如「详情」「订阅」),回调 ctx 含 editorRef、addNodeToCanvas、defaultContent。返回 undefined 使用内置行。外层 drag 由基座托管,自定义 UI 不会 破坏拖放。
import { Button, List } from "antd";
<WechenAgentEditor
libraryGroups={groups}
renderComponentLibraryItem={(ctx) => (
<List.Item
actions={[
<Button
key="detail"
type="link"
size="small"
onClick={(e) => {
e.stopPropagation();
openAgentDetail(ctx.item.key);
}}
>
详情
</Button>,
]}
>
{ctx.defaultContent}
</List.Item>
)}
/>自定义按钮 应 在 click / pointerdown 上 stopPropagation,避免误触拖拽。
组件库分组折叠(libraryGroupCollapse)
多分组场景下可 opt-in 启用分组折叠:传 libraryGroupCollapse={true} 后,分组标题变为可点击按钮(chevron + 条目数);未传 时保持现网平铺标题,无行为变化。
- 默认 全部展开;可用
defaultCollapsedGroupKeys指定初始收起的group.key - 搜索 时含匹配项的分组 强制展开;清空搜索后恢复用户折叠状态
persist(默认true)将折叠状态写入 sessionStorage- ref:
getLibraryGroupCollapseEnabled()/setLibraryGroupCollapseEnabled(true|false)可运行时开/关;setLibraryGroupExpanded/toggleLibraryGroup控制单组。调试面板 概览 Tab 提供开关。
<WechenAgentEditor
libraryGroups={groups}
libraryGroupCollapse={{
defaultCollapsedGroupKeys: ["app"],
persist: true,
}}
/>样式类名(宿主可覆盖):组件库 DOM 使用 wechen-agent-editor__* 前缀,可在宿主全局/模块样式中按需覆盖(例如修复宿主 button { min-height } 导致分组标题过高、或折叠后分组间距过大):
| 类名 | 说明 |
| --- | --- |
| wechen-agent-editor__library-panel | 组件库根容器(搜索 + 列表) |
| wechen-agent-editor__library-search | 搜索框 |
| wechen-agent-editor__library-groups | 可滚动分组列表容器 |
| wechen-agent-editor__library-group | 单个分组外壳 |
| wechen-agent-editor__library-group--expanded / --collapsed | 展开 / 收起状态 |
| wechen-agent-editor__library-group--flat | 未启用折叠时的平铺分组 |
| wechen-agent-editor__library-group-header | 可点击分组标题(<button>) |
| wechen-agent-editor__library-group-body | 分组内容区(条目列表) |
| wechen-agent-editor__library-group-title / --static | 标题文字 / 不可折叠标题 |
| wechen-agent-editor__library-group-chevron / __library-group-count | chevron / 条目数 |
| wechen-agent-editor__library-group-list / __library-list | 条目 List 外层 / antd List |
| wechen-agent-editor__library-item | 可拖拽条目外壳 |
| wechen-agent-editor__library-item-row / __library-item-meta | antd List.Item / Meta |
分组元素还带 data-library-group-key;条目带 data-library-item-key、data-library-group-key,便于按 key 精确定位。
/* 示例:宿主 AgentEditor 中修复折叠标题高度 */
.wechen-agent-editor__library-group-header {
min-height: unset;
padding: 0;
}
.wechen-agent-editor__library-group--collapsed {
margin-bottom: 4px;
}renderFlowNode(props, context):props.type = 落盘 item.type(不是 item.key);context 含 selected、可选 libraryGroupKey / libraryItemKey、editorRef、nodeError(setNodeError / clearNodeError / getNodeErrorState)、editorScope(与 prop 同引用)。宿主侧自动化/保存等 仍建议 挂 ref。四边感应与接线桩仍由包渲染,勿 在返回值内放置 Handle。类型见 WechenAgentRenderFlowNodeContext 等导出。
节点运行时异常态与 editorScope
异常态为 运行时状态,不写入 getFlow() / 保存快照;删除节点或 replaceFlow 后自动清理。默认 blockSaveOnNodeError={true}:任一节点为异常态时,顶栏保存与 ref.save() 不调用 onSave,仅弹出提示(SAVE_BLOCKED_NODE_ERROR_HINT);设为 false 可恢复由宿主在 onBeforeSave 自行门控。
Playground ?tab=editor-scope 专页演示;与 nodeError 联用见 ?tab=render-flow-node。
保存门控顺序(evaluateSaveGate):配置未保存蒙层 → 节点异常态 → onBeforeSave。
| API | 说明 |
| --- | --- |
| context.nodeError.setNodeError(reason?) | 将当前节点标为异常,右上角显示角标 |
| context.nodeError.clearNodeError() | 恢复正常 |
| context.nodeError.getNodeErrorState() | { status: 'ok' \| 'error', reason? } |
| renderConfigPanel(kind: 'node') | 同上 nodeError API,可展示 Alert |
| ref.setNodeError(id, reason?) / getNodesInError() | 命令式按 nodeId 操作;批量查询异常节点 |
| editorScope prop | 宿主传入可变字典;context.editorScope / ref.getEditorScope() 读同一引用 |
| blockSaveOnNodeError | 默认 true:有异常节点时阻止保存 |
| nodeErrorValidator | 实例级校验器 (ctx) => reason \| falsy;flow 载入 / patch / editorScope 变更时自动调度(不经 render 透传) |
| nodeErrorValidateRevision | 递增时全图重校(字典异步就绪且 editorScope 引用不变时) |
| ref.revalidateNodeErrors(ids?) | 手动触发校验 |
setNodeError / clearNodeError 在状态未变化时为 no-op(幂等),且默认经 微任务批量落盘(不在 renderFlowNode 渲染中途同步触发订阅更新),因此可在渲染函数内根据 node.data 同步标异常而不会死循环。推荐:配置类校验优先用 nodeErrorValidator(声明式、首屏即标角标);无 validator 时仍可在 renderFlowNode 内手动 setNodeError。
nodeErrorValidator 推荐模式(稳定 editorScope + 无闭包):
const editorScope = useRef({ skillDict: [] as { value: string }[] }).current;
const [validateRevision, setValidateRevision] = useState(0);
useEffect(() => {
editorScope.skillDict = loadedSkills;
setValidateRevision((r) => r + 1);
}, [loadedSkills]);
const nodeErrorValidator = useCallback((ctx) => {
const skills = ctx.nodeData.skill as string[] | undefined;
const dict = (ctx.editorScope.skillDict as { value: string }[]) ?? [];
const bad = skills?.filter((id) => !dict.some((d) => d.value === id));
return bad?.length ? `异常插件: ${bad.join(",")}` : null;
}, []);
<WechenAgentEditor
editorScope={editorScope}
nodeErrorValidator={nodeErrorValidator}
nodeErrorValidateRevision={validateRevision}
/>有 nodeErrorValidator 时 勿 在 renderFlowNode 内重复 setNodeError 做同一类校验(调度结果为 SSOT)。
renderNodeErrorBadge(ctx):与 renderConfigPanel 类似的回调;按 ctx.state / ctx.reason / ctx.nodeType 返回不同图标。返回 undefined 用内置图标;null 不显示角标。可选 nodeErrorBadgeClassName / nodeErrorBadgeStyle 覆盖默认样式。默认角标在节点壳层 右上角内缩 4px(--wechen-node-error-badge-inset-top / --wechen-node-error-badge-inset-right 可覆盖)。
const scope = useRef({ modelDict: MODEL_MAP }).current;
<WechenAgentEditor
editorScope={scope}
renderNodeErrorBadge={(ctx) =>
ctx.reason?.includes("模型") ? <CloseCircleFilled /> : undefined
}
renderFlowNode={(props, ctx) => {
if (!props.data.model) ctx.nodeError.setNodeError("未选择模型");
else ctx.nodeError.clearNodeError();
return <MyNode data={props.data} dict={ctx.editorScope.modelDict} />;
}}
renderConfigPanel={(ctx) => {
if (ctx.kind !== "node") return undefined;
const err = ctx.nodeError.getNodeErrorState();
return err.status === "error" ? (
<Alert type="error" message={err.reason} />
) : undefined;
}}
/>Monaco Worker
必须在首次渲染编辑器之前完成 Monaco worker 引导,否则代码编辑区无法初始化。可参考本仓库 playground/src/monacoBootstrap.ts 与 playground/src/main.tsx,并按 Monaco / @monaco-editor/react 文档适配 Vite 或 Webpack。
Umi 4 / Webpack 宿主(常见 worker-javascript.js 返回 HTML):见 docs/UMI-MONACO-WORKER.md(入口 bootstrap、MFSU exclude、monaco-editor-webpack-plugin 备选)。
与快速开始一致的最小页(可选 title)
与上文 L0 相同;若需要顶栏文案可传 title。未传 initialFlow 时为空画布。需要内置示例图时,从本包导入 defaultInitialFlow、cloneWechenAgentFlow 并传入 initialFlow={cloneWechenAgentFlow(defaultInitialFlow)}。
行为与扩展(摘要)
- 连线:从节点的 source Handle 拖到 target Handle;选中边可在右侧调整动画与执行方向等。执行方向与
source→target一致:箭头在 目标 端;选「反向」会 交换源与目标(及 handle),不再依赖仅反转 dash 的持久化字段。历史edge.data.wechenFlowDirection: 'reverse'在normalizeWechenAgentFlowJson/cloneWechenAgentFlow/parseWechenAgentFlowJson中 幂等归一 为交换端点并清除该键。宿主也可调用swapWechenFlowEdgeEndpoints(edge)得到与内置面板一致的patchEdge补丁。 - 数据合并:节点
data的更新遵循导出的mergeNodeData(对象深度合并,数组在补丁中出现时整段替换)。详见下文patchNode与patchNodeData。 - 自定义右侧面板:可选
renderConfigPanel(节点与连线统一入口)或renderNodeConfig;同时存在时以renderConfigPanel为准(返回undefined时回退内置)。
patchNodeData 与配置区 ctx.patchNode
二者语义一致:把 patch 深度合并进 node.data(内部为 mergeNodeData)。区别仅在于 是否由调用方传入节点 id(右栏已选中节点时,id 由编辑器绑定)。
| API | 签名 | 使用场景 |
|-----|------|----------|
| ctx.patchNode | (patch) => void | renderConfigPanel 选中节点时;勿传 node.id,当前节点已固定 |
| ctx.editorRef | RefObject<WechenAgentEditorRef \| null> | renderConfigPanel 节点/边共有;内部 API,无需 宿主挂 ref,在事件内读 current |
| ref.patchNodeData | (nodeId, patch) => void | 命令式改 node.data(按钮、自动化、跨节点批量) |
patch 的顶层键 = node.data 的键,不是整颗 React Flow Node(不含 id / type / position / measured 等)。
ref:节点坐标与壳层(patchNode / setNodePosition)
| API | 作用 |
|-----|------|
| ref.getNode(id) / ref.getEdge(id) | 按 id 读取当前实体(浅拷贝) |
| ref.setNodePosition(id, { x, y }) | 只改 node.position(等同 patchNode(id, { position })) |
| ref.patchNode(id, patch) | 改 position / type / width / height;勿 用来写 data |
| ref.setTitle(title) | 只改标题,不替换整图 |
对称读写示例(无需 replaceFlow 整图替换坐标):
const flow = editorRef.current?.getFlow();
const n = flow?.nodes.find((x) => x.id === "my-node");
if (n) {
editorRef.current?.setNodePosition("my-node", { x: n.position.x + 80, y: n.position.y });
}程序化改坐标会经 onFlowOperation 的 node.patch(source: ref);与 patchNodeData 的 data 补丁区分。
ref 实例 vs 包导出(常见误找)
下列能力 不在 editorRef.current 上,需从包 直接 import(完整对照见 docs/EDITOR-REF-API-MATRIX.md):
| 需求 | 用法 |
|------|------|
| 后端 DTO 导出/导入 | exportBackendWorkflowJson / importBackendWorkflowToFlow → 再 ref.replaceFlow |
| 图合法性校验 | validateWechenAgentFlow(ref.getFlow()) |
| 克隆/解析 JSON | cloneWechenAgentFlow / parseWechenAgentFlowJson |
| 边端点交换补丁 | swapWechenFlowEdgeEndpoints → ref.patchEdge |
| 画布 重连 | ref.reconnectEdge(edgeId, connection)(同 UI onReconnect) |
| 拖动 门控 | onFlowOperation node.move(拖放结束;拒绝则回滚坐标) |
| 聚焦节点 | ref.centerOnNode(nodeId) |
| 校验当前图 | ref.validateFlow() |
node = { id, type, position, data: { label, config, … } }
▲
│ mergeNodeData(node.data, patch)
patch = { label: "x" } ──────┘ → node.data.label === "x"推荐写法
1. 改单个字段(最常用)
// renderConfigPanel
if (ctx.kind === "node") {
ctx.patchNode({ label: "新名称" });
}
// ref
editorRef.current?.patchNodeData("node-1", { label: "新名称" });2. 一次保存多个 data 顶层键
ctx.patchNode({
label: "主入口",
config: { maxSteps: 8 },
});3. 嵌套对象(深度合并,未出现在 patch 中的兄弟键保留)
// 已有 node.data.config = { a: 1, b: 2 }
ctx.patchNode({ config: { a: 3 } });
// → node.data.config === { a: 3, b: 2 }4. antd Form 即时写回(renderConfigPanel 需自建 Form;renderNodeConfig 由外层包一层 Form)
if (ctx.kind === "node" && ctx.node.type === "MAIN") {
const disabled = ctx.readOnly || ctx.readingMode;
return (
<Form
key={ctx.node.id}
layout="vertical"
initialValues={ctx.node.data}
disabled={disabled}
onValuesChange={(_, all) => ctx.patchNode(all)}
>
<Form.Item name="label" label="节点名称">
<Input />
</Form.Item>
</Form>
);
}initialValues用node.data,不要用整颗node。- 切换选中节点时用
key={node.id}或form.setFieldsValue,避免initialValues只在首次挂载生效。 - 只读:
disabled={ctx.readOnly || ctx.readingMode}(上下文 无ctx.disabled字段)。
5. 受控输入 + 按钮「应用」(防抖 / 显式保存)
const [draft, setDraft] = useState(String(ctx.node.data?.label ?? ""));
useEffect(() => {
setDraft(String(ctx.node.data?.label ?? ""));
}, [ctx.node.id, ctx.node.data]);
<Button onClick={() => ctx.patchNode({ label: draft })}>应用</Button>Playground 中 card 节点示例见 playground/src/playgroundPanels.tsx。
常见错误(会导致不生效或 data 被污染)
| 错误写法 | 实际效果 |
|----------|----------|
| patchNode(node.id, { label: "x" }) | 只接收 一个 参数;node.id 字符串被当作 patch merge 进 data,出现 "0":"m" 等下标键 |
| patchNode(node.data) | 闭包里的 旧 node.data 盖回表单新值,表现为回弹或不更新 |
| patchNode({ data: { label: "x" } }) | 写入 node.data.data.label,除非业务里真有名为 data 的嵌套对象 |
| patchNode(allValues) 且 initialValues={node} | 把 id / position 等 merge 进 data,结构错乱 |
| patchNodeData(id, node) | 应传 node.data 上的补丁,不是整颗 Node |
对比:想改 node.data.label
ctx.patchNode({ label: "x" }); // ✅
ctx.patchNodeData(id, { label: "x" }); // ✅ ref
ctx.patchNode({ data: { label: "x" } }); // ❌ → data.data.label
ctx.patchNode(node.id, { label: "x" }); // ❌ 多传了 id数组与其它规则
- 数组:
patch里若包含某数组键,则 整段替换该数组(不按下标合并)。 undefined键:mergeNodeData跳过undefined,不会用其删除已有字段;要清空请显式传null或业务约定值。- 边:
ctx.patchEdge/ref.patchEdge为边对象 顶层浅合并(与节点data规则不同),见WechenAgentConfigPanelEdgeContext。
setConfigUnsavedGuard 与延迟保存
在 renderConfigPanel 中若采用 编辑草稿 + 按钮保存(不每次 onValuesChange 调用 patchNode),可调用 ctx.setConfigUnsavedGuard(true/false):
true:在 顶栏、组件库、画布 上显示半透明蒙层(不挡 右侧配置区),点击蒙层提示「当前有未保存的修改…」(LOCALE_KEYS.CONFIG_UNSAVED_GUARD_HINT,可localeMessages覆盖)。ref.save()/ctx.save()与顶栏保存走同一门控,守卫为 true 时 不调用onSave。false:关闭蒙层;保存成功后应显式调用。- 选中变化(切换节点/边、点击画布空白)时编辑器会 自动 置
false。 readingMode下为 no-op。
const [draft, setDraft] = useState(() => String(ctx.node.data?.label ?? ""));
useEffect(() => {
setDraft(String(ctx.node.data?.label ?? ""));
ctx.setConfigUnsavedGuard(false);
}, [ctx.node.id]);
<Input
value={draft}
onChange={(e) => {
setDraft(e.target.value);
ctx.setConfigUnsavedGuard(true);
}}
/>
<Button
onClick={() => {
ctx.patchNode({ label: draft });
ctx.setConfigUnsavedGuard(false);
}}
>
保存
</Button>Playground renderConfigPanel Tab 中 card / 边配置演示了该流程。
新建节点/连线后默认选中
在可编辑模式下,以下操作成功后会 自动选中 新建对象,右侧配置区立即展示对应面板:
- 从 组件库拖入 节点;
- 画布 拖拽连线(
onConnect); - ref
addNodeFromLibrary/addNode/connect(或addEdge)。
拖入新节点后,宿主宜在 useEffect 依赖 node.id 上重置表单草稿并 setConfigUnsavedGuard(false)(见上节)。
- 顶栏左侧:可选
renderHeaderLeading,入参含当前解析标题title(与ref.getTitle()一致)及editorRef;未传时显示默认Title。改标题数据仍用titleprop 或replaceFlow。 - 顶栏右侧:可选
renderHeaderActions,入参含与默认行为一致的save/testRun及editorRef。左右可 同时 自定义。Playground:?tab=header-leading、?tab=header-actions。
完整类型与符号列表以 src/index.ts 的导出为准。
集成自检
在宿主项目根目录建议逐项确认:
| 检查项 | 说明 |
|--------|------|
| Peer 依赖 | npm ls react、npm ls @xyflow/react、npm ls dagre 各仅一棵有效树、无重复主版本 |
| 构建 | npm run build(或 Umi max build)通过 |
| Monaco | 已在首屏前完成 worker 引导(见上文「Monaco Worker」) |
| 空画布 | 未传 initialFlow 时中间画布无预置节点;业务示例需 cloneWechenAgentFlow(defaultInitialFlow) 或 ref.replaceFlow(...) 显式注入 |
| 烟测 | 拖入节点、连线、readOnly / readingMode、保存或 getSnapshot;边路径添加拐点/拖顶点 |
| ref 增图 | ref.addNodeFromLibrary / ref.connect 与拖放、连线行为一致 |
| DTO 往返 | exportBackendWorkflowJson → importBackendWorkflowToFlow → replaceFlow;condition/api/script 经 data.backendNodeType |
| 图操作门控 | onFlowOperation:拒 node.remove 时节点与级联边均保留(Playground ?tab=flow-operation-policy) |
| ref 坐标回写 | getFlow 读 position → setNodePosition 写 position(?tab=ref-api) |
| 图校验 | validateWechenAgentFlow;可选 onBeforeSave 拦截 |
本地开发(本仓库)
包管理器:本仓库使用 pnpm workspace(根 + playground/)。需 Node 20+;推荐 corepack enable 后由 packageManager 字段自动对齐 pnpm 版本。
pnpm install
pnpm run dev # Playground 文档站(分 Tab 演示;ref 全表见 ?tab=ref-api,见 playground/README.md)
pnpm run dev:minimal # 仅最小宿主集成(对照 README「快速开始」)
pnpm run baseline # 工程基线:测试数、最大 LOC、dist 体积(见 docs/ENGINEERING-BASELINE.md)
pnpm test # Vitest(含 flowOperation / deleteGuard 表驱动与 editorGateSmoke)
pnpm run typecheck
pnpm run check:docs # 文档覆盖 WARN(onFlowOperation kind ↔ README)
pnpm run release:prep # 发版门禁:test + build + pack:check + playground build + baseline
pnpm run verify:change # 可选:test + typecheck + audit:white-screen(见 docs/ENGINEERING-BASELINE.md)门控冒烟(无 DOM):editorGateSmoke.test.ts、readonlyModeGuards.test.ts — 图操作门控与只读改图拒绝;已包含在 pnpm test / release:prep。
浏览器地址以终端输出为准(默认 http://localhost:5173)。文档站默认 ?tab=overview。
维护者:发版
技术债与路线图
全项目优缺点与 P0–P3 优化优先级见 docs/PROJECT-AUDIT-2026.md(壳层拆分见 EDITOR-SPLIT-PLAN.md,性能见 PERFORMANCE-BUDGET.md)。OpenSpec 制品:openspec/changes/project-holistic-audit-and-optimization/。
版本摘要
| 版本 | 宿主需注意 |
|------|------------|
| 1.3.6 | 企业化文档(架构/私有化/性能预算);autoLayout({ only });FLOW-OPERATION-REF;manual 路径清除修复;peer 同 1.3.2 |
| 1.3.2 | onFlowOperation 统一图操作门控;拒 node.remove 时级联边原子保留;legacy 边/只读/配置区白屏加固;peer 同 1.3.0 |
| 1.3.0 | 自由折线路径编辑、选中浮动条、路径 ref;peer 要求同 1.2.3 |
| 1.2.3 | Breaking(集成):@xyflow/react、dagre 改为 peer,须宿主显式安装 |
| 1.1.x | buildWechenAgentEditorProps、后端 DTO、删除守卫等 |
完整变更见 CHANGELOG.md。
发版清单(当前版本见 package.json)
- 版本号:确认根目录
package.jsonversion与 CHANGELOG 最新条目一致(npm 以该字段为准;当前 1.3.6)。 - 依赖与锁文件:
pnpm install(workspace 一次安装根与playground/)。 - 门禁(推荐一条命令):
pnpm run release:prep等价分步:pnpm test → pnpm run build → pnpm run pack:check → pnpm --filter wechen-agent-playground run build → pnpm run baseline(基线摘要)。可选加强:pnpm run verify:change(含 audit:white-screen)、pnpm run check:docs。
日志写入 .release-check.log(release:prep 脚本)。
- 登录 registry:
npm whoami(registry.npmjs.org);国内镜像发版用npm run release:cn(勿与 npmjs 混用同一 token 流程 unless intentional)。 - 发布:
pnpm run release
# 或:pnpm publish --access public --registry=https://registry.npmjs.org/- 发后验证:
npm view wechen-agent-editor-v1 version
npm pack --dry-run # tarball 应仅含 dist/、LICENSE(见 package.json files)- 空目录烟测(将
<version>换为package.json中的版本):
mkdir /tmp/wechen-smoke && cd /tmp/wechen-smoke
npm init -y
npm install wechen-agent-editor-v1@<version> react react-dom antd @xyflow/react dagre monaco-editor @monaco-editor/react
node -e "import('wechen-agent-editor-v1').then(m => console.log('ok', typeof m.WechenAgentEditor))"- Git 标签(建议):
git tag v1.3.6 && git push origin v1.3.6(版本号与package.json一致,按团队规范)。
发版前可将 repository / bugs / homepage 中的占位 URL 换成真实仓库地址(私有仓库可仅在内部文档记录,见「集成排错」说明)。
