| 1 | # Agent Store |
| 2 | |
| 3 | 全局 AI Agent 任务状态管理模块。 |
| 4 | |
| 5 | ## 目录结构 |
| 6 | |
| 7 | ```text |
| 8 | src/store/agent/ |
| 9 | ├── index.ts # 主入口,组装 store 并导出 |
| 10 | ├── agent.types.ts # 类型定义 |
| 11 | ├── agent.constants.ts # 常量定义 |
| 12 | ├── agent.state.ts # 初始状态 |
| 13 | ├── agent.methods.ts # 核心方法 |
| 14 | ├── handlers/ # 处理器目录 |
| 15 | │ ├── index.ts # 处理器导出 |
| 16 | │ └── action.handlers.ts # Action 处理器 |
| 17 | ├── utils/ # 工具函数目录 |
| 18 | │ ├── index.ts # 工具导出 |
| 19 | │ ├── refs.ts # Refs 管理 |
| 20 | │ ├── message.ts # 消息工具 |
| 21 | │ └── progress.ts # 进度工具 |
| 22 | ├── task-instance/ # 任务实例目录 |
| 23 | │ ├── index.ts # 模块导出 |
| 24 | │ ├── task-instance.types.ts # 类型定义 |
| 25 | │ ├── TaskInstance.ts # 核心类(代理层) |
| 26 | │ ├── message.handler.ts # 消息处理逻辑 |
| 27 | │ ├── workflow.handler.ts # 工作流处理逻辑 |
| 28 | │ └── sse.handler.ts # SSE 消息处理逻辑 |
| 29 | └── README.md # 本文档 |
| 30 | ``` |
| 31 | |
| 32 | ## 模块说明 |
| 33 | |
| 34 | ### index.ts - 主入口 |
| 35 | |
| 36 | 组装 store 并导出所有模块: |
| 37 | |
| 38 | ```typescript |
| 39 | import { useAgentStore } from '@/store/agent' |
| 40 | |
| 41 | const { isGenerating, messages, createTask, stopTask } = useAgentStore() |
| 42 | ``` |
| 43 | |
| 44 | ### handlers/ - 处理器目录 |
| 45 | |
| 46 | #### Action 处理器 (action.handlers.ts) |
| 47 | |
| 48 | 使用策略模式处理任务结果: |
| 49 | |
| 50 | ```typescript |
| 51 | import { ActionRegistry } from '@/store/agent' |
| 52 | |
| 53 | // 注册自定义 Action |
| 54 | ActionRegistry.register({ |
| 55 | type: 'customAction', |
| 56 | canHandle: (taskData) => taskData.action === 'customAction', |
| 57 | execute: async (taskData, context) => { |
| 58 | // 处理逻辑 |
| 59 | }, |
| 60 | }) |
| 61 | ``` |
| 62 | |
| 63 | 内置 Action: |
| 64 | |
| 65 | - `navigateToPublish` - 导航到发布页面 |
| 66 | - `navigateToDraft` - 导航到草稿箱 |
| 67 | - `saveDraft` - 保存草稿 |
| 68 | - `updateChannel` - 更新频道授权 |
| 69 | - `loginChannel` - 登录频道 |
| 70 | |
| 71 | ### utils/ - 工具目录 |
| 72 | |
| 73 | #### refs.ts - Refs 管理 |
| 74 | |
| 75 | 管理内部引用变量,避免闭包问题: |
| 76 | |
| 77 | ```typescript |
| 78 | import { createAgentRefs, resetAgentRefs } from '@/store/agent' |
| 79 | |
| 80 | const refs = createAgentRefs() |
| 81 | resetAgentRefs(refs) // 重置 refs |
| 82 | ``` |
| 83 | |
| 84 | #### message.ts - 消息工具 |
| 85 | |
| 86 | 消息创建和状态管理: |
| 87 | |
| 88 | ```typescript |
| 89 | import { createMessageUtils } from '@/store/agent' |
| 90 | |
| 91 | const messageUtils = createMessageUtils({ refs, set, get }) |
| 92 | const userMsg = messageUtils.createUserMessage('Hello', []) |
| 93 | messageUtils.markMessageDone() |
| 94 | ``` |
| 95 | |
| 96 | #### progress.ts - 进度工具 |
| 97 | |
| 98 | 进度计算和状态配置(内部使用)。 |
| 99 | |
| 100 | ### task-instance/ - 任务实例目录 |
| 101 | |
| 102 | TaskInstance 是解决多任务消息混乱问题的核心架构。每个 Agent 任务对应一个独立的 TaskInstance 实例,确保消息状态完全隔离。 |
| 103 | |
| 104 | #### 模块拆分结构 |
| 105 | |
| 106 | | 文件 | 行数 | 职责 | |
| 107 | | ------------------------ | ---- | ---------------------------------- | |
| 108 | | `task-instance.types.ts` | ~100 | 类型定义(上下文接口、回调接口等) | |
| 109 | | `message.handler.ts` | ~250 | 消息处理(创建、更新、状态管理) | |
| 110 | | `workflow.handler.ts` | ~260 | 工作流处理(步骤管理、工具调用) | |
| 111 | | `sse.handler.ts` | ~290 | SSE 消息分发和处理 | |
| 112 | | `TaskInstance.ts` | ~415 | 核心类(代理层,整合各模块) | |
| 113 | |
| 114 | #### TaskInstance.ts - 核心类 |
| 115 | |
| 116 | **核心设计:** |
| 117 | |
| 118 | - **instanceId**: 实例唯一标识(创建时生成,不可变) |
| 119 | - **taskId**: 任务ID(可从临时ID迁移到真实ID) |
| 120 | - **实例级 refs**: 每个实例有独立的 `currentAssistantMessageId`、`streamingText`、`currentStepWorkflow` 等 |
| 121 | - **SSE 回调绑定**: SSE 回调绑定到具体 TaskInstance,消除多任务切换时的竞态条件 |
| 122 | - **代理模式**: TaskInstance 作为门面,代理到各个 handler 模块 |
| 123 | |
| 124 | #### message.handler.ts - 消息处理 |
| 125 | |
| 126 | 提供消息创建和状态更新功能: |
| 127 | |
| 128 | ```typescript |
| 129 | // 消息创建 |
| 130 | createUserMessage(content, medias?) // 创建用户消息 |
| 131 | createAssistantMessage(ctx) // 创建 AI 回复消息 |
| 132 | |
| 133 | // 消息状态更新 |
| 134 | markMessageDone(ctx) // 标记完成 |
| 135 | markMessageError(ctx, error) // 标记错误 |
| 136 | updateMessageContent(ctx, content) // 更新内容 |
| 137 | updateMessageWithActions(ctx, content, actions) // 更新带 actions |
| 138 | ``` |
| 139 | |
| 140 | #### workflow.handler.ts - 工作流处理 |
| 141 | |
| 142 | 处理工作流步骤和工具调用: |
| 143 | |
| 144 | ```typescript |
| 145 | startNewStep(ctx) // 开始新步骤 |
| 146 | addWorkflowStep(ctx, step) // 添加工作流步骤 |
| 147 | updateLastWorkflowStep(ctx, updater) // 更新最后一步 |
| 148 | handleToolCallComplete(ctx, name, input) // 处理工具调用完成 |
| 149 | handleToolResult(ctx, resultText) // 处理工具结果 |
| 150 | ``` |
| 151 | |
| 152 | #### sse.handler.ts - SSE 消息处理 |
| 153 | |
| 154 | 处理来自服务端的 SSE 事件: |
| 155 | |
| 156 | ```typescript |
| 157 | handleSSEMessage(ctx, msg, callbacks?) // SSE 消息处理主入口 |
| 158 | // 内部处理: stream_event, assistant, user, result, error, done |
| 159 | ``` |
| 160 | |
| 161 | **使用示例:** |
| 162 | |
| 163 | ```typescript |
| 164 | import { TaskInstance, getTaskInstance, getOrCreateTaskInstance } from '@/store/agent' |
| 165 | |
| 166 | // 创建任务实例上下文 |
| 167 | const instanceContext: ITaskInstanceContext = { |
| 168 | syncToStore: (taskId, updater) => updateTaskData(taskId, updater), |
| 169 | getData: (taskId) => getTaskData(taskId), |
| 170 | migrateTaskData: (fromTaskId, toTaskId) => { |
| 171 | /* 迁移数据 */ |
| 172 | }, |
| 173 | setCurrentTaskId: (taskId) => set({ currentTaskId: taskId }), |
| 174 | } |
| 175 | |
| 176 | // 创建新的任务实例 |
| 177 | const instance = new TaskInstance(tempTaskId, instanceContext) |
| 178 | |
| 179 | // 设置翻译函数和 Action 上下文 |
| 180 | instance.setTranslation(t) |
| 181 | instance.setActionContext(actionContext) |
| 182 | |
| 183 | // 通过实例添加消息 |
| 184 | const userMessage = instance.createUserMessage(prompt, medias) |
| 185 | instance.addMessage(userMessage) |
| 186 | |
| 187 | // 通过实例处理 SSE 消息 |
| 188 | instance.handleSSEMessage(sseMessage, callbacks) |
| 189 | |
| 190 | // 获取或创建任务实例 |
| 191 | const instance = getOrCreateTaskInstance(taskId, context) |
| 192 | |
| 193 | // 获取现有实例 |
| 194 | const existing = getTaskInstance(taskId) |
| 195 | ``` |
| 196 | |
| 197 | **核心 API:** |
| 198 | |
| 199 | | 方法 | 说明 | |
| 200 | | --------------------------------------- | -------------------------------- | |
| 201 | | `createUserMessage(content, medias?)` | 创建用户消息 | |
| 202 | | `createAssistantMessage()` | 创建 AI 回复消息 | |
| 203 | | `addMessage(message)` | 添加消息到当前任务 | |
| 204 | | `handleSSEMessage(message, callbacks?)` | 处理 SSE 消息 | |
| 205 | | `markMessageDone()` | 标记当前消息完成 | |
| 206 | | `markMessageError(error)` | 标记当前消息错误 | |
| 207 | | `setIsGenerating(value)` | 设置生成状态 | |
| 208 | | `setProgress(value)` | 设置进度 | |
| 209 | | `migrateToRealTaskId(newTaskId)` | 更新任务ID(临时ID迁移到真实ID) | |
| 210 | | `resetForNewRound()` | 重置实例状态(新一轮对话) | |
| 211 | | `abort()` | 中止 SSE 连接 | |
| 212 | |
| 213 | ## 使用示例 |
| 214 | |
| 215 | ### 基础使用 |
| 216 | |
| 217 | ```tsx |
| 218 | import { useAgentStore } from '@/store/agent' |
| 219 | import { useShallow } from 'zustand/react/shallow' |
| 220 | |
| 221 | function ChatPage() { |
| 222 | const { messages, isGenerating, createTask, setActionContext } = useAgentStore( |
| 223 | useShallow((state) => ({ |
| 224 | messages: state.messages, |
| 225 | isGenerating: state.isGenerating, |
| 226 | createTask: state.createTask, |
| 227 | setActionContext: state.setActionContext, |
| 228 | })) |
| 229 | ) |
| 230 | |
| 231 | const router = useRouter() |
| 232 | const { t } = useTranslation() |
| 233 | |
| 234 | // 设置 action 上下文 |
| 235 | useEffect(() => { |
| 236 | setActionContext({ router, lng: 'zh-CN', t }) |
| 237 | }, [router, t]) |
| 238 | |
| 239 | const handleSend = async (prompt: string) => { |
| 240 | await createTask({ prompt, t }) |
| 241 | } |
| 242 | |
| 243 | return <div>{/* ... */}</div> |
| 244 | } |
| 245 | ``` |
| 246 | |
| 247 | ### 继续对话 |
| 248 | |
| 249 | ```tsx |
| 250 | const { continueTask } = useAgentStore() |
| 251 | |
| 252 | const handleContinue = async (prompt: string, taskId: string) => { |
| 253 | await continueTask({ prompt, taskId, t }) |
| 254 | } |
| 255 | ``` |
| 256 | |
| 257 | ## 扩展指南 |
| 258 | |
| 259 | ### 添加 Action 处理器 |
| 260 | |
| 261 | 1. 在 `handlers/action.handlers.ts` 中添加: |
| 262 | |
| 263 | ```typescript |
| 264 | const customActionHandler: IActionHandler = { |
| 265 | type: 'customAction', |
| 266 | canHandle: (taskData) => taskData.action === 'customAction', |
| 267 | execute: async (taskData, context) => { |
| 268 | // 处理逻辑 |
| 269 | }, |
| 270 | } |
| 271 | ``` |
| 272 | |
| 273 | 2. 添加到处理器数组或运行时注册: |
| 274 | |
| 275 | ```typescript |
| 276 | ActionRegistry.register(customActionHandler) |
| 277 | ``` |
| 278 | |
| 279 | ### 添加工具函数 |
| 280 | |
| 281 | 在 `utils/` 目录下创建新文件,并在 `utils/index.ts` 中导出。 |
| 282 | |
| 283 | ## 注意事项 |
| 284 | |
| 285 | 1. **消息 ID 匹配**:消息更新使用 ID 精确匹配,避免更新错误消息。 |
| 286 | 2. **Refs vs State**:频繁更新的数据使用 ref,避免闭包问题。 |
| 287 | 3. **Action 上下文**:使用 action 前需要调用 `setActionContext`。 |
| 288 | 4. **插件发布**:小红书/抖音需要浏览器插件支持。 |
| 289 | |
| 290 | ## ⚠️ 重要:避免 `this` 上下文丢失问题 |
| 291 | |
| 292 | ### 问题描述 |
| 293 | |
| 294 | 在使用工厂函数(如 `createStoreMethods`)返回对象字面量时,如果方法内部使用 `this` 来调用其他方法,当这些方法被作为回调函数传递时,`this` 的上下文会丢失。 |
| 295 | |
| 296 | ### 错误示例 |
| 297 | |
| 298 | ```typescript |
| 299 | // ❌ 错误:在对象字面量中使用 this |
| 300 | function createMethods() { |
| 301 | return { |
| 302 | methodA() { |
| 303 | console.log('A') |
| 304 | }, |
| 305 | methodB() { |
| 306 | // 当 methodB 作为回调传递时,this 会丢失 |
| 307 | this.methodA() // TypeError: Cannot read properties of undefined |
| 308 | }, |
| 309 | } |
| 310 | } |
| 311 | |
| 312 | // 使用场景(会出错) |
| 313 | const methods = createMethods() |
| 314 | someAsyncFunction((data) => { |
| 315 | methods.methodB() // 正常工作 |
| 316 | }) |
| 317 | |
| 318 | // 或者 |
| 319 | someAsyncFunction(methods.methodB) // this 丢失! |
| 320 | ``` |
| 321 | |
| 322 | ### 正确做法 |
| 323 | |
| 324 | 将方法定义为独立的闭包函数,然后在返回对象中引用它们: |
| 325 | |
| 326 | ```typescript |
| 327 | // ✅ 正确:使用闭包引用 |
| 328 | function createMethods() { |
| 329 | // 先定义为独立函数 |
| 330 | function methodA() { |
| 331 | console.log('A') |
| 332 | } |
| 333 | |
| 334 | function methodB() { |
| 335 | // 直接调用闭包中的函数,不依赖 this |
| 336 | methodA() |
| 337 | } |
| 338 | |
| 339 | // 返回方法对象 |
| 340 | return { |
| 341 | methodA, |
| 342 | methodB, |
| 343 | } |
| 344 | } |
| 345 | ``` |
| 346 | |
| 347 | ### 检查清单 |
| 348 | |
| 349 | 编写新方法时,请检查: |
| 350 | |
| 351 | 1. 方法是否会作为回调函数传递? |
| 352 | 2. 方法内部是否调用了同一对象的其他方法? |
| 353 | 3. 如果是,是否使用了 `this`? |
| 354 | |
| 355 | 如果满足以上条件,请使用闭包模式而非 `this`。 |
| 356 |