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