@lobehub/chat 1.90.2 → 1.90.4
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.
- package/.cursor/rules/backend-architecture.mdc +12 -9
- package/.cursor/rules/cursor-ux-optimize.mdc +1 -1
- package/.cursor/rules/define-database-model.mdc +1 -1
- package/.cursor/rules/drizzle-schema-style-guide.mdc +1 -1
- package/.cursor/rules/i18n/i18n.mdc +1 -1
- package/.cursor/rules/project-introduce.mdc +2 -1
- package/.cursor/rules/system-role.mdc +42 -0
- package/.cursor/rules/zustand-action-patterns.mdc +318 -0
- package/.cursor/rules/zustand-slice-organization.mdc +300 -0
- package/CHANGELOG.md +58 -0
- package/README.md +2 -2
- package/README.zh-CN.md +2 -2
- package/changelog/v1.json +21 -0
- package/docs/self-hosting/advanced/model-list.mdx +1 -1
- package/docs/self-hosting/advanced/model-list.zh-CN.mdx +1 -1
- package/docs/self-hosting/environment-variables/model-provider.mdx +2 -2
- package/docs/self-hosting/environment-variables/model-provider.zh-CN.mdx +2 -2
- package/package.json +44 -44
- package/src/config/aiModels/qwen.ts +64 -42
- package/src/config/modelProviders/qwen.ts +2 -5
- package/src/config/modelProviders/xai.ts +1 -1
- package/src/features/PluginsUI/Render/utils/iframeOnReady.test.ts +1 -1
- package/src/features/PluginsUI/Render/utils/pluginSettings.test.ts +1 -1
- package/src/features/PluginsUI/Render/utils/pluginState.test.ts +1 -1
- package/src/libs/model-runtime/BaseAI.ts +3 -3
- package/src/libs/model-runtime/ModelRuntime.ts +2 -2
- package/src/libs/model-runtime/UniformRuntime/index.ts +2 -2
- package/src/libs/model-runtime/ai21/index.ts +2 -2
- package/src/libs/model-runtime/ai360/index.ts +2 -2
- package/src/libs/model-runtime/anthropic/index.ts +15 -11
- package/src/libs/model-runtime/azureOpenai/index.ts +2 -2
- package/src/libs/model-runtime/azureai/index.ts +4 -4
- package/src/libs/model-runtime/baichuan/index.ts +2 -2
- package/src/libs/model-runtime/bedrock/index.ts +4 -4
- package/src/libs/model-runtime/cloudflare/index.ts +2 -2
- package/src/libs/model-runtime/cohere/index.ts +2 -2
- package/src/libs/model-runtime/deepseek/index.ts +2 -2
- package/src/libs/model-runtime/fireworksai/index.ts +2 -2
- package/src/libs/model-runtime/giteeai/index.ts +2 -2
- package/src/libs/model-runtime/github/index.ts +2 -2
- package/src/libs/model-runtime/google/index.ts +7 -5
- package/src/libs/model-runtime/groq/index.ts +2 -2
- package/src/libs/model-runtime/higress/index.ts +2 -2
- package/src/libs/model-runtime/huggingface/index.ts +2 -2
- package/src/libs/model-runtime/hunyuan/index.ts +2 -2
- package/src/libs/model-runtime/index.ts +1 -1
- package/src/libs/model-runtime/infiniai/index.ts +2 -2
- package/src/libs/model-runtime/internlm/index.ts +7 -9
- package/src/libs/model-runtime/jina/index.ts +2 -2
- package/src/libs/model-runtime/lmstudio/index.ts +2 -2
- package/src/libs/model-runtime/minimax/index.ts +2 -2
- package/src/libs/model-runtime/mistral/index.ts +2 -2
- package/src/libs/model-runtime/modelscope/index.ts +2 -3
- package/src/libs/model-runtime/moonshot/index.ts +2 -2
- package/src/libs/model-runtime/novita/index.ts +2 -2
- package/src/libs/model-runtime/nvidia/index.ts +2 -2
- package/src/libs/model-runtime/ollama/index.ts +2 -2
- package/src/libs/model-runtime/openai/index.ts +3 -3
- package/src/libs/model-runtime/openrouter/index.ts +2 -2
- package/src/libs/model-runtime/perplexity/index.ts +2 -2
- package/src/libs/model-runtime/ppio/index.ts +2 -2
- package/src/libs/model-runtime/qiniu/index.ts +2 -2
- package/src/libs/model-runtime/qwen/index.ts +2 -2
- package/src/libs/model-runtime/sambanova/index.ts +2 -2
- package/src/libs/model-runtime/search1api/index.ts +2 -2
- package/src/libs/model-runtime/sensenova/index.ts +2 -2
- package/src/libs/model-runtime/siliconcloud/index.ts +2 -2
- package/src/libs/model-runtime/spark/index.ts +15 -13
- package/src/libs/model-runtime/stepfun/index.ts +2 -2
- package/src/libs/model-runtime/taichu/index.ts +2 -2
- package/src/libs/model-runtime/tencentcloud/index.ts +2 -2
- package/src/libs/model-runtime/togetherai/index.ts +2 -2
- package/src/libs/model-runtime/types/chat.ts +1 -1
- package/src/libs/model-runtime/upstage/index.ts +2 -2
- package/src/libs/model-runtime/utils/openaiCompatibleFactory/index.test.ts +7 -7
- package/src/libs/model-runtime/utils/openaiCompatibleFactory/index.ts +3 -3
- package/src/libs/model-runtime/vllm/index.ts +2 -2
- package/src/libs/model-runtime/volcengine/index.ts +2 -2
- package/src/libs/model-runtime/wenxin/index.ts +2 -2
- package/src/libs/model-runtime/xai/index.ts +6 -3
- package/src/libs/model-runtime/xinference/index.ts +2 -2
- package/src/libs/model-runtime/zeroone/index.ts +2 -2
- package/src/libs/model-runtime/zhipu/index.ts +2 -2
- package/src/middleware.ts +3 -1
- package/src/server/globalConfig/index.ts +3 -0
- package/src/server/routers/tools/search.test.ts +2 -4
- package/src/services/chat.ts +1 -0
- package/src/store/chat/slices/aiChat/actions/__tests__/generateAIChat.test.ts +4 -2
- package/src/store/chat/slices/message/action.test.ts +2 -1
- package/src/store/chat/slices/topic/action.test.ts +3 -2
- package/src/types/aiProvider.ts +1 -0
@@ -29,37 +29,40 @@ LobeChat 的后端设计注重模块化、可测试性和灵活性,以适应
|
|
29
29
|
其主要分层如下:
|
30
30
|
|
31
31
|
1. **客户端服务层 (`src/services`)**:
|
32
|
-
* 位于
|
32
|
+
* 位于 src/services/。
|
33
33
|
* 这是客户端业务逻辑的核心层,负责封装各种业务操作和数据处理逻辑。
|
34
34
|
* **环境适配**: 根据不同的运行环境,服务层会选择合适的数据访问方式:
|
35
35
|
* **本地数据库模式**: 直接调用 `Model` 层进行数据操作,适用于浏览器 PGLite 和本地 Electron 应用。
|
36
36
|
* **远程数据库模式**: 通过 `tRPC` 客户端调用服务端 API,适用于需要云同步的场景。
|
37
|
-
* **类型转换**: 对于简单的数据类型转换,直接在此层进行类型断言,如 `this.pluginModel.query() as Promise<LobeTool[]
|
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
|
-
* 位于
|
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
|
-
* 位于
|
47
|
+
* 位于 src/server/services/。
|
47
48
|
* 核心职责是封装独立的、可复用的业务逻辑单元。这些服务应易于测试。
|
48
|
-
* **平台差异抽象**: 一个关键特性是通过其内部的 `impls` 子目录(例如
|
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
|
-
* 位于
|
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
|
-
* 位于
|
61
|
-
* 提供对数据库中各个表(由
|
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),数据存储在用户浏览器本地。
|
@@ -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 组织模式确保了代码的一致性、可维护性,并提供了优秀的用户体验。
|