feat(agent-mui): add agent canvas, nodes, and related components

docs(agent-hooks-guide): add comprehensive guide for agent hooks usage and implementation
This commit is contained in:
2025-11-07 17:49:44 +08:00
parent b610ee0a8f
commit ed6e0ab282
24 changed files with 528 additions and 166 deletions

308
docs/agent-hooks-guide.md Normal file
View File

@@ -0,0 +1,308 @@
# Agent Hooks GUI 学习与复现指南
本指南逐一梳理 `src/pages/agent/hooks` 目录下的所有 Agent 相关 Hooks帮助你理解画布、抽屉、运行与日志的全链路逻辑并可据此复现功能。文档涵盖职责、导出、参数与返回、关键逻辑、典型用法及注意事项。
> 约定:`useGraphStore` 为画布状态中心(维护 `nodes`/`edges` 及更新方法);`@xyflow/react` 用于节点/边渲染与交互;`Operator`、`NodeHandleId`、`NodeMap` 定义节点类型、句柄及渲染映射。
---
## 图数据获取与构建
### use-fetch-data.ts
- 作用:页面挂载时获取 Agent 详情并注入画布。
- 导出:`useFetchDataOnMount()``{ loading, flowDetail }`
- 关键逻辑:
- 读取 `useFetchAgent()``data.dsl.graph`,通过 `useSetGraphInfo()` 写入 `nodes/edges`
- 首次挂载 `refetch()` 刷新详情。
- 用法:在页面 `useEffect` 中调用以初始化画布。
- 注意:`data.dsl.graph` 可能为空,已做空处理。
### use-set-graph.ts
- 作用:将后端返回的 `IGraph` 写入画布。
- 导出:`useSetGraphInfo()``setGraphInfo({ nodes = [], edges = [] }: IGraph)`
- 关键逻辑:有数据才更新,避免清空现有状态。
- 关联:与 `useFetchDataOnMount` 搭配使用。
### use-build-dsl.ts
- 作用:根据当前 `nodes/edges` 构建可保存的 DSL 数据对象,并过滤占位符节点(`Operator.Placeholder`)。
- 导出:`useBuildDslData()``{ buildDslData(currentNodes?) }`
- 关键逻辑:
- 过滤占位符节点与相关边。
- `buildDslComponentsByGraph(filteredNodes, filteredEdges, data.dsl.components)` 生成组件列表。
- 返回 `{ ...data.dsl, graph, components }`
- 用法:保存前构建 DSL导出 JSON 时使用。
### use-export-json.ts
- 作用:导出当前画布图为 JSON 文件。
- 导出:`useHandleExportJsonFile()``{ handleExportJson }`
- 关键逻辑:`downloadJsonFile(buildDslData().graph, \
\`\${data.title}.json\`)`
### use-save-graph.ts
- 作用:保存画布到后端;在打开调试时确保最新 DSL。
- 导出:
- `useSaveGraph(showMessage?: boolean)` → `{ saveGraph(currentNodes?), loading }`
- `useSaveGraphBeforeOpeningDebugDrawer(show)` → `{ handleRun(nextNodes?), loading }`
- `useWatchAgentChange(chatDrawerVisible)` → 自动 debounce 保存,并回显更新时间字符串。
- 关键逻辑:
- 基于路由 `id` + `data.title` + `buildDslData` 组装 `dsl` 调用 `useSetAgent`。
- 打开调试前先 `saveGraph()`,再 `resetAgent()` 清空旧消息。
- `useDebounceEffect`:每 20 秒在节点/边变化时自动保存(聊天抽屉打开时不保存)。
---
## 节点创建与拖拽
### use-add-node.ts
- 作用核心节点添加逻辑含初始参数、坐标计算、特殊类型处理Agent/Tool/Iteration/Note
- 导出:
- `useInitializeOperatorParams()` → `{ initializeOperatorParams, initialFormValuesMap }`
- `useGetNodeName()` → 根据 `Operator` 返回国际化默认名称。
- `useCalculateNewlyChildPosition()` → 计算新增子节点位置避免覆盖。
- `useAddNode(reactFlowInstance?)` → `{ addCanvasNode, addNoteNode }`
- 内部逻辑:`useAddChildEdge()`、`useAddToolNode()`、`useResizeIterationNode()`。
- 关键逻辑:
- 初始化各 `Operator` 的 `form` 默认值Agent/Extractor 等会注入 `llm_id`、默认 prompts。
- 迭代节点 `Operator.Iteration` 自动创建 `Operator.IterationStart` 子节点并设为 `extent='parent'`。
- Agent 底部子 Agent 的水平分布通过已有子节点 `x` 轴最大值计算避免重叠Tool 节点仅允许一个(检查是否已有 `NodeHandleId.Tool` 连接)。
- 通过 `reactFlowInstance.screenToFlowPosition` 将点击位置转换为画布坐标;右侧新增子节点时自动用 `NodeHandleId` 连线。
- 容器内新增子节点时可能触发父容器宽度调整以容纳子节点。
- 典型用法:
```ts
const { addCanvasNode } = useAddNode(reactFlowInstance);
// 在菜单点击或连接拖拽结束时:
addCanvasNode(Operator.Agent, { nodeId, position: Position.Right, id: NodeHandleId.Start })(event);
```
- 注意:
- `Operator.Placeholder` 节点 `draggable=false`。
- Tool 节点唯一性;容器内节点可能触发 resize。
### use-connection-drag.ts
- 作用:连接拖拽起止处理;在拖拽终止位置弹出节点选择下拉,创建占位符节点并连线。
- 导出:`useConnectionDrag(reactFlowInstance, onConnect, showModal, hideModal, setDropdownPosition, setCreatedPlaceholderRef, calculateDropdownPosition, removePlaceholderNode, clearActiveDropdown, checkAndRemoveExistingPlaceholder)` → `{ nodeId, onConnectStart, onConnectEnd, handleConnect, getConnectionStartContext, shouldPreventClose, onMove }`
- 关键逻辑:
- `onConnectStart` 记录起点节点/句柄及鼠标起始位置,区分点击/拖拽(<5px 移动视为点击)。
- `onConnectEnd` 计算下拉面板位置;点击则清理状态并关闭下拉;拖拽时先移除旧占位符,再创建新占位符并连线。
- `onMove` 在画布滚动/缩放时隐藏下拉并清理占位符。
### use-dropdown-position.ts
- 作用:屏幕与 Flow 坐标互转,计算下拉菜单位置使其对齐占位节点。
- 导出:`useDropdownPosition(reactFlowInstance)` → `{ calculateDropdownPosition, getPlaceholderNodePosition, flowToScreenPosition, screenToFlowPosition }`
- 关键逻辑:按常量 `HALF_PLACEHOLDER_NODE_WIDTH`、`DROPDOWN_HORIZONTAL_OFFSET`、`DROPDOWN_VERTICAL_OFFSET` 计算偏移。
### use-placeholder-manager.ts
- 作用:占位符节点的创建、删除与状态追踪;替换为真实节点后自动建立连接并同步位置。
- 导出:`usePlaceholderManager(reactFlowInstance)` → `{ removePlaceholderNode, onNodeCreated, setCreatedPlaceholderRef, resetUserSelectedFlag, checkAndRemoveExistingPlaceholder, createdPlaceholderRef, userSelectedNodeRef }`
- 关键逻辑:
- 保证面板上仅有一个占位符。
- `onNodeCreated(newNodeId)`:真实节点位置与占位符对齐;按占位符的起点连线;删除占位符及相关边。
- 使用 `reactFlowInstance.deleteElements` 执行批量删除。
### use-before-delete.tsx
- 作用:拦截删除操作,保护 `Operator.Begin`、容器首节点(`Operator.IterationStart` 非成对删除时阻止)及下游 Agent/Tool 的联动删除。
- 导出:`useBeforeDelete()` → `{ handleBeforeDelete }`
- 关键逻辑:
- `UndeletableNodes`Begin 与 IterationStart 的特殊保护。
- 当包含 Agent 节点时,额外删除其下游 Agent/Tool节点与边
- 用法:作为 React Flow 的 `onBeforeDelete` 回调。
### use-change-node-name.ts
- 作用:处理节点名称与 Tool 名称变更,避免重复命名并同步到画布。
- 导出:`useHandleNodeNameChange({ id, data })` → `{ name, handleNameBlur, handleNameChange }`
- 关键逻辑:
- Tool 名称变更写入父 Agent 的 `tools` 字段,通过 `updateNodeForm(agentId, nextTools, ['tools'])`。
- 普通节点名通过 `updateNodeName(id, name)` 更新;同一画布内不可重名,重复则提示 `message.error('The name cannot be repeated')`。
### use-agent-tool-initial-values.ts
- 作用:为 Agent Tool非画布节点生成初始参数裁剪/重组)以适配对话调用。
- 导出:`useAgentToolInitialValues()` → `{ initializeAgentToolValues(operatorName) }`
- 关键逻辑:对不同 `Operator` 精简参数,如去除 `query/sql/stock_code`,或仅保留 `smtp_*` 邮件字段等。
### use-form-values.ts
- 作用:表单初始值合并,优先使用节点已有 `data.form`,否则回退为 `defaultValues`。
- 导出:`useFormValues(defaultValues, node?)`
### use-watch-form-change.ts
- 作用:监听 `react-hook-form` 表单变化并同步到画布节点 `form`。
- 导出:`useWatchFormChange(id?, form?)`
- 关键逻辑:手动获取 `form.getValues()`,再 `updateNodeForm(id, values)`。
### use-move-note.ts
- 作用便签Note悬浮预览位置跟随鼠标。
- 导出:`useMoveNote()` → `{ ref, showImage, hideImage, mouse, imgVisible }`
- 关键逻辑:使用 `useMouse()` 监听坐标,更新 `ref.current.style.top/left`。
---
## 运行与日志
### use-run-dataflow.ts
- 作用保存画布后触发数据流运行SSE打开日志抽屉并设置当前 `messageId`。
- 导出:`useRunDataflow({ showLogSheet, setMessageId })` → `{ run(fileResponseData), loading, uploadedFileData }`
- 关键逻辑:
- 先 `saveGraph()`,再 `send({ id, query: '', session_id: null, files: [file] })` 到 `api.runCanvas`。
- 校验响应成功后设置 `uploadedFileData/file` 与 `messageId`。
### use-cancel-dataflow.ts
- 作用:取消当前数据流,并停止日志拉取。
- 导出:`useCancelCurrentDataflow({ messageId, stopFetchTrace })` → `{ handleCancel }`
- 关键逻辑:`cancelDataflow(messageId)` 返回 `code===0` 时调用 `stopFetchTrace()`。
### use-fetch-pipeline-log.ts
- 作用拉取运行日志trace判断是否完成与是否为空控制轮询。
- 导出:`useFetchPipelineLog(logSheetVisible)` → `{ logs, isLogEmpty, isCompleted, loading, isParsing, messageId, setMessageId, stopFetchTrace }`
- 关键逻辑:
- `END` 且 `trace[0].message` 非空视为完成,随后 `stopFetchTrace()`;抽屉打开时重置 `isStopFetchTrace=false` 继续拉取。
### use-download-output.ts
- 作用:从日志中提取 `END` 节点输出并下载 JSON。
- 导出:`findEndOutput(list)`, `isEndOutputEmpty(list)`, `useDownloadOutput(data?)` → `{ handleDownloadJson }`
---
## 抽屉与面板显示
### use-show-drawer.tsx
- 作用:统一管理“运行/聊天抽屉”、“单节点调试抽屉”、“表单抽屉”、“日志抽屉”的显示逻辑。
- 导出:
- `useShowFormDrawer()` → `{ formDrawerVisible, hideFormDrawer, showFormDrawer(e, nodeId), clickedNode }`
- `useShowSingleDebugDrawer()` → `{ singleDebugDrawerVisible, hideSingleDebugDrawer, showSingleDebugDrawer }`
- `useShowDrawer({ drawerVisible, hideDrawer })` → `{ chatVisible, runVisible, onPaneClick, singleDebugDrawerVisible, showSingleDebugDrawer, hideSingleDebugDrawer, formDrawerVisible, showFormDrawer, clickedNode, onNodeClick, hideFormDrawer, hideRunOrChatDrawer, showChatModal }`
- `useShowLogSheet({ setCurrentMessageId })` → `{ logSheetVisible, hideLogSheet, showLogSheet(messageId) }`
- `useHideFormSheetOnNodeDeletion({ hideFormDrawer })` → 在节点被删除时自动关闭表单抽屉。
- 关键逻辑:
- 当 `drawerVisible=true`:若 Begin 有输入则显示“运行”抽屉;否则显示“聊天”抽屉。
- 点击节点打开表单抽屉(排除 Note/Placeholder/File点击节点上的“播放”图标打开单节点调试抽屉。
- `hideRunOrChatDrawer` 同时关闭运行/聊天抽屉与外层抽屉。
---
## Begin 相关选项与变量
### use-get-begin-query.tsx
- 作用:围绕 Begin 节点数据构建输入、输出、变量与组件引用选项。
- 导出:
- `useSelectBeginNodeDataInputs()` → Begin 的输入 `BeginQuery[]`
- `useIsTaskMode(isTask?)` → 返回是否任务模式(从 Begin 的 `form.mode` 或传入)。
- `useGetBeginNodeDataQuery()` → 读取 Begin 的 `inputs` 并转换为列表。
- `useBuildNodeOutputOptions(nodeId?)` → 输出引用选项(带 `OperatorIcon`)。
- `useBuildVariableOptions(nodeId?, parentId?)` → Begin 变量与节点输出选项合并。
- `useBuildComponentIdOptions(nodeId?, parentId?)` → 组件引用(排除某些节点,容器内仅引用同层或外部节点)。
- `useBuildComponentIdAndBeginOptions(nodeId?, parentId?)` → Begin + 组件引用。
- `useGetComponentLabelByValue(nodeId)` / `useGetVariableLabelByValue(nodeId)` → 根据值反查标签。
- 注意:容器内节点引用受限:子节点只能引用同容器同层节点或外部节点,不能引用父节点。
### use-build-options.tsx
- 作用:轻量版,仅构建节点输出选项。
- 导出:`useBuildNodeOutputOptions(nodeId?)`
### use-is-pipeline.ts
- 作用:读取路由查询判断是否显示数据流画布(`AgentQuery.Category === DataflowCanvas`)。
- 导出:`useIsPipeline()`
---
## 聊天与共享
### use-chat-logic.ts
- 作用:对话中处理“等待用户填写”的表单组件逻辑,组装提交时的 `beginInputs`。
- 导出:`useAwaitCompentData({ derivedMessages, sendFormMessage, canvasId })` → `{ getInputs, buildInputList, handleOk, isWaitting }`
- 关键逻辑:将消息中的 `data.inputs` 与用户填写值合并后发送。
### use-cache-chat-log.ts
- 作用:聊天事件按 `message_id` 分片缓存,支持过滤与清空。
- 导出:`useCacheChatLog()` → `{ eventList, currentEventListWithoutMessage, currentEventListWithoutMessageById, setEventList, clearEventList, addEventList, filterEventListByEventType, filterEventListByMessageId, setCurrentMessageId, currentMessageId }`
- 关键逻辑:排除 `Message/MessageEnd` 事件,保留节点事件日志用于“运行日志”展示。
### use-send-shared-message.ts
- 作用:分享页的消息发送逻辑(可任务模式),包含参数弹窗与自动触发任务。
- 导出:
- `useSendButtonDisabled(value)` → 空字符串时禁用。
- `useGetSharedChatSearchParams()` → 从 URL 解析 `from/shared_id/locale` 等。
- `useSendNextSharedMessage(addEventList)` → `{ sendMessage, hasError, parameterDialogVisible, inputsData, isTaskMode, hideParameterDialog, showParameterDialog, ok }`
- 关键逻辑:
- `url` 动态拼接:`agentbots` 或 `chatbots`。
- 任务模式且 `inputs` 为空时自动触发一次空参数运行。
---
## 外部资源与工具
### use-find-mcp-by-id.ts
- 作用:在 MCP 服务列表中按 ID 查找服务器对象。
- 导出:`useFindMcpById()` → `{ findMcpById(id) }`
### use-open-document.ts
- 作用打开在线文档Agent 组件说明)。
- 导出:`useOpenDocument()` → `openDocument()``window.open` 到 `https://ragflow.io/docs/dev/category/agent-components`)。
### use-show-dialog.ts
- 作用:系统 API Key 管理与分享预览。
- 导出:
- `useOperateApiKey(idKey, dialogId?)` → `{ removeToken, createToken, tokenList, creatingLoading, listLoading }`
- `usePreviewChat(idKey)` → `{ handlePreview }`
- `useSelectChartStatsList()` → 将统计数据转换为图表用的 `{ xAxis, yAxis }[]`。
- 关键逻辑:创建/删除系统 token打开分享页预览 URL根据 `idKey` 判断 Agent 或 Chat
---
## 布局与其他
### use-calculate-sheet-right.ts
- 作用:根据 `body` 宽度返回抽屉的右侧定位类名。
- 导出:`useCalculateSheetRight()` → `'right-[620px]' | 'right-1/3'`
### use-iteration.ts
- 作用:当前文件为空,预留迭代相关的后续逻辑。
- 导出:暂无(空文件)。
---
## 复现建议与流程
- 画布初始化:
- 页面挂载时调用 `useFetchDataOnMount()`,保证 `nodes/edges` 与后端一致。
- 如需区分是否数据流画布,调用 `useIsPipeline()` 控制界面模式。
- 节点与连接交互:
- 连接拖拽中,使用 `useConnectionDrag` 的 `onConnectStart/onConnectEnd/onMove` 管理占位符与下拉。
- 选择节点类型后,配合 `usePlaceholderManager.onNodeCreated(newNodeId)` 落地真实节点并连线。
- 添加节点通过 `useAddNode.addCanvasNode(type,{ nodeId, position, id })(event)`,自动计算位置与连线。
- 表单与抽屉:
- 点击节点打开 `useShowFormDrawer.showFormDrawer(e, nodeId)`;点击“播放”打开 `useShowSingleDebugDrawer`。
- 外层抽屉控制 `useShowDrawer`;运行/聊天抽屉根据 Begin 输入状态切换。
- 表单变更用 `useWatchFormChange(id, form)` 将 `react-hook-form` 值同步到 `data.form`。
- 运行与日志:
- 上传文件后,调用 `useRunDataflow.run(fileResponseData)`;在日志抽屉中用 `useFetchPipelineLog(logSheetVisible)` 拉取状态。
- 取消运行用 `useCancelCurrentDataflow.handleCancel()`;下载输出用 `useDownloadOutput.handleDownloadJson()`。
- 保存与导出:
- 使用 `useSaveGraphBeforeOpeningDebugDrawer` 在每次调试前保存并重置状态。
- 自动保存:`useWatchAgentChange(chatDrawerVisible)` 在无聊天抽屉时 debounce 落库。
- 导出 JSON`useHandleExportJsonFile.handleExportJson()`。
---
## 易踩坑与注意事项
- 占位符节点必须在保存 DSL 时过滤,否则会污染组件图(`useBuildDslData` 已处理)。
- Tool 节点只允许有一个;`useAddNode` 中对 Tool 的添加有唯一性校验。
- 容器类Iteration中的引用受限子节点只能引用同容器同层节点或外部节点不能引用父节点`useBuildComponentIdOptions`)。
- 名称唯一性:画布内节点与 Agent 下的 Tool name 均需唯一(`useChangeNodeName`)。
- 聊天日志拉取需在 END 且 trace 有 message 时停止,否则会持续轮询(`useFetchPipelineLog`)。
- 运行/聊天抽屉切换逻辑依赖 Begin 输入是否存在(`useShowDrawer`)。
---
## 关联常量与工具(理解 hooks 需要)
- `Operator`、`NodeHandleId`、`NodeMap`:定义节点类型和画布渲染类型。
- `generateNodeNamesWithIncreasingIndex`:为新增节点生成不重复的默认名称。
- `getNodeDragHandle(type)`:为不同节点类型定义可拖拽句柄范围。
- `buildNodeOutputOptions`、`buildBeginInputListFromObject`、`buildBeginQueryWithObject`:构建下拉选项与输入/变量结构。
---
如需按模块拆分为多页或增加“最小复现代码片段”,可进一步细化。也可以优先从“连接拖拽 + 占位符 + 节点添加”链路开始,逐步验证 UI 与数据正确性。