@lobehub/chat 1.90.3 → 1.91.0

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 (106) hide show
  1. package/.cursor/rules/backend-architecture.mdc +12 -9
  2. package/.cursor/rules/cursor-ux-optimize.mdc +1 -1
  3. package/.cursor/rules/define-database-model.mdc +1 -1
  4. package/.cursor/rules/drizzle-schema-style-guide.mdc +1 -1
  5. package/.cursor/rules/i18n/i18n.mdc +1 -1
  6. package/.cursor/rules/project-introduce.mdc +2 -1
  7. package/.cursor/rules/system-role.mdc +42 -0
  8. package/.cursor/rules/zustand-action-patterns.mdc +318 -0
  9. package/.cursor/rules/zustand-slice-organization.mdc +300 -0
  10. package/CHANGELOG.md +66 -0
  11. package/README.md +2 -2
  12. package/README.zh-CN.md +2 -2
  13. package/changelog/v1.json +24 -0
  14. package/docs/self-hosting/advanced/model-list.mdx +1 -1
  15. package/docs/self-hosting/advanced/model-list.zh-CN.mdx +1 -1
  16. package/docs/self-hosting/environment-variables/model-provider.mdx +2 -2
  17. package/docs/self-hosting/environment-variables/model-provider.zh-CN.mdx +2 -2
  18. package/package.json +44 -44
  19. package/packages/web-crawler/src/crawImpl/exa.ts +93 -0
  20. package/packages/web-crawler/src/crawImpl/firecrawl.ts +97 -0
  21. package/packages/web-crawler/src/crawImpl/index.ts +6 -0
  22. package/packages/web-crawler/src/crawImpl/tavily.ts +94 -0
  23. package/src/config/aiModels/modelscope.ts +3 -3
  24. package/src/config/modelProviders/modelscope.ts +3 -3
  25. package/src/database/client/migrations.json +10 -0
  26. package/src/database/migrations/0023_remove_param_and_doubao.sql +6 -0
  27. package/src/database/migrations/meta/0023_snapshot.json +5340 -0
  28. package/src/database/migrations/meta/_journal.json +7 -0
  29. package/src/features/PluginsUI/Render/utils/iframeOnReady.test.ts +1 -1
  30. package/src/features/PluginsUI/Render/utils/pluginSettings.test.ts +1 -1
  31. package/src/features/PluginsUI/Render/utils/pluginState.test.ts +1 -1
  32. package/src/libs/model-runtime/BaseAI.ts +3 -3
  33. package/src/libs/model-runtime/ModelRuntime.ts +2 -2
  34. package/src/libs/model-runtime/UniformRuntime/index.ts +2 -2
  35. package/src/libs/model-runtime/ai21/index.ts +2 -2
  36. package/src/libs/model-runtime/ai360/index.ts +2 -2
  37. package/src/libs/model-runtime/anthropic/index.ts +15 -11
  38. package/src/libs/model-runtime/azureOpenai/index.ts +2 -2
  39. package/src/libs/model-runtime/azureai/index.ts +4 -4
  40. package/src/libs/model-runtime/baichuan/index.ts +2 -2
  41. package/src/libs/model-runtime/bedrock/index.ts +4 -4
  42. package/src/libs/model-runtime/cloudflare/index.ts +2 -2
  43. package/src/libs/model-runtime/cohere/index.ts +2 -2
  44. package/src/libs/model-runtime/deepseek/index.ts +2 -2
  45. package/src/libs/model-runtime/fireworksai/index.ts +2 -2
  46. package/src/libs/model-runtime/giteeai/index.ts +2 -2
  47. package/src/libs/model-runtime/github/index.ts +2 -2
  48. package/src/libs/model-runtime/google/index.ts +7 -5
  49. package/src/libs/model-runtime/groq/index.ts +2 -2
  50. package/src/libs/model-runtime/higress/index.ts +2 -2
  51. package/src/libs/model-runtime/huggingface/index.ts +2 -2
  52. package/src/libs/model-runtime/hunyuan/index.ts +2 -2
  53. package/src/libs/model-runtime/index.ts +1 -1
  54. package/src/libs/model-runtime/infiniai/index.ts +2 -2
  55. package/src/libs/model-runtime/internlm/index.ts +7 -9
  56. package/src/libs/model-runtime/jina/index.ts +2 -2
  57. package/src/libs/model-runtime/lmstudio/index.ts +2 -2
  58. package/src/libs/model-runtime/minimax/index.ts +2 -2
  59. package/src/libs/model-runtime/mistral/index.ts +2 -2
  60. package/src/libs/model-runtime/modelscope/index.ts +2 -3
  61. package/src/libs/model-runtime/moonshot/index.ts +2 -2
  62. package/src/libs/model-runtime/novita/index.ts +2 -2
  63. package/src/libs/model-runtime/nvidia/index.ts +2 -2
  64. package/src/libs/model-runtime/ollama/index.ts +2 -2
  65. package/src/libs/model-runtime/openai/index.ts +3 -3
  66. package/src/libs/model-runtime/openrouter/index.ts +2 -2
  67. package/src/libs/model-runtime/perplexity/index.ts +2 -2
  68. package/src/libs/model-runtime/ppio/index.ts +2 -2
  69. package/src/libs/model-runtime/qiniu/index.ts +2 -2
  70. package/src/libs/model-runtime/qwen/index.ts +2 -2
  71. package/src/libs/model-runtime/sambanova/index.ts +2 -2
  72. package/src/libs/model-runtime/search1api/index.ts +2 -2
  73. package/src/libs/model-runtime/sensenova/index.ts +2 -2
  74. package/src/libs/model-runtime/siliconcloud/index.ts +2 -2
  75. package/src/libs/model-runtime/spark/index.ts +15 -13
  76. package/src/libs/model-runtime/stepfun/index.ts +2 -2
  77. package/src/libs/model-runtime/taichu/index.ts +2 -2
  78. package/src/libs/model-runtime/tencentcloud/index.ts +2 -2
  79. package/src/libs/model-runtime/togetherai/index.ts +2 -2
  80. package/src/libs/model-runtime/types/chat.ts +1 -1
  81. package/src/libs/model-runtime/upstage/index.ts +2 -2
  82. package/src/libs/model-runtime/utils/openaiCompatibleFactory/index.test.ts +7 -7
  83. package/src/libs/model-runtime/utils/openaiCompatibleFactory/index.ts +3 -3
  84. package/src/libs/model-runtime/vllm/index.ts +2 -2
  85. package/src/libs/model-runtime/volcengine/index.ts +2 -2
  86. package/src/libs/model-runtime/wenxin/index.ts +2 -2
  87. package/src/libs/model-runtime/xai/index.ts +6 -3
  88. package/src/libs/model-runtime/xinference/index.ts +2 -2
  89. package/src/libs/model-runtime/zeroone/index.ts +2 -2
  90. package/src/libs/model-runtime/zhipu/index.ts +2 -2
  91. package/src/middleware.ts +3 -1
  92. package/src/server/routers/tools/search.test.ts +2 -4
  93. package/src/server/services/search/impls/bocha/index.ts +124 -0
  94. package/src/server/services/search/impls/bocha/type.ts +47 -0
  95. package/src/server/services/search/impls/exa/index.ts +129 -0
  96. package/src/server/services/search/impls/exa/type.ts +39 -0
  97. package/src/server/services/search/impls/firecrawl/index.ts +128 -0
  98. package/src/server/services/search/impls/firecrawl/type.ts +35 -0
  99. package/src/server/services/search/impls/index.ts +31 -0
  100. package/src/server/services/search/impls/jina/index.ts +109 -0
  101. package/src/server/services/search/impls/jina/type.ts +26 -0
  102. package/src/server/services/search/impls/tavily/index.ts +124 -0
  103. package/src/server/services/search/impls/tavily/type.ts +36 -0
  104. package/src/store/chat/slices/aiChat/actions/__tests__/generateAIChat.test.ts +4 -2
  105. package/src/store/chat/slices/message/action.test.ts +2 -1
  106. package/src/store/chat/slices/topic/action.test.ts +3 -2
@@ -29,37 +29,40 @@ LobeChat 的后端设计注重模块化、可测试性和灵活性,以适应
29
29
  其主要分层如下:
30
30
 
31
31
  1. **客户端服务层 (`src/services`)**:
32
- * 位于 [src/services/](mdc:src/services)。
32
+ * 位于 src/services/。
33
33
  * 这是客户端业务逻辑的核心层,负责封装各种业务操作和数据处理逻辑。
34
34
  * **环境适配**: 根据不同的运行环境,服务层会选择合适的数据访问方式:
35
35
  * **本地数据库模式**: 直接调用 `Model` 层进行数据操作,适用于浏览器 PGLite 和本地 Electron 应用。
36
36
  * **远程数据库模式**: 通过 `tRPC` 客户端调用服务端 API,适用于需要云同步的场景。
37
- * **类型转换**: 对于简单的数据类型转换,直接在此层进行类型断言,如 `this.pluginModel.query() as Promise<LobeTool[]>`(参见 [src/services/plugin/client.ts](mdc:src/services/plugin/client.ts))。
38
- * 每个服务模块通常包含 `client.ts`(本地模式)、`server.ts`(远程模式)和 `type.ts`(接口定义)文件。
37
+ * **类型转换**: 对于简单的数据类型转换,直接在此层进行类型断言,如 `this.pluginModel.query() as Promise<LobeTool[]>`
38
+ * 每个服务模块通常包含 `client.ts`(本地模式)、`server.ts`(远程模式)和 `type.ts`(接口定义)文件,在实现时应该确保本地模式和远程模式业务逻辑实现一致,只是数据库不同。
39
39
 
40
40
  2. **API 接口层 (`TRPC`)**:
41
- * 位于 [src/server/routers/](mdc:src/server/routers)。
41
+ * 位于 src/server/routers/
42
42
  * 使用 `tRPC` 构建类型安全的 API。Router 根据运行时环境(如 Edge Functions, Node.js Lambda)进行组织。
43
43
  * 负责接收客户端请求,并将其路由到相应的 `Service` 层进行处理。
44
+ * 新建 lambda 端点时可以参考 src/server/routers/lambda/_template.ts
44
45
 
45
46
  3. **服务端服务层 (`server/services`)**:
46
- * 位于 [src/server/services/](mdc:src/server/services)。
47
+ * 位于 src/server/services/。
47
48
  * 核心职责是封装独立的、可复用的业务逻辑单元。这些服务应易于测试。
48
- * **平台差异抽象**: 一个关键特性是通过其内部的 `impls` 子目录(例如 [src/server/services/file/impls/](mdc:src/server/services/file/impls) 包含 `s3.ts``local.ts`)来抹平不同运行环境带来的差异(例如云端使用 S3 存储,桌面版使用本地文件系统)。这使得上层(如 `tRPC` routers)无需关心底层具体实现。
49
+ * **平台差异抽象**: 一个关键特性是通过其内部的 `impls` 子目录(例如 src/server/services/file/impls 包含 s3.ts 和 local.ts)来抹平不同运行环境带来的差异(例如云端使用 S3 存储,桌面版使用本地文件系统)。这使得上层(如 `tRPC` routers)无需关心底层具体实现。
49
50
  * 目标是使 `tRPC` router 层的逻辑尽可能纯粹,专注于请求处理和业务流程编排。
50
51
  * 服务会调用 `Repository` 层或直接调用 `Model` 层进行数据持久化和检索,也可能调用其他服务。
51
52
 
52
53
  4. **仓库层 (`Repositories`)**:
53
- * 位于 [src/database/repositories/](mdc:src/database/repositories)。
54
+ * 位于 src/database/repositories/。
54
55
  * 主要处理**复杂的跨表查询和数据聚合**逻辑,特别是当需要从**多个 `Model`** 获取数据并进行组合时。
55
56
  * 与 `Model` 层不同,`Repository` 层专注于复杂的业务查询场景,而不涉及简单的领域模型转换。
56
57
  * 当业务逻辑涉及多表关联、复杂的数据统计或需要事务处理时,会使用 `Repository` 层。
57
58
  * 如果数据操作简单(仅涉及单个 `Model`),则通常直接在 `src/services` 层调用 `Model` 并进行简单的类型断言。
58
59
 
59
60
  5. **模型层 (`Models`)**:
60
- * 位于 [src/database/models/](mdc:src/database/models) (例如 [src/database/models/plugin.ts](mdc:src/database/models/plugin.ts)[src/database/models/document.ts](mdc:src/database/models/document.ts))
61
- * 提供对数据库中各个表(由 [src/database/schemas/](mdc:src/database/schemas) 中的 Drizzle ORM schema 定义)的基本 CRUD (创建、读取、更新、删除) 操作和简单的查询能力。
61
+ * 位于 src/database/models/ (例如 src/database/models/plugin.ts 和 src/database/models/document.ts)。
62
+ * 提供对数据库中各个表(由 src/database/schemas/ 中的 Drizzle ORM schema 定义)的基本 CRUD (创建、读取、更新、删除) 操作和简单的查询能力。
62
63
  * `Model` 类专注于单个数据表的直接操作,**不涉及复杂的领域模型转换**,这些转换通常在上层的 `src/services` 中通过类型断言完成。
64
+ * model(例如 Topic) 层接口经常需要从对应的 schema 层导入 NewTopic 和 TopicItem
65
+ * 创建新的 model 时可以参考 src/database/models/_template.ts
63
66
 
64
67
  6. **数据库 (`Database`)**:
65
68
  * **客户端模式 (浏览器/PWA)**: 使用 PGLite (基于 WASM 的 PostgreSQL),数据存储在用户浏览器本地。
@@ -24,4 +24,4 @@ Example:
24
24
  | 2 | Bob | User |
25
25
  | 3 | Charlie | Moderator |
26
26
  +----+---------+-----------+
27
- ```
27
+ ```
@@ -5,4 +5,4 @@ alwaysApply: false
5
5
  ---
6
6
  1. first read [lobe-chat-backend-architecture.mdc](mdc:.cursor/rules/lobe-chat-backend-architecture.mdc)
7
7
  2. refer to the [_template.ts](mdc:src/database/models/_template.ts) to create new model
8
- 3. if an operation involves multiple models or complex queries, consider defining it in the `repositories` layer under `src/database/repositories/`
8
+ 3. if an operation involves multiple models or complex queries, consider defining it in the `repositories` layer under `src/database/repositories/`
@@ -199,4 +199,4 @@ slugUserIdUnique: uniqueIndex('slug_user_id_unique').on(t.slug, t.userId),
199
199
  **Future refactor**: Will likely be replaced with `isDefault: boolean()` field
200
200
  **Note**: Avoid using slugs for new features - prefer explicit boolean flags for status tracking
201
201
 
202
- By following these guidelines, maintain consistency, type safety, and maintainability across database schema definitions.
202
+ By following these guidelines, maintain consistency, type safety, and maintainability across database schema definitions.
@@ -180,4 +180,4 @@ export default {
180
180
 
181
181
  - Check if the key exists in src/locales/default/namespace.ts
182
182
  - Ensure proper namespace import in component
183
- - Ensure new namespaces are exported in [src/locales/default/index.ts](mdc:src/locales/default/index.ts)
183
+ - Ensure new namespaces are exported in [src/locales/default/index.ts](mdc:src/locales/default/index.ts)
@@ -34,6 +34,7 @@ The project uses the following technologies:
34
34
  - aHooks for react hooks library
35
35
  - dayjs for date and time library
36
36
  - lodash-es for utility library
37
+ - fast-deep-equal for deep comparison of JavaScript objects
37
38
  - zod for data validation
38
39
  - TRPC for type safe backend
39
40
  - PGLite for client DB and PostgreSQL for backend DB
@@ -43,4 +44,4 @@ The project uses the following technologies:
43
44
  - ESLint for code linting
44
45
  - Cursor AI for code editing and AI coding assistance
45
46
 
46
- Note: All tools and libraries used are the latest versions. The application only needs to be compatible with the latest browsers;
47
+ Note: All tools and libraries used are the latest versions. The application only needs to be compatible with the latest browsers;
@@ -0,0 +1,42 @@
1
+ ---
2
+ description:
3
+ globs:
4
+ alwaysApply: true
5
+ ---
6
+ ## System Role
7
+
8
+ You are an expert in full-stack Web development, proficient in JavaScript, TypeScript, CSS, React, Node.js, Next.js, Postgresql, all kinds of network protocols.
9
+
10
+ You are an expert in LLM and Ai art. In Ai image generation, you are proficient in Stable Diffusion and ComfyUI's architectural principles, workflows, model structures, parameter configurations, training methods, and inference optimization.
11
+
12
+ You are an expert in UI/UX design, proficient in web interaction patterns, responsive design, accessibility, and user behavior optimization. You excel at improving user retention and paid conversion rates through various interaction details.
13
+
14
+
15
+ ## Problem Solving
16
+
17
+ - When modifying existing code, clearly describe the differences and reasons for the changes
18
+ - Provide alternative solutions that may be better overall or superior in specific aspects
19
+ - Always consider using the latest technologies, standards, and APIs to strive for code optimization, not just the conventional wisdom
20
+ - Provide optimization suggestions for deprecated API usage
21
+ - Cite sources whenever possible at the end, not inline
22
+ - When you provide multiple solutions, provide the recommended solution first, and note it as `Recommended`
23
+ - Express uncertainty when there might not be a correct answer
24
+ - Admit when you don't know something instead of guessing
25
+
26
+ ## Code Implementation
27
+
28
+ - Write minimal code changes that are ONLY directly related to the requirements
29
+ - Write correct, up-to-date, bug-free, fully functional, secure, maintainable and efficient code
30
+ - First, think step-by-step: describe your plan in detailed pseudocode before implementation
31
+ - Confirm the plan before writing code
32
+ - Focus on maintainable over being performant
33
+ - Leave NO TODOs, placeholders, or missing pieces
34
+ - Be sure to reference file names
35
+ - Please respect my prettier preferences when you provide code
36
+ - When you notice I have manually modified the code, that was definitely on purpose and do not revert them
37
+ - Don't remove meaningful code comments, be sure to keep original comments when providing applied code
38
+ - Update the code comments when needed after you modify the related code
39
+ - If documentation links or required files are missing, ask for them before proceeding with the task rather than making assumptions
40
+ - If you're unable to access or retrieve content from websites, please inform me immediately and request the specific information needed rather than making assumptions
41
+ - Sometimes ESLint errors may not be reasonable, and making changes could introduce logical bugs. If you find an ESLint rule unreasonable, disable it directly. For example, with the 'prefer-dom-node-text-content' rule, there are actual differences between innerText and textContent
42
+ - You can use emojis, npm packages like `chalk`/`chalk-animation`/`terminal-link`/`gradient-string`/`log-symbols`/`boxen`/`consola`/`@clack/prompts` to create beautiful terminal output
@@ -0,0 +1,318 @@
1
+ ---
2
+ description:
3
+ globs: src/store/**
4
+ alwaysApply: false
5
+ ---
6
+ # LobeChat Zustand Action 组织模式
7
+
8
+ 本文档详细说明了 LobeChat 项目中 Zustand Action 的组织方式、命名规范和实现模式,特别关注乐观更新与后端服务的集成。
9
+
10
+ ## Action 类型分层
11
+
12
+ LobeChat 的 Action 采用分层架构,明确区分不同职责:
13
+
14
+ ### 1. Public Actions
15
+ 对外暴露的主要接口,供 UI 组件调用:
16
+ - 命名:动词形式(`createTopic`, `sendMessage`, `updateTopicTitle`)
17
+ - 职责:参数验证、流程编排、调用 internal actions
18
+ - 示例:[src/store/chat/slices/topic/action.ts](mdc:src/store/chat/slices/topic/action.ts)
19
+
20
+ ```typescript
21
+ // Public Action 示例
22
+ createTopic: async () => {
23
+ const { activeId, internal_createTopic } = get();
24
+ const messages = chatSelectors.activeBaseChats(get());
25
+
26
+ if (messages.length === 0) return;
27
+
28
+ const topicId = await internal_createTopic({
29
+ sessionId: activeId,
30
+ title: t('defaultTitle', { ns: 'topic' }),
31
+ messages: messages.map((m) => m.id),
32
+ });
33
+
34
+ return topicId;
35
+ },
36
+ ```
37
+
38
+ ### 2. Internal Actions (`internal_*`)
39
+ 内部实现细节,处理核心业务逻辑:
40
+ - 命名:`internal_` 前缀 + 动词(`internal_createTopic`, `internal_updateMessageContent`)
41
+ - 职责:乐观更新、服务调用、错误处理、状态同步
42
+ - 不应该被 UI 组件直接调用
43
+
44
+ ```typescript
45
+ // Internal Action 示例 - 乐观更新模式
46
+ internal_createTopic: async (params) => {
47
+ const tmpId = Date.now().toString();
48
+
49
+ // 1. 立即更新前端状态(乐观更新)
50
+ get().internal_dispatchTopic(
51
+ { type: 'addTopic', value: { ...params, id: tmpId } },
52
+ 'internal_createTopic',
53
+ );
54
+ get().internal_updateTopicLoading(tmpId, true);
55
+
56
+ // 2. 调用后端服务
57
+ const topicId = await topicService.createTopic(params);
58
+ get().internal_updateTopicLoading(tmpId, false);
59
+
60
+ // 3. 刷新数据确保一致性
61
+ get().internal_updateTopicLoading(topicId, true);
62
+ await get().refreshTopic();
63
+ get().internal_updateTopicLoading(topicId, false);
64
+
65
+ return topicId;
66
+ },
67
+ ```
68
+
69
+ ### 3. Dispatch Methods (`internal_dispatch*`)
70
+ 专门处理状态更新的方法:
71
+ - 命名:`internal_dispatch` + 实体名(`internal_dispatchTopic`, `internal_dispatchMessage`)
72
+ - 职责:调用 reducer、更新 Zustand store、处理状态对比
73
+
74
+ ```typescript
75
+ // Dispatch Method 示例
76
+ internal_dispatchTopic: (payload, action) => {
77
+ const nextTopics = topicReducer(topicSelectors.currentTopics(get()), payload);
78
+ const nextMap = { ...get().topicMaps, [get().activeId]: nextTopics };
79
+
80
+ if (isEqual(nextMap, get().topicMaps)) return;
81
+
82
+ set({ topicMaps: nextMap }, false, action ?? n(`dispatchTopic/${payload.type}`));
83
+ },
84
+ ```
85
+
86
+ ## 何时使用 Reducer 模式 vs. 简单 `set`
87
+
88
+ ### 使用 Reducer 模式的场景
89
+
90
+ **适用于复杂的数据结构管理**,特别是:
91
+ - 管理对象列表或映射(如 `messagesMap`, `topicMaps`)
92
+ - 需要乐观更新的场景
93
+ - 状态转换逻辑复杂
94
+ - 需要类型安全的 action payload
95
+
96
+ ```typescript
97
+ // Reducer 模式示例 - 复杂消息状态管理
98
+ export const messagesReducer = (state: ChatMessage[], payload: MessageDispatch): ChatMessage[] => {
99
+ switch (payload.type) {
100
+ case 'updateMessage': {
101
+ return produce(state, (draftState) => {
102
+ const index = draftState.findIndex((i) => i.id === payload.id);
103
+ if (index < 0) return;
104
+ draftState[index] = merge(draftState[index], {
105
+ ...payload.value,
106
+ updatedAt: Date.now()
107
+ });
108
+ });
109
+ }
110
+ case 'createMessage': {
111
+ return produce(state, (draftState) => {
112
+ draftState.push({
113
+ ...payload.value,
114
+ id: payload.id,
115
+ createdAt: Date.now(),
116
+ updatedAt: Date.now(),
117
+ meta: {}
118
+ });
119
+ });
120
+ }
121
+ // ...其他复杂状态转换
122
+ }
123
+ };
124
+ ```
125
+
126
+ ### 使用简单 `set` 的场景
127
+
128
+ **适用于简单状态更新**:
129
+ - 切换布尔值
130
+ - 更新简单字符串/数字
131
+ - 设置单一状态字段
132
+
133
+ ```typescript
134
+ // 简单 set 示例
135
+ updateInputMessage: (message) => {
136
+ if (isEqual(message, get().inputMessage)) return;
137
+ set({ inputMessage: message }, false, n('updateInputMessage'));
138
+ },
139
+
140
+ togglePortal: (open?: boolean) => {
141
+ set({ showPortal: open ?? !get().showPortal }, false, 'togglePortal');
142
+ },
143
+ ```
144
+
145
+ ## 乐观更新实现模式
146
+
147
+ 乐观更新是 LobeChat 中的核心模式,用于提供流畅的用户体验:
148
+
149
+ ### 标准乐观更新流程
150
+
151
+ ```typescript
152
+ // 完整的乐观更新示例
153
+ internal_updateMessageContent: async (id, content, extra) => {
154
+ const { internal_dispatchMessage, refreshMessages } = get();
155
+
156
+ // 1. 立即更新前端状态(乐观更新)
157
+ internal_dispatchMessage({
158
+ id,
159
+ type: 'updateMessage',
160
+ value: { content },
161
+ });
162
+
163
+ // 2. 调用后端服务
164
+ await messageService.updateMessage(id, {
165
+ content,
166
+ tools: extra?.toolCalls ? internal_transformToolCalls(extra.toolCalls) : undefined,
167
+ // ...其他字段
168
+ });
169
+
170
+ // 3. 刷新确保数据一致性
171
+ await refreshMessages();
172
+ },
173
+ ```
174
+
175
+ ### 创建操作的乐观更新
176
+
177
+ ```typescript
178
+ internal_createMessage: async (message, context) => {
179
+ const { internal_createTmpMessage, refreshMessages, internal_toggleMessageLoading } = get();
180
+
181
+ let tempId = context?.tempMessageId;
182
+ if (!tempId) {
183
+ // 创建临时消息用于乐观更新
184
+ tempId = internal_createTmpMessage(message);
185
+ internal_toggleMessageLoading(true, tempId);
186
+ }
187
+
188
+ try {
189
+ const id = await messageService.createMessage(message);
190
+ if (!context?.skipRefresh) {
191
+ await refreshMessages();
192
+ }
193
+ internal_toggleMessageLoading(false, tempId);
194
+ return id;
195
+ } catch (e) {
196
+ internal_toggleMessageLoading(false, tempId);
197
+ // 错误处理:更新消息错误状态
198
+ internal_dispatchMessage({
199
+ id: tempId,
200
+ type: 'updateMessage',
201
+ value: { error: { type: ChatErrorType.CreateMessageError, message: e.message } },
202
+ });
203
+ }
204
+ },
205
+ ```
206
+
207
+ ## 加载状态管理模式
208
+
209
+ LobeChat 使用统一的加载状态管理模式:
210
+
211
+ ### 数组式加载状态
212
+
213
+ ```typescript
214
+ // 在 initialState.ts 中定义
215
+ export interface ChatMessageState {
216
+ messageLoadingIds: string[]; // 消息加载状态
217
+ messageEditingIds: string[]; // 消息编辑状态
218
+ chatLoadingIds: string[]; // 对话生成状态
219
+ }
220
+
221
+ // 在 action 中管理
222
+ internal_toggleMessageLoading: (loading, id) => {
223
+ set({
224
+ messageLoadingIds: toggleBooleanList(get().messageLoadingIds, id, loading),
225
+ }, false, `internal_toggleMessageLoading/${loading ? 'start' : 'end'}`);
226
+ },
227
+ ```
228
+
229
+ ### 统一的加载状态工具
230
+
231
+ ```typescript
232
+ // 通用的加载状态切换工具
233
+ internal_toggleLoadingArrays: (key, loading, id, action) => {
234
+ const abortControllerKey = `${key}AbortController`;
235
+
236
+ if (loading) {
237
+ const abortController = new AbortController();
238
+ set({
239
+ [abortControllerKey]: abortController,
240
+ [key]: toggleBooleanList(get()[key] as string[], id!, loading),
241
+ }, false, action);
242
+ return abortController;
243
+ } else {
244
+ set({
245
+ [abortControllerKey]: undefined,
246
+ [key]: id ? toggleBooleanList(get()[key] as string[], id, loading) : [],
247
+ }, false, action);
248
+ }
249
+ },
250
+ ```
251
+
252
+ ## SWR 集成模式
253
+
254
+ LobeChat 使用 SWR 进行数据获取和缓存管理:
255
+
256
+ ### Hook 式数据获取
257
+
258
+ ```typescript
259
+ // 在 action.ts 中定义 SWR hook
260
+ useFetchMessages: (enable, sessionId, activeTopicId) =>
261
+ useClientDataSWR<ChatMessage[]>(
262
+ enable ? [SWR_USE_FETCH_MESSAGES, sessionId, activeTopicId] : null,
263
+ async ([, sessionId, topicId]) => messageService.getMessages(sessionId, topicId),
264
+ {
265
+ onSuccess: (messages, key) => {
266
+ const nextMap = {
267
+ ...get().messagesMap,
268
+ [messageMapKey(sessionId, activeTopicId)]: messages,
269
+ };
270
+
271
+ if (get().messagesInit && isEqual(nextMap, get().messagesMap)) return;
272
+
273
+ set({ messagesInit: true, messagesMap: nextMap }, false, n('useFetchMessages'));
274
+ },
275
+ },
276
+ ),
277
+ ```
278
+
279
+ ### 缓存失效和刷新
280
+
281
+ ```typescript
282
+ // 刷新数据的标准模式
283
+ refreshMessages: async () => {
284
+ await mutate([SWR_USE_FETCH_MESSAGES, get().activeId, get().activeTopicId]);
285
+ },
286
+
287
+ refreshTopic: async () => {
288
+ return mutate([SWR_USE_FETCH_TOPIC, get().activeId]);
289
+ },
290
+ ```
291
+
292
+ ## 命名规范总结
293
+
294
+ ### Action 命名模式
295
+ - **Public Actions**: 动词形式,描述用户意图
296
+ - `createTopic`, `sendMessage`, `regenerateMessage`
297
+ - **Internal Actions**: `internal_` + 动词,描述内部操作
298
+ - `internal_createTopic`, `internal_updateMessageContent`
299
+ - **Dispatch Methods**: `internal_dispatch` + 实体名
300
+ - `internal_dispatchTopic`, `internal_dispatchMessage`
301
+ - **Toggle Methods**: `internal_toggle` + 状态名
302
+ - `internal_toggleMessageLoading`, `internal_toggleChatLoading`
303
+
304
+ ### 状态命名模式
305
+ - **ID 数组**: `[entity]LoadingIds`, `[entity]EditingIds`
306
+ - **映射结构**: `[entity]Maps`, `[entity]Map`
307
+ - **当前激活**: `active[Entity]Id`
308
+ - **初始化标记**: `[entity]sInit`
309
+
310
+ ## 最佳实践
311
+
312
+ 1. **始终实现乐观更新**:对于用户交互频繁的操作
313
+ 2. **加载状态管理**:使用统一的加载状态数组管理并发操作
314
+ 3. **类型安全**:为所有 action payload 定义 TypeScript 接口
315
+ 4. **SWR 集成**:使用 SWR 管理数据获取和缓存失效
316
+ 5. **AbortController**:为长时间运行的操作提供取消能力
317
+
318
+ 这套 Action 组织模式确保了代码的一致性、可维护性,并提供了优秀的用户体验。