wechen-agent-editor-v1 1.3.5 → 1.3.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. package/README.md +947 -874
  2. package/dist/WechenAgentEditor.d.ts +40 -2
  3. package/dist/WechenAgentEditor.d.ts.map +1 -1
  4. package/dist/backendWorkflowJson.d.ts.map +1 -1
  5. package/dist/config/componentLibraryConfig.d.ts +1 -0
  6. package/dist/config/componentLibraryConfig.d.ts.map +1 -1
  7. package/dist/debugPanel/RefDebugResult.d.ts +5 -0
  8. package/dist/debugPanel/RefDebugResult.d.ts.map +1 -0
  9. package/dist/debugPanel/WechenEditorDebugPanel.d.ts +4 -0
  10. package/dist/debugPanel/WechenEditorDebugPanel.d.ts.map +1 -0
  11. package/dist/debugPanel/debugPanelUtils.d.ts +4 -0
  12. package/dist/debugPanel/debugPanelUtils.d.ts.map +1 -0
  13. package/dist/debugPanel/refDebugSelection.d.ts +5 -0
  14. package/dist/debugPanel/refDebugSelection.d.ts.map +1 -0
  15. package/dist/debugPanel/tabs/FlowTab.d.ts +8 -0
  16. package/dist/debugPanel/tabs/FlowTab.d.ts.map +1 -0
  17. package/dist/debugPanel/tabs/GraphTab.d.ts +7 -0
  18. package/dist/debugPanel/tabs/GraphTab.d.ts.map +1 -0
  19. package/dist/debugPanel/tabs/LocaleTab.d.ts +10 -0
  20. package/dist/debugPanel/tabs/LocaleTab.d.ts.map +1 -0
  21. package/dist/debugPanel/tabs/OverviewTab.d.ts +13 -0
  22. package/dist/debugPanel/tabs/OverviewTab.d.ts.map +1 -0
  23. package/dist/debugPanel/tabs/RefTab.d.ts +7 -0
  24. package/dist/debugPanel/tabs/RefTab.d.ts.map +1 -0
  25. package/dist/debugPanel/tabs/ValidateTab.d.ts +7 -0
  26. package/dist/debugPanel/tabs/ValidateTab.d.ts.map +1 -0
  27. package/dist/debugPanel/types.d.ts +25 -0
  28. package/dist/debugPanel/types.d.ts.map +1 -0
  29. package/dist/debugPanel/useDebugPanelTab.d.ts +3 -0
  30. package/dist/debugPanel/useDebugPanelTab.d.ts.map +1 -0
  31. package/dist/debugPanel/useRefDebugRunner.d.ts +12 -0
  32. package/dist/debugPanel/useRefDebugRunner.d.ts.map +1 -0
  33. package/dist/flow/CanvasErrorBoundary.d.ts +1 -1
  34. package/dist/flow/CanvasErrorBoundary.d.ts.map +1 -1
  35. package/dist/flow/PathEditContext.d.ts +1 -1
  36. package/dist/flow/PathEditContext.d.ts.map +1 -1
  37. package/dist/flow/autoLayoutBalance.d.ts.map +1 -1
  38. package/dist/flow/edgeLayoutSnapshot.d.ts +12 -11
  39. package/dist/flow/edgeLayoutSnapshot.d.ts.map +1 -1
  40. package/dist/flow/edgeOrthogonalPath.d.ts +3 -9
  41. package/dist/flow/edgeOrthogonalPath.d.ts.map +1 -1
  42. package/dist/flow/edgePolylinePath.d.ts +5 -4
  43. package/dist/flow/edgePolylinePath.d.ts.map +1 -1
  44. package/dist/flow/flowGraphMutations.d.ts.map +1 -1
  45. package/dist/flow/flowJsonUtils.d.ts.map +1 -1
  46. package/dist/flow/flowOperationEvents.d.ts +7 -2
  47. package/dist/flow/flowOperationEvents.d.ts.map +1 -1
  48. package/dist/flow/integratedAutoLayout.d.ts +4 -3
  49. package/dist/flow/integratedAutoLayout.d.ts.map +1 -1
  50. package/dist/flow/nodeChangeSyncAttachSides.d.ts +1 -0
  51. package/dist/flow/nodeChangeSyncAttachSides.d.ts.map +1 -1
  52. package/dist/flow/nodeShellPatch.d.ts +2 -2
  53. package/dist/flow/nodeShellPatch.d.ts.map +1 -1
  54. package/dist/flow/nodes/CardNode.d.ts +1 -1
  55. package/dist/flow/nodes/CardNode.d.ts.map +1 -1
  56. package/dist/flow/nodes/DeclaredHandles.d.ts +1 -1
  57. package/dist/flow/nodes/DeclaredHandles.d.ts.map +1 -1
  58. package/dist/flow/nodes/MainAgentNode.d.ts +1 -1
  59. package/dist/flow/nodes/MainAgentNode.d.ts.map +1 -1
  60. package/dist/flow/nodes/NodeHandlesLayer.d.ts +1 -1
  61. package/dist/flow/nodes/NodeHandlesLayer.d.ts.map +1 -1
  62. package/dist/flow/nodes/PillNode.d.ts +1 -1
  63. package/dist/flow/nodes/PillNode.d.ts.map +1 -1
  64. package/dist/flow/nodes/ZoneHandles.d.ts +1 -1
  65. package/dist/flow/nodes/ZoneHandles.d.ts.map +1 -1
  66. package/dist/flow/nodes/flowNodeDefaultContent.d.ts +3 -3
  67. package/dist/flow/nodes/flowNodeDefaultContent.d.ts.map +1 -1
  68. package/dist/flow/selectionToolbar/EdgeSelectionToolbar.d.ts +2 -2
  69. package/dist/flow/selectionToolbar/EdgeSelectionToolbar.d.ts.map +1 -1
  70. package/dist/flow/selectionToolbar/SelectionToolbar.d.ts +4 -7
  71. package/dist/flow/selectionToolbar/SelectionToolbar.d.ts.map +1 -1
  72. package/dist/flow/selectionToolbar/WechenNodeSelectionToolbar.d.ts +1 -1
  73. package/dist/flow/selectionToolbar/WechenNodeSelectionToolbar.d.ts.map +1 -1
  74. package/dist/flow/syncEdgeLayout.d.ts +7 -6
  75. package/dist/flow/syncEdgeLayout.d.ts.map +1 -1
  76. package/dist/flow/wechenAgentEdgeTypes.d.ts +10 -6
  77. package/dist/flow/wechenAgentEdgeTypes.d.ts.map +1 -1
  78. package/dist/flow/zoneConnectionAttach.d.ts.map +1 -1
  79. package/dist/flowTypes.d.ts +27 -4
  80. package/dist/flowTypes.d.ts.map +1 -1
  81. package/dist/hostEditorConfig.d.ts +7 -0
  82. package/dist/hostEditorConfig.d.ts.map +1 -1
  83. package/dist/i18n/LocaleContext.d.ts +9 -9
  84. package/dist/i18n/LocaleContext.d.ts.map +1 -1
  85. package/dist/i18n/buildReactFlowAriaLabelConfig.d.ts +9 -3
  86. package/dist/i18n/buildReactFlowAriaLabelConfig.d.ts.map +1 -1
  87. package/dist/i18n/defaultLocaleMessagesEn.d.ts +3 -0
  88. package/dist/i18n/defaultLocaleMessagesEn.d.ts.map +1 -0
  89. package/dist/i18n/exportLocaleBundle.d.ts +3 -3
  90. package/dist/i18n/exportLocaleBundle.d.ts.map +1 -1
  91. package/dist/i18n/fallbackLocaleEn.d.ts +4 -0
  92. package/dist/i18n/fallbackLocaleEn.d.ts.map +1 -0
  93. package/dist/i18n/localeRegistry.d.ts +11 -0
  94. package/dist/i18n/localeRegistry.d.ts.map +1 -0
  95. package/dist/i18n/resolveEditorLocale.d.ts +18 -0
  96. package/dist/i18n/resolveEditorLocale.d.ts.map +1 -0
  97. package/dist/i18n/resolveLocaleMessage.d.ts +6 -7
  98. package/dist/i18n/resolveLocaleMessage.d.ts.map +1 -1
  99. package/dist/i18n/shellLocaleEn.d.ts +4 -0
  100. package/dist/i18n/shellLocaleEn.d.ts.map +1 -0
  101. package/dist/i18n/useControllableLocale.d.ts +8 -0
  102. package/dist/i18n/useControllableLocale.d.ts.map +1 -0
  103. package/dist/index.d.ts +4 -1
  104. package/dist/index.d.ts.map +1 -1
  105. package/dist/index.js +11898 -10176
  106. package/dist/index.js.map +1 -1
  107. package/dist/panels/ComponentLibraryPanel.d.ts +15 -1
  108. package/dist/panels/ComponentLibraryPanel.d.ts.map +1 -1
  109. package/dist/panels/ConfigPanelErrorBoundary.d.ts +1 -1
  110. package/dist/panels/ConfigPanelErrorBoundary.d.ts.map +1 -1
  111. package/dist/panels/ConfigUnsavedGuardOverlay.d.ts +1 -1
  112. package/dist/panels/ConfigUnsavedGuardOverlay.d.ts.map +1 -1
  113. package/dist/panels/EdgeConfigPanel.d.ts +1 -1
  114. package/dist/panels/EdgeConfigPanel.d.ts.map +1 -1
  115. package/dist/panels/LibraryGroupSection.d.ts +13 -0
  116. package/dist/panels/LibraryGroupSection.d.ts.map +1 -0
  117. package/dist/panels/NodeConfigPanel.d.ts +3 -2
  118. package/dist/panels/NodeConfigPanel.d.ts.map +1 -1
  119. package/dist/panels/RightConfigPanel.d.ts +1 -1
  120. package/dist/panels/RightConfigPanel.d.ts.map +1 -1
  121. package/dist/panels/componentLibraryRenderTypes.d.ts +38 -0
  122. package/dist/panels/componentLibraryRenderTypes.d.ts.map +1 -0
  123. package/dist/panels/libraryGroupCollapseBridge.d.ts +8 -0
  124. package/dist/panels/libraryGroupCollapseBridge.d.ts.map +1 -0
  125. package/dist/panels/libraryGroupCollapseTypes.d.ts +31 -0
  126. package/dist/panels/libraryGroupCollapseTypes.d.ts.map +1 -0
  127. package/dist/panels/libraryItemRow.d.ts +9 -0
  128. package/dist/panels/libraryItemRow.d.ts.map +1 -0
  129. package/dist/panels/paletteAntIcon.d.ts +1 -1
  130. package/dist/panels/paletteAntIcon.d.ts.map +1 -1
  131. package/dist/panels/useLibraryGroupCollapse.d.ts +9 -0
  132. package/dist/panels/useLibraryGroupCollapse.d.ts.map +1 -0
  133. package/dist/style.css +1 -1
  134. package/dist/testUtils/testConnection.d.ts +4 -0
  135. package/dist/testUtils/testConnection.d.ts.map +1 -0
  136. package/dist/wechenAgentConfigPanel.d.ts +5 -1
  137. package/dist/wechenAgentConfigPanel.d.ts.map +1 -1
  138. package/dist/xyflowTypes.d.ts +15 -0
  139. package/dist/xyflowTypes.d.ts.map +1 -0
  140. package/package.json +28 -23
package/README.md CHANGED
@@ -1,874 +1,947 @@
1
- # wechen-agent-editor-v1
2
-
3
- 面向宿主应用的 **Agent 流程编排编辑器** React 组件:左侧可拖入的组件库、中间 **@xyflow/react** 画布、右侧基于 **antd** 与 **Monaco** 的配置区。图上的 **节点** 表示功能模块,**边** 表示执行顺序;持久化的 `flow` 可由你的运行时或执行引擎消费。
4
-
5
- **包名**:以 `package.json` 的 `name` 为准(`wechen-agent-editor-v1`)。安装与 `import` 均使用该名称。
6
-
7
- **当前发布版本**:**1.3.2**(变更见 [CHANGELOG.md](CHANGELOG.md))。发版前自检见文末 [维护者:发版](#维护者发版)。
8
-
9
- > **宿主集成必读**:本包 **`dependencies` 为空**,运行时能力全部来自 **peerDependencies**。除 `npm install wechen-agent-editor-v1` 外,须在**业务工程根目录**一并安装 **`react` / `react-dom` / `antd` / `@xyflow/react` / `dagre` / `monaco-editor` / `@monaco-editor/react`**(见下文 [安装](#安装))。缺任一项会导致画布空白、连线失效、自动排版失败或 Monaco 无法加载。
10
-
11
- **文档导航**:[能力地图](docs/CAPABILITY-MAP.md) · [自动布局 ref](docs/AUTO-LAYOUT-REF.md) · [ref 矩阵](docs/EDITOR-REF-API-MATRIX.md) · [集成阶梯 L0–L3](docs/HOST-INTEGRATION-LADDER.md) · [工程基线](docs/ENGINEERING-BASELINE.md) · Playground [`?tab=overview`](playground/README.md)
12
-
13
- ## 快速开始
14
-
15
- ### 60 秒最小集成(L0)
16
-
17
- 只需 **`onSave`** 与 antd **`ConfigProvider`**,并保证父容器有高度(例如 `height: 100%` 链或 `100vh`)。未传 **`initialFlow`** 时为**空画布**(与编辑器默认一致)。
18
-
19
- **安装**(在宿主项目根目录执行,勿只装本包):
20
-
21
- ```bash
22
- npm install wechen-agent-editor-v1 react react-dom antd @xyflow/react dagre monaco-editor @monaco-editor/react
23
- ```
24
-
25
- Monaco 还需在应用入口配置 **worker**(见 [Monaco Worker](#monaco-worker))。
26
-
27
- ```tsx
28
- import { ConfigProvider } from "antd";
29
- import {
30
- WechenAgentEditor,
31
- getDefaultComponentLibraryGroups,
32
- } from "wechen-agent-editor-v1";
33
- // 若生产构建后缺少 Flow 全局样式,再增加:import "wechen-agent-editor-v1/style.css";
34
-
35
- export function AgentPage() {
36
- return (
37
- <ConfigProvider>
38
- <div style={{ height: "100vh" }}>
39
- <WechenAgentEditor
40
- libraryGroups={getDefaultComponentLibraryGroups()}
41
- onSave={(s) => console.log("flow", s.flow)}
42
- />
43
- </div>
44
- </ConfigProvider>
45
- );
46
- }
47
- ```
48
-
49
- ### 常用配置一条对象(L1)
50
-
51
- 标题、初始流、只读、以及在**默认组件库**上按条目打补丁,可用 **`buildWechenAgentEditorProps`** 一次展开为 `WechenAgentEditor` 的 props(类型见包内导出)。
52
-
53
- ```tsx
54
- import { ConfigProvider } from "antd";
55
- import {
56
- WechenAgentEditor,
57
- buildWechenAgentEditorProps,
58
- cloneWechenAgentFlow,
59
- COMPONENT_LIBRARY_GROUP_APP,
60
- defaultInitialFlow,
61
- } from "wechen-agent-editor-v1";
62
-
63
- const editorProps = buildWechenAgentEditorProps({
64
- title: "我的流程",
65
- initialFlow: cloneWechenAgentFlow(defaultInitialFlow),
66
- readOnly: false,
67
- library: {
68
- preset: "default",
69
- itemPatches: [
70
- { groupKey: COMPONENT_LIBRARY_GROUP_APP, itemKey: "alert", patch: { subtitle: "自定义副标题" } },
71
- ],
72
- },
73
- onSave: (s) => console.log("flow", s.flow),
74
- });
75
-
76
- export function AgentPage() {
77
- return (
78
- <ConfigProvider>
79
- <div style={{ height: "100vh" }}>
80
- <WechenAgentEditor {...editorProps} />
81
- </div>
82
- </ConfigProvider>
83
- );
84
- }
85
- ```
86
-
87
- **高级扩展**(自定义画布节点、右侧配置面板、组件库列表映射、顶栏按钮等)仍使用完整 **`WechenAgentEditorProps`**;说明见下文「行为与扩展(摘要)」与 **`src/index.ts`** 导出。
88
-
89
- ### 后端工作流 JSON(存库 / 执行 / 回显)
90
-
91
- 编辑器内部持久化形态为 **`WechenAgentFlowJson`**(React Flow 的 `nodes` / `edges` + 业务 `data`)。若后端希望 **扁平 DTO**(`flowId`、`flowName`、`nodes[].type` + `config`、`edges[].source/target` 等),请使用:
92
-
93
- - **`exportBackendWorkflowJson(snapshot | flow, meta)`** → **`WechenBackendWorkflowJson`**(根字段 **`schemaVersion`** 当前为 **`"1"`**,与业务字段 **`version`** 区分)
94
- - **`importBackendWorkflowToFlow(dto)`** → **`WechenAgentFlowJson`**,再作为 **`initialFlow`** 或 **`ref.replaceFlow`** 回显
95
-
96
- **边 `id`(删边 / 命令式 API)**:
97
-
98
- - **`onSave` / `getSnapshot` / `flowJson`**:`edges[].id` 为画布稳定 id,**`ref.deleteEdge(edgeId)`** / **`getSelectedEdgeId()`** 应使用该 id。
99
- - **后端 DTO**:`exportBackendWorkflowJson` **会写出** `edges[].id`(与画布一致);`importBackendWorkflowToFlow` **优先** 使用 DTO 中的 `id`,省略时按 `e-${source}-${target}-${index}` 合成。仅持久化 DTO 时,回灌后删边请用 **回灌后的** `flow.edges[].id`,勿沿用导出前的 id(除非 DTO 已含 `id` 且已往返)。
100
- - 推荐:需要完整画布状态时优先存 **`snapshot.flow`** 或 **`flowJson`**;仅存 DTO 时务必带上 **`edges[].id`** 字段。
101
-
102
- **有损字段**:导出会去掉 RF 运行期字段与部分边样式;导入会按边 `id` 分配 `sourceHandle`/`targetHandle`。详见类型 **`WechenBackendWorkflowJson`** 的 JSDoc。
103
-
104
- **连线布局**:
105
-
106
- - **`flowJson` / `getSnapshot`**:在 `edge.data` 中持久化 **`wechenSourceAttachSide` / `wechenTargetAttachSide`** 与 **`wechenPathOptions`**(`offset`、`borderRadius`),回显时 **优先使用存盘布局**,与编辑态一致。
107
- - **后端 DTO**:`edges[].layout` 可选携带上述字段(`exportBackendWorkflowJson` / `importBackendWorkflowToFlow`);若需 **完整** 画布状态(含全部 `edge.data`),请优先持久化 **`flowJson`** 而非仅 DTO 拓扑。
108
-
109
- **节点类型映射**(画布 ↔ DTO):
110
-
111
- | 画布 | DTO `nodes[].type` | 说明 |
112
- |------|-------------------|------|
113
- | `pill` | `start` | 入口 |
114
- | `mainAgent` | `agent` | 主 Agent |
115
- | `card`(默认) | `task` | 通用任务 |
116
- | `card` + `data.backendNodeType` | `condition` / `api` / `script` | 扩展类型;导入后写回 `backendNodeType` |
117
-
118
- **画布类型与 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`** 一致)。
119
-
120
- | 字段 | 含义 |
121
- |------|------|
122
- | 画布 `node.type` | React Flow 节点类型;拖入时 **仅** 由 **`items[].type`**(必填,任意非空字符串)决定 |
123
- | DTO `nodes[].type` | 后端执行类型(`agent` / `task` / `start` …) |
124
- | `config.canvasNodeType` | 往返时还原画布 `node.type` |
125
- | `data.libraryGroupKey` / `libraryItemKey` | 库分组 catalog id / 库项 id(**非** `props.type`) |
126
-
127
- 自定义节点若需稳定 DTO 类型,请在 `node.data` 设置 **`backendNodeType`**(取值与 `WechenBackendWorkflowNodeType` 一致)。
128
-
129
- ```tsx
130
- import {
131
- WechenAgentEditor,
132
- exportBackendWorkflowJson,
133
- importBackendWorkflowToFlow,
134
- } from "wechen-agent-editor-v1";
135
-
136
- <WechenAgentEditor
137
- onSave={(s) => {
138
- const dto = exportBackendWorkflowJson(s, {
139
- flowId: "agent_flow_001",
140
- workflowVersion: "1.0",
141
- });
142
- void fetch("/api/flows", {
143
- method: "POST",
144
- body: JSON.stringify(dto),
145
- headers: { "Content-Type": "application/json" },
146
- });
147
- }}
148
- />;
149
-
150
- // 回显:从接口取 dto 后
151
- const flow = importBackendWorkflowToFlow(dtoFromApi);
152
- // <WechenAgentEditor initialFlow={flow} />
153
- ```
154
-
155
- 本仓库 **playground** 提供对照:
156
-
157
- - **`npm run dev`**:文档站(侧栏能力 Tab + **仓库文档** `?tab=repo-docs` + 分栏独立滚动;默认 `?tab=overview`),见 [playground/README.md](playground/README.md)。
158
- - **`npm run dev:minimal`**:仅最小宿主集成(对照上文「60 秒最小集成」)。
159
-
160
- ### 编辑器与 Agent 运行时
161
-
162
- 本包是 **编排 UI + 序列化 + ref API**,**不包含** Agent / LLM **执行引擎**。推荐分层:
163
-
164
- ```
165
- WechenAgentEditor(编排) → onSave / exportBackendWorkflowJson → 宿主持久化 → 你们的运行时调度
166
- ```
167
-
168
- - **`onTestRun` / `ref.testRun()`**:仅触发宿主回调,具体执行由宿主实现。
169
- - **`validateWechenAgentFlow(flow)`**:纯函数图校验(默认不阻断保存);可在 `onBeforeSave` 内返回 `false` 拦截。
170
- - **ref 增删改查**:`getSnapshot` / **`getFlow` / `getInitialFlow`** / **`getNode` / `getEdge`** / **`replaceFlow` / `setInitialFlow`** / **`patchNodeData`(`node.data`)** / **`patchNode` / `setNodePosition`(坐标与壳层)** / **`setTitle`** / `removeNode` / **`duplicateNode`** / **`removeEdge` / `deleteEdge`** / 路径与布局 ref 等(见 `WechenAgentEditorRef`、Playground **`?tab=ref-api`**)。
171
- - **连线路径(自由折线)**:选中边后浮动条「添加拐点」;仅 **中间拐点** 可拖拽(任意角度线段),首尾随节点桩固定;**水平/垂直线段** 可整条平移(鼠标移到线段上,光标变为 ↔ 或 ↕);`Delete` 在顶点选中时删点。`normalizeWechenAgentFlowJson` 会清除无效 `path` 并回到 `auto`。
172
- - **选中浮动操作条**:单选节点时显示 **删除 / 复制**;单选边时显示 **删除**、**添加/删除拐点**、手动边 **恢复自动路径**。与边上 **路径顶点手柄**(几何编辑)分工;浮动条锚点在路径外侧,避免压住连线与节点。`readOnly` / `readingMode` / 多选时不显示。可选 **`renderSelectionToolbar`** 扩展。
173
- - **布局相关 ref**(`readOnly` / `readingMode` 下均为 no-op):**完整说明 → [docs/AUTO-LAYOUT-REF.md](./docs/AUTO-LAYOUT-REF.md)**
174
-
175
- | 方法 | 作用 |
176
- |------|------|
177
- | **`relayoutEdges()`** | 重算连线,**不移动节点**,**保留**手调路径中间拐点 |
178
- | **`autoLayoutNodes(options?)`** | 低层节点排版(默认一体化);默认布线但 **不清** 手调拐点 |
179
- | **`autoLayout(options?)`** | 顶栏推荐:无参 = `node`+`edge`(清手调路径)+`fitView`;`only` 白名单 |
180
- | **`fitView()`** | 仅适配视口 |
181
-
182
- ```tsx
183
- editorRef.current?.autoLayout();
184
- editorRef.current?.autoLayout({ only: ["node"] });
185
- editorRef.current?.autoLayout({ only: ["edge"] });
186
- editorRef.current?.autoLayoutNodes({ direction: "TB" });
187
- editorRef.current?.relayoutEdges();
188
- ```
189
-
190
- - **回显后连线布局**:`getSnapshot` / `flowJson` 保存锚边与路径选项;`replaceFlow` 优先恢复存盘布局。
191
- - **路径避让 / 一体化排版**:见 [AUTO-LAYOUT-REF.md](./docs/AUTO-LAYOUT-REF.md)。
192
-
193
- ```tsx
194
- import { validateWechenAgentFlow } from "wechen-agent-editor-v1";
195
-
196
- <WechenAgentEditor
197
- onBeforeSave={(snap) => {
198
- const { valid, issues } = validateWechenAgentFlow(snap.flow);
199
- if (!valid) {
200
- console.warn(issues);
201
- return false;
202
- }
203
- }}
204
- onSave={(s) => { /* POST exportBackendWorkflowJson(s, meta) */ }}
205
- />
206
- ```
207
-
208
- ---
209
-
210
- ## 安装
211
-
212
- 本包为 **纯组件库**:`package.json` 中 **`dependencies` 为空**,**不会**把 React Flow、Dagre、Monaco 等打进你的 `node_modules` 树里作为本包的私有副本。宿主必须在**应用根**声明下列 **peer**,并与本包解析到**同一份**运行时(尤其 `@xyflow/react`)。
213
-
214
- ### 推荐:一条命令装齐
215
-
216
- ```bash
217
- npm install wechen-agent-editor-v1 react react-dom antd @xyflow/react dagre monaco-editor @monaco-editor/react
218
- ```
219
-
220
- 使用 **pnpm** / **yarn** 时同样在业务包中声明上述依赖,勿仅在 monorepo 子包里安装而根目录缺失 peer。
221
-
222
- ### 仅安装本包时常见现象
223
-
224
- | 缺失 peer | 现象 |
225
- |-----------|------|
226
- | `react` / `react-dom` | 无法渲染 |
227
- | `antd` | 壳层/配置区样式或组件报错 |
228
- | `@xyflow/react` | 画布不显示、连线/拖线无响应、Handle 失效 |
229
- | `dagre` | `autoLayout` / `autoLayoutNodes` 报错或无效 |
230
- | `monaco-editor` + `@monaco-editor/react` | 代码编辑区空白(另需 [Monaco Worker](#monaco-worker)) |
231
-
232
- ### Peer 依赖清单(须在宿主 `package.json` 中声明)
233
-
234
- 版本须 **≥** 本包 `peerDependencies` 下限,并与业务栈锁定策略一致(下表为开发本仓库时使用的参考版本):
235
-
236
- | 包 | 下限(peer) | 用途 |
237
- |----|-------------|------|
238
- | `react`、`react-dom` | ≥18 | UI 运行时 |
239
- | `antd` | ≥5 | 编辑器壳层、配置区、顶栏 |
240
- | `@xyflow/react` | ≥12 | 画布(节点/边/连线) |
241
- | `dagre` | ≥0.8.5 | 节点自动排版(`autoLayoutNodes`) |
242
- | `monaco-editor` | ≥0.45 | 代码编辑内核 |
243
- | `@monaco-editor/react` | ≥4.6 | React 封装 |
244
-
245
- 自 **1.2.3** 起 **`@xyflow/react`**、**`dagre`** 由 **peer** 提供(不再随本包内嵌)。自 **1.0.x** 起 **`react` / `antd` / `monaco-*`** 即为 peer。升级自内嵌 Flow 的旧版时,请务必在宿主根目录 **新增** `@xyflow/react` 与 `dagre` 依赖。
246
-
247
- 自 **1.3.2** 起推荐用 **`onFlowOperation`** 统一图操作策略(替代仅覆盖删除的 `onBeforeRemove*`);**peer 清单无变化**,从 **1.3.0** 升级通常只需 bump 版本并回归构建与画布烟测。
248
-
249
- **不要**再依赖本包 tarball 内自带的 `@xyflow/react`(历史 `bundledDependencies` 已移除)。宿主与编辑器 **必须** `npm ls @xyflow/react` 仅一棵有效树。
250
-
251
- 安装后建议自检:
252
-
253
- ```bash
254
- npm ls @xyflow/react
255
- npm ls react
256
- ```
257
-
258
- 理想情况为各包 **仅一棵** 依赖树、无重复主版本。若出现多份,请在打包配置中对 `@xyflow/react`(及必要时 `react`)配置 **`resolve.alias`** / dedupe,或统一锁定版本后重装 `node_modules`。
259
-
260
- ### 安装失败(`ERESOLVE`)
261
-
262
- 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` 验证,并计划去掉该放宽)。
263
-
264
- ### Umi / MFSU
265
-
266
- 一般可直接使用。若遇预编译或模块解析异常,可在配置里将 **`wechen-agent-editor-v1`**、**`@xyflow/react`**、**`monaco-editor`**、**`@monaco-editor/react`** 加入 **`mfsu.exclude`**(见 [Umi MFSU](https://umijs.org/docs/api/config#mfsu)),删除 `.umi` 后重启;或用 **`mfsu: false`** 做对比排查。
267
-
268
- **依赖示例**(版本请按团队锁定,须满足本包 `peerDependencies`):
269
-
270
- ```json
271
- {
272
- "dependencies": {
273
- "@monaco-editor/react": "^4.6.0",
274
- "@xyflow/react": "^12.3.6",
275
- "antd": "^5.22.0",
276
- "dagre": "^0.8.5",
277
- "monaco-editor": "^0.45.0",
278
- "react": "^18.3.0",
279
- "react-dom": "^18.3.0",
280
- "wechen-agent-editor-v1": "^1.3.2"
281
- }
282
- }
283
- ```
284
-
285
- ### 集成排错
286
-
287
- **白屏优先**:打开控制台过滤 `[wechen-agent-editor]`,对照 [docs/WHITE-SCREEN-RISK-REGISTER.md](docs/WHITE-SCREEN-RISK-REGISTER.md) 中的 ID/code;Playground 手测 `?tab=legacy-host-api-flow`(JSON 文本域)与 `?tab=custom-config-panel`(配置区边界)。
288
-
289
- | 现象 | 可能原因 | 处理 |
290
- |------|----------|------|
291
- | 代码编辑区空白 / 控制台 Monaco worker 报错 | 未配置 **Monaco worker** | 按 README「Monaco Worker」在应用入口引导 worker |
292
- | `worker-javascript.js` MIME **`text/html`** / `importScripts` 失败 | 开发服务器对 worker 返回 **HTML 404** | 配置 `MonacoEnvironment`;**Umi** 见 [docs/UMI-MONACO-WORKER.md](docs/UMI-MONACO-WORKER.md) |
293
- | 选中边后白屏、`Rendered more hooks than during the previous render` | 旧版边组件在 `return null` 后调用 hook | 升级含边组件 Hooks 修复的版本(见 CHANGELOG) |
294
- | 有边即白屏、`reading 'offset'` | 旧 flow 无 `wechenPathOptions` 或几何未就绪 | 升级含 legacy 边容错版本;控制台搜 **`[wechen-agent-editor]`**(如 `EDGE_PATH_OPTIONS_NULL`、`EDGE_GEOMETRY_UNAVAILABLE`) |
295
- | 画布区域 Alert「渲染异常」、其余页面正常 | 画布子树未捕获异常 | 看控制台 **`CANVAS_RENDER_CRASH`** 堆栈;修复宿主 `renderFlowNode` 或数据 |
296
- | 配置区 Alert「配置区渲染异常」、画布仍可用 | `renderConfigPanel` 抛错 | 看 **`CONFIG_PANEL_RENDER_CRASH`**;修复宿主配置面板实现 |
297
- | 画布不可见或高度为 0 | 父级无 **定高** 或 flex 链缺 `minHeight: 0` | 使用「嵌入 flex 布局」示例;或传编辑器 **`minHeight`** |
298
- | 连线异常、Handle 无响应、样式错乱 | **未装 peer 或 `@xyflow/react` 双实例** | 按上文 **一条命令** 装齐 peer;`npm ls @xyflow/react` 仅一棵;打包器 **alias/dedupe** |
299
- | `autoLayout` 报错 | **未安装 `dagre`** | 在宿主 `dependencies` 添加 `dagre` |
300
- | `ref.deleteEdge(id)` 无效 | 持久化载体无稳定 **`edges[].id`** | 优先存 **`snapshot.flow` / `flowJson`**;仅存 DTO 时导出须含 **`edges[].id`**,回灌后用 **新** id |
301
- | 只读仍出现浮动条或可删节点 | **`readOnly` 未传给编辑器根组件** | `<WechenAgentEditor readOnly />`;勿仅 `Form disabled` |
302
- | 存库后连线形状与编辑时不一致 | 仅存拓扑 DTO、缺 **layout** | 使用 **`flowJson`** 或 DTO **`edges[].layout`**(见「连线布局」) |
303
-
304
- Playground **`?tab=overview`** 提供术语表与持久化对比;工程清单见 **`docs/ENGINEERING-BASELINE.md`**。
305
-
306
- > **`package.json` 的 `repository` / `homepage` / `bugs`** 若为占位 URL,表示尚未绑定公开仓库;私有 fork 可忽略,发版前请改为真实地址。
307
-
308
- ---
309
-
310
- ## 使用
311
-
312
- ### 入口与样式
313
-
314
- ```ts
315
- import { WechenAgentEditor, type WechenAgentSnapshot } from "wechen-agent-editor-v1";
316
- ```
317
-
318
- 发布产物中 **`dist/index.js`** 会包含对 **`./style.css`** 的引用,正常情况下图样式会随主入口进入打包结果。若生产环境仍看不到 Flow 全局样式,可增加:
319
-
320
- ```ts
321
- import "wechen-agent-editor-v1/style.css";
322
- ```
323
-
324
- antd 5 为 CSS-in-JS,请用 **`ConfigProvider`** 包裹应用(与官方用法一致)。
325
-
326
- ### 嵌入 flex 布局
327
-
328
- 在 **列方向 flex** 页面中,推荐与 playground 相同的外层结构:父级定高(或 `100vh`),编辑器所在子项使用 **`flex: 1`**、**`minHeight: 0`**,编辑器根节点会 **`flex: 1`** 并 **`height/width: 100%`** 填满该区域。
329
-
330
- ```tsx
331
- <div style={{ height: "100vh", display: "flex", flexDirection: "column", minHeight: 0 }}>
332
- <YourToolbar />
333
- <div style={{ flex: 1, minHeight: 0, display: "flex", flexDirection: "column" }}>
334
- <WechenAgentEditor {...props} />
335
- </div>
336
- </div>
337
- ```
338
-
339
- **行为说明**:自 **1.1.x** 起,画布区域 **不再** 默认施加 `min-height: 400px`;若需要最小可视高度,请对编辑器传入 **`minHeight`** prop,或在页面级容器设置 `min-height`。
340
-
341
- ### 骨架 CSS 类名(自定义样式)
342
-
343
- 加载 **`wechen-agent-editor-v1/style.css`** 后,可用下列稳定类名覆盖布局分区样式(与根 **`className`** 合并,不会替换默认类):
344
-
345
- | 类名 | 区域 |
346
- | --- | --- |
347
- | `wechen-agent-editor` | 根布局 |
348
- | `wechen-agent-editor--reading` | 阅读模式根修饰 |
349
- | `wechen-agent-editor__workspace` | 工作区(顶栏 + 主体) |
350
- | `wechen-agent-editor__header` | 顶栏 |
351
- | `wechen-agent-editor__body` | 主体行(左栏 + 画布 + 右栏) |
352
- | `wechen-agent-editor__library-sider` | 左侧组件库 Sider |
353
- | `wechen-agent-editor__library-panel` | 组件库面板内容 |
354
- | `wechen-agent-editor__main` | 中间主内容 |
355
- | `wechen-agent-editor__canvas` / `wechen-agent-canvas` | 画布壳(二者同元素,后者为兼容别名) |
356
- | `wechen-agent-editor__config-sider` | 右侧配置 Sider |
357
- | `wechen-agent-editor__config-panel` | 配置面板内容 |
358
- | `wechen-agent-node-shell` / `wechen-agent-node-shell--selected` | 内置节点外壳 / 选中态 |
359
- | `wechen-agent-node-shell--mainAgent` / `--card` / `--pill` | 节点类型修饰 |
360
- | `wechen-agent-edge` / `wechen-agent-edge--selected` | 默认连线 / 选中态 |
361
-
362
- **自定义选中高亮**(颜色写在 CSS 变量中,勿依赖行内 style):
363
-
364
- | CSS 变量 | 默认 | 说明 |
365
- | --- | --- | --- |
366
- | `--wechen-selection-color` | `#52c41a` | 节点选中边框、默认边选中描边 |
367
- | `--wechen-selection-border-width` | `2px` | 节点选中边框宽度 |
368
- | `--wechen-selection-shadow` | 绿晕阴影 | 节点选中阴影 |
369
- | `--wechen-node-border-color` | `#d9d9d9` | 未选中节点边框 |
370
- | `--wechen-edge-stroke` | `#b1b1b7` | 未选中边描边 |
371
- | `--wechen-edge-stroke-selected-width` | `2` | 选中边线宽 |
372
- | `--wechen-zone-strip-border-color` | `rgba(22, 119, 255, 0.55)` | 四边感应条 hover 描边色 |
373
- | `--wechen-zone-strip-background` | `rgba(22, 119, 255, 0.06)` | 四边感应条 hover 背景色 |
374
- | `--wechen-zone-strip-border-width` | `2px` | 感应条 hover 描边宽度 |
375
-
376
- 四边感应条稳定类名:`wechen-agent-zone-strip`(基类)、`wechen-agent-zone-strip--top` / `--right` / `--bottom` / `--left`(边向)、`wechen-agent-zone-strip--active`(当前边 hover)。宿主可直接写选择器覆盖,或与上表变量一并设置在 `.wechen-agent-editor` 上。
377
-
378
- ```css
379
- /* 例:自定义顶栏背景 */
380
- .wechen-agent-editor__header {
381
- background: #fafafa !important;
382
- }
383
-
384
- /* 例:选中高亮改为品牌蓝 */
385
- .my-app .wechen-agent-editor {
386
- --wechen-selection-color: #1677ff;
387
- }
388
-
389
- /* 例:四边感应条 hover 色 */
390
- .my-app .wechen-agent-editor {
391
- --wechen-zone-strip-border-color: #1677ff;
392
- --wechen-zone-strip-background: rgba(22, 119, 255, 0.12);
393
- }
394
- ```
395
-
396
- ### 国际化(壳层 vs 宿主自定义)
397
-
398
- 编辑器是 **编排基座**:画布节点由 **`renderFlowNode`**、右侧内容区由 **`renderConfigPanel` / `renderNodeConfig`** 自定义时,**业务文案由宿主自行 i18n**。包内 **`localeMessages`** 仅覆盖 **壳层**(顶栏按钮、左右栏分区标题、组件库搜索/空状态、画布缩放控件等;经 React Flow **`ariaLabelConfig`** 注入)。
399
-
400
- #### 语言契约(`localeMessages` 大 JSON)
401
-
402
- | 条件 | 编辑器显示语言 |
403
- | --- | --- |
404
- | **未传** `localeMessages` | **始终内置中文**(`DEFAULT_ZH_CN`),与 antd `ConfigProvider`、浏览器语言 **无关** |
405
- | **已传** `localeMessages` | **仅以该 JSON 为准**;缺失 key **不回退中文**,显示 key 字符串 |
406
- | 存在 `localeTranslate` | **最高优先级**(便于对接 i18next) |
407
-
408
- 语言包 **key 为默认中文**(与界面中文一致),便于对照扩展,例如 `{ "保存": "Save", "配置": "Settings" }`。代码中可用 **`LOCALE_KEYS`** / **`FALLBACK_LOCALE_KEYS`** 常量(值为中文 key,如 `LOCALE_KEYS.HEADER_SAVE === "保存"`)。
409
-
410
- 生产推荐:从 **`exportWechenAgentEditorLocaleBundle()`** 复制整表,将 value 译为目标语言后传入;或用 **`mergeWechenAgentEditorLocaleMessages(译文)`** 在宿主侧生成完整大 JSON。
411
-
412
- ```tsx
413
- import {
414
- WechenAgentEditor,
415
- LOCALE_KEYS,
416
- mergeWechenAgentEditorLocaleMessages,
417
- validateLocaleMessagesCompleteness,
418
- } from "wechen-agent-editor-v1";
419
-
420
- // 中文(默认):不传 localeMessages
421
- <WechenAgentEditor renderConfigPanel={(ctx) => <MyPanel {...ctx} />} />;
422
-
423
- // 英文:key 为中文,value 为目标语言
424
- const enPack = mergeWechenAgentEditorLocaleMessages({
425
- 保存: "Save",
426
- 配置: "Settings",
427
- 组件库: "Component library",
428
- // 或使用常量: [LOCALE_KEYS.HEADER_SAVE]: "Save",
429
- });
430
- validateLocaleMessagesCompleteness(enPack);
431
-
432
- <WechenAgentEditor localeMessages={enPack} />;
433
- ```
434
-
435
- ```tsx
436
- // antd 与编辑器语言独立配置
437
- <ConfigProvider locale={antdLocale}>
438
- <WechenAgentEditor localeMessages={wechenEditorLocalePack} />
439
- </ConfigProvider>
440
- ```
441
-
442
- - **`exportWechenAgentEditorShellLocaleBundle('zh-CN')`**:仅壳层 key(生产常用子集)。
443
- - **`exportWechenAgentEditorFallbackLocaleBundle`**:内置回退面板 key(未传 `renderConfigPanel` 时)。
444
- - **`exportWechenAgentEditorLocaleBundle`**:壳层 + 回退 **完整** key 表(推荐作翻译模板)。
445
- - **`configSiderTitle`**:可完全覆盖右侧栏外框标题。
446
- - **`validateLocaleMessagesCompleteness`**:检查大 JSON 是否缺 key(开发期 `console.warn`)。
447
-
448
- 完整契约演示见 `playground/src/App.tsx`(中文不传包;English 传 `playgroundLocaleEnFull.ts`)。
449
-
450
- ### 嵌入尺寸、`readOnly` 与 `readingMode`
451
-
452
- 根组件默认 **`height` / `width` 为 `100%`**,**`flex: 1`**、**`minHeight: 0`**(便于 flex 父级);可通过 **`className` / `style`** 透传;可选 **`minHeight`**。请保证父级链路上有**明确高度**(见上文 flex 示例),否则中间画布可能无法撑满。
453
-
454
- **`readOnly` 须挂在 `<WechenAgentEditor readOnly />` 根组件上**;仅对右侧 `Form` 设 `disabled` **不会** 禁止画布改图、浮动条或组件库拖入。
455
-
456
- | | 三栏 UI | 画布选中 | 右侧配置 | 浮动条 / 改图 |
457
- |---|:---:|:---:|:---:|:---:|
458
- | 默认 | ✓ | ✓ | 可编辑 | ✓ |
459
- | **`readOnly`** | ✓ | ✓(查看) | `disabled` | ✗(无浮动条;ref 改图 no-op) |
460
- | **`readingMode`** | 仅画布 | ✗ | 无右栏 | ✗ |
461
-
462
- - **`readOnly`**:禁止对图的增删改(组件库拖入、连线、删除键、拖动节点、选中浮动条、`ref.removeNode` / `patchNodeData` 等),仍可使用平移/缩放查看,**可点选节点/边** 在右侧只读查看配置。**`readOnly`** 与 **`readingMode`** 会出现在 **`renderConfigPanel` / `renderNodeConfig`** 的入参中。
463
- - **`readingMode`**:**仅展示中间流程画布**(隐藏顶栏与左右栏),并**禁用选中**与一切改图,适合文档内嵌、大屏预览等纯展示场景(隐含只读)。根节点会附加 **`wechen-agent-editor--reading`**。
464
-
465
- ### 删除守卫(`onBeforeRemoveNode` / `onBeforeRemoveEdge`)— **@deprecated**
466
-
467
- > **新集成请用** 下文 **[图操作事件(`onFlowOperation`)](#图操作事件onflowoperation)**。本 API 仅覆盖删除;与 `onFlowOperation` 同时传入时 **事件优先**。迁移见 [docs/CAPABILITY-MAP.md](docs/CAPABILITY-MAP.md)。
468
-
469
- 在可编辑模式下,可通过回调 **按节点/边粒度** 决定是否允许删除。同步返回 **`false`** 时取消该次删除(**Backspace** **`ref.removeNode` / `ref.removeEdge`** 均生效)。**`readOnly` / `readingMode`** 下不调用回调。删除节点时 **级联移除** 的关联边 **不会** 逐条触发 `onBeforeRemoveEdge`。
470
-
471
- ```tsx
472
- <WechenAgentEditor
473
- onBeforeRemoveNode={(node) => {
474
- // 例:禁止删除入口节点,或 data.deletable === false 的节点
475
- if (node.id === "start_node") return false;
476
- if (node.data?.deletable === false) return false;
477
- return true;
478
- }}
479
- onBeforeRemoveEdge={(edge) => edge.data?.locked !== true}
480
- />
481
- ```
482
-
483
- L1 工厂同样支持:`buildWechenAgentEditorProps({ onBeforeRemoveNode: ... })`。
484
-
485
- ### 图操作事件(`onFlowOperation`)
486
-
487
- 在可编辑模式下,**删/复制/新增/连线/patch/路径重置** 等改图操作在应用前会派发 **`WechenFlowOperationEvent`**。宿主在 **`onFlowOperation`** 中按 **`event.kind`**(如 `node.remove`、`node.duplicate`、`edge.add`)与 **`event.node` / `event.edge`** 返回 **`false`** 即可取消该次操作(键盘、浮动条、ref 路径一致)。**`onFlowOperation` 对 `node.remove` / `edge.remove` 优先于** `onBeforeRemoveNode` / `onBeforeRemoveEdge`。**`readOnly` / `readingMode`** 下不调用。
488
-
489
- 拒绝 **`node.remove`** 时,编辑器会 **原子取消** 该次删节点操作:节点保留,且 **不会** 因该次键盘/ref 删除而连带移除关联边(级联删边 **不** 单独派发 `edge.remove`)。宿主 **只需** 拦截 `node.remove` / `node.duplicate` 等,**不必** 为级联边再返回 `edge.remove: false`。显式选中边后删除仍走 **`edge.remove`** 门控。
490
-
491
- | `kind`(节选) | 含义 |
492
- |----------------|------|
493
- | `node.remove` | 删除节点 |
494
- | `node.duplicate` | 复制节点 |
495
- | `node.add` | 新增节点(拖库、ref) |
496
- | `node.patch` | 补丁 `node.data` |
497
- | `edge.remove` | 删除边 |
498
- | `edge.add` | 新建连线 |
499
- | `edge.reconnect` | 重连连线 |
500
- | `edge.patch` | 补丁边 |
501
- | `edge.path.reset` | 恢复自动路径 |
502
-
503
- ```tsx
504
- <WechenAgentEditor
505
- onFlowOperation={(event) => {
506
- if (event.node?.type === "pill") {
507
- if (event.kind === "node.remove" || event.kind === "node.duplicate") {
508
- return false;
509
- }
510
- }
511
- return true;
512
- }}
513
- />
514
- ```
515
-
516
- Playground:`?tab=flow-operation-policy`。L1:`buildWechenAgentEditorProps({ onFlowOperation: ... })`。
517
-
518
- ### 顶底感应区连线(默认,上进下出)
519
-
520
- 内置 **`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`**、底边扇出 **共干** 路径配合。
521
-
522
- 周界四向感应的 **完整方案说明(交互、角区、数据字段、代码映射)** 见仓库 **`openspec/changes/node-perimeter-sensing-scheme/design.md`**。
523
-
524
- ### 组件库 legacy 接线桩(`flowHandles`,可选)
525
-
526
- **已弃用优先**:编排默认以 **四边感应区** 为准。**`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`**。
527
-
528
- **`items[].defaultData`(可选)**:拖入或 **`ref.addNodeFromLibrary`** 时 **浅合并** 进 **`node.data`**(在工厂基础字段之后;**`libraryGroupKey` / `libraryItemKey` 始终由包写入**,不会被 `defaultData` 覆盖)。嵌套对象整键替换(与 **`patchNode` 深度合并** 不同)。**`flowHandles` 仅来自 `item.flowHandles`**,写在 `defaultData` 内的 `flowHandles` 会被忽略。内置 **`mainAgent`** 仍有包内默认(如 `baseModel`),可用 **`defaultData` 覆盖同名键**。
529
-
530
- ```tsx
531
- const libraryGroups = [
532
- {
533
- key: "MAIN",
534
- title: "主Agent",
535
- items: [
536
- {
537
- key: "planner",
538
- title: "Planner",
539
- type: "MAIN",
540
- defaultData: { strategy: "Plan-Execute" },
541
- },
542
- { key: "supervisor", title: "Supervisor", type: "MAIN" },
543
- ],
544
- },
545
- {
546
- key: "SUB",
547
- title: "应用Agent",
548
- items: [
549
- {
550
- key: "alert",
551
- title: "告警分析",
552
- type: "SUB",
553
- defaultData: { skill: "alert-analysis" },
554
- },
555
- ],
556
- },
557
- ];
558
- ```
559
-
560
- 非内置 **`type`** 使用与 **`card`** 相同的通用节点外壳与四边感应区;缺 **`item.type`** 时拖放会提示且不会创建节点。宿主可在加载配置后调用 **`validateComponentLibraryGroups(libraryGroups)`** 预检缺 `type` 的条目。
561
-
562
- **`renderFlowNode(props, context)`**:**`props.type`** = 落盘 **`item.type`**(**不是** `item.key`);**`context`** 含 **`selected`**、可选 **`libraryGroupKey`** / **`libraryItemKey`**、**`editorRef`**(编辑器内部命令式 API;**不依赖** 宿主是否写 `<WechenAgentEditor ref={...} />`,在点击等事件内读 `current`)。宿主侧自动化/保存等 **仍建议** 挂 `ref`。四边感应与接线桩仍由包渲染,**勿** 在返回值内放置 **`Handle`**。类型见 **`WechenAgentRenderFlowNodeContext`**、**`resolveLibraryItemNodeType`**、**`findComponentLibraryItem`** 等导出。
563
-
564
- ### Monaco Worker
565
-
566
- 必须在**首次渲染编辑器之前**完成 Monaco worker 引导,否则代码编辑区无法初始化。可参考本仓库 **`playground/src/monacoBootstrap.ts`** 与 **`playground/src/main.tsx`**,并按 [Monaco](https://github.com/microsoft/monaco-editor) / [@monaco-editor/react](https://github.com/suren-atoyan/monaco-react) 文档适配 Vite 或 Webpack。
567
-
568
- **Umi 4 / Webpack 宿主**(常见 `worker-javascript.js` 返回 HTML):见 **[docs/UMI-MONACO-WORKER.md](docs/UMI-MONACO-WORKER.md)**(入口 bootstrap、MFSU `exclude`、`monaco-editor-webpack-plugin` 备选)。
569
-
570
- ### 与快速开始一致的最小页(可选 `title`)
571
-
572
- 与上文 **L0** 相同;若需要顶栏文案可传 **`title`**。未传 **`initialFlow`** 时为**空画布**。需要内置示例图时,从本包导入 **`defaultInitialFlow`**、**`cloneWechenAgentFlow`** 并传入 `initialFlow={cloneWechenAgentFlow(defaultInitialFlow)}`。
573
-
574
- ### 行为与扩展(摘要)
575
-
576
- - **连线**:从节点的 source Handle 拖到 target Handle;选中边可在右侧调整动画与**执行方向**等。**执行方向**与 **`source` → `target`** 一致:箭头在 **目标** 端;选「反向」会 **交换源与目标**(及 handle),不再依赖仅反转 dash 的持久化字段。历史 **`edge.data.wechenFlowDirection: 'reverse'`** 在 **`normalizeWechenAgentFlowJson` / `cloneWechenAgentFlow` / `parseWechenAgentFlowJson`** 中 **幂等归一** 为交换端点并清除该键。宿主也可调用 **`swapWechenFlowEdgeEndpoints(edge)`** 得到与内置面板一致的 **`patchEdge`** 补丁。
577
- - **数据合并**:节点 `data` 的更新遵循导出的 **`mergeNodeData`**(对象深度合并,数组在补丁中出现时整段替换)。详见下文 **[`patchNode` `patchNodeData`](#patchnode-与-patchnodedata)**。
578
- - **自定义右侧面板**:可选 **`renderConfigPanel`**(节点与连线统一入口)或 **`renderNodeConfig`**;同时存在时以 `renderConfigPanel` 为准(返回 `undefined` 时回退内置)。
579
-
580
- ### `patchNodeData` 与配置区 `ctx.patchNode`
581
-
582
- 二者语义一致:把 **`patch` 深度合并进 `node.data`**(内部为 **`mergeNodeData`**)。区别仅在于 **是否由调用方传入节点 id**(右栏已选中节点时,id 由编辑器绑定)。
583
-
584
- | API | 签名 | 使用场景 |
585
- |-----|------|----------|
586
- | **`ctx.patchNode`** | `(patch) => void` | **`renderConfigPanel`** 选中节点时;**勿传 `node.id`**,当前节点已固定 |
587
- | **`ctx.editorRef`** | `RefObject<WechenAgentEditorRef \| null>` | **`renderConfigPanel`** 节点/边共有;内部 API,**无需** 宿主挂 `ref`,在事件内读 `current` |
588
- | **`ref.patchNodeData`** | `(nodeId, patch) => void` | 命令式改 **`node.data`**(按钮、自动化、跨节点批量) |
589
-
590
- **`patch` 的顶层键 = `node.data` 的键**,不是整颗 React Flow `Node`(不含 `id` / `type` / `position` / `measured` 等)。
591
-
592
- ### ref:节点坐标与壳层(`patchNode` / `setNodePosition`)
593
-
594
- | API | 作用 |
595
- |-----|------|
596
- | **`ref.getNode(id)`** / **`ref.getEdge(id)`** | 按 id 读取当前实体(浅拷贝) |
597
- | **`ref.setNodePosition(id, { x, y })`** | 只改 **`node.position`**(等同 `patchNode(id, { position })`) |
598
- | **`ref.patchNode(id, patch)`** | 改 **`position` / `type` / `width` / `height`**;**勿** 用来写 `data` |
599
- | **`ref.setTitle(title)`** | 只改标题,不替换整图 |
600
-
601
- **对称读写示例**(无需 `replaceFlow` 整图替换坐标):
602
-
603
- ```tsx
604
- const flow = editorRef.current?.getFlow();
605
- const n = flow?.nodes.find((x) => x.id === "my-node");
606
- if (n) {
607
- editorRef.current?.setNodePosition("my-node", { x: n.position.x + 80, y: n.position.y });
608
- }
609
- ```
610
-
611
- 程序化改坐标会经 **`onFlowOperation`** 的 **`node.patch`**(`source: ref`);与 **`patchNodeData`** 的 data 补丁区分。
612
-
613
- ### ref 实例 vs 包导出(常见误找)
614
-
615
- 下列能力 **不在** `editorRef.current` 上,需从包 **直接 import**(完整对照见 [docs/EDITOR-REF-API-MATRIX.md](docs/EDITOR-REF-API-MATRIX.md)):
616
-
617
- | 需求 | 用法 |
618
- |------|------|
619
- | 后端 DTO 导出/导入 | `exportBackendWorkflowJson` / `importBackendWorkflowToFlow` → 再 `ref.replaceFlow` |
620
- | 图合法性校验 | `validateWechenAgentFlow(ref.getFlow())` |
621
- | 克隆/解析 JSON | `cloneWechenAgentFlow` / `parseWechenAgentFlowJson` |
622
- | 边端点交换补丁 | `swapWechenFlowEdgeEndpoints` → `ref.patchEdge` |
623
- | 画布 **重连** | `ref.reconnectEdge(edgeId, connection)`(同 UI `onReconnect`) |
624
- | 拖动 **门控** | `onFlowOperation` **`node.move`**(拖放结束;拒绝则回滚坐标) |
625
- | 聚焦节点 | `ref.centerOnNode(nodeId)` |
626
- | 校验当前图 | `ref.validateFlow()` |
627
-
628
- ```
629
- node = { id, type, position, data: { label, config, } }
630
-
631
- mergeNodeData(node.data, patch)
632
- patch = { label: "x" } ──────┘ → node.data.label === "x"
633
- ```
634
-
635
- #### 推荐写法
636
-
637
- **1. 改单个字段(最常用)**
638
-
639
- ```tsx
640
- // renderConfigPanel
641
- if (ctx.kind === "node") {
642
- ctx.patchNode({ label: "新名称" });
643
- }
644
-
645
- // ref
646
- editorRef.current?.patchNodeData("node-1", { label: "新名称" });
647
- ```
648
-
649
- **2. 一次保存多个 `data` 顶层键**
650
-
651
- ```tsx
652
- ctx.patchNode({
653
- label: "主入口",
654
- config: { maxSteps: 8 },
655
- });
656
- ```
657
-
658
- **3. 嵌套对象(深度合并,未出现在 patch 中的兄弟键保留)**
659
-
660
- ```tsx
661
- // 已有 node.data.config = { a: 1, b: 2 }
662
- ctx.patchNode({ config: { a: 3 } });
663
- // → node.data.config === { a: 3, b: 2 }
664
- ```
665
-
666
- **4. antd `Form` 即时写回(`renderConfigPanel` 需自建 `Form`;`renderNodeConfig` 由外层包一层 `Form`)**
667
-
668
- ```tsx
669
- if (ctx.kind === "node" && ctx.node.type === "MAIN") {
670
- const disabled = ctx.readOnly || ctx.readingMode;
671
- return (
672
- <Form
673
- key={ctx.node.id}
674
- layout="vertical"
675
- initialValues={ctx.node.data}
676
- disabled={disabled}
677
- onValuesChange={(_, all) => ctx.patchNode(all)}
678
- >
679
- <Form.Item name="label" label="节点名称">
680
- <Input />
681
- </Form.Item>
682
- </Form>
683
- );
684
- }
685
- ```
686
-
687
- - **`initialValues` `node.data`**,不要用整颗 `node`。
688
- - 切换选中节点时用 **`key={node.id}`** `form.setFieldsValue`,避免 `initialValues` 只在首次挂载生效。
689
- - 只读:`disabled={ctx.readOnly || ctx.readingMode}`(上下文 **无** `ctx.disabled` 字段)。
690
-
691
- **5. 受控输入 + 按钮「应用」(防抖 / 显式保存)**
692
-
693
- ```tsx
694
- const [draft, setDraft] = useState(String(ctx.node.data?.label ?? ""));
695
- useEffect(() => {
696
- setDraft(String(ctx.node.data?.label ?? ""));
697
- }, [ctx.node.id, ctx.node.data]);
698
-
699
- <Button onClick={() => ctx.patchNode({ label: draft })}>应用</Button>
700
- ```
701
-
702
- Playground 中 `card` 节点示例见 `playground/src/playgroundPanels.tsx`。
703
-
704
- #### 常见错误(会导致不生效或 `data` 被污染)
705
-
706
- | 错误写法 | 实际效果 |
707
- |----------|----------|
708
- | `patchNode(node.id, { label: "x" })` | 只接收 **一个** 参数;`node.id` 字符串被当作 `patch` merge 进 `data`,出现 `"0":"m"` 等下标键 |
709
- | `patchNode(node.data)` | 闭包里的 **旧** `node.data` 盖回表单新值,表现为回弹或不更新 |
710
- | `patchNode({ data: { label: "x" } })` | 写入 **`node.data.data.label`**,除非业务里真有名为 `data` 的嵌套对象 |
711
- | `patchNode(allValues)` 且 `initialValues={node}` | 把 `id` / `position` 等 merge 进 `data`,结构错乱 |
712
- | `patchNodeData(id, node)` | 应传 **`node.data` 上的补丁**,不是整颗 `Node` |
713
-
714
- **对比:想改 `node.data.label`**
715
-
716
- ```tsx
717
- ctx.patchNode({ label: "x" }); // ✅
718
- ctx.patchNodeData(id, { label: "x" }); // ✅ ref
719
-
720
- ctx.patchNode({ data: { label: "x" } }); // ❌ → data.data.label
721
- ctx.patchNode(node.id, { label: "x" }); // ❌ 多传了 id
722
- ```
723
-
724
- #### 数组与其它规则
725
-
726
- - **数组**:`patch` 里若包含某数组键,则 **整段替换**该数组(不按下标合并)。
727
- - **`undefined` 键**:`mergeNodeData` **跳过** `undefined`,不会用其删除已有字段;要清空请显式传 `null` 或业务约定值。
728
- - **边**:**`ctx.patchEdge`** / **`ref.patchEdge`** 为边对象 **顶层浅合并**(与节点 `data` 规则不同),见 `WechenAgentConfigPanelEdgeContext`。
729
-
730
- ### `setConfigUnsavedGuard` 与延迟保存
731
-
732
- 在 **`renderConfigPanel`** 中若采用 **编辑草稿 + 按钮保存**(不每次 `onValuesChange` 调用 `patchNode`),可调用 **`ctx.setConfigUnsavedGuard(true/false)`**:
733
-
734
- - **`true`**:在 **顶栏、组件库、画布** 上显示半透明蒙层(**不挡** 右侧配置区),点击蒙层提示「当前有未保存的修改…」(`LOCALE_KEYS.CONFIG_UNSAVED_GUARD_HINT`,可 `localeMessages` 覆盖)。
735
- - **`false`**:关闭蒙层;保存成功后应显式调用。
736
- - **选中变化**(切换节点/边、点击画布空白)时编辑器会 **自动** `false`。
737
- - **`readingMode`** 下为 no-op。
738
-
739
- ```tsx
740
- const [draft, setDraft] = useState(() => String(ctx.node.data?.label ?? ""));
741
-
742
- useEffect(() => {
743
- setDraft(String(ctx.node.data?.label ?? ""));
744
- ctx.setConfigUnsavedGuard(false);
745
- }, [ctx.node.id]);
746
-
747
- <Input
748
- value={draft}
749
- onChange={(e) => {
750
- setDraft(e.target.value);
751
- ctx.setConfigUnsavedGuard(true);
752
- }}
753
- />
754
- <Button
755
- onClick={() => {
756
- ctx.patchNode({ label: draft });
757
- ctx.setConfigUnsavedGuard(false);
758
- }}
759
- >
760
- 保存
761
- </Button>
762
- ```
763
-
764
- Playground **`renderConfigPanel`** Tab 中 `card` / 边配置演示了该流程。
765
-
766
- ### 新建节点/连线后默认选中
767
-
768
- 在可编辑模式下,以下操作成功后会 **自动选中** 新建对象,右侧配置区立即展示对应面板:
769
-
770
- - 从 **组件库拖入** 节点;
771
- - 画布 **拖拽连线**(`onConnect`);
772
- - ref **`addNodeFromLibrary`** / **`addNode`** / **`connect`**(或 `addEdge`)。
773
-
774
- 拖入新节点后,宿主宜在 `useEffect` 依赖 **`node.id`** 上重置表单草稿并 `setConfigUnsavedGuard(false)`(见上节)。
775
-
776
- - **顶栏左侧**:可选 **`renderHeaderLeading`**,入参含当前解析标题 **`title`**(与 **`ref.getTitle()`** 一致)及 **`editorRef`**;未传时显示默认 `Title`。改标题数据仍用 **`title` prop** 或 **`replaceFlow`**。
777
- - **顶栏右侧**:可选 **`renderHeaderActions`**,入参含与默认行为一致的 **`save`** / **`testRun`** **`editorRef`**。左右可 **同时** 自定义。Playground:`?tab=header-leading`、`?tab=header-actions`。
778
-
779
- 完整类型与符号列表以 **`src/index.ts`** 的导出为准。
780
-
781
- ### 集成自检
782
-
783
- 在宿主项目根目录建议逐项确认:
784
-
785
- | 检查项 | 说明 |
786
- |--------|------|
787
- | Peer 依赖 | **`npm ls react`**、**`npm ls @xyflow/react`**、**`npm ls dagre`** 各仅一棵有效树、无重复主版本 |
788
- | 构建 | **`npm run build`**(或 Umi `max build`)通过 |
789
- | Monaco | 已在首屏前完成 **worker 引导**(见上文「Monaco Worker」) |
790
- | 空画布 | **未传 `initialFlow`** 时中间画布无预置节点;业务示例需 **`cloneWechenAgentFlow(defaultInitialFlow)`** 或 `ref.replaceFlow(...)` 显式注入 |
791
- | 烟测 | 拖入节点、连线、`readOnly` / `readingMode`、保存或 `getSnapshot`;边路径添加拐点/拖顶点 |
792
- | ref 增图 | `ref.addNodeFromLibrary` / `ref.connect` 与拖放、连线行为一致 |
793
- | DTO 往返 | `exportBackendWorkflowJson` `importBackendWorkflowToFlow` → `replaceFlow`;`condition`/`api`/`script` 经 `data.backendNodeType` |
794
- | 图操作门控 | **`onFlowOperation`**:拒 `node.remove` 时节点与级联边均保留(Playground `?tab=flow-operation-policy`) |
795
- | ref 坐标回写 | **`getFlow` position** → **`setNodePosition` position**(`?tab=ref-api`) |
796
- | 图校验 | `validateWechenAgentFlow`;可选 `onBeforeSave` 拦截 |
797
-
798
- ---
799
-
800
- ## 本地开发(本仓库)
801
-
802
- ```bash
803
- npm install
804
- npm install --prefix playground
805
- npm run dev # Playground 文档站(分 Tab 演示;ref 全表见 ?tab=ref-api,见 playground/README.md)
806
- npm run dev:minimal # 仅最小宿主集成(对照 README「快速开始」)
807
- npm run baseline # 工程基线:测试数、最大 LOC、dist 体积(见 docs/ENGINEERING-BASELINE.md)
808
- npm test # Vitest(含 flowOperation / deleteGuard 表驱动与 editorGateSmoke)
809
- npm run typecheck
810
- npm run check:docs # 文档覆盖 WARN(onFlowOperation kind ↔ README)
811
- npm run release:prep # 发版门禁:test + build + pack:check + playground build + baseline
812
- npm run verify:change # 可选:test + typecheck + audit:white-screen(见 docs/ENGINEERING-BASELINE.md)
813
- ```
814
-
815
- **门控冒烟**(无 DOM):`src/flow/editorGateSmoke.test.ts` — 验证 `onFlowOperation` 拒删 pill 时级联边过滤;已包含在 `npm test` / `release:prep`。
816
-
817
- 浏览器地址以终端输出为准(默认 `http://localhost:5173`)。文档站默认 **`?tab=overview`**。
818
-
819
- ---
820
-
821
- ## 维护者:发版
822
-
823
- ### 版本摘要
824
-
825
- | 版本 | 宿主需注意 |
826
- |------|------------|
827
- | **1.3.2** | **`onFlowOperation`** 统一图操作门控;拒 `node.remove` 时级联边原子保留;legacy 边/只读/配置区白屏加固;**peer 同 1.3.0** |
828
- | **1.3.0** | 自由折线路径编辑、选中浮动条、路径 ref;**peer 要求同 1.2.3** |
829
- | **1.2.3** | **Breaking(集成)**:`@xyflow/react`、`dagre` 改为 peer,须宿主显式安装 |
830
- | **1.1.x** | `buildWechenAgentEditorProps`、后端 DTO、删除守卫等 |
831
-
832
- 完整变更见 **`CHANGELOG.md`**。
833
-
834
- ### 发版清单(1.3.2)
835
-
836
- 1. **版本号**:确认根目录 `package.json` **`version`** 为 **`1.3.2`**(npm 以该字段为准;README / CHANGELOG 已对齐)。
837
- 2. **依赖与锁文件**:`npm install`;若改过 `playground/`,执行 `npm install --prefix playground`。
838
- 3. **门禁**(推荐一条命令):
839
-
840
- ```bash
841
- npm run release:prep
842
- ```
843
-
844
- 等价分步:`npm test` `npm run build` `npm run pack:check` → `npm --prefix playground run build` → `npm run baseline`(基线摘要)。可选加强:`npm run verify:change`(含 `audit:white-screen`)、`npm run check:docs`。
845
-
846
- 日志写入 **`.release-check.log`**(`release:prep` 脚本)。
847
-
848
- 4. **登录 registry**:`npm whoami`(`registry.npmjs.org`);国内镜像发版用 **`npm run release:cn`**(勿与 npmjs 混用同一 token 流程 unless intentional)。
849
- 5. **发布**:
850
-
851
- ```bash
852
- npm run release
853
- # 或:npm publish --access public --registry=https://registry.npmjs.org/
854
- ```
855
-
856
- 6. **发后验证**:
857
-
858
- ```bash
859
- npm view wechen-agent-editor-v1 version
860
- npm pack --dry-run # tarball 应仅含 dist/、LICENSE(见 package.json files)
861
- ```
862
-
863
- 7. **空目录烟测**(将 `<version>` 换为 `package.json` 中的版本):
864
-
865
- ```bash
866
- mkdir /tmp/wechen-smoke && cd /tmp/wechen-smoke
867
- npm init -y
868
- npm install wechen-agent-editor-v1@<version> react react-dom antd @xyflow/react dagre monaco-editor @monaco-editor/react
869
- node -e "import('wechen-agent-editor-v1').then(m => console.log('ok', typeof m.WechenAgentEditor))"
870
- ```
871
-
872
- 8. **Git 标签(建议)**:`git tag v1.3.2 && git push origin v1.3.2`(按团队规范)。
873
-
874
- 发版前可将 `repository` / `bugs` / `homepage` 中的占位 URL 换成真实仓库地址(私有仓库可仅在内部文档记录,见「集成排错」说明)。
1
+ # wechen-agent-editor-v1
2
+
3
+ 面向宿主应用的 **Agent 流程编排编辑器** React 组件:左侧可拖入的组件库、中间 **@xyflow/react** 画布、右侧基于 **antd** 与 **Monaco** 的配置区。图上的 **节点** 表示功能模块,**边** 表示执行顺序;持久化的 `flow` 可由你的运行时或执行引擎消费。
4
+
5
+ **包名**:以 `package.json` 的 `name` 为准(`wechen-agent-editor-v1`)。安装与 `import` 均使用该名称。
6
+
7
+ **当前发布版本**:见根目录 **`package.json` 的 `version`**(变更见 [CHANGELOG.md](CHANGELOG.md))。发版前自检见文末 [维护者:发版](#维护者发版)。
8
+
9
+ > **宿主集成必读**:本包 **`dependencies` 为空**,运行时能力全部来自 **peerDependencies**。除 `npm install wechen-agent-editor-v1` 外,须在**业务工程根目录**一并安装 **`react` / `react-dom` / `antd` / `@xyflow/react` / `dagre` / `monaco-editor` / `@monaco-editor/react`**(见下文 [安装](#安装))。缺任一项会导致画布空白、连线失效、自动排版失败或 Monaco 无法加载。
10
+
11
+ **文档导航**:[能力地图](docs/CAPABILITY-MAP.md) · [架构说明](docs/ARCHITECTURE.md) · [私有化部署](docs/ENTERPRISE-DEPLOYMENT.md) · [性能预算](docs/PERFORMANCE-BUDGET.md) · [图操作门控 ref](docs/FLOW-OPERATION-REF.md) · [自动布局 ref](docs/AUTO-LAYOUT-REF.md) · [ref 矩阵](docs/EDITOR-REF-API-MATRIX.md) · [集成阶梯 L0–L3](docs/HOST-INTEGRATION-LADDER.md) · [工程基线](docs/ENGINEERING-BASELINE.md) · [2026 全库审计](docs/PROJECT-AUDIT-2026.md) · Playground [`?tab=overview`](playground/README.md)
12
+
13
+ ## 快速开始
14
+
15
+ ### 60 秒最小集成(L0)
16
+
17
+ 只需 **`onSave`** 与 antd **`ConfigProvider`**,并保证父容器有高度(例如 `height: 100%` 链或 `100vh`)。未传 **`initialFlow`** 时为**空画布**(与编辑器默认一致)。
18
+
19
+ **安装**(在宿主项目根目录执行,勿只装本包):
20
+
21
+ ```bash
22
+ npm install wechen-agent-editor-v1 react react-dom antd @xyflow/react dagre monaco-editor @monaco-editor/react
23
+ ```
24
+
25
+ Monaco 还需在应用入口配置 **worker**(见 [Monaco Worker](#monaco-worker))。
26
+
27
+ ```tsx
28
+ import { ConfigProvider } from "antd";
29
+ import {
30
+ WechenAgentEditor,
31
+ getDefaultComponentLibraryGroups,
32
+ } from "wechen-agent-editor-v1";
33
+ // 若生产构建后缺少 Flow 全局样式,再增加:import "wechen-agent-editor-v1/style.css";
34
+
35
+ export function AgentPage() {
36
+ return (
37
+ <ConfigProvider>
38
+ <div style={{ height: "100vh" }}>
39
+ <WechenAgentEditor
40
+ libraryGroups={getDefaultComponentLibraryGroups()}
41
+ onSave={(s) => console.log("flow", s.flow)}
42
+ />
43
+ </div>
44
+ </ConfigProvider>
45
+ );
46
+ }
47
+ ```
48
+
49
+ ### 常用配置一条对象(L1)
50
+
51
+ 标题、初始流、只读、以及在**默认组件库**上按条目打补丁,可用 **`buildWechenAgentEditorProps`** 一次展开为 `WechenAgentEditor` 的 props(类型见包内导出)。
52
+
53
+ ```tsx
54
+ import { ConfigProvider } from "antd";
55
+ import {
56
+ WechenAgentEditor,
57
+ buildWechenAgentEditorProps,
58
+ cloneWechenAgentFlow,
59
+ COMPONENT_LIBRARY_GROUP_APP,
60
+ defaultInitialFlow,
61
+ } from "wechen-agent-editor-v1";
62
+
63
+ const editorProps = buildWechenAgentEditorProps({
64
+ title: "我的流程",
65
+ initialFlow: cloneWechenAgentFlow(defaultInitialFlow),
66
+ readOnly: false,
67
+ library: {
68
+ preset: "default",
69
+ itemPatches: [
70
+ { groupKey: COMPONENT_LIBRARY_GROUP_APP, itemKey: "alert", patch: { subtitle: "自定义副标题" } },
71
+ ],
72
+ },
73
+ onSave: (s) => console.log("flow", s.flow),
74
+ });
75
+
76
+ export function AgentPage() {
77
+ return (
78
+ <ConfigProvider>
79
+ <div style={{ height: "100vh" }}>
80
+ <WechenAgentEditor {...editorProps} />
81
+ </div>
82
+ </ConfigProvider>
83
+ );
84
+ }
85
+ ```
86
+
87
+ **高级扩展**(自定义画布节点、右侧配置面板、组件库列表映射、顶栏按钮等)仍使用完整 **`WechenAgentEditorProps`**;说明见下文「行为与扩展(摘要)」与 **`src/index.ts`** 导出。
88
+
89
+ ### 后端工作流 JSON(存库 / 执行 / 回显)
90
+
91
+ 编辑器内部持久化形态为 **`WechenAgentFlowJson`**(React Flow 的 `nodes` / `edges` + 业务 `data`)。若后端希望 **扁平 DTO**(`flowId`、`flowName`、`nodes[].type` + `config`、`edges[].source/target` 等),请使用:
92
+
93
+ - **`exportBackendWorkflowJson(snapshot | flow, meta)`** → **`WechenBackendWorkflowJson`**(根字段 **`schemaVersion`** 当前为 **`"1"`**,与业务字段 **`version`** 区分)
94
+ - **`importBackendWorkflowToFlow(dto)`** → **`WechenAgentFlowJson`**,再作为 **`initialFlow`** 或 **`ref.replaceFlow`** 回显
95
+
96
+ **边 `id`(删边 / 命令式 API)**:
97
+
98
+ - **`onSave` / `getSnapshot` / `flowJson`**:`edges[].id` 为画布稳定 id,**`ref.deleteEdge(edgeId)`** / **`getSelectedEdgeId()`** 应使用该 id。
99
+ - **后端 DTO**:`exportBackendWorkflowJson` **会写出** `edges[].id`(与画布一致);`importBackendWorkflowToFlow` **优先** 使用 DTO 中的 `id`,省略时按 `e-${source}-${target}-${index}` 合成。仅持久化 DTO 时,回灌后删边请用 **回灌后的** `flow.edges[].id`,勿沿用导出前的 id(除非 DTO 已含 `id` 且已往返)。
100
+ - 推荐:需要完整画布状态时优先存 **`snapshot.flow`** 或 **`flowJson`**;仅存 DTO 时务必带上 **`edges[].id`** 字段。
101
+
102
+ **有损字段**:导出会去掉 RF 运行期字段与部分边样式;导入会按边 `id` 分配 `sourceHandle`/`targetHandle`。详见类型 **`WechenBackendWorkflowJson`** 的 JSDoc。
103
+
104
+ **连线布局**:
105
+
106
+ - **`flowJson` / `getSnapshot`**:在 `edge.data` 中持久化 **`wechenSourceAttachSide` / `wechenTargetAttachSide`** 与 **`wechenPathOptions`**(`offset`、`borderRadius`),回显时 **优先使用存盘布局**,与编辑态一致。
107
+ - **后端 DTO**:`edges[].layout` 可选携带上述字段(`exportBackendWorkflowJson` / `importBackendWorkflowToFlow`);若需 **完整** 画布状态(含全部 `edge.data`),请优先持久化 **`flowJson`** 而非仅 DTO 拓扑。
108
+
109
+ **节点类型映射**(画布 ↔ DTO):
110
+
111
+ | 画布 | DTO `nodes[].type` | 说明 |
112
+ |------|-------------------|------|
113
+ | `pill` | `start` | 入口 |
114
+ | `mainAgent` | `agent` | 主 Agent |
115
+ | `card`(默认) | `task` | 通用任务 |
116
+ | `card` + `data.backendNodeType` | `condition` / `api` / `script` | 扩展类型;导入后写回 `backendNodeType` |
117
+
118
+ **画布类型与 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`** 一致)。
119
+
120
+ | 字段 | 含义 |
121
+ |------|------|
122
+ | 画布 `node.type` | React Flow 节点类型;拖入时 **仅** 由 **`items[].type`**(必填,任意非空字符串)决定 |
123
+ | DTO `nodes[].type` | 后端执行类型(`agent` / `task` / `start` …) |
124
+ | `config.canvasNodeType` | 往返时还原画布 `node.type` |
125
+ | `data.libraryGroupKey` / `libraryItemKey` | 库分组 catalog id / 库项 id(**非** `props.type`) |
126
+
127
+ 自定义节点若需稳定 DTO 类型,请在 `node.data` 设置 **`backendNodeType`**(取值与 `WechenBackendWorkflowNodeType` 一致)。
128
+
129
+ ```tsx
130
+ import {
131
+ WechenAgentEditor,
132
+ exportBackendWorkflowJson,
133
+ importBackendWorkflowToFlow,
134
+ } from "wechen-agent-editor-v1";
135
+
136
+ <WechenAgentEditor
137
+ onSave={(s) => {
138
+ const dto = exportBackendWorkflowJson(s, {
139
+ flowId: "agent_flow_001",
140
+ workflowVersion: "1.0",
141
+ });
142
+ void fetch("/api/flows", {
143
+ method: "POST",
144
+ body: JSON.stringify(dto),
145
+ headers: { "Content-Type": "application/json" },
146
+ });
147
+ }}
148
+ />;
149
+
150
+ // 回显:从接口取 dto 后
151
+ const flow = importBackendWorkflowToFlow(dtoFromApi);
152
+ // <WechenAgentEditor initialFlow={flow} />
153
+ ```
154
+
155
+ 本仓库 **playground** 提供对照:
156
+
157
+ - **`pnpm run dev`**:文档站(侧栏能力 Tab + **仓库文档** `?tab=repo-docs` + 分栏独立滚动;默认 `?tab=overview`),见 [playground/README.md](playground/README.md)。
158
+ - **`pnpm run dev:minimal`**:仅最小宿主集成(对照上文「60 秒最小集成」)。
159
+
160
+ ### 编辑器与 Agent 运行时
161
+
162
+ 本包是 **编排 UI + 序列化 + ref API**,**不包含** Agent / LLM **执行引擎**。推荐分层:
163
+
164
+ ```
165
+ WechenAgentEditor(编排) → onSave / exportBackendWorkflowJson → 宿主持久化 → 你们的运行时调度
166
+ ```
167
+
168
+ - **`onTestRun` / `ref.testRun()`**:仅触发宿主回调,具体执行由宿主实现。
169
+ - **`validateWechenAgentFlow(flow)`**:纯函数图校验(默认不阻断保存);可在 `onBeforeSave` 内返回 `false` 拦截。
170
+ - **ref 增删改查**:`getSnapshot` / **`getFlow` / `getInitialFlow`** / **`getNode` / `getEdge`** / **`replaceFlow` / `setInitialFlow`** / **`patchNodeData`(`node.data`)** / **`patchNode` / `setNodePosition`(坐标与壳层)** / **`setTitle`** / `removeNode` / **`duplicateNode`** / **`removeEdge` / `deleteEdge`** / 路径与布局 ref 等(见 `WechenAgentEditorRef`、Playground **`?tab=ref-api`**)。
171
+ - **连线路径(自由折线)**:选中边后浮动条「添加拐点」;仅 **中间拐点** 可拖拽(任意角度线段),首尾随节点桩固定;**水平/垂直线段** 可整条平移(鼠标移到线段上,光标变为 ↔ 或 ↕);`Delete` 在顶点选中时删点。`normalizeWechenAgentFlowJson` 会清除无效 `path` 并回到 `auto`。
172
+ - **选中浮动操作条**:单选节点时显示 **删除 / 复制**;单选边时显示 **删除**、**添加/删除拐点**、手动边 **恢复自动路径**。与边上 **路径顶点手柄**(几何编辑)分工;浮动条锚点在路径外侧,避免压住连线与节点。`readOnly` / `readingMode` / 多选时不显示。可选 **`renderSelectionToolbar`** 扩展。
173
+ - **布局相关 ref**(`readOnly` / `readingMode` 下均为 no-op):**完整说明 → [docs/AUTO-LAYOUT-REF.md](./docs/AUTO-LAYOUT-REF.md)**
174
+
175
+ | 方法 | 作用 |
176
+ |------|------|
177
+ | **`relayoutEdges()`** | 重算连线,**不移动节点**,**保留**手调路径中间拐点 |
178
+ | **`autoLayoutNodes(options?)`** | 低层节点排版(默认一体化);默认布线但 **不清** 手调拐点 |
179
+ | **`autoLayout(options?)`** | 顶栏推荐:无参 = `node`+`edge`(清手调路径)+`fitView`;`only` 白名单 |
180
+ | **`fitView()`** | 仅适配视口 |
181
+
182
+ ```tsx
183
+ editorRef.current?.autoLayout();
184
+ editorRef.current?.autoLayout({ only: ["node"] });
185
+ editorRef.current?.autoLayout({ only: ["edge"] });
186
+ editorRef.current?.autoLayoutNodes({ direction: "TB" });
187
+ editorRef.current?.relayoutEdges();
188
+ ```
189
+
190
+ - **回显后连线布局**:`getSnapshot` / `flowJson` 保存锚边与路径选项;`replaceFlow` 优先恢复存盘布局。
191
+ - **路径避让 / 一体化排版**:见 [AUTO-LAYOUT-REF.md](./docs/AUTO-LAYOUT-REF.md)。
192
+
193
+ ```tsx
194
+ import { validateWechenAgentFlow } from "wechen-agent-editor-v1";
195
+
196
+ <WechenAgentEditor
197
+ onBeforeSave={(snap) => {
198
+ const { valid, issues } = validateWechenAgentFlow(snap.flow);
199
+ if (!valid) {
200
+ console.warn(issues);
201
+ return false;
202
+ }
203
+ }}
204
+ onSave={(s) => { /* POST exportBackendWorkflowJson(s, meta) */ }}
205
+ />
206
+ ```
207
+
208
+ ---
209
+
210
+ ## 安装
211
+
212
+ 本包为 **纯组件库**:`package.json` 中 **`dependencies` 为空**,**不会**把 React Flow、Dagre、Monaco 等打进你的 `node_modules` 树里作为本包的私有副本。宿主必须在**应用根**声明下列 **peer**,并与本包解析到**同一份**运行时(尤其 `@xyflow/react`)。
213
+
214
+ ### 推荐:一条命令装齐
215
+
216
+ ```bash
217
+ npm install wechen-agent-editor-v1 react react-dom antd @xyflow/react dagre monaco-editor @monaco-editor/react
218
+ ```
219
+
220
+ 使用 **pnpm** / **yarn** 时同样在业务包中声明上述依赖,勿仅在 monorepo 子包里安装而根目录缺失 peer。
221
+
222
+ ### 仅安装本包时常见现象
223
+
224
+ | 缺失 peer | 现象 |
225
+ |-----------|------|
226
+ | `react` / `react-dom` | 无法渲染 |
227
+ | `antd` | 壳层/配置区样式或组件报错 |
228
+ | `@xyflow/react` | 画布不显示、连线/拖线无响应、Handle 失效 |
229
+ | `dagre` | `autoLayout` / `autoLayoutNodes` 报错或无效 |
230
+ | `monaco-editor` + `@monaco-editor/react` | 代码编辑区空白(另需 [Monaco Worker](#monaco-worker)) |
231
+
232
+ ### Peer 依赖清单(须在宿主 `package.json` 中声明)
233
+
234
+ 版本须 **≥** 本包 `peerDependencies` 下限,并与业务栈锁定策略一致(下表为开发本仓库时使用的参考版本):
235
+
236
+ | 包 | 下限(peer) | 用途 |
237
+ |----|-------------|------|
238
+ | `react`、`react-dom` | ≥18 | UI 运行时 |
239
+ | `antd` | ≥5 | 编辑器壳层、配置区、顶栏 |
240
+ | `@xyflow/react` | ≥12 | 画布(节点/边/连线) |
241
+ | `dagre` | ≥0.8.5 | 节点自动排版(`autoLayoutNodes`) |
242
+ | `monaco-editor` | ≥0.45 | 代码编辑内核 |
243
+ | `@monaco-editor/react` | ≥4.6 | React 封装 |
244
+
245
+ 自 **1.2.3** 起 **`@xyflow/react`**、**`dagre`** 由 **peer** 提供(不再随本包内嵌)。自 **1.0.x** 起 **`react` / `antd` / `monaco-*`** 即为 peer。升级自内嵌 Flow 的旧版时,请务必在宿主根目录 **新增** `@xyflow/react` 与 `dagre` 依赖。
246
+
247
+ 自 **1.3.2** 起推荐用 **`onFlowOperation`** 统一图操作策略(替代仅覆盖删除的 `onBeforeRemove*`);**peer 清单无变化**,从 **1.3.0** 升级通常只需 bump 版本并回归构建与画布烟测。
248
+
249
+ **不要**再依赖本包 tarball 内自带的 `@xyflow/react`(历史 `bundledDependencies` 已移除)。宿主与编辑器 **必须** `npm ls @xyflow/react` 仅一棵有效树。
250
+
251
+ 安装后建议自检:
252
+
253
+ ```bash
254
+ npm ls @xyflow/react
255
+ npm ls react
256
+ ```
257
+
258
+ 理想情况为各包 **仅一棵** 依赖树、无重复主版本。若出现多份,请在打包配置中对 `@xyflow/react`(及必要时 `react`)配置 **`resolve.alias`** / dedupe,或统一锁定版本后重装 `node_modules`。
259
+
260
+ ### 安装失败(`ERESOLVE`)
261
+
262
+ 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` 验证,并计划去掉该放宽)。
263
+
264
+ ### Umi / MFSU
265
+
266
+ 一般可直接使用。若遇预编译或模块解析异常,可在配置里将 **`wechen-agent-editor-v1`**、**`@xyflow/react`**、**`monaco-editor`**、**`@monaco-editor/react`** 加入 **`mfsu.exclude`**(见 [Umi MFSU](https://umijs.org/docs/api/config#mfsu)),删除 `.umi` 后重启;或用 **`mfsu: false`** 做对比排查。
267
+
268
+ **依赖示例**(版本请按团队锁定,须满足本包 `peerDependencies`):
269
+
270
+ ```json
271
+ {
272
+ "dependencies": {
273
+ "@monaco-editor/react": "^4.6.0",
274
+ "@xyflow/react": "^12.3.6",
275
+ "antd": "^5.22.0",
276
+ "dagre": "^0.8.5",
277
+ "monaco-editor": "^0.45.0",
278
+ "react": "^18.3.0",
279
+ "react-dom": "^18.3.0",
280
+ "wechen-agent-editor-v1": "^1.3.6"
281
+ }
282
+ }
283
+ ```
284
+
285
+ ### 集成排错
286
+
287
+ **白屏优先**:打开控制台过滤 `[wechen-agent-editor]`,对照 [docs/WHITE-SCREEN-RISK-REGISTER.md](docs/WHITE-SCREEN-RISK-REGISTER.md) 中的 ID/code;Playground 手测 `?tab=legacy-host-api-flow`(JSON 文本域)与 `?tab=custom-config-panel`(配置区边界)。
288
+
289
+ | 现象 | 可能原因 | 处理 |
290
+ |------|----------|------|
291
+ | 代码编辑区空白 / 控制台 Monaco worker 报错 | 未配置 **Monaco worker** | 按 README「Monaco Worker」在应用入口引导 worker |
292
+ | `worker-javascript.js` MIME **`text/html`** / `importScripts` 失败 | 开发服务器对 worker 返回 **HTML 404** | 配置 `MonacoEnvironment`;**Umi** 见 [docs/UMI-MONACO-WORKER.md](docs/UMI-MONACO-WORKER.md) |
293
+ | 选中边后白屏、`Rendered more hooks than during the previous render` | 旧版边组件在 `return null` 后调用 hook | 升级含边组件 Hooks 修复的版本(见 CHANGELOG) |
294
+ | 有边即白屏、`reading 'offset'` | 旧 flow 无 `wechenPathOptions` 或几何未就绪 | 升级含 legacy 边容错版本;控制台搜 **`[wechen-agent-editor]`**(如 `EDGE_PATH_OPTIONS_NULL`、`EDGE_GEOMETRY_UNAVAILABLE`) |
295
+ | 画布区域 Alert「渲染异常」、其余页面正常 | 画布子树未捕获异常 | 看控制台 **`CANVAS_RENDER_CRASH`** 堆栈;修复宿主 `renderFlowNode` 或数据 |
296
+ | 配置区 Alert「配置区渲染异常」、画布仍可用 | `renderConfigPanel` 抛错 | 看 **`CONFIG_PANEL_RENDER_CRASH`**;修复宿主配置面板实现 |
297
+ | 画布不可见或高度为 0 | 父级无 **定高** 或 flex 链缺 `minHeight: 0` | 使用「嵌入 flex 布局」示例;或传编辑器 **`minHeight`** |
298
+ | 连线异常、Handle 无响应、样式错乱 | **未装 peer 或 `@xyflow/react` 双实例** | 按上文 **一条命令** 装齐 peer;`npm ls @xyflow/react` 仅一棵;打包器 **alias/dedupe** |
299
+ | `autoLayout` 报错 | **未安装 `dagre`** | 在宿主 `dependencies` 添加 `dagre` |
300
+ | `ref.deleteEdge(id)` 无效 | 持久化载体无稳定 **`edges[].id`** | 优先存 **`snapshot.flow` / `flowJson`**;仅存 DTO 时导出须含 **`edges[].id`**,回灌后用 **新** id |
301
+ | 只读仍出现浮动条或可删节点 | **`readOnly` 未传给编辑器根组件** | `<WechenAgentEditor readOnly />`;勿仅 `Form disabled` |
302
+ | 存库后连线形状与编辑时不一致 | 仅存拓扑 DTO、缺 **layout** | 使用 **`flowJson`** 或 DTO **`edges[].layout`**(见「连线布局」) |
303
+
304
+ Playground **`?tab=overview`** 提供术语表与持久化对比;工程清单见 **`docs/ENGINEERING-BASELINE.md`**。
305
+
306
+ > **`package.json` 的 `repository` / `homepage` / `bugs`** 若为占位 URL,表示尚未绑定公开仓库;私有 fork 可忽略,发版前请改为真实地址。
307
+
308
+ ---
309
+
310
+ ## 使用
311
+
312
+ ### 入口与样式
313
+
314
+ ```ts
315
+ import { WechenAgentEditor, type WechenAgentSnapshot } from "wechen-agent-editor-v1";
316
+ ```
317
+
318
+ 发布产物中 **`dist/index.js`** 会包含对 **`./style.css`** 的引用,正常情况下图样式会随主入口进入打包结果。若生产环境仍看不到 Flow 全局样式,可增加:
319
+
320
+ ```ts
321
+ import "wechen-agent-editor-v1/style.css";
322
+ ```
323
+
324
+ antd 5 为 CSS-in-JS,请用 **`ConfigProvider`** 包裹应用(与官方用法一致)。
325
+
326
+ ### 嵌入 flex 布局
327
+
328
+ 在 **列方向 flex** 页面中,推荐与 playground 相同的外层结构:父级定高(或 `100vh`),编辑器所在子项使用 **`flex: 1`**、**`minHeight: 0`**,编辑器根节点会 **`flex: 1`** 并 **`height/width: 100%`** 填满该区域。
329
+
330
+ ```tsx
331
+ <div style={{ height: "100vh", display: "flex", flexDirection: "column", minHeight: 0 }}>
332
+ <YourToolbar />
333
+ <div style={{ flex: 1, minHeight: 0, display: "flex", flexDirection: "column" }}>
334
+ <WechenAgentEditor {...props} />
335
+ </div>
336
+ </div>
337
+ ```
338
+
339
+ **行为说明**:自 **1.1.x** 起,画布区域 **不再** 默认施加 `min-height: 400px`;若需要最小可视高度,请对编辑器传入 **`minHeight`** prop,或在页面级容器设置 `min-height`。
340
+
341
+ ### 骨架 CSS 类名(自定义样式)
342
+
343
+ 加载 **`wechen-agent-editor-v1/style.css`** 后,可用下列稳定类名覆盖布局分区样式(与根 **`className`** 合并,不会替换默认类):
344
+
345
+ | 类名 | 区域 |
346
+ | --- | --- |
347
+ | `wechen-agent-editor` | 根布局 |
348
+ | `wechen-agent-editor--reading` | 阅读模式根修饰 |
349
+ | `wechen-agent-editor__workspace` | 工作区(顶栏 + 主体) |
350
+ | `wechen-agent-editor__header` | 顶栏 |
351
+ | `wechen-agent-editor__body` | 主体行(左栏 + 画布 + 右栏) |
352
+ | `wechen-agent-editor__library-sider` | 左侧组件库 Sider |
353
+ | `wechen-agent-editor__library-panel` | 组件库面板内容 |
354
+ | `wechen-agent-editor__main` | 中间主内容 |
355
+ | `wechen-agent-editor__canvas` / `wechen-agent-canvas` | 画布壳(二者同元素,后者为兼容别名) |
356
+ | `wechen-agent-editor__config-sider` | 右侧配置 Sider |
357
+ | `wechen-agent-editor__config-panel` | 配置面板内容 |
358
+ | `wechen-agent-node-shell` / `wechen-agent-node-shell--selected` | 内置节点外壳 / 选中态 |
359
+ | `wechen-agent-node-shell--mainAgent` / `--card` / `--pill` | 节点类型修饰 |
360
+ | `wechen-agent-edge` / `wechen-agent-edge--selected` | 默认连线 / 选中态 |
361
+
362
+ **自定义选中高亮**(颜色写在 CSS 变量中,勿依赖行内 style):
363
+
364
+ | CSS 变量 | 默认 | 说明 |
365
+ | --- | --- | --- |
366
+ | `--wechen-selection-color` | `#52c41a` | 节点选中边框、默认边选中描边 |
367
+ | `--wechen-selection-border-width` | `2px` | 节点选中边框宽度 |
368
+ | `--wechen-selection-shadow` | 绿晕阴影 | 节点选中阴影 |
369
+ | `--wechen-node-border-color` | `#d9d9d9` | 未选中节点边框 |
370
+ | `--wechen-edge-stroke` | `#b1b1b7` | 未选中边描边 |
371
+ | `--wechen-edge-stroke-selected-width` | `2` | 选中边线宽 |
372
+ | `--wechen-zone-strip-border-color` | `rgba(22, 119, 255, 0.55)` | 四边感应条 hover 描边色 |
373
+ | `--wechen-zone-strip-background` | `rgba(22, 119, 255, 0.06)` | 四边感应条 hover 背景色 |
374
+ | `--wechen-zone-strip-border-width` | `2px` | 感应条 hover 描边宽度 |
375
+
376
+ 四边感应条稳定类名:`wechen-agent-zone-strip`(基类)、`wechen-agent-zone-strip--top` / `--right` / `--bottom` / `--left`(边向)、`wechen-agent-zone-strip--active`(当前边 hover)。宿主可直接写选择器覆盖,或与上表变量一并设置在 `.wechen-agent-editor` 上。
377
+
378
+ ```css
379
+ /* 例:自定义顶栏背景 */
380
+ .wechen-agent-editor__header {
381
+ background: #fafafa !important;
382
+ }
383
+
384
+ /* 例:选中高亮改为品牌蓝 */
385
+ .my-app .wechen-agent-editor {
386
+ --wechen-selection-color: #1677ff;
387
+ }
388
+
389
+ /* 例:四边感应条 hover 色 */
390
+ .my-app .wechen-agent-editor {
391
+ --wechen-zone-strip-border-color: #1677ff;
392
+ --wechen-zone-strip-background: rgba(22, 119, 255, 0.12);
393
+ }
394
+ ```
395
+
396
+ ### 国际化(壳层 vs 宿主自定义)
397
+
398
+ 编辑器是 **编排基座**:画布节点由 **`renderFlowNode`**、右侧内容区由 **`renderConfigPanel` / `renderNodeConfig`** 自定义时,**业务文案由宿主自行 i18n**。包内 **`locale`** / **`localeMessages`** 覆盖 **壳层**(顶栏按钮、左右栏分区标题、组件库搜索/空状态、画布缩放控件等;经 React Flow **`ariaLabelConfig`** 注入)。
399
+
400
+ #### 语言契约
401
+
402
+ | 条件 | 编辑器显示语言 |
403
+ | --- | --- |
404
+ | **未传** `locale` 与 `localeMessages` | **内置中文**(`zh-CN` / `DEFAULT_ZH_CN`),与 antd `ConfigProvider`、浏览器语言 **无关** |
405
+ | **`locale="en-US"`**(无 `localeMessages`) | **内置英文**(`DEFAULT_EN_US`) |
406
+ | **`locale` + `localeMessages`** | 先解析 `locale` 底包,再 **浅合并** `localeMessages` 覆盖同 key |
407
+ | **仅传** `localeMessages`(无 `locale`) | **legacy**:仅以 pack 为准;缺失 key 显示 key 字符串(与旧版一致) |
408
+ | 存在 `localeTranslate` | **最高优先级**(便于对接 i18next) |
409
+
410
+ 语言包 **key 为默认中文**(与界面中文一致),例如 `{ "保存": "Save", "配置": "Settings" }`。代码中可用 **`LOCALE_KEYS`** / **`FALLBACK_LOCALE_KEYS`** 常量。
411
+
412
+ ```tsx
413
+ import {
414
+ WechenAgentEditor,
415
+ LOCALE_KEYS,
416
+ registerWechenAgentEditorLocale,
417
+ exportWechenAgentEditorLocaleBundle,
418
+ mergeWechenAgentEditorLocaleMessages,
419
+ } from "wechen-agent-editor-v1";
420
+
421
+ // 中文(默认)
422
+ <WechenAgentEditor />;
423
+
424
+ // 英文:一行 prop,无需维护整包 JSON
425
+ <WechenAgentEditor locale="en-US" />;
426
+
427
+ // 注册自定义语言后切换
428
+ registerWechenAgentEditorLocale("ja-JP", jaPartialMessages);
429
+ <WechenAgentEditor locale="ja-JP" />;
430
+
431
+ // legacy:整包 localeMessages(key 为中文)
432
+ const enPack = mergeWechenAgentEditorLocaleMessages({
433
+ [LOCALE_KEYS.HEADER_SAVE]: "Save",
434
+ });
435
+ <WechenAgentEditor localeMessages={enPack} />;
436
+
437
+ // ref:读取当前语言包或命令式切换(受控 locale prop 优先)
438
+ editorRef.current?.getLocaleMessages();
439
+ editorRef.current?.setLocale("en-US");
440
+ ```
441
+
442
+ `renderConfigPanel` / `renderNodeConfig` / `renderSelectionToolbar` 的 **`ctx.locale`** 含 **`t`**、**`locale`**(id)、**`messages`**(与 `getLocaleMessages()` 一致)。
443
+
444
+ ```tsx
445
+ // antd 与编辑器语言独立配置
446
+ <ConfigProvider locale={antdLocale}>
447
+ <WechenAgentEditor locale="en-US" />
448
+ </ConfigProvider>
449
+ ```
450
+
451
+ - **`exportWechenAgentEditorLocaleBundle('en-US')`**:导出内置英文整包(作翻译模板)。
452
+ - **`exportWechenAgentEditorShellLocaleBundle(locale)`** / **`exportWechenAgentEditorFallbackLocaleBundle(locale)`**:按 locale 导出子集。
453
+ - **`registerWechenAgentEditorLocale` / `listWechenAgentEditorLocaleIds`**:扩展语言列表。
454
+ - **`configSiderTitle`**:可完全覆盖右侧栏外框标题。
455
+ - **`validateLocaleMessagesCompleteness`**:检查大 JSON 是否缺 key(开发期 `console.warn`)。
456
+
457
+ 完整演示见 Playground **`?tab=i18n`**(`locale` prop、注册表、ref `getLocaleMessages`、legacy pack)。
458
+
459
+ ### 嵌入尺寸、`readOnly` `readingMode`
460
+
461
+ 根组件默认 **`height` / `width` 为 `100%`**,**`flex: 1`**、**`minHeight: 0`**(便于 flex 父级);可通过 **`className` / `style`** 透传;可选 **`minHeight`**。请保证父级链路上有**明确高度**(见上文 flex 示例),否则中间画布可能无法撑满。
462
+
463
+ **`readOnly` 须挂在 `<WechenAgentEditor readOnly />` 根组件上**;仅对右侧 `Form` 设 `disabled` **不会** 禁止画布改图、浮动条或组件库拖入。
464
+
465
+ | | 三栏 UI | 画布选中 | 右侧配置 | 浮动条 / 改图 |
466
+ |---|:---:|:---:|:---:|:---:|
467
+ | 默认 | | | 可编辑 | ✓ |
468
+ | **`readOnly`** | ✓ | ✓(查看) | `disabled` | ✗(无浮动条;ref 改图 no-op) |
469
+ | **`readingMode`** | 仅画布 | | 无右栏 | |
470
+
471
+ - **`readOnly`**:禁止对图的增删改(组件库拖入、连线、删除键、拖动节点、选中浮动条、`ref.removeNode` / `patchNodeData` 等),仍可使用平移/缩放查看,**可点选节点/边** 在右侧只读查看配置。**`readOnly`** 与 **`readingMode`** 会出现在 **`renderConfigPanel` / `renderNodeConfig`** 的入参中。
472
+ - **`readingMode`**:**仅展示中间流程画布**(隐藏顶栏与左右栏),并**禁用选中**与一切改图,适合文档内嵌、大屏预览等纯展示场景(隐含只读)。根节点会附加 **`wechen-agent-editor--reading`**。
473
+
474
+ ### 删除守卫(`onBeforeRemoveNode` / `onBeforeRemoveEdge`)— **@deprecated**
475
+
476
+ > **新集成请用** 下文 **[图操作事件(`onFlowOperation`)](#图操作事件onflowoperation)**。本 API 仅覆盖删除;与 `onFlowOperation` 同时传入时 **事件优先**。迁移见 [docs/CAPABILITY-MAP.md](docs/CAPABILITY-MAP.md)。
477
+
478
+ 在可编辑模式下,可通过回调 **按节点/边粒度** 决定是否允许删除。同步返回 **`false`** 时取消该次删除(**Backspace** 与 **`ref.removeNode` / `ref.removeEdge`** 均生效)。**`readOnly` / `readingMode`** 下不调用回调。删除节点时 **级联移除** 的关联边 **不会** 逐条触发 `onBeforeRemoveEdge`。
479
+
480
+ ```tsx
481
+ <WechenAgentEditor
482
+ onBeforeRemoveNode={(node) => {
483
+ // 例:禁止删除入口节点,或 data.deletable === false 的节点
484
+ if (node.id === "start_node") return false;
485
+ if (node.data?.deletable === false) return false;
486
+ return true;
487
+ }}
488
+ onBeforeRemoveEdge={(edge) => edge.data?.locked !== true}
489
+ />
490
+ ```
491
+
492
+ L1 工厂同样支持:`buildWechenAgentEditorProps({ onBeforeRemoveNode: ... })`。
493
+
494
+ ### 图操作事件(`onFlowOperation`)
495
+
496
+ 在可编辑模式下,**删/复制/新增/连线/patch/路径** 等改图操作在应用前会派发 **`WechenFlowOperationEvent`**。宿主在 **`onFlowOperation`** 中按 **`event.kind`** 与 **`event.node` / `event.edge`** 同步返回 **`false`** 即可取消(键盘、浮动条、ref 一致)。**`onFlowOperation` 对 `node.remove` / `edge.remove` 优先于** legacy 删除回调。**`readOnly` / `readingMode`** 下不调用。
497
+
498
+ **完整 `kind` / `source` 说明表、载荷字段与反模式 → [docs/FLOW-OPERATION-REF.md](./docs/FLOW-OPERATION-REF.md)**(SSOT)。登记类型:`WechenFlowOperationKind`(`src/flow/flowOperationEvents.ts` `WECHEN_FLOW_OPERATION_KINDS`)。
499
+
500
+ 拒绝 **`node.remove`** 时 **原子保留** 节点及因该次删除会连带移除的边(级联边 **不** 再派发 `edge.remove`);显式删边仍走 **`edge.remove`**。
501
+
502
+ | `kind` | 含义(摘要) |
503
+ |--------|----------------|
504
+ | `node.remove` | 删节点(键盘 / 浮动条 / ref) |
505
+ | `node.duplicate` | 复制节点 |
506
+ | `node.add` | 新增节点(拖库 / ref) |
507
+ | `node.patch` | 补丁 `data`(配置区)或壳层 `position`/`type` 等(ref) |
508
+ | `node.move` | 拖节点结束落位(拒绝则回滚坐标) |
509
+ | `edge.remove` | 显式删边 |
510
+ | `edge.add` | 新建连线 |
511
+ | `edge.reconnect` | 拖拽改端点 |
512
+ | `edge.patch` | 补丁边 |
513
+ | `edge.path.reset` | 单条边恢复自动路径 |
514
+ | `edge.path.insertVertex` / `edge.path.removeVertex` | 折线路径插/删拐点 |
515
+
516
+ ```tsx
517
+ <WechenAgentEditor
518
+ onFlowOperation={(event) => {
519
+ if (event.node?.type === "pill") {
520
+ if (event.kind === "node.remove" || event.kind === "node.duplicate") {
521
+ return false;
522
+ }
523
+ }
524
+ return true;
525
+ }}
526
+ />
527
+ ```
528
+
529
+ Playground:`?tab=flow-operation-policy`。L1:`buildWechenAgentEditorProps({ onFlowOperation: ... })`。
530
+
531
+ ### 顶底感应区连线(默认,上进下出)
532
+
533
+ 内置 **`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`**、底边扇出 **共干** 路径配合。
534
+
535
+ 周界四向感应的 **完整方案说明(交互、角区、数据字段、代码映射)** 见仓库 **`openspec/changes/node-perimeter-sensing-scheme/design.md`**。
536
+
537
+ ### 组件库 legacy 接线桩(`flowHandles`,可选)
538
+
539
+ **已弃用优先**:编排默认以 **四边感应区** 为准。**`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`**。
540
+
541
+ **`items[].defaultData`(可选)**:拖入或 **`ref.addNodeFromLibrary`** 时 **浅合并** 进 **`node.data`**(在工厂基础字段之后;**`libraryGroupKey` / `libraryItemKey` 始终由包写入**,不会被 `defaultData` 覆盖)。嵌套对象整键替换(与 **`patchNode` 深度合并** 不同)。**`flowHandles` 仅来自 `item.flowHandles`**,写在 `defaultData` 内的 `flowHandles` 会被忽略。内置 **`mainAgent`** 仍有包内默认(如 `baseModel`),可用 **`defaultData` 覆盖同名键**。
542
+
543
+ ```tsx
544
+ const libraryGroups = [
545
+ {
546
+ key: "MAIN",
547
+ title: "Agent",
548
+ items: [
549
+ {
550
+ key: "planner",
551
+ title: "Planner",
552
+ type: "MAIN",
553
+ defaultData: { strategy: "Plan-Execute" },
554
+ },
555
+ { key: "supervisor", title: "Supervisor", type: "MAIN" },
556
+ ],
557
+ },
558
+ {
559
+ key: "SUB",
560
+ title: "应用Agent",
561
+ items: [
562
+ {
563
+ key: "alert",
564
+ title: "告警分析",
565
+ type: "SUB",
566
+ defaultData: { skill: "alert-analysis" },
567
+ },
568
+ ],
569
+ },
570
+ ];
571
+ ```
572
+
573
+ 非内置 **`type`** 使用与 **`card`** 相同的通用节点外壳与四边感应区;缺 **`item.type`** 时拖放会提示且不会创建节点。宿主可在加载配置后调用 **`validateComponentLibraryGroups(libraryGroups)`** 预检缺 `type` 的条目。
574
+
575
+ ### 组件库条目 UI(`renderComponentLibraryItem`)
576
+
577
+ 左侧列表 **默认无「+」按钮**,添节点主路径为 **拖拽** 到画布。若需行尾点击添加,在条目上设 **`showAddButton: true`**(调用与拖放相同的 `addNodeToCanvas`)。
578
+
579
+ 宿主可通过 **`renderComponentLibraryItem`** 自定义 **每一行** 的呈现与 trailing 操作(如「详情」「订阅」),回调 **`ctx`** 含 **`editorRef`**、**`addNodeToCanvas`**、**`defaultContent`**。返回 **`undefined`** 使用内置行。**外层 drag 由基座托管**,自定义 UI **不会** 破坏拖放。
580
+
581
+ ```tsx
582
+ import { Button, List } from "antd";
583
+
584
+ <WechenAgentEditor
585
+ libraryGroups={groups}
586
+ renderComponentLibraryItem={(ctx) => (
587
+ <List.Item
588
+ actions={[
589
+ <Button
590
+ key="detail"
591
+ type="link"
592
+ size="small"
593
+ onClick={(e) => {
594
+ e.stopPropagation();
595
+ openAgentDetail(ctx.item.key);
596
+ }}
597
+ >
598
+ 详情
599
+ </Button>,
600
+ ]}
601
+ >
602
+ {ctx.defaultContent}
603
+ </List.Item>
604
+ )}
605
+ />
606
+ ```
607
+
608
+ 自定义按钮 **应** 在 `click` / `pointerdown` 上 **`stopPropagation`**,避免误触拖拽。
609
+
610
+ ### 组件库分组折叠(`libraryGroupCollapse`)
611
+
612
+ 多分组场景下可 **opt-in** 启用分组折叠:传 **`libraryGroupCollapse={true}`** 后,分组标题变为可点击按钮(chevron + 条目数);**未传** 时保持现网平铺标题,无行为变化。
613
+
614
+ - 默认 **全部展开**;可用 **`defaultCollapsedGroupKeys`** 指定初始收起的 `group.key`
615
+ - **搜索** 时含匹配项的分组 **强制展开**;清空搜索后恢复用户折叠状态
616
+ - **`persist`**(默认 `true`)将折叠状态写入 **sessionStorage**
617
+ - **ref**:`getLibraryGroupCollapseEnabled()` / `setLibraryGroupCollapseEnabled(true|false)` 可运行时开/关;`setLibraryGroupExpanded` / `toggleLibraryGroup` 控制单组。调试面板 **概览 Tab** 提供开关。
618
+
619
+ ```tsx
620
+ <WechenAgentEditor
621
+ libraryGroups={groups}
622
+ libraryGroupCollapse={{
623
+ defaultCollapsedGroupKeys: ["app"],
624
+ persist: true,
625
+ }}
626
+ />
627
+ ```
628
+
629
+ **`renderFlowNode(props, context)`**:**`props.type`** = 落盘 **`item.type`**(**不是** `item.key`);**`context`** **`selected`**、可选 **`libraryGroupKey`** / **`libraryItemKey`**、**`editorRef`**(编辑器内部命令式 API;**不依赖** 宿主是否写 `<WechenAgentEditor ref={...} />`,在点击等事件内读 `current`)。宿主侧自动化/保存等 **仍建议** 挂 `ref`。四边感应与接线桩仍由包渲染,**勿** 在返回值内放置 **`Handle`**。类型见 **`WechenAgentRenderFlowNodeContext`**、**`resolveLibraryItemNodeType`**、**`findComponentLibraryItem`** 等导出。
630
+
631
+ ### Monaco Worker
632
+
633
+ 必须在**首次渲染编辑器之前**完成 Monaco worker 引导,否则代码编辑区无法初始化。可参考本仓库 **`playground/src/monacoBootstrap.ts`** 与 **`playground/src/main.tsx`**,并按 [Monaco](https://github.com/microsoft/monaco-editor) / [@monaco-editor/react](https://github.com/suren-atoyan/monaco-react) 文档适配 Vite 或 Webpack。
634
+
635
+ **Umi 4 / Webpack 宿主**(常见 `worker-javascript.js` 返回 HTML):见 **[docs/UMI-MONACO-WORKER.md](docs/UMI-MONACO-WORKER.md)**(入口 bootstrap、MFSU `exclude`、`monaco-editor-webpack-plugin` 备选)。
636
+
637
+ ### 与快速开始一致的最小页(可选 `title`)
638
+
639
+ 与上文 **L0** 相同;若需要顶栏文案可传 **`title`**。未传 **`initialFlow`** 时为**空画布**。需要内置示例图时,从本包导入 **`defaultInitialFlow`**、**`cloneWechenAgentFlow`** 并传入 `initialFlow={cloneWechenAgentFlow(defaultInitialFlow)}`。
640
+
641
+ ### 行为与扩展(摘要)
642
+
643
+ - **连线**:从节点的 source Handle 拖到 target Handle;选中边可在右侧调整动画与**执行方向**等。**执行方向**与 **`source` → `target`** 一致:箭头在 **目标** 端;选「反向」会 **交换源与目标**(及 handle),不再依赖仅反转 dash 的持久化字段。历史 **`edge.data.wechenFlowDirection: 'reverse'`** 在 **`normalizeWechenAgentFlowJson` / `cloneWechenAgentFlow` / `parseWechenAgentFlowJson`** 中 **幂等归一** 为交换端点并清除该键。宿主也可调用 **`swapWechenFlowEdgeEndpoints(edge)`** 得到与内置面板一致的 **`patchEdge`** 补丁。
644
+ - **数据合并**:节点 `data` 的更新遵循导出的 **`mergeNodeData`**(对象深度合并,数组在补丁中出现时整段替换)。详见下文 **[`patchNode` 与 `patchNodeData`](#patchnode-与-patchnodedata)**。
645
+ - **自定义右侧面板**:可选 **`renderConfigPanel`**(节点与连线统一入口)或 **`renderNodeConfig`**;同时存在时以 `renderConfigPanel` 为准(返回 `undefined` 时回退内置)。
646
+
647
+ ### `patchNodeData` 与配置区 `ctx.patchNode`
648
+
649
+ 二者语义一致:把 **`patch` 深度合并进 `node.data`**(内部为 **`mergeNodeData`**)。区别仅在于 **是否由调用方传入节点 id**(右栏已选中节点时,id 由编辑器绑定)。
650
+
651
+ | API | 签名 | 使用场景 |
652
+ |-----|------|----------|
653
+ | **`ctx.patchNode`** | `(patch) => void` | **`renderConfigPanel`** 选中节点时;**勿传 `node.id`**,当前节点已固定 |
654
+ | **`ctx.editorRef`** | `RefObject<WechenAgentEditorRef \| null>` | **`renderConfigPanel`** 节点/边共有;内部 API,**无需** 宿主挂 `ref`,在事件内读 `current` |
655
+ | **`ref.patchNodeData`** | `(nodeId, patch) => void` | 命令式改 **`node.data`**(按钮、自动化、跨节点批量) |
656
+
657
+ **`patch` 的顶层键 = `node.data` 的键**,不是整颗 React Flow `Node`(不含 `id` / `type` / `position` / `measured` 等)。
658
+
659
+ ### ref:节点坐标与壳层(`patchNode` / `setNodePosition`)
660
+
661
+ | API | 作用 |
662
+ |-----|------|
663
+ | **`ref.getNode(id)`** / **`ref.getEdge(id)`** | id 读取当前实体(浅拷贝) |
664
+ | **`ref.setNodePosition(id, { x, y })`** | 只改 **`node.position`**(等同 `patchNode(id, { position })`) |
665
+ | **`ref.patchNode(id, patch)`** | 改 **`position` / `type` / `width` / `height`**;**勿** 用来写 `data` |
666
+ | **`ref.setTitle(title)`** | 只改标题,不替换整图 |
667
+
668
+ **对称读写示例**(无需 `replaceFlow` 整图替换坐标):
669
+
670
+ ```tsx
671
+ const flow = editorRef.current?.getFlow();
672
+ const n = flow?.nodes.find((x) => x.id === "my-node");
673
+ if (n) {
674
+ editorRef.current?.setNodePosition("my-node", { x: n.position.x + 80, y: n.position.y });
675
+ }
676
+ ```
677
+
678
+ 程序化改坐标会经 **`onFlowOperation`** 的 **`node.patch`**(`source: ref`);与 **`patchNodeData`** 的 data 补丁区分。
679
+
680
+ ### ref 实例 vs 包导出(常见误找)
681
+
682
+ 下列能力 **不在** `editorRef.current` 上,需从包 **直接 import**(完整对照见 [docs/EDITOR-REF-API-MATRIX.md](docs/EDITOR-REF-API-MATRIX.md)):
683
+
684
+ | 需求 | 用法 |
685
+ |------|------|
686
+ | 后端 DTO 导出/导入 | `exportBackendWorkflowJson` / `importBackendWorkflowToFlow` → 再 `ref.replaceFlow` |
687
+ | 图合法性校验 | `validateWechenAgentFlow(ref.getFlow())` |
688
+ | 克隆/解析 JSON | `cloneWechenAgentFlow` / `parseWechenAgentFlowJson` |
689
+ | 边端点交换补丁 | `swapWechenFlowEdgeEndpoints` `ref.patchEdge` |
690
+ | 画布 **重连** | `ref.reconnectEdge(edgeId, connection)`(同 UI `onReconnect`) |
691
+ | 拖动 **门控** | `onFlowOperation` **`node.move`**(拖放结束;拒绝则回滚坐标) |
692
+ | 聚焦节点 | `ref.centerOnNode(nodeId)` |
693
+ | 校验当前图 | `ref.validateFlow()` |
694
+
695
+ ```
696
+ node = { id, type, position, data: { label, config, … } }
697
+
698
+ │ mergeNodeData(node.data, patch)
699
+ patch = { label: "x" } ──────┘ → node.data.label === "x"
700
+ ```
701
+
702
+ #### 推荐写法
703
+
704
+ **1. 改单个字段(最常用)**
705
+
706
+ ```tsx
707
+ // renderConfigPanel
708
+ if (ctx.kind === "node") {
709
+ ctx.patchNode({ label: "新名称" });
710
+ }
711
+
712
+ // ref
713
+ editorRef.current?.patchNodeData("node-1", { label: "新名称" });
714
+ ```
715
+
716
+ **2. 一次保存多个 `data` 顶层键**
717
+
718
+ ```tsx
719
+ ctx.patchNode({
720
+ label: "主入口",
721
+ config: { maxSteps: 8 },
722
+ });
723
+ ```
724
+
725
+ **3. 嵌套对象(深度合并,未出现在 patch 中的兄弟键保留)**
726
+
727
+ ```tsx
728
+ // 已有 node.data.config = { a: 1, b: 2 }
729
+ ctx.patchNode({ config: { a: 3 } });
730
+ // node.data.config === { a: 3, b: 2 }
731
+ ```
732
+
733
+ **4. antd `Form` 即时写回(`renderConfigPanel` 需自建 `Form`;`renderNodeConfig` 由外层包一层 `Form`)**
734
+
735
+ ```tsx
736
+ if (ctx.kind === "node" && ctx.node.type === "MAIN") {
737
+ const disabled = ctx.readOnly || ctx.readingMode;
738
+ return (
739
+ <Form
740
+ key={ctx.node.id}
741
+ layout="vertical"
742
+ initialValues={ctx.node.data}
743
+ disabled={disabled}
744
+ onValuesChange={(_, all) => ctx.patchNode(all)}
745
+ >
746
+ <Form.Item name="label" label="节点名称">
747
+ <Input />
748
+ </Form.Item>
749
+ </Form>
750
+ );
751
+ }
752
+ ```
753
+
754
+ - **`initialValues` 用 `node.data`**,不要用整颗 `node`。
755
+ - 切换选中节点时用 **`key={node.id}`** `form.setFieldsValue`,避免 `initialValues` 只在首次挂载生效。
756
+ - 只读:`disabled={ctx.readOnly || ctx.readingMode}`(上下文 **无** `ctx.disabled` 字段)。
757
+
758
+ **5. 受控输入 + 按钮「应用」(防抖 / 显式保存)**
759
+
760
+ ```tsx
761
+ const [draft, setDraft] = useState(String(ctx.node.data?.label ?? ""));
762
+ useEffect(() => {
763
+ setDraft(String(ctx.node.data?.label ?? ""));
764
+ }, [ctx.node.id, ctx.node.data]);
765
+
766
+ <Button onClick={() => ctx.patchNode({ label: draft })}>应用</Button>
767
+ ```
768
+
769
+ Playground 中 `card` 节点示例见 `playground/src/playgroundPanels.tsx`。
770
+
771
+ #### 常见错误(会导致不生效或 `data` 被污染)
772
+
773
+ | 错误写法 | 实际效果 |
774
+ |----------|----------|
775
+ | `patchNode(node.id, { label: "x" })` | 只接收 **一个** 参数;`node.id` 字符串被当作 `patch` merge 进 `data`,出现 `"0":"m"` 等下标键 |
776
+ | `patchNode(node.data)` | 闭包里的 **旧** `node.data` 盖回表单新值,表现为回弹或不更新 |
777
+ | `patchNode({ data: { label: "x" } })` | 写入 **`node.data.data.label`**,除非业务里真有名为 `data` 的嵌套对象 |
778
+ | `patchNode(allValues)` 且 `initialValues={node}` | 把 `id` / `position` 等 merge 进 `data`,结构错乱 |
779
+ | `patchNodeData(id, node)` | 应传 **`node.data` 上的补丁**,不是整颗 `Node` |
780
+
781
+ **对比:想改 `node.data.label`**
782
+
783
+ ```tsx
784
+ ctx.patchNode({ label: "x" }); // ✅
785
+ ctx.patchNodeData(id, { label: "x" }); // ✅ ref
786
+
787
+ ctx.patchNode({ data: { label: "x" } }); // data.data.label
788
+ ctx.patchNode(node.id, { label: "x" }); // 多传了 id
789
+ ```
790
+
791
+ #### 数组与其它规则
792
+
793
+ - **数组**:`patch` 里若包含某数组键,则 **整段替换**该数组(不按下标合并)。
794
+ - **`undefined` 键**:`mergeNodeData` **跳过** `undefined`,不会用其删除已有字段;要清空请显式传 `null` 或业务约定值。
795
+ - **边**:**`ctx.patchEdge`** / **`ref.patchEdge`** 为边对象 **顶层浅合并**(与节点 `data` 规则不同),见 `WechenAgentConfigPanelEdgeContext`。
796
+
797
+ ### `setConfigUnsavedGuard` 与延迟保存
798
+
799
+ 在 **`renderConfigPanel`** 中若采用 **编辑草稿 + 按钮保存**(不每次 `onValuesChange` 调用 `patchNode`),可调用 **`ctx.setConfigUnsavedGuard(true/false)`**:
800
+
801
+ - **`true`**:在 **顶栏、组件库、画布** 上显示半透明蒙层(**不挡** 右侧配置区),点击蒙层提示「当前有未保存的修改…」(`LOCALE_KEYS.CONFIG_UNSAVED_GUARD_HINT`,可 `localeMessages` 覆盖)。
802
+ - **`false`**:关闭蒙层;保存成功后应显式调用。
803
+ - **选中变化**(切换节点/边、点击画布空白)时编辑器会 **自动** 置 `false`。
804
+ - **`readingMode`** 下为 no-op。
805
+
806
+ ```tsx
807
+ const [draft, setDraft] = useState(() => String(ctx.node.data?.label ?? ""));
808
+
809
+ useEffect(() => {
810
+ setDraft(String(ctx.node.data?.label ?? ""));
811
+ ctx.setConfigUnsavedGuard(false);
812
+ }, [ctx.node.id]);
813
+
814
+ <Input
815
+ value={draft}
816
+ onChange={(e) => {
817
+ setDraft(e.target.value);
818
+ ctx.setConfigUnsavedGuard(true);
819
+ }}
820
+ />
821
+ <Button
822
+ onClick={() => {
823
+ ctx.patchNode({ label: draft });
824
+ ctx.setConfigUnsavedGuard(false);
825
+ }}
826
+ >
827
+ 保存
828
+ </Button>
829
+ ```
830
+
831
+ Playground **`renderConfigPanel`** Tab 中 `card` / 边配置演示了该流程。
832
+
833
+ ### 新建节点/连线后默认选中
834
+
835
+ 在可编辑模式下,以下操作成功后会 **自动选中** 新建对象,右侧配置区立即展示对应面板:
836
+
837
+ - **组件库拖入** 节点;
838
+ - 画布 **拖拽连线**(`onConnect`);
839
+ - ref **`addNodeFromLibrary`** / **`addNode`** / **`connect`**(或 `addEdge`)。
840
+
841
+ 拖入新节点后,宿主宜在 `useEffect` 依赖 **`node.id`** 上重置表单草稿并 `setConfigUnsavedGuard(false)`(见上节)。
842
+
843
+ - **顶栏左侧**:可选 **`renderHeaderLeading`**,入参含当前解析标题 **`title`**(与 **`ref.getTitle()`** 一致)及 **`editorRef`**;未传时显示默认 `Title`。改标题数据仍用 **`title` prop** 或 **`replaceFlow`**。
844
+ - **顶栏右侧**:可选 **`renderHeaderActions`**,入参含与默认行为一致的 **`save`** / **`testRun`** **`editorRef`**。左右可 **同时** 自定义。Playground:`?tab=header-leading`、`?tab=header-actions`。
845
+
846
+ 完整类型与符号列表以 **`src/index.ts`** 的导出为准。
847
+
848
+ ### 集成自检
849
+
850
+ 在宿主项目根目录建议逐项确认:
851
+
852
+ | 检查项 | 说明 |
853
+ |--------|------|
854
+ | Peer 依赖 | **`npm ls react`**、**`npm ls @xyflow/react`**、**`npm ls dagre`** 各仅一棵有效树、无重复主版本 |
855
+ | 构建 | **`npm run build`**(或 Umi `max build`)通过 |
856
+ | Monaco | 已在首屏前完成 **worker 引导**(见上文「Monaco Worker」) |
857
+ | 空画布 | **未传 `initialFlow`** 时中间画布无预置节点;业务示例需 **`cloneWechenAgentFlow(defaultInitialFlow)`** 或 `ref.replaceFlow(...)` 显式注入 |
858
+ | 烟测 | 拖入节点、连线、`readOnly` / `readingMode`、保存或 `getSnapshot`;边路径添加拐点/拖顶点 |
859
+ | ref 增图 | `ref.addNodeFromLibrary` / `ref.connect` 与拖放、连线行为一致 |
860
+ | DTO 往返 | `exportBackendWorkflowJson` `importBackendWorkflowToFlow` → `replaceFlow`;`condition`/`api`/`script` 经 `data.backendNodeType` |
861
+ | 图操作门控 | **`onFlowOperation`**:拒 `node.remove` 时节点与级联边均保留(Playground `?tab=flow-operation-policy`) |
862
+ | ref 坐标回写 | **`getFlow` 读 position** → **`setNodePosition` 写 position**(`?tab=ref-api`) |
863
+ | 图校验 | `validateWechenAgentFlow`;可选 `onBeforeSave` 拦截 |
864
+
865
+ ---
866
+
867
+ ## 本地开发(本仓库)
868
+
869
+ **包管理器**:本仓库使用 **pnpm workspace**(根 + `playground/`)。需 **Node 20+**;推荐 `corepack enable` 后由 `packageManager` 字段自动对齐 pnpm 版本。
870
+
871
+ ```bash
872
+ pnpm install
873
+ pnpm run dev # Playground 文档站(分 Tab 演示;ref 全表见 ?tab=ref-api,见 playground/README.md)
874
+ pnpm run dev:minimal # 仅最小宿主集成(对照 README「快速开始」)
875
+ pnpm run baseline # 工程基线:测试数、最大 LOC、dist 体积(见 docs/ENGINEERING-BASELINE.md)
876
+ pnpm test # Vitest(含 flowOperation / deleteGuard 表驱动与 editorGateSmoke)
877
+ pnpm run typecheck
878
+ pnpm run check:docs # 文档覆盖 WARN(onFlowOperation kind ↔ README)
879
+ pnpm run release:prep # 发版门禁:test + build + pack:check + playground build + baseline
880
+ pnpm run verify:change # 可选:test + typecheck + audit:white-screen(见 docs/ENGINEERING-BASELINE.md)
881
+ ```
882
+
883
+ **门控冒烟**(无 DOM):`editorGateSmoke.test.ts`、`readonlyModeGuards.test.ts` — 图操作门控与只读改图拒绝;已包含在 `pnpm test` / `release:prep`。
884
+
885
+ 浏览器地址以终端输出为准(默认 `http://localhost:5173`)。文档站默认 **`?tab=overview`**。
886
+
887
+ ---
888
+
889
+ ## 维护者:发版
890
+
891
+ ### 技术债与路线图
892
+
893
+ 全项目优缺点与 **P0–P3** 优化优先级见 **[docs/PROJECT-AUDIT-2026.md](docs/PROJECT-AUDIT-2026.md)**(壳层拆分见 [EDITOR-SPLIT-PLAN.md](docs/EDITOR-SPLIT-PLAN.md),性能见 [PERFORMANCE-BUDGET.md](docs/PERFORMANCE-BUDGET.md))。OpenSpec 制品:`openspec/changes/project-holistic-audit-and-optimization/`。
894
+
895
+ ### 版本摘要
896
+
897
+ | 版本 | 宿主需注意 |
898
+ |------|------------|
899
+ | **1.3.6** | 企业化文档(架构/私有化/性能预算);`autoLayout({ only })`;`FLOW-OPERATION-REF`;manual 路径清除修复;**peer 同 1.3.2** |
900
+ | **1.3.2** | **`onFlowOperation`** 统一图操作门控;拒 `node.remove` 时级联边原子保留;legacy 边/只读/配置区白屏加固;**peer 同 1.3.0** |
901
+ | **1.3.0** | 自由折线路径编辑、选中浮动条、路径 ref;**peer 要求同 1.2.3** |
902
+ | **1.2.3** | **Breaking(集成)**:`@xyflow/react`、`dagre` 改为 peer,须宿主显式安装 |
903
+ | **1.1.x** | `buildWechenAgentEditorProps`、后端 DTO、删除守卫等 |
904
+
905
+ 完整变更见 **`CHANGELOG.md`**。
906
+
907
+ ### 发版清单(当前版本见 `package.json`)
908
+
909
+ 1. **版本号**:确认根目录 `package.json` **`version`** 与 CHANGELOG 最新条目一致(npm 以该字段为准;当前 **1.3.6**)。
910
+ 2. **依赖与锁文件**:`pnpm install`(workspace 一次安装根与 `playground/`)。
911
+ 3. **门禁**(推荐一条命令):
912
+
913
+ ```bash
914
+ pnpm run release:prep
915
+ ```
916
+
917
+ 等价分步:`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`。
918
+
919
+ 日志写入 **`.release-check.log`**(`release:prep` 脚本)。
920
+
921
+ 4. **登录 registry**:`npm whoami`(`registry.npmjs.org`);国内镜像发版用 **`npm run release:cn`**(勿与 npmjs 混用同一 token 流程 unless intentional)。
922
+ 5. **发布**:
923
+
924
+ ```bash
925
+ pnpm run release
926
+ # 或:pnpm publish --access public --registry=https://registry.npmjs.org/
927
+ ```
928
+
929
+ 6. **发后验证**:
930
+
931
+ ```bash
932
+ npm view wechen-agent-editor-v1 version
933
+ npm pack --dry-run # tarball 应仅含 dist/、LICENSE(见 package.json files)
934
+ ```
935
+
936
+ 7. **空目录烟测**(将 `<version>` 换为 `package.json` 中的版本):
937
+
938
+ ```bash
939
+ mkdir /tmp/wechen-smoke && cd /tmp/wechen-smoke
940
+ npm init -y
941
+ npm install wechen-agent-editor-v1@<version> react react-dom antd @xyflow/react dagre monaco-editor @monaco-editor/react
942
+ node -e "import('wechen-agent-editor-v1').then(m => console.log('ok', typeof m.WechenAgentEditor))"
943
+ ```
944
+
945
+ 8. **Git 标签(建议)**:`git tag v1.3.6 && git push origin v1.3.6`(版本号与 `package.json` 一致,按团队规范)。
946
+
947
+ 发版前可将 `repository` / `bugs` / `homepage` 中的占位 URL 换成真实仓库地址(私有仓库可仅在内部文档记录,见「集成排错」说明)。