@lobehub/lobehub 2.0.0-next.172 → 2.0.0-next.174
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/db-migrations.mdc +10 -4
- package/.cursor/rules/drizzle-schema-style-guide.mdc +33 -17
- package/.cursor/rules/hotkey.mdc +161 -0
- package/.cursor/rules/i18n.mdc +2 -5
- package/.cursor/rules/project-introduce.mdc +3 -2
- package/.cursor/rules/project-structure.mdc +33 -37
- package/.cursor/rules/react.mdc +155 -0
- package/.cursor/rules/recent-data-usage.mdc +138 -0
- package/.cursor/rules/rules-index.mdc +1 -1
- package/.cursor/rules/testing-guide/agent-runtime-e2e.mdc +285 -0
- package/.cursor/rules/testing-guide/zustand-store-action-test.mdc +11 -16
- package/.cursor/rules/typescript.mdc +0 -4
- package/.cursor/rules/zustand-action-patterns.mdc +137 -169
- package/.cursor/rules/zustand-slice-organization.mdc +16 -8
- package/.husky/pre-commit +1 -1
- package/.i18nrc.js +3 -1
- package/AGENTS.md +3 -2
- package/CHANGELOG.md +50 -0
- package/CLAUDE.md +10 -3
- package/GEMINI.md +2 -1
- package/README.md +3 -3
- package/README.zh-CN.md +6 -6
- package/changelog/v1.json +18 -0
- package/docs/development/database-schema.dbml +18 -3
- package/docs/development/state-management/state-management-selectors.mdx +1 -1
- package/glossary.json +8 -0
- package/package.json +3 -3
- package/packages/database/migrations/0063_add_columns_for_several_tables.sql +30 -0
- package/packages/database/migrations/meta/0063_snapshot.json +9113 -0
- package/packages/database/migrations/meta/_journal.json +7 -0
- package/packages/database/src/core/migrations.json +29 -0
- package/packages/database/src/schemas/_helpers.ts +5 -1
- package/packages/database/src/schemas/agent.ts +2 -1
- package/packages/database/src/schemas/aiInfra.ts +18 -3
- package/packages/database/src/schemas/message.ts +11 -6
- package/packages/database/src/schemas/topic.ts +17 -2
- package/packages/database/src/schemas/user.ts +5 -2
- package/packages/database/src/schemas/userMemories.ts +9 -8
- package/packages/model-runtime/src/core/contextBuilders/openai.ts +8 -2
- package/packages/model-runtime/src/providers/github/index.test.ts +4 -4
- package/packages/types/src/topic/thread.ts +24 -0
- package/packages/types/src/user/index.ts +1 -0
- package/packages/types/src/user/onboarding.ts +16 -0
- package/src/features/KnowledgeBaseModal/AssignKnowledgeBase/List.tsx +5 -2
- package/src/store/agent/slices/chat/action.ts +3 -3
- package/vitest.config.mts +3 -0
- package/.cursor/rules/group-chat.mdc +0 -35
- package/.cursor/rules/react-component.mdc +0 -173
|
@@ -1,137 +1,126 @@
|
|
|
1
1
|
---
|
|
2
|
-
description:
|
|
2
|
+
description:
|
|
3
3
|
globs: src/store/**
|
|
4
4
|
alwaysApply: false
|
|
5
5
|
---
|
|
6
|
-
# LobeChat Zustand Action 组织模式
|
|
7
6
|
|
|
8
|
-
|
|
7
|
+
# LobeChat Zustand Action Patterns
|
|
9
8
|
|
|
10
|
-
## Action
|
|
9
|
+
## Action Type Hierarchy
|
|
11
10
|
|
|
12
|
-
LobeChat
|
|
11
|
+
LobeChat Actions use a layered architecture with clear separation of responsibilities:
|
|
13
12
|
|
|
14
13
|
### 1. Public Actions
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
-
|
|
14
|
+
|
|
15
|
+
Main interfaces exposed for UI component consumption:
|
|
16
|
+
|
|
17
|
+
- Naming: Verb form (`createTopic`, `sendMessage`, `updateTopicTitle`)
|
|
18
|
+
- Responsibilities: Parameter validation, flow orchestration, calling internal actions
|
|
19
|
+
- Example: [src/store/chat/slices/topic/action.ts](mdc:src/store/chat/slices/topic/action.ts)
|
|
19
20
|
|
|
20
21
|
```typescript
|
|
21
|
-
// Public Action
|
|
22
|
+
// Public Action example
|
|
22
23
|
createTopic: async () => {
|
|
23
|
-
|
|
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
|
-
|
|
24
|
+
// ...
|
|
34
25
|
return topicId;
|
|
35
26
|
},
|
|
36
27
|
```
|
|
37
28
|
|
|
38
29
|
### 2. Internal Actions (`internal_*`)
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
-
|
|
30
|
+
|
|
31
|
+
Internal implementation details handling core business logic:
|
|
32
|
+
|
|
33
|
+
- Naming: `internal_` prefix + verb (`internal_createTopic`, `internal_updateMessageContent`)
|
|
34
|
+
- Responsibilities: Optimistic updates, service calls, error handling, state synchronization
|
|
35
|
+
- Should not be called directly by UI components
|
|
43
36
|
|
|
44
37
|
```typescript
|
|
45
|
-
// Internal Action
|
|
38
|
+
// Internal Action example - Optimistic update pattern
|
|
46
39
|
internal_createTopic: async (params) => {
|
|
47
40
|
const tmpId = Date.now().toString();
|
|
48
|
-
|
|
49
|
-
// 1.
|
|
41
|
+
|
|
42
|
+
// 1. Immediately update frontend state (optimistic update)
|
|
50
43
|
get().internal_dispatchTopic(
|
|
51
44
|
{ type: 'addTopic', value: { ...params, id: tmpId } },
|
|
52
45
|
'internal_createTopic',
|
|
53
46
|
);
|
|
54
47
|
get().internal_updateTopicLoading(tmpId, true);
|
|
55
|
-
|
|
56
|
-
// 2.
|
|
48
|
+
|
|
49
|
+
// 2. Call backend service
|
|
57
50
|
const topicId = await topicService.createTopic(params);
|
|
58
51
|
get().internal_updateTopicLoading(tmpId, false);
|
|
59
|
-
|
|
60
|
-
// 3.
|
|
52
|
+
|
|
53
|
+
// 3. Refresh data to ensure consistency
|
|
61
54
|
get().internal_updateTopicLoading(topicId, true);
|
|
62
55
|
await get().refreshTopic();
|
|
63
56
|
get().internal_updateTopicLoading(topicId, false);
|
|
64
|
-
|
|
57
|
+
|
|
65
58
|
return topicId;
|
|
66
59
|
},
|
|
67
60
|
```
|
|
68
61
|
|
|
69
62
|
### 3. Dispatch Methods (`internal_dispatch*`)
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
63
|
+
|
|
64
|
+
Methods dedicated to handling state updates:
|
|
65
|
+
|
|
66
|
+
- Naming: `internal_dispatch` + entity name (`internal_dispatchTopic`, `internal_dispatchMessage`)
|
|
67
|
+
- Responsibilities: Calling reducers, updating Zustand store, handling state comparison
|
|
73
68
|
|
|
74
69
|
```typescript
|
|
75
|
-
// Dispatch Method
|
|
70
|
+
// Dispatch Method example
|
|
76
71
|
internal_dispatchTopic: (payload, action) => {
|
|
77
72
|
const nextTopics = topicReducer(topicSelectors.currentTopics(get()), payload);
|
|
78
73
|
const nextMap = { ...get().topicMaps, [get().activeId]: nextTopics };
|
|
79
74
|
|
|
80
75
|
if (isEqual(nextMap, get().topicMaps)) return;
|
|
81
|
-
|
|
76
|
+
|
|
82
77
|
set({ topicMaps: nextMap }, false, action ?? n(`dispatchTopic/${payload.type}`));
|
|
83
78
|
},
|
|
84
79
|
```
|
|
85
80
|
|
|
86
|
-
##
|
|
81
|
+
## When to Use Reducer Pattern vs. Simple `set`
|
|
82
|
+
|
|
83
|
+
### Use Reducer Pattern When
|
|
87
84
|
|
|
88
|
-
|
|
85
|
+
Suitable for complex data structure management, especially:
|
|
89
86
|
|
|
90
|
-
|
|
91
|
-
-
|
|
92
|
-
-
|
|
93
|
-
-
|
|
94
|
-
- 需要类型安全的 action payload
|
|
87
|
+
- Managing object lists or maps (e.g., `messagesMap`, `topicMaps`)
|
|
88
|
+
- Scenarios requiring optimistic updates
|
|
89
|
+
- Complex state transition logic
|
|
90
|
+
- Type-safe action payloads needed
|
|
95
91
|
|
|
96
92
|
```typescript
|
|
97
|
-
// Reducer
|
|
93
|
+
// Reducer pattern example - Complex message state management
|
|
98
94
|
export const messagesReducer = (state: ChatMessage[], payload: MessageDispatch): ChatMessage[] => {
|
|
99
95
|
switch (payload.type) {
|
|
100
96
|
case 'updateMessage': {
|
|
101
97
|
return produce(state, (draftState) => {
|
|
102
98
|
const index = draftState.findIndex((i) => i.id === payload.id);
|
|
103
99
|
if (index < 0) return;
|
|
104
|
-
draftState[index] = merge(draftState[index], {
|
|
105
|
-
...payload.value,
|
|
106
|
-
updatedAt: Date.now()
|
|
100
|
+
draftState[index] = merge(draftState[index], {
|
|
101
|
+
...payload.value,
|
|
102
|
+
updatedAt: Date.now(),
|
|
107
103
|
});
|
|
108
104
|
});
|
|
109
105
|
}
|
|
110
106
|
case 'createMessage': {
|
|
111
|
-
|
|
112
|
-
draftState.push({
|
|
113
|
-
...payload.value,
|
|
114
|
-
id: payload.id,
|
|
115
|
-
createdAt: Date.now(),
|
|
116
|
-
updatedAt: Date.now(),
|
|
117
|
-
meta: {}
|
|
118
|
-
});
|
|
119
|
-
});
|
|
107
|
+
// ...
|
|
120
108
|
}
|
|
121
|
-
//
|
|
109
|
+
// ...other complex state transitions
|
|
122
110
|
}
|
|
123
111
|
};
|
|
124
112
|
```
|
|
125
113
|
|
|
126
|
-
###
|
|
114
|
+
### Use Simple `set` When
|
|
115
|
+
|
|
116
|
+
Suitable for simple state updates:
|
|
127
117
|
|
|
128
|
-
|
|
129
|
-
-
|
|
130
|
-
-
|
|
131
|
-
- 设置单一状态字段
|
|
118
|
+
- Toggling boolean values
|
|
119
|
+
- Updating simple strings/numbers
|
|
120
|
+
- Setting single state fields
|
|
132
121
|
|
|
133
122
|
```typescript
|
|
134
|
-
//
|
|
123
|
+
// Simple set example
|
|
135
124
|
updateInputMessage: (message) => {
|
|
136
125
|
if (isEqual(message, get().inputMessage)) return;
|
|
137
126
|
set({ inputMessage: message }, false, n('updateInputMessage'));
|
|
@@ -142,45 +131,45 @@ togglePortal: (open?: boolean) => {
|
|
|
142
131
|
},
|
|
143
132
|
```
|
|
144
133
|
|
|
145
|
-
##
|
|
134
|
+
## Optimistic Update Implementation Patterns
|
|
146
135
|
|
|
147
|
-
|
|
136
|
+
Optimistic updates are a core pattern in LobeChat for providing smooth user experience:
|
|
148
137
|
|
|
149
|
-
###
|
|
138
|
+
### Standard Optimistic Update Flow
|
|
150
139
|
|
|
151
140
|
```typescript
|
|
152
|
-
//
|
|
141
|
+
// Complete optimistic update example
|
|
153
142
|
internal_updateMessageContent: async (id, content, extra) => {
|
|
154
143
|
const { internal_dispatchMessage, refreshMessages } = get();
|
|
155
144
|
|
|
156
|
-
// 1.
|
|
145
|
+
// 1. Immediately update frontend state (optimistic update)
|
|
157
146
|
internal_dispatchMessage({
|
|
158
147
|
id,
|
|
159
148
|
type: 'updateMessage',
|
|
160
149
|
value: { content },
|
|
161
150
|
});
|
|
162
151
|
|
|
163
|
-
// 2.
|
|
152
|
+
// 2. Call backend service
|
|
164
153
|
await messageService.updateMessage(id, {
|
|
165
154
|
content,
|
|
166
155
|
tools: extra?.toolCalls ? internal_transformToolCalls(extra.toolCalls) : undefined,
|
|
167
|
-
//
|
|
156
|
+
// ...other fields
|
|
168
157
|
});
|
|
169
158
|
|
|
170
|
-
// 3.
|
|
159
|
+
// 3. Refresh to ensure data consistency
|
|
171
160
|
await refreshMessages();
|
|
172
161
|
},
|
|
173
162
|
```
|
|
174
163
|
|
|
175
|
-
###
|
|
164
|
+
### Optimistic Update for Create Operations
|
|
176
165
|
|
|
177
166
|
```typescript
|
|
178
167
|
internal_createMessage: async (message, context) => {
|
|
179
168
|
const { internal_createTmpMessage, refreshMessages, internal_toggleMessageLoading } = get();
|
|
180
|
-
|
|
169
|
+
|
|
181
170
|
let tempId = context?.tempMessageId;
|
|
182
171
|
if (!tempId) {
|
|
183
|
-
//
|
|
172
|
+
// Create temporary message for optimistic update
|
|
184
173
|
tempId = internal_createTmpMessage(message);
|
|
185
174
|
internal_toggleMessageLoading(true, tempId);
|
|
186
175
|
}
|
|
@@ -194,7 +183,7 @@ internal_createMessage: async (message, context) => {
|
|
|
194
183
|
return id;
|
|
195
184
|
} catch (e) {
|
|
196
185
|
internal_toggleMessageLoading(false, tempId);
|
|
197
|
-
//
|
|
186
|
+
// Error handling: update message error state
|
|
198
187
|
internal_dispatchMessage({
|
|
199
188
|
id: tempId,
|
|
200
189
|
type: 'updateMessage',
|
|
@@ -204,96 +193,77 @@ internal_createMessage: async (message, context) => {
|
|
|
204
193
|
},
|
|
205
194
|
```
|
|
206
195
|
|
|
207
|
-
###
|
|
196
|
+
### Delete Operation Pattern (No Optimistic Update)
|
|
208
197
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
-
|
|
212
|
-
-
|
|
198
|
+
Delete operations typically don't suit optimistic updates because:
|
|
199
|
+
|
|
200
|
+
- Deletion is destructive; error recovery is complex
|
|
201
|
+
- Users have lower expectations for immediate feedback on deletions
|
|
202
|
+
- Restoring state on deletion failure causes confusion
|
|
213
203
|
|
|
214
204
|
```typescript
|
|
215
|
-
//
|
|
205
|
+
// Standard delete operation pattern - No optimistic update
|
|
216
206
|
removeGenerationTopic: async (id: string) => {
|
|
217
207
|
const { internal_removeGenerationTopic } = get();
|
|
218
208
|
await internal_removeGenerationTopic(id);
|
|
219
209
|
},
|
|
220
210
|
|
|
221
211
|
internal_removeGenerationTopic: async (id: string) => {
|
|
222
|
-
// 1.
|
|
212
|
+
// 1. Show loading state
|
|
223
213
|
get().internal_updateGenerationTopicLoading(id, true);
|
|
224
|
-
|
|
214
|
+
|
|
225
215
|
try {
|
|
226
|
-
// 2.
|
|
216
|
+
// 2. Directly call backend service
|
|
227
217
|
await generationTopicService.deleteTopic(id);
|
|
228
|
-
|
|
229
|
-
// 3.
|
|
218
|
+
|
|
219
|
+
// 3. Refresh data to get latest state
|
|
230
220
|
await get().refreshGenerationTopics();
|
|
231
221
|
} finally {
|
|
232
|
-
// 4.
|
|
222
|
+
// 4. Ensure loading state is cleared (whether success or failure)
|
|
233
223
|
get().internal_updateGenerationTopicLoading(id, false);
|
|
234
224
|
}
|
|
235
225
|
},
|
|
236
226
|
```
|
|
237
227
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
-
|
|
241
|
-
-
|
|
242
|
-
-
|
|
228
|
+
Delete operation characteristics:
|
|
229
|
+
|
|
230
|
+
- Directly call service without pre-updating state
|
|
231
|
+
- Rely on loading state for user feedback
|
|
232
|
+
- Refresh entire list after operation to ensure consistency
|
|
233
|
+
- Use `try/finally` to ensure loading state is always cleaned up
|
|
243
234
|
|
|
244
|
-
##
|
|
235
|
+
## Loading State Management Pattern
|
|
245
236
|
|
|
246
|
-
LobeChat
|
|
237
|
+
LobeChat uses a unified loading state management pattern:
|
|
247
238
|
|
|
248
|
-
###
|
|
239
|
+
### Array-based Loading State
|
|
249
240
|
|
|
250
241
|
```typescript
|
|
251
|
-
//
|
|
242
|
+
// Define in initialState.ts
|
|
252
243
|
export interface ChatMessageState {
|
|
253
|
-
|
|
254
|
-
messageEditingIds: string[]; // 消息编辑状态
|
|
255
|
-
chatLoadingIds: string[]; // 对话生成状态
|
|
244
|
+
messageEditingIds: string[]; // Message editing state
|
|
256
245
|
}
|
|
257
246
|
|
|
258
|
-
//
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
```typescript
|
|
269
|
-
// 通用的加载状态切换工具
|
|
270
|
-
internal_toggleLoadingArrays: (key, loading, id, action) => {
|
|
271
|
-
const abortControllerKey = `${key}AbortController`;
|
|
272
|
-
|
|
273
|
-
if (loading) {
|
|
274
|
-
const abortController = new AbortController();
|
|
275
|
-
set({
|
|
276
|
-
[abortControllerKey]: abortController,
|
|
277
|
-
[key]: toggleBooleanList(get()[key] as string[], id!, loading),
|
|
278
|
-
}, false, action);
|
|
279
|
-
return abortController;
|
|
280
|
-
} else {
|
|
281
|
-
set({
|
|
282
|
-
[abortControllerKey]: undefined,
|
|
283
|
-
[key]: id ? toggleBooleanList(get()[key] as string[], id, loading) : [],
|
|
284
|
-
}, false, action);
|
|
285
|
-
}
|
|
286
|
-
},
|
|
247
|
+
// Manage in action
|
|
248
|
+
{
|
|
249
|
+
toggleMessageEditing: (id, editing) => {
|
|
250
|
+
set(
|
|
251
|
+
{ messageEditingIds: toggleBooleanList(get().messageEditingIds, id, editing) },
|
|
252
|
+
false,
|
|
253
|
+
'toggleMessageEditing',
|
|
254
|
+
);
|
|
255
|
+
};
|
|
256
|
+
}
|
|
287
257
|
```
|
|
288
258
|
|
|
289
|
-
## SWR
|
|
259
|
+
## SWR Integration Pattern
|
|
290
260
|
|
|
291
|
-
LobeChat
|
|
261
|
+
LobeChat uses SWR for data fetching and cache management:
|
|
292
262
|
|
|
293
|
-
### Hook
|
|
263
|
+
### Hook-based Data Fetching
|
|
294
264
|
|
|
295
265
|
```typescript
|
|
296
|
-
//
|
|
266
|
+
// Define SWR hook in action.ts
|
|
297
267
|
useFetchMessages: (enable, sessionId, activeTopicId) =>
|
|
298
268
|
useClientDataSWR<ChatMessage[]>(
|
|
299
269
|
enable ? [SWR_USE_FETCH_MESSAGES, sessionId, activeTopicId] : null,
|
|
@@ -304,57 +274,55 @@ useFetchMessages: (enable, sessionId, activeTopicId) =>
|
|
|
304
274
|
...get().messagesMap,
|
|
305
275
|
[messageMapKey(sessionId, activeTopicId)]: messages,
|
|
306
276
|
};
|
|
307
|
-
|
|
277
|
+
|
|
308
278
|
if (get().messagesInit && isEqual(nextMap, get().messagesMap)) return;
|
|
309
|
-
|
|
279
|
+
|
|
310
280
|
set({ messagesInit: true, messagesMap: nextMap }, false, n('useFetchMessages'));
|
|
311
281
|
},
|
|
312
282
|
},
|
|
313
283
|
),
|
|
314
284
|
```
|
|
315
285
|
|
|
316
|
-
###
|
|
286
|
+
### Cache Invalidation and Refresh
|
|
317
287
|
|
|
318
288
|
```typescript
|
|
319
|
-
//
|
|
289
|
+
// Standard data refresh pattern
|
|
320
290
|
refreshMessages: async () => {
|
|
321
291
|
await mutate([SWR_USE_FETCH_MESSAGES, get().activeId, get().activeTopicId]);
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
refreshTopic: async () => {
|
|
325
|
-
return mutate([SWR_USE_FETCH_TOPIC, get().activeId]);
|
|
326
|
-
},
|
|
292
|
+
};
|
|
327
293
|
```
|
|
328
294
|
|
|
329
|
-
##
|
|
295
|
+
## Naming Convention Summary
|
|
330
296
|
|
|
331
|
-
### Action
|
|
332
|
-
|
|
297
|
+
### Action Naming Patterns
|
|
298
|
+
|
|
299
|
+
- Public Actions: Verb form, describing user intent
|
|
333
300
|
- `createTopic`, `sendMessage`, `regenerateMessage`
|
|
334
|
-
- Internal Actions: `internal_` +
|
|
301
|
+
- Internal Actions: `internal_` + verb, describing internal operation
|
|
335
302
|
- `internal_createTopic`, `internal_updateMessageContent`
|
|
336
|
-
- Dispatch Methods: `internal_dispatch` +
|
|
303
|
+
- Dispatch Methods: `internal_dispatch` + entity name
|
|
337
304
|
- `internal_dispatchTopic`, `internal_dispatchMessage`
|
|
338
|
-
- Toggle Methods: `internal_toggle` +
|
|
305
|
+
- Toggle Methods: `internal_toggle` + state name
|
|
339
306
|
- `internal_toggleMessageLoading`, `internal_toggleChatLoading`
|
|
340
307
|
|
|
341
|
-
###
|
|
342
|
-
|
|
343
|
-
-
|
|
344
|
-
-
|
|
345
|
-
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
-
|
|
359
|
-
|
|
360
|
-
|
|
308
|
+
### State Naming Patterns
|
|
309
|
+
|
|
310
|
+
- ID arrays: `[entity]LoadingIds`, `[entity]EditingIds`
|
|
311
|
+
- Map structures: `[entity]Maps`, `[entity]Map`
|
|
312
|
+
- Currently active: `active[Entity]Id`
|
|
313
|
+
- Initialization flags: `[entity]sInit`
|
|
314
|
+
|
|
315
|
+
## Best Practices
|
|
316
|
+
|
|
317
|
+
1. Use optimistic updates appropriately:
|
|
318
|
+
- ✅ Suitable: Create, update operations (frequent user interaction)
|
|
319
|
+
- ❌ Avoid: Delete operations (destructive, complex error recovery)
|
|
320
|
+
2. Loading state management: Use unified loading state arrays to manage concurrent operations
|
|
321
|
+
3. Type safety: Define TypeScript interfaces for all action payloads
|
|
322
|
+
4. SWR integration: Use SWR to manage data fetching and cache invalidation
|
|
323
|
+
5. AbortController: Provide cancellation capability for long-running operations
|
|
324
|
+
6. Operation mode selection:
|
|
325
|
+
- Create/Update: Optimistic update + eventual consistency
|
|
326
|
+
- Delete: Loading state + service call + data refresh
|
|
327
|
+
|
|
328
|
+
This Action organization pattern ensures code consistency, maintainability, and provides excellent user experience.
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
---
|
|
2
|
-
description:
|
|
2
|
+
description:
|
|
3
3
|
globs: src/store/**
|
|
4
4
|
alwaysApply: false
|
|
5
5
|
---
|
|
6
|
+
|
|
6
7
|
# LobeChat Zustand Store Slice 组织架构
|
|
7
8
|
|
|
8
9
|
本文档描述了 LobeChat 项目中 Zustand Store 的模块化 Slice 组织方式,展示如何通过分片架构管理复杂的应用状态。
|
|
@@ -69,7 +70,7 @@ export const useChatStore = createWithEqualityFn<ChatStore>()(
|
|
|
69
70
|
|
|
70
71
|
每个 slice 位于 `src/store/chat/slices/[sliceName]/` 目录下:
|
|
71
72
|
|
|
72
|
-
```
|
|
73
|
+
```plaintext
|
|
73
74
|
src/store/chat/slices/
|
|
74
75
|
└── [sliceName]/ # 例如 message, topic, aiChat, builtinTool
|
|
75
76
|
├── action.ts # 定义 actions (或者是一个 actions/ 目录)
|
|
@@ -159,15 +160,16 @@ export const topicReducer = (state: ChatTopic[] = [], payload: ChatTopicDispatch
|
|
|
159
160
|
// 典型的 selectors.ts 结构
|
|
160
161
|
import { ChatStoreState } from '../../initialState';
|
|
161
162
|
|
|
162
|
-
const currentTopics = (s: ChatStoreState): ChatTopic[] | undefined =>
|
|
163
|
-
s.topicMaps[s.activeId];
|
|
163
|
+
const currentTopics = (s: ChatStoreState): ChatTopic[] | undefined => s.topicMaps[s.activeId];
|
|
164
164
|
|
|
165
165
|
const currentActiveTopic = (s: ChatStoreState): ChatTopic | undefined => {
|
|
166
166
|
return currentTopics(s)?.find((topic) => topic.id === s.activeTopicId);
|
|
167
167
|
};
|
|
168
168
|
|
|
169
|
-
const getTopicById =
|
|
170
|
-
|
|
169
|
+
const getTopicById =
|
|
170
|
+
(id: string) =>
|
|
171
|
+
(s: ChatStoreState): ChatTopic | undefined =>
|
|
172
|
+
currentTopics(s)?.find((topic) => topic.id === id);
|
|
171
173
|
|
|
172
174
|
// 核心模式:使用 xxxSelectors 聚合导出
|
|
173
175
|
export const topicSelectors = {
|
|
@@ -219,13 +221,15 @@ src/store/chat/slices/builtinTool/
|
|
|
219
221
|
## 状态设计模式
|
|
220
222
|
|
|
221
223
|
### 1. Map 结构用于关联数据
|
|
224
|
+
|
|
222
225
|
```typescript
|
|
223
226
|
// 以 sessionId 为 key,管理多个会话的数据
|
|
224
|
-
topicMaps: Record<string, ChatTopic[]
|
|
225
|
-
messagesMap: Record<string, ChatMessage[]
|
|
227
|
+
topicMaps: Record<string, ChatTopic[]>;
|
|
228
|
+
messagesMap: Record<string, ChatMessage[]>;
|
|
226
229
|
```
|
|
227
230
|
|
|
228
231
|
### 2. 数组用于加载状态管理
|
|
232
|
+
|
|
229
233
|
```typescript
|
|
230
234
|
// 管理多个并发操作的加载状态
|
|
231
235
|
messageLoadingIds: string[]
|
|
@@ -234,6 +238,7 @@ chatLoadingIds: string[]
|
|
|
234
238
|
```
|
|
235
239
|
|
|
236
240
|
### 3. 可选字段用于当前活动项
|
|
241
|
+
|
|
237
242
|
```typescript
|
|
238
243
|
// 当前激活的实体 ID
|
|
239
244
|
activeId: string
|
|
@@ -244,6 +249,7 @@ activeThreadId?: string
|
|
|
244
249
|
## Slice 集成到顶层 Store
|
|
245
250
|
|
|
246
251
|
### 1. 状态聚合
|
|
252
|
+
|
|
247
253
|
```typescript
|
|
248
254
|
// 在 initialState.ts 中
|
|
249
255
|
export type ChatStoreState = ChatTopicState &
|
|
@@ -253,6 +259,7 @@ export type ChatStoreState = ChatTopicState &
|
|
|
253
259
|
```
|
|
254
260
|
|
|
255
261
|
### 2. Action 接口聚合
|
|
262
|
+
|
|
256
263
|
```typescript
|
|
257
264
|
// 在 store.ts 中
|
|
258
265
|
export interface ChatStoreAction
|
|
@@ -263,6 +270,7 @@ export interface ChatStoreAction
|
|
|
263
270
|
```
|
|
264
271
|
|
|
265
272
|
### 3. Selector 统一导出
|
|
273
|
+
|
|
266
274
|
```typescript
|
|
267
275
|
// 在 selectors.ts 中 - 统一聚合 selectors
|
|
268
276
|
export { chatSelectors } from './slices/message/selectors';
|
package/.husky/pre-commit
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
npm run
|
|
1
|
+
npm run type-check
|
|
2
2
|
npx --no-install lint-staged
|
package/.i18nrc.js
CHANGED
|
@@ -30,7 +30,9 @@ module.exports = defineConfig({
|
|
|
30
30
|
jsonMode: true,
|
|
31
31
|
},
|
|
32
32
|
markdown: {
|
|
33
|
-
reference:
|
|
33
|
+
reference:
|
|
34
|
+
'你需要保持 mdx 的组件格式,输出文本不需要在最外层包裹任何代码块语法。以下是一些词汇的固定翻译:\n' +
|
|
35
|
+
JSON.stringify(require('./glossary.json'), null, 2),
|
|
34
36
|
entry: ['./README.zh-CN.md', './contributing/**/*.zh-CN.md', './docs/**/*.zh-CN.mdx'],
|
|
35
37
|
entryLocale: 'zh-CN',
|
|
36
38
|
outputLocales: ['en-US'],
|
package/AGENTS.md
CHANGED
|
@@ -26,6 +26,7 @@ The project follows a well-organized monorepo structure:
|
|
|
26
26
|
- `src/` - Main source code
|
|
27
27
|
- `docs/` - Documentation
|
|
28
28
|
- `.cursor/rules/` - Development rules and guidelines
|
|
29
|
+
- PR titles starting with `✨ feat/` or `🐛 fix` will trigger the release workflow upon merge. Only use these prefixes for significant user-facing feature changes or bug fixes
|
|
29
30
|
|
|
30
31
|
## Development Workflow
|
|
31
32
|
|
|
@@ -65,7 +66,7 @@ The project follows a well-organized monorepo structure:
|
|
|
65
66
|
|
|
66
67
|
### Type Checking
|
|
67
68
|
|
|
68
|
-
- Use `bun run
|
|
69
|
+
- Use `bun run type-check` to check for type errors
|
|
69
70
|
|
|
70
71
|
### i18n
|
|
71
72
|
|
|
@@ -83,7 +84,7 @@ All following rules are saved under `.cursor/rules/` directory:
|
|
|
83
84
|
|
|
84
85
|
### Frontend
|
|
85
86
|
|
|
86
|
-
- `react
|
|
87
|
+
- `react.mdc` – React component style guide and conventions
|
|
87
88
|
- `i18n.mdc` – Internationalization guide using react-i18next
|
|
88
89
|
- `typescript.mdc` – TypeScript code style guide
|
|
89
90
|
- `packages/react-layout-kit.mdc` – Usage guide for react-layout-kit
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,56 @@
|
|
|
2
2
|
|
|
3
3
|
# Changelog
|
|
4
4
|
|
|
5
|
+
## [Version 2.0.0-next.174](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.173...v2.0.0-next.174)
|
|
6
|
+
|
|
7
|
+
<sup>Released on **2025-12-20**</sup>
|
|
8
|
+
|
|
9
|
+
#### ♻ Code Refactoring
|
|
10
|
+
|
|
11
|
+
- **misc**: Refactor database schema.
|
|
12
|
+
|
|
13
|
+
<br/>
|
|
14
|
+
|
|
15
|
+
<details>
|
|
16
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
|
17
|
+
|
|
18
|
+
#### Code refactoring
|
|
19
|
+
|
|
20
|
+
- **misc**: Refactor database schema, closes [#10860](https://github.com/lobehub/lobe-chat/issues/10860) ([5c489bc](https://github.com/lobehub/lobe-chat/commit/5c489bc))
|
|
21
|
+
|
|
22
|
+
</details>
|
|
23
|
+
|
|
24
|
+
<div align="right">
|
|
25
|
+
|
|
26
|
+
[](#readme-top)
|
|
27
|
+
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
## [Version 2.0.0-next.173](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.172...v2.0.0-next.173)
|
|
31
|
+
|
|
32
|
+
<sup>Released on **2025-12-16**</sup>
|
|
33
|
+
|
|
34
|
+
#### 🐛 Bug Fixes
|
|
35
|
+
|
|
36
|
+
- **misc**: Request to gpt5 series should not with `top_p`, temperature when reasoning effort is not none.
|
|
37
|
+
|
|
38
|
+
<br/>
|
|
39
|
+
|
|
40
|
+
<details>
|
|
41
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
|
42
|
+
|
|
43
|
+
#### What's fixed
|
|
44
|
+
|
|
45
|
+
- **misc**: Request to gpt5 series should not with `top_p`, temperature when reasoning effort is not none, closes [#10800](https://github.com/lobehub/lobe-chat/issues/10800) ([b4ad470](https://github.com/lobehub/lobe-chat/commit/b4ad470))
|
|
46
|
+
|
|
47
|
+
</details>
|
|
48
|
+
|
|
49
|
+
<div align="right">
|
|
50
|
+
|
|
51
|
+
[](#readme-top)
|
|
52
|
+
|
|
53
|
+
</div>
|
|
54
|
+
|
|
5
55
|
## [Version 2.0.0-next.172](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.171...v2.0.0-next.172)
|
|
6
56
|
|
|
7
57
|
<sup>Released on **2025-12-15**</sup>
|