@lobehub/lobehub 2.0.0-next.353 → 2.0.0-next.354

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 (119) hide show
  1. package/.agents/skills/add-provider-doc/SKILL.md +90 -0
  2. package/.agents/skills/add-setting-env/SKILL.md +106 -0
  3. package/.agents/skills/debug/SKILL.md +66 -0
  4. package/.agents/skills/desktop/SKILL.md +78 -0
  5. package/.agents/skills/desktop/references/feature-implementation.md +99 -0
  6. package/.agents/skills/desktop/references/local-tools.md +133 -0
  7. package/.agents/skills/desktop/references/menu-config.md +103 -0
  8. package/.agents/skills/desktop/references/window-management.md +143 -0
  9. package/.agents/skills/drizzle/SKILL.md +129 -0
  10. package/.agents/skills/drizzle/references/db-migrations.md +50 -0
  11. package/.agents/skills/hotkey/SKILL.md +90 -0
  12. package/{.cursor/rules/i18n.mdc → .agents/skills/i18n/SKILL.md} +14 -23
  13. package/.agents/skills/linear/SKILL.md +51 -0
  14. package/.agents/skills/microcopy/SKILL.md +83 -0
  15. package/.agents/skills/modal/SKILL.md +102 -0
  16. package/{.cursor/rules/project-structure.mdc → .agents/skills/project-overview/SKILL.md} +65 -37
  17. package/.agents/skills/react/SKILL.md +73 -0
  18. package/.agents/skills/react/references/layout-kit.md +100 -0
  19. package/.agents/skills/recent-data/SKILL.md +108 -0
  20. package/.agents/skills/testing/SKILL.md +89 -0
  21. package/.agents/skills/testing/references/agent-runtime-e2e.md +126 -0
  22. package/.agents/skills/testing/references/db-model-test.md +124 -0
  23. package/.agents/skills/testing/references/desktop-controller-test.md +124 -0
  24. package/.agents/skills/testing/references/electron-ipc-test.md +63 -0
  25. package/.agents/skills/testing/references/zustand-store-action-test.md +150 -0
  26. package/.agents/skills/typescript/SKILL.md +52 -0
  27. package/.agents/skills/zustand/SKILL.md +78 -0
  28. package/.agents/skills/zustand/references/action-patterns.md +125 -0
  29. package/.agents/skills/zustand/references/slice-organization.md +125 -0
  30. package/AGENTS.md +42 -55
  31. package/CHANGELOG.md +33 -0
  32. package/CLAUDE.md +57 -46
  33. package/GEMINI.md +47 -39
  34. package/changelog/v1.json +9 -0
  35. package/package.json +1 -1
  36. package/src/features/FileViewer/Renderer/PDF/index.tsx +2 -3
  37. package/src/features/ShareModal/SharePdf/PdfPreview.tsx +1 -2
  38. package/src/libs/pdfjs/index.tsx +25 -0
  39. package/src/store/test-coverage.md +5 -5
  40. package/.cursor/rules/add-provider-doc.mdc +0 -183
  41. package/.cursor/rules/add-setting-env.mdc +0 -175
  42. package/.cursor/rules/cursor-rules.mdc +0 -28
  43. package/.cursor/rules/db-migrations.mdc +0 -46
  44. package/.cursor/rules/debug-usage.mdc +0 -86
  45. package/.cursor/rules/desktop-controller-tests.mdc +0 -189
  46. package/.cursor/rules/desktop-feature-implementation.mdc +0 -155
  47. package/.cursor/rules/desktop-local-tools-implement.mdc +0 -81
  48. package/.cursor/rules/desktop-menu-configuration.mdc +0 -209
  49. package/.cursor/rules/desktop-window-management.mdc +0 -301
  50. package/.cursor/rules/drizzle-schema-style-guide.mdc +0 -218
  51. package/.cursor/rules/hotkey.mdc +0 -162
  52. package/.cursor/rules/linear.mdc +0 -53
  53. package/.cursor/rules/microcopy-cn.mdc +0 -158
  54. package/.cursor/rules/microcopy-en.mdc +0 -148
  55. package/.cursor/rules/modal-imperative.mdc +0 -162
  56. package/.cursor/rules/packages/react-layout-kit.mdc +0 -122
  57. package/.cursor/rules/project-introduce.mdc +0 -36
  58. package/.cursor/rules/react.mdc +0 -169
  59. package/.cursor/rules/recent-data-usage.mdc +0 -139
  60. package/.cursor/rules/rules-index.mdc +0 -44
  61. package/.cursor/rules/testing-guide/agent-runtime-e2e.mdc +0 -285
  62. package/.cursor/rules/testing-guide/db-model-test.mdc +0 -455
  63. package/.cursor/rules/testing-guide/electron-ipc-test.mdc +0 -80
  64. package/.cursor/rules/testing-guide/testing-guide.mdc +0 -534
  65. package/.cursor/rules/testing-guide/zustand-store-action-test.mdc +0 -574
  66. package/.cursor/rules/typescript.mdc +0 -55
  67. package/.cursor/rules/zustand-action-patterns.mdc +0 -328
  68. package/.cursor/rules/zustand-slice-organization.mdc +0 -308
  69. package/src/libs/pdfjs/pdf.worker.ts +0 -1
  70. package/src/libs/pdfjs/worker.ts +0 -12
  71. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/AGENTS.md +0 -0
  72. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/SKILL.md +0 -0
  73. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/advanced-event-handler-refs.md +0 -0
  74. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/advanced-use-latest.md +0 -0
  75. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/async-api-routes.md +0 -0
  76. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/async-defer-await.md +0 -0
  77. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/async-dependencies.md +0 -0
  78. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/async-parallel.md +0 -0
  79. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/async-suspense-boundaries.md +0 -0
  80. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/bundle-barrel-imports.md +0 -0
  81. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/bundle-conditional.md +0 -0
  82. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/bundle-defer-third-party.md +0 -0
  83. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/bundle-dynamic-imports.md +0 -0
  84. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/bundle-preload.md +0 -0
  85. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/client-event-listeners.md +0 -0
  86. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/client-localstorage-schema.md +0 -0
  87. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/client-passive-event-listeners.md +0 -0
  88. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/client-swr-dedup.md +0 -0
  89. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-batch-dom-css.md +0 -0
  90. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-cache-function-results.md +0 -0
  91. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-cache-property-access.md +0 -0
  92. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-cache-storage.md +0 -0
  93. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-combine-iterations.md +0 -0
  94. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-early-exit.md +0 -0
  95. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-hoist-regexp.md +0 -0
  96. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-index-maps.md +0 -0
  97. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-length-check-first.md +0 -0
  98. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-min-max-loop.md +0 -0
  99. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-set-map-lookups.md +0 -0
  100. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-tosorted-immutable.md +0 -0
  101. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rendering-activity.md +0 -0
  102. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rendering-animate-svg-wrapper.md +0 -0
  103. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rendering-conditional-render.md +0 -0
  104. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rendering-content-visibility.md +0 -0
  105. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rendering-hoist-jsx.md +0 -0
  106. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rendering-hydration-no-flicker.md +0 -0
  107. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rendering-svg-precision.md +0 -0
  108. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rerender-defer-reads.md +0 -0
  109. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rerender-dependencies.md +0 -0
  110. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rerender-derived-state.md +0 -0
  111. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rerender-functional-setstate.md +0 -0
  112. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rerender-lazy-state-init.md +0 -0
  113. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rerender-memo.md +0 -0
  114. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rerender-transitions.md +0 -0
  115. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/server-after-nonblocking.md +0 -0
  116. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/server-cache-lru.md +0 -0
  117. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/server-cache-react.md +0 -0
  118. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/server-parallel-fetching.md +0 -0
  119. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/server-serialization.md +0 -0
@@ -1,328 +0,0 @@
1
- ---
2
- description:
3
- globs: src/store/**
4
- alwaysApply: false
5
- ---
6
-
7
- # LobeChat Zustand Action Patterns
8
-
9
- ## Action Type Hierarchy
10
-
11
- LobeChat Actions use a layered architecture with clear separation of responsibilities:
12
-
13
- ### 1. Public Actions
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`
20
-
21
- ```typescript
22
- // Public Action example
23
- createTopic: async () => {
24
- // ...
25
- return topicId;
26
- },
27
- ```
28
-
29
- ### 2. Internal Actions (`internal_*`)
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
36
-
37
- ```typescript
38
- // Internal Action example - Optimistic update pattern
39
- internal_createTopic: async (params) => {
40
- const tmpId = Date.now().toString();
41
-
42
- // 1. Immediately update frontend state (optimistic update)
43
- get().internal_dispatchTopic(
44
- { type: 'addTopic', value: { ...params, id: tmpId } },
45
- 'internal_createTopic',
46
- );
47
- get().internal_updateTopicLoading(tmpId, true);
48
-
49
- // 2. Call backend service
50
- const topicId = await topicService.createTopic(params);
51
- get().internal_updateTopicLoading(tmpId, false);
52
-
53
- // 3. Refresh data to ensure consistency
54
- get().internal_updateTopicLoading(topicId, true);
55
- await get().refreshTopic();
56
- get().internal_updateTopicLoading(topicId, false);
57
-
58
- return topicId;
59
- },
60
- ```
61
-
62
- ### 3. Dispatch Methods (`internal_dispatch*`)
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
68
-
69
- ```typescript
70
- // Dispatch Method example
71
- internal_dispatchTopic: (payload, action) => {
72
- const nextTopics = topicReducer(topicSelectors.currentTopics(get()), payload);
73
- const nextMap = { ...get().topicMaps, [get().activeId]: nextTopics };
74
-
75
- if (isEqual(nextMap, get().topicMaps)) return;
76
-
77
- set({ topicMaps: nextMap }, false, action ?? n(`dispatchTopic/${payload.type}`));
78
- },
79
- ```
80
-
81
- ## When to Use Reducer Pattern vs. Simple `set`
82
-
83
- ### Use Reducer Pattern When
84
-
85
- Suitable for complex data structure management, especially:
86
-
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
91
-
92
- ```typescript
93
- // Reducer pattern example - Complex message state management
94
- export const messagesReducer = (state: ChatMessage[], payload: MessageDispatch): ChatMessage[] => {
95
- switch (payload.type) {
96
- case 'updateMessage': {
97
- return produce(state, (draftState) => {
98
- const index = draftState.findIndex((i) => i.id === payload.id);
99
- if (index < 0) return;
100
- draftState[index] = merge(draftState[index], {
101
- ...payload.value,
102
- updatedAt: Date.now(),
103
- });
104
- });
105
- }
106
- case 'createMessage': {
107
- // ...
108
- }
109
- // ...other complex state transitions
110
- }
111
- };
112
- ```
113
-
114
- ### Use Simple `set` When
115
-
116
- Suitable for simple state updates:
117
-
118
- - Toggling boolean values
119
- - Updating simple strings/numbers
120
- - Setting single state fields
121
-
122
- ```typescript
123
- // Simple set example
124
- updateInputMessage: (message) => {
125
- if (isEqual(message, get().inputMessage)) return;
126
- set({ inputMessage: message }, false, n('updateInputMessage'));
127
- },
128
-
129
- togglePortal: (open?: boolean) => {
130
- set({ showPortal: open ?? !get().showPortal }, false, 'togglePortal');
131
- },
132
- ```
133
-
134
- ## Optimistic Update Implementation Patterns
135
-
136
- Optimistic updates are a core pattern in LobeChat for providing smooth user experience:
137
-
138
- ### Standard Optimistic Update Flow
139
-
140
- ```typescript
141
- // Complete optimistic update example
142
- internal_updateMessageContent: async (id, content, extra) => {
143
- const { internal_dispatchMessage, refreshMessages } = get();
144
-
145
- // 1. Immediately update frontend state (optimistic update)
146
- internal_dispatchMessage({
147
- id,
148
- type: 'updateMessage',
149
- value: { content },
150
- });
151
-
152
- // 2. Call backend service
153
- await messageService.updateMessage(id, {
154
- content,
155
- tools: extra?.toolCalls ? internal_transformToolCalls(extra.toolCalls) : undefined,
156
- // ...other fields
157
- });
158
-
159
- // 3. Refresh to ensure data consistency
160
- await refreshMessages();
161
- },
162
- ```
163
-
164
- ### Optimistic Update for Create Operations
165
-
166
- ```typescript
167
- internal_createMessage: async (message, context) => {
168
- const { internal_createTmpMessage, refreshMessages, internal_toggleMessageLoading } = get();
169
-
170
- let tempId = context?.tempMessageId;
171
- if (!tempId) {
172
- // Create temporary message for optimistic update
173
- tempId = internal_createTmpMessage(message);
174
- internal_toggleMessageLoading(true, tempId);
175
- }
176
-
177
- try {
178
- const id = await messageService.createMessage(message);
179
- if (!context?.skipRefresh) {
180
- await refreshMessages();
181
- }
182
- internal_toggleMessageLoading(false, tempId);
183
- return id;
184
- } catch (e) {
185
- internal_toggleMessageLoading(false, tempId);
186
- // Error handling: update message error state
187
- internal_dispatchMessage({
188
- id: tempId,
189
- type: 'updateMessage',
190
- value: { error: { type: ChatErrorType.CreateMessageError, message: e.message } },
191
- });
192
- }
193
- },
194
- ```
195
-
196
- ### Delete Operation Pattern (No Optimistic Update)
197
-
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
203
-
204
- ```typescript
205
- // Standard delete operation pattern - No optimistic update
206
- removeGenerationTopic: async (id: string) => {
207
- const { internal_removeGenerationTopic } = get();
208
- await internal_removeGenerationTopic(id);
209
- },
210
-
211
- internal_removeGenerationTopic: async (id: string) => {
212
- // 1. Show loading state
213
- get().internal_updateGenerationTopicLoading(id, true);
214
-
215
- try {
216
- // 2. Directly call backend service
217
- await generationTopicService.deleteTopic(id);
218
-
219
- // 3. Refresh data to get latest state
220
- await get().refreshGenerationTopics();
221
- } finally {
222
- // 4. Ensure loading state is cleared (whether success or failure)
223
- get().internal_updateGenerationTopicLoading(id, false);
224
- }
225
- },
226
- ```
227
-
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
234
-
235
- ## Loading State Management Pattern
236
-
237
- LobeChat uses a unified loading state management pattern:
238
-
239
- ### Array-based Loading State
240
-
241
- ```typescript
242
- // Define in initialState.ts
243
- export interface ChatMessageState {
244
- messageEditingIds: string[]; // Message editing state
245
- }
246
-
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
- }
257
- ```
258
-
259
- ## SWR Integration Pattern
260
-
261
- LobeChat uses SWR for data fetching and cache management:
262
-
263
- ### Hook-based Data Fetching
264
-
265
- ```typescript
266
- // Define SWR hook in action.ts
267
- useFetchMessages: (enable, sessionId, activeTopicId) =>
268
- useClientDataSWR<ChatMessage[]>(
269
- enable ? [SWR_USE_FETCH_MESSAGES, sessionId, activeTopicId] : null,
270
- async ([, sessionId, topicId]) => messageService.getMessages(sessionId, topicId),
271
- {
272
- onSuccess: (messages, key) => {
273
- const nextMap = {
274
- ...get().messagesMap,
275
- [messageMapKey(sessionId, activeTopicId)]: messages,
276
- };
277
-
278
- if (get().messagesInit && isEqual(nextMap, get().messagesMap)) return;
279
-
280
- set({ messagesInit: true, messagesMap: nextMap }, false, n('useFetchMessages'));
281
- },
282
- },
283
- ),
284
- ```
285
-
286
- ### Cache Invalidation and Refresh
287
-
288
- ```typescript
289
- // Standard data refresh pattern
290
- refreshMessages: async () => {
291
- await mutate([SWR_USE_FETCH_MESSAGES, get().activeId, get().activeTopicId]);
292
- };
293
- ```
294
-
295
- ## Naming Convention Summary
296
-
297
- ### Action Naming Patterns
298
-
299
- - Public Actions: Verb form, describing user intent
300
- - `createTopic`, `sendMessage`, `regenerateMessage`
301
- - Internal Actions: `internal_` + verb, describing internal operation
302
- - `internal_createTopic`, `internal_updateMessageContent`
303
- - Dispatch Methods: `internal_dispatch` + entity name
304
- - `internal_dispatchTopic`, `internal_dispatchMessage`
305
- - Toggle Methods: `internal_toggle` + state name
306
- - `internal_toggleMessageLoading`, `internal_toggleChatLoading`
307
-
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,308 +0,0 @@
1
- ---
2
- description:
3
- globs: src/store/**
4
- alwaysApply: false
5
- ---
6
-
7
- # LobeChat Zustand Store Slice 组织架构
8
-
9
- 本文档描述了 LobeChat 项目中 Zustand Store 的模块化 Slice 组织方式,展示如何通过分片架构管理复杂的应用状态。
10
-
11
- ## 顶层 Store 结构
12
-
13
- LobeChat 的 `chat` store (`src/store/chat/`) 采用模块化的 slice 结构来组织状态和逻辑。
14
-
15
- ### 关键聚合文件
16
-
17
- - `src/store/chat/initialState.ts`: 聚合所有 slice 的初始状态
18
- - `src/store/chat/store.ts`: 定义顶层的 `ChatStore`,组合所有 slice 的 actions
19
- - `src/store/chat/selectors.ts`: 统一导出所有 slice 的 selectors
20
- - `src/store/chat/helpers.ts`: 提供聊天相关的辅助函数
21
-
22
- ### Store 聚合模式
23
-
24
- ```typescript
25
- // src/store/chat/initialState.ts
26
- import { ChatTopicState, initialTopicState } from './slices/topic/initialState';
27
- import { ChatMessageState, initialMessageState } from './slices/message/initialState';
28
- import { ChatAIChatState, initialAiChatState } from './slices/aiChat/initialState';
29
-
30
- export type ChatStoreState = ChatTopicState &
31
- ChatMessageState &
32
- ChatAIChatState &
33
- // ...其他 slice states
34
-
35
- export const initialState: ChatStoreState = {
36
- ...initialMessageState,
37
- ...initialTopicState,
38
- ...initialAiChatState,
39
- // ...其他 initial slice states
40
- };
41
- ```
42
-
43
- ```typescript
44
- // src/store/chat/store.ts
45
- import { ChatMessageAction, chatMessage } from './slices/message/action';
46
- import { ChatTopicAction, chatTopic } from './slices/topic/action';
47
- import { ChatAIChatAction, chatAiChat } from './slices/aiChat/actions';
48
-
49
- export interface ChatStoreAction
50
- extends ChatMessageAction,
51
- ChatTopicAction,
52
- ChatAIChatAction,
53
- // ...其他 slice actions
54
-
55
- const createStore: StateCreator<ChatStore, [['zustand/devtools', never]]> = (...params) => ({
56
- ...initialState,
57
- ...chatMessage(...params),
58
- ...chatTopic(...params),
59
- ...chatAiChat(...params),
60
- // ...其他 slice action creators
61
- });
62
-
63
- export const useChatStore = createWithEqualityFn<ChatStore>()(
64
- subscribeWithSelector(devtools(createStore)),
65
- shallow,
66
- );
67
- ```
68
-
69
- ## 单个 Slice 的标准结构
70
-
71
- 每个 slice 位于 `src/store/chat/slices/[sliceName]/` 目录下:
72
-
73
- ```plaintext
74
- src/store/chat/slices/
75
- └── [sliceName]/ # 例如 message, topic, aiChat, builtinTool
76
- ├── action.ts # 定义 actions (或者是一个 actions/ 目录)
77
- ├── initialState.ts # 定义 state 结构和初始值
78
- ├── reducer.ts # (可选) 如果使用 reducer 模式
79
- ├── selectors.ts # 定义 selectors
80
- └── index.ts # (可选) 重新导出模块内容
81
- ```
82
-
83
- ### 文件职责说明
84
-
85
- 1. `initialState.ts`:
86
- - 定义 slice 的 TypeScript 状态接口
87
- - 提供初始状态默认值
88
-
89
- ```typescript
90
- // 典型的 initialState.ts 结构
91
- export interface ChatTopicState {
92
- activeTopicId?: string;
93
- topicMaps: Record<string, ChatTopic[]>; // 核心数据结构
94
- topicsInit: boolean;
95
- topicLoadingIds: string[];
96
- // ...其他状态字段
97
- }
98
-
99
- export const initialTopicState: ChatTopicState = {
100
- activeTopicId: undefined,
101
- topicMaps: {},
102
- topicsInit: false,
103
- topicLoadingIds: [],
104
- // ...其他初始值
105
- };
106
- ```
107
-
108
- 1. `reducer.ts` (复杂状态使用):
109
- - 定义纯函数 reducer,处理同步状态转换
110
- - 使用 `immer` 确保不可变更新
111
-
112
- ```typescript
113
- // 典型的 reducer.ts 结构
114
- import { produce } from 'immer';
115
-
116
- interface AddChatTopicAction {
117
- type: 'addTopic';
118
- value: CreateTopicParams & { id?: string };
119
- }
120
-
121
- interface UpdateChatTopicAction {
122
- id: string;
123
- type: 'updateTopic';
124
- value: Partial<ChatTopic>;
125
- }
126
-
127
- export type ChatTopicDispatch = AddChatTopicAction | UpdateChatTopicAction;
128
-
129
- export const topicReducer = (state: ChatTopic[] = [], payload: ChatTopicDispatch): ChatTopic[] => {
130
- switch (payload.type) {
131
- case 'addTopic': {
132
- return produce(state, (draftState) => {
133
- draftState.unshift({
134
- ...payload.value,
135
- id: payload.value.id ?? Date.now().toString(),
136
- createdAt: Date.now(),
137
- });
138
- });
139
- }
140
- case 'updateTopic': {
141
- return produce(state, (draftState) => {
142
- const index = draftState.findIndex((topic) => topic.id === payload.id);
143
- if (index !== -1) {
144
- draftState[index] = { ...draftState[index], ...payload.value };
145
- }
146
- });
147
- }
148
- default:
149
- return state;
150
- }
151
- };
152
- ```
153
-
154
- 1. `selectors.ts`:
155
- - 提供状态查询和计算函数
156
- - 供 UI 组件使用的状态订阅接口
157
- - 重要: 使用 `export const xxxSelectors` 模式聚合所有 selectors
158
-
159
- ```typescript
160
- // 典型的 selectors.ts 结构
161
- import { ChatStoreState } from '../../initialState';
162
-
163
- const currentTopics = (s: ChatStoreState): ChatTopic[] | undefined => s.topicMaps[s.activeId];
164
-
165
- const currentActiveTopic = (s: ChatStoreState): ChatTopic | undefined => {
166
- return currentTopics(s)?.find((topic) => topic.id === s.activeTopicId);
167
- };
168
-
169
- const getTopicById =
170
- (id: string) =>
171
- (s: ChatStoreState): ChatTopic | undefined =>
172
- currentTopics(s)?.find((topic) => topic.id === id);
173
-
174
- // 核心模式:使用 xxxSelectors 聚合导出
175
- export const topicSelectors = {
176
- currentActiveTopic,
177
- currentTopics,
178
- getTopicById,
179
- // ...其他 selectors
180
- };
181
- ```
182
-
183
- ## 特殊 Slice 组织模式
184
-
185
- ### 复杂 Actions 的子目录结构 (aiChat Slice)
186
-
187
- 当 slice 的 actions 过于复杂时,可以拆分到子目录:
188
-
189
- ```plaintext
190
- src/store/chat/slices/aiChat/
191
- ├── actions/
192
- │ ├── generateAIChat.ts # AI 对话生成
193
- │ ├── rag.ts # RAG 检索增强生成
194
- │ ├── memory.ts # 对话记忆管理
195
- │ └── index.ts # 聚合所有 actions
196
- ├── initialState.ts
197
- ├── selectors.ts
198
- └── index.ts
199
- ```
200
-
201
- 参考:`src/store/chat/slices/aiChat/actions/`
202
-
203
- ### 工具类 Slice (builtinTool)
204
-
205
- 管理多种内置工具的状态:
206
-
207
- ```plaintext
208
- src/store/chat/slices/builtinTool/
209
- ├── actions/
210
- │ ├── dalle.ts # DALL-E 图像生成
211
- │ ├── search.ts # 搜索功能
212
- │ ├── localFile.ts # 本地文件操作
213
- │ └── index.ts
214
- ├── initialState.ts
215
- ├── selectors.ts
216
- └── index.ts
217
- ```
218
-
219
- 参考:`src/store/chat/slices/builtinTool/`
220
-
221
- ## 状态设计模式
222
-
223
- ### 1. Map 结构用于关联数据
224
-
225
- ```typescript
226
- // 以 sessionId 为 key,管理多个会话的数据
227
- topicMaps: Record<string, ChatTopic[]>;
228
- messagesMap: Record<string, ChatMessage[]>;
229
- ```
230
-
231
- ### 2. 数组用于加载状态管理
232
-
233
- ```typescript
234
- // 管理多个并发操作的加载状态
235
- messageLoadingIds: string[]
236
- topicLoadingIds: string[]
237
- chatLoadingIds: string[]
238
- ```
239
-
240
- ### 3. 可选字段用于当前活动项
241
-
242
- ```typescript
243
- // 当前激活的实体 ID
244
- activeId: string
245
- activeTopicId?: string
246
- activeThreadId?: string
247
- ```
248
-
249
- ## Slice 集成到顶层 Store
250
-
251
- ### 1. 状态聚合
252
-
253
- ```typescript
254
- // 在 initialState.ts 中
255
- export type ChatStoreState = ChatTopicState &
256
- ChatMessageState &
257
- ChatAIChatState &
258
- // ...其他 slice states
259
- ```
260
-
261
- ### 2. Action 接口聚合
262
-
263
- ```typescript
264
- // 在 store.ts 中
265
- export interface ChatStoreAction
266
- extends ChatMessageAction,
267
- ChatTopicAction,
268
- ChatAIChatAction,
269
- // ...其他 slice actions
270
- ```
271
-
272
- ### 3. Selector 统一导出
273
-
274
- ```typescript
275
- // 在 selectors.ts 中 - 统一聚合 selectors
276
- export { chatSelectors } from './slices/message/selectors';
277
- export { topicSelectors } from './slices/topic/selectors';
278
- export { aiChatSelectors } from './slices/aiChat/selectors';
279
-
280
- // 每个 slice 的 selectors.ts 都使用 xxxSelectors 模式:
281
- // export const chatSelectors = { ... }
282
- // export const topicSelectors = { ... }
283
- // export const aiChatSelectors = { ... }
284
- ```
285
-
286
- ## 最佳实践
287
-
288
- 1. Slice 划分原则:
289
- - 按功能领域划分(message, topic, aiChat 等)
290
- - 每个 slice 管理相关的状态和操作
291
- - 避免 slice 之间的强耦合
292
-
293
- 2. 文件命名规范:
294
- - 使用小驼峰命名 slice 目录
295
- - 文件名使用一致的模式(action.ts, selectors.ts 等)
296
- - 复杂 actions 时使用 actions/ 子目录
297
-
298
- 3. 状态结构设计:
299
- - 扁平化的状态结构,避免深层嵌套
300
- - 使用 Map 结构管理列表数据
301
- - 分离加载状态和业务数据
302
-
303
- 4. 类型安全:
304
- - 为每个 slice 定义清晰的 TypeScript 接口
305
- - 使用 Zustand 的 StateCreator 确保类型一致性
306
- - 在顶层聚合时保持类型安全
307
-
308
- 这种模块化的 slice 组织方式使得大型应用的状态管理变得清晰、可维护,并且易于扩展。
@@ -1 +0,0 @@
1
- import 'pdfjs-dist/build/pdf.worker.min.mjs';
@@ -1,12 +0,0 @@
1
- 'use client';
2
-
3
- import { pdfjs } from 'react-pdf';
4
-
5
- pdfjs.GlobalWorkerOptions.workerSrc = `https://registry.npmmirror.com/pdfjs-dist/${pdfjs.version}/files/build/pdf.worker.min.mjs`;
6
-
7
- // TODO: Re-enable module worker when fully on Turbopack.
8
- // if (typeof Worker !== 'undefined' && !pdfjs.GlobalWorkerOptions.workerPort) {
9
- // pdfjs.GlobalWorkerOptions.workerPort = new Worker(new URL('./pdf.worker.ts', import.meta.url), {
10
- // type: 'module',
11
- // });
12
- // }