# 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 与数据正确性。