@lobehub/lobehub 2.0.0-next.249 → 2.0.0-next.250

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 (87) hide show
  1. package/.github/workflows/release.yml +4 -0
  2. package/CHANGELOG.md +25 -0
  3. package/changelog/v1.json +5 -0
  4. package/locales/en-US/file.json +2 -0
  5. package/locales/zh-CN/discover.json +3 -0
  6. package/locales/zh-CN/file.json +2 -0
  7. package/package.json +3 -3
  8. package/packages/types/package.json +2 -2
  9. package/packages/types/src/discover/mcp.ts +3 -1
  10. package/src/app/[variants]/(main)/community/(list)/(home)/index.tsx +2 -0
  11. package/src/app/[variants]/(main)/community/(list)/features/SortButton/index.tsx +4 -0
  12. package/src/app/[variants]/(main)/community/(list)/mcp/features/Category/index.tsx +7 -3
  13. package/src/app/[variants]/(main)/community/(list)/mcp/index.tsx +2 -2
  14. package/src/app/[variants]/(main)/home/_layout/Body/Project/List/Editing.tsx +1 -1
  15. package/src/app/[variants]/(main)/home/_layout/Body/Project/List/Item.tsx +1 -1
  16. package/src/app/[variants]/(main)/home/_layout/Body/Project/List/index.tsx +1 -1
  17. package/src/app/[variants]/(main)/home/_layout/Body/Project/List/useDropdownMenu.tsx +1 -1
  18. package/src/app/[variants]/(main)/resource/(home)/_layout/Body/LibraryList/{List/Item → Item}/Editing.tsx +1 -1
  19. package/src/app/[variants]/(main)/resource/(home)/_layout/Body/LibraryList/{List/Item → Item}/index.tsx +1 -1
  20. package/src/app/[variants]/(main)/resource/(home)/_layout/Body/LibraryList/{List/Item → Item}/useDropdownMenu.tsx +1 -1
  21. package/src/app/[variants]/(main)/resource/(home)/_layout/Body/LibraryList/index.tsx +16 -6
  22. package/src/app/[variants]/(main)/resource/(home)/_layout/Body/{KnowledgeBase.tsx → index.tsx} +2 -3
  23. package/src/app/[variants]/(main)/resource/(home)/_layout/Sidebar.tsx +2 -2
  24. package/src/app/[variants]/(main)/resource/(home)/index.tsx +23 -10
  25. package/src/app/[variants]/(main)/resource/features/DndContextWrapper.tsx +12 -37
  26. package/src/app/[variants]/(main)/resource/features/hooks/useKnowledgeItem.ts +1 -1
  27. package/src/app/[variants]/(main)/resource/features/store/action.ts +9 -39
  28. package/src/app/[variants]/(main)/resource/library/_layout/Header/LibraryHead.tsx +1 -1
  29. package/src/app/[variants]/(main)/resource/library/index.tsx +13 -6
  30. package/src/features/LibraryModal/AddFilesToKnowledgeBase/SelectForm.tsx +1 -1
  31. package/src/features/LibraryModal/CreateNew/CreateForm.tsx +1 -1
  32. package/src/features/PageEditor/Header/Breadcrumb.tsx +1 -1
  33. package/src/features/PageEditor/store/action.ts +5 -2
  34. package/src/features/PageExplorer/PageExplorerPlaceholder.tsx +5 -7
  35. package/src/features/ResourceManager/components/Explorer/Header/Breadcrumb.tsx +1 -1
  36. package/src/features/ResourceManager/components/Explorer/ItemDropdown/useFileItemDropdown.tsx +57 -26
  37. package/src/features/ResourceManager/components/Explorer/ListView/ListItem/index.tsx +35 -6
  38. package/src/features/ResourceManager/components/Explorer/ListView/Skeleton.tsx +20 -14
  39. package/src/features/ResourceManager/components/Explorer/ListView/index.tsx +41 -31
  40. package/src/features/ResourceManager/components/Explorer/MasonryView/MasonryFileItem/index.tsx +1 -1
  41. package/src/features/ResourceManager/components/Explorer/MasonryView/Skeleton.tsx +6 -2
  42. package/src/features/ResourceManager/components/Explorer/MasonryView/index.tsx +29 -18
  43. package/src/features/ResourceManager/components/Explorer/MoveToFolderModal.tsx +7 -34
  44. package/src/features/ResourceManager/components/Explorer/ToolBar/BatchActionsDropdown.tsx +1 -1
  45. package/src/features/ResourceManager/components/Explorer/index.tsx +58 -18
  46. package/src/features/ResourceManager/components/Explorer/useCheckTaskStatus.ts +6 -4
  47. package/src/features/ResourceManager/components/Header/AddButton.tsx +58 -35
  48. package/src/features/ResourceManager/components/Header/hooks/useNotionImport.ts +5 -5
  49. package/src/features/ResourceManager/components/Tree/TreeSkeleton.tsx +19 -9
  50. package/src/features/ResourceManager/components/Tree/index.tsx +110 -5
  51. package/src/features/ResourceManager/components/UploadDock/index.tsx +2 -1
  52. package/src/features/ResourceManager/constants.ts +3 -0
  53. package/src/hooks/useMCPCategory.tsx +7 -0
  54. package/src/locales/default/discover.ts +3 -0
  55. package/src/locales/default/file.ts +2 -0
  56. package/src/services/file/index.ts +34 -1
  57. package/src/services/resource/index.ts +249 -0
  58. package/src/store/discover/slices/mcp/action.ts +1 -1
  59. package/src/store/file/slices/chat/action.ts +2 -1
  60. package/src/store/file/slices/document/action.ts +10 -7
  61. package/src/store/file/slices/fileManager/action.ts +14 -4
  62. package/src/store/file/slices/fileManager/initialState.ts +2 -0
  63. package/src/store/file/slices/resource/action.ts +432 -0
  64. package/src/store/file/slices/resource/hooks.ts +82 -0
  65. package/src/store/file/slices/resource/initialState.ts +67 -0
  66. package/src/store/file/slices/resource/syncEngine.ts +326 -0
  67. package/src/store/file/store.ts +6 -1
  68. package/src/store/{knowledgeBase → library}/initialState.ts +2 -2
  69. package/src/store/{knowledgeBase → library}/slices/content/action.test.ts +37 -51
  70. package/src/store/{knowledgeBase → library}/slices/content/action.ts +8 -4
  71. package/src/store/{knowledgeBase → library}/slices/crud/action.ts +1 -1
  72. package/src/store/{knowledgeBase → library}/slices/crud/selectors.ts +1 -1
  73. package/src/store/{knowledgeBase → library}/slices/ragEval/actions/dataset.ts +1 -1
  74. package/src/store/{knowledgeBase → library}/slices/ragEval/actions/evaluation.ts +1 -1
  75. package/src/store/{knowledgeBase → library}/slices/ragEval/actions/index.ts +1 -1
  76. package/src/types/resource.ts +133 -0
  77. package/src/app/[variants]/(main)/resource/(home)/_layout/Body/LibraryList/List/index.tsx +0 -25
  78. /package/src/app/[variants]/(main)/resource/(home)/_layout/Body/LibraryList/{List/Item → Item}/Actions.tsx +0 -0
  79. /package/src/store/{knowledgeBase → library}/index.ts +0 -0
  80. /package/src/store/{knowledgeBase → library}/selectors.ts +0 -0
  81. /package/src/store/{knowledgeBase → library}/slices/content/index.ts +0 -0
  82. /package/src/store/{knowledgeBase → library}/slices/crud/action.test.ts +0 -0
  83. /package/src/store/{knowledgeBase → library}/slices/crud/index.ts +0 -0
  84. /package/src/store/{knowledgeBase → library}/slices/crud/initialState.ts +0 -0
  85. /package/src/store/{knowledgeBase → library}/slices/ragEval/index.ts +0 -0
  86. /package/src/store/{knowledgeBase → library}/slices/ragEval/initialState.ts +0 -0
  87. /package/src/store/{knowledgeBase → library}/store.ts +0 -0
@@ -0,0 +1,326 @@
1
+ import { debounce } from 'es-toolkit';
2
+ import type { DebouncedFunc } from 'es-toolkit/compat';
3
+ import pMap from 'p-map';
4
+
5
+ import { resourceService } from '@/services/resource';
6
+ import type { ResourceItem, SyncOperation } from '@/types/resource';
7
+
8
+ /**
9
+ * Sync configuration
10
+ */
11
+ const SYNC_CONFIG = {
12
+ BACKOFF_MULTIPLIER: 2,
13
+ BATCH_SIZE: 10,
14
+ CONCURRENCY: 3,
15
+ DEBOUNCE_MS: 300, // Wait after last operation
16
+ MAX_RETRIES: 3,
17
+ MAX_WAIT_MS: 2000, // Force sync after 2s
18
+ RETRY_DELAY_MS: 1000,
19
+ } as const;
20
+
21
+ /**
22
+ * State getter/setter interface
23
+ */
24
+ interface StateManager {
25
+ getState: () => {
26
+ resourceList: ResourceItem[];
27
+ resourceMap: Map<string, ResourceItem>;
28
+ syncQueue: SyncOperation[];
29
+ syncingIds: Set<string>;
30
+ };
31
+ setState: (partial: {
32
+ resourceList?: ResourceItem[];
33
+ resourceMap?: Map<string, ResourceItem>;
34
+ syncQueue?: SyncOperation[];
35
+ syncingIds?: Set<string>;
36
+ }) => void;
37
+ }
38
+
39
+ /**
40
+ * ResourceSyncEngine - Background sync engine for resource operations
41
+ *
42
+ * Features:
43
+ * - Debounced sync (300ms, max 2s)
44
+ * - Batch processing (10 operations per batch)
45
+ * - Concurrency control (3 parallel requests)
46
+ * - Retry logic with exponential backoff (3 attempts)
47
+ * - Error state marking (no rollback)
48
+ */
49
+ export class ResourceSyncEngine {
50
+ private debouncedSync: DebouncedFunc<() => Promise<void>>;
51
+ private stateManager: StateManager;
52
+
53
+ constructor(getState: StateManager['getState'], setState: StateManager['setState']) {
54
+ this.stateManager = { getState, setState };
55
+
56
+ this.debouncedSync = debounce(() => this.processQueue(), SYNC_CONFIG.DEBOUNCE_MS, {
57
+ edges: ['trailing'],
58
+ }) as DebouncedFunc<() => Promise<void>>;
59
+
60
+ // Add maxWait behavior manually since es-toolkit debounce doesn't support it
61
+ let lastExecution = 0;
62
+ const originalSync = this.debouncedSync;
63
+
64
+ this.debouncedSync = (() => {
65
+ const now = Date.now();
66
+ if (now - lastExecution >= SYNC_CONFIG.MAX_WAIT_MS) {
67
+ lastExecution = now;
68
+ originalSync.flush?.();
69
+ return originalSync();
70
+ } else {
71
+ return originalSync();
72
+ }
73
+ }) as unknown as DebouncedFunc<() => Promise<void>>;
74
+
75
+ this.debouncedSync.flush = originalSync.flush;
76
+ this.debouncedSync.cancel = originalSync.cancel;
77
+ }
78
+
79
+ /**
80
+ * Enqueue a sync operation and return a Promise that resolves when it completes
81
+ * For 'create' operations, resolves with the real resource ID
82
+ * For other operations, resolves with void
83
+ */
84
+ enqueue(operation: Omit<SyncOperation, 'resolve' | 'reject'>): Promise<any> {
85
+ return new Promise((resolve, reject) => {
86
+ const { syncQueue } = this.stateManager.getState();
87
+ const operationWithPromise: SyncOperation = {
88
+ ...operation,
89
+ reject,
90
+ resolve,
91
+ };
92
+
93
+ this.stateManager.setState({
94
+ syncQueue: [...syncQueue, operationWithPromise],
95
+ });
96
+
97
+ // Trigger debounced sync
98
+ this.debouncedSync();
99
+ });
100
+ }
101
+
102
+ /**
103
+ * Flush pending operations immediately
104
+ */
105
+ async flush(): Promise<void> {
106
+ this.debouncedSync.flush?.();
107
+ await this.processQueue();
108
+ }
109
+
110
+ /**
111
+ * Process the sync queue
112
+ */
113
+ private async processQueue(): Promise<void> {
114
+ const { syncQueue } = this.stateManager.getState();
115
+
116
+ if (syncQueue.length === 0) return;
117
+
118
+ // Take batch from queue
119
+ const batch = syncQueue.slice(0, SYNC_CONFIG.BATCH_SIZE);
120
+ const remaining = syncQueue.slice(SYNC_CONFIG.BATCH_SIZE);
121
+
122
+ // Update queue (remove batch)
123
+ this.stateManager.setState({ syncQueue: remaining });
124
+
125
+ // Process batch with concurrency limit
126
+ await pMap(
127
+ batch,
128
+ async (operation) => {
129
+ try {
130
+ await this.processOperation(operation);
131
+ } catch (error) {
132
+ await this.handleOperationError(operation, error as Error);
133
+ }
134
+ },
135
+ { concurrency: SYNC_CONFIG.CONCURRENCY },
136
+ );
137
+
138
+ // Continue processing if there are more operations
139
+ if (remaining.length > 0) {
140
+ await this.processQueue();
141
+ }
142
+ }
143
+
144
+ /**
145
+ * Process a single sync operation
146
+ */
147
+ private async processOperation(operation: SyncOperation): Promise<void> {
148
+ const { resourceId, type, payload } = operation;
149
+
150
+ // Mark as syncing
151
+ const { syncingIds } = this.stateManager.getState();
152
+ syncingIds.add(resourceId);
153
+ this.stateManager.setState({ syncingIds: new Set(syncingIds) });
154
+
155
+ try {
156
+ let realId: string | undefined;
157
+
158
+ switch (type) {
159
+ case 'create': {
160
+ const created = await resourceService.createResource(payload);
161
+ this.replaceTempResource(resourceId, created);
162
+ realId = created.id;
163
+ break;
164
+ }
165
+
166
+ case 'update': {
167
+ const updated = await resourceService.updateResource(resourceId, payload);
168
+ this.updateResourceInStore(updated);
169
+ break;
170
+ }
171
+
172
+ case 'delete': {
173
+ await resourceService.deleteResource(resourceId);
174
+ // Resource already removed from store optimistically
175
+ break;
176
+ }
177
+
178
+ case 'move': {
179
+ await resourceService.moveResource(resourceId, payload.parentId);
180
+ // Don't update store - resource has already been removed optimistically
181
+ // and should stay removed since it moved to a different location
182
+ break;
183
+ }
184
+ }
185
+
186
+ // Clear optimistic state on success
187
+ this.clearOptimisticState(resourceId);
188
+
189
+ // Resolve promise for this operation (return real ID for create)
190
+ operation.resolve?.(realId);
191
+ } finally {
192
+ // Unmark as syncing
193
+ const { syncingIds: currentSyncingIds } = this.stateManager.getState();
194
+ currentSyncingIds.delete(resourceId);
195
+ this.stateManager.setState({ syncingIds: new Set(currentSyncingIds) });
196
+ }
197
+ }
198
+
199
+ /**
200
+ * Handle operation error
201
+ */
202
+ private async handleOperationError(operation: SyncOperation, error: Error): Promise<void> {
203
+ const { resourceId, retryCount } = operation;
204
+
205
+ if (retryCount < SYNC_CONFIG.MAX_RETRIES) {
206
+ // Retry: increment count and re-queue with delay
207
+ const delay = SYNC_CONFIG.RETRY_DELAY_MS * SYNC_CONFIG.BACKOFF_MULTIPLIER ** retryCount;
208
+
209
+ setTimeout(() => {
210
+ const { syncQueue } = this.stateManager.getState();
211
+ this.stateManager.setState({
212
+ syncQueue: [
213
+ ...syncQueue,
214
+ {
215
+ ...operation,
216
+ retryCount: retryCount + 1,
217
+ },
218
+ ],
219
+ });
220
+ this.debouncedSync();
221
+ }, delay);
222
+ } else {
223
+ // Max retries reached: mark resource with error state and reject promise
224
+ this.markResourceError(resourceId, error);
225
+ operation.reject?.(error);
226
+ }
227
+ }
228
+
229
+ /**
230
+ * Replace temp resource with real resource from server
231
+ */
232
+ private replaceTempResource(tempId: string, realResource: ResourceItem): void {
233
+ const { resourceMap, resourceList } = this.stateManager.getState();
234
+
235
+ // Remove temp from map, add real
236
+ resourceMap.delete(tempId);
237
+ resourceMap.set(realResource.id, realResource);
238
+
239
+ // Replace in list
240
+ const listIndex = resourceList.findIndex((r) => r.id === tempId);
241
+ if (listIndex >= 0) {
242
+ resourceList[listIndex] = realResource;
243
+ }
244
+
245
+ this.stateManager.setState({
246
+ resourceList: [...resourceList],
247
+ resourceMap: new Map(resourceMap),
248
+ });
249
+ }
250
+
251
+ /**
252
+ * Update resource in store with fresh data from server
253
+ */
254
+ private updateResourceInStore(resource: ResourceItem): void {
255
+ const { resourceMap, resourceList } = this.stateManager.getState();
256
+
257
+ resourceMap.set(resource.id, resource);
258
+
259
+ const listIndex = resourceList.findIndex((r) => r.id === resource.id);
260
+ if (listIndex >= 0) {
261
+ resourceList[listIndex] = resource;
262
+ }
263
+
264
+ this.stateManager.setState({
265
+ resourceList: [...resourceList],
266
+ resourceMap: new Map(resourceMap),
267
+ });
268
+ }
269
+
270
+ /**
271
+ * Clear optimistic state from resource
272
+ */
273
+ private clearOptimisticState(resourceId: string): void {
274
+ const { resourceMap, resourceList } = this.stateManager.getState();
275
+ const resource = resourceMap.get(resourceId);
276
+
277
+ if (resource?._optimistic) {
278
+ const updated = { ...resource };
279
+ delete updated._optimistic;
280
+
281
+ resourceMap.set(resourceId, updated);
282
+
283
+ const listIndex = resourceList.findIndex((r) => r.id === resourceId);
284
+ if (listIndex >= 0) {
285
+ resourceList[listIndex] = updated;
286
+ }
287
+
288
+ this.stateManager.setState({
289
+ resourceList: [...resourceList],
290
+ resourceMap: new Map(resourceMap),
291
+ });
292
+ }
293
+ }
294
+
295
+ /**
296
+ * Mark resource with error state
297
+ */
298
+ private markResourceError(resourceId: string, error: Error): void {
299
+ const { resourceMap, resourceList } = this.stateManager.getState();
300
+ const resource = resourceMap.get(resourceId);
301
+
302
+ if (resource) {
303
+ const updated = {
304
+ ...resource,
305
+ _optimistic: {
306
+ error,
307
+ isPending: false,
308
+ lastSyncAttempt: new Date(),
309
+ retryCount: SYNC_CONFIG.MAX_RETRIES,
310
+ },
311
+ };
312
+
313
+ resourceMap.set(resourceId, updated);
314
+
315
+ const listIndex = resourceList.findIndex((r) => r.id === resourceId);
316
+ if (listIndex >= 0) {
317
+ resourceList[listIndex] = updated;
318
+ }
319
+
320
+ this.stateManager.setState({
321
+ resourceList: [...resourceList],
322
+ resourceMap: new Map(resourceMap),
323
+ });
324
+ }
325
+ }
326
+ }
@@ -8,6 +8,8 @@ import { type FileAction, createFileSlice } from './slices/chat';
8
8
  import { type FileChunkAction, createFileChunkSlice } from './slices/chunk';
9
9
  import { type DocumentAction, createDocumentSlice } from './slices/document';
10
10
  import { type FileManageAction, createFileManageSlice } from './slices/fileManager';
11
+ import { type ResourceAction, createResourceSlice } from './slices/resource/action';
12
+ import { type ResourceState } from './slices/resource/initialState';
11
13
  import { type TTSFileAction, createTTSFileSlice } from './slices/tts';
12
14
  import { type FileUploadAction, createFileUploadSlice } from './slices/upload/action';
13
15
 
@@ -19,7 +21,9 @@ export type FileStore = FilesStoreState &
19
21
  TTSFileAction &
20
22
  FileManageAction &
21
23
  FileChunkAction &
22
- FileUploadAction;
24
+ FileUploadAction &
25
+ ResourceAction &
26
+ ResourceState;
23
27
 
24
28
  const createStore: StateCreator<FileStore, [['zustand/devtools', never]]> = (...parameters) => ({
25
29
  ...initialState,
@@ -29,6 +33,7 @@ const createStore: StateCreator<FileStore, [['zustand/devtools', never]]> = (...
29
33
  ...createTTSFileSlice(...parameters),
30
34
  ...createFileChunkSlice(...parameters),
31
35
  ...createFileUploadSlice(...parameters),
36
+ ...createResourceSlice(...parameters),
32
37
  });
33
38
 
34
39
  // =============== Implement useStore ============ //
@@ -1,5 +1,5 @@
1
- import { type KnowledgeBaseState, initialKnowledgeBaseState } from '../knowledgeBase/slices/crud';
2
- import { type RAGEvalState, initialDatasetState } from '../knowledgeBase/slices/ragEval';
1
+ import { type KnowledgeBaseState, initialKnowledgeBaseState } from '../library/slices/crud';
2
+ import { type RAGEvalState, initialDatasetState } from '../library/slices/ragEval';
3
3
 
4
4
  export type KnowledgeBaseStoreState = KnowledgeBaseState & RAGEvalState;
5
5
 
@@ -2,7 +2,7 @@ import { act, renderHook } from '@testing-library/react';
2
2
  import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
3
3
 
4
4
  import { knowledgeBaseService } from '@/services/knowledgeBase';
5
- import { useFileStore } from '@/store/file';
5
+ import * as resourceHooks from '@/store/file/slices/resource/hooks';
6
6
 
7
7
  import { useKnowledgeBaseStore as useStore } from '../../store';
8
8
 
@@ -47,10 +47,9 @@ describe('KnowledgeBaseContentActions', () => {
47
47
  },
48
48
  ]);
49
49
 
50
- const refreshFileListSpy = vi.fn().mockResolvedValue(undefined);
51
- vi.spyOn(useFileStore, 'getState').mockReturnValue({
52
- refreshFileList: refreshFileListSpy,
53
- } as any);
50
+ const revalidateResourcesSpy = vi
51
+ .spyOn(resourceHooks, 'revalidateResources')
52
+ .mockResolvedValue(undefined);
54
53
 
55
54
  await act(async () => {
56
55
  await result.current.addFilesToKnowledgeBase(knowledgeBaseId, fileIds);
@@ -58,8 +57,8 @@ describe('KnowledgeBaseContentActions', () => {
58
57
 
59
58
  expect(addFilesSpy).toHaveBeenCalledWith(knowledgeBaseId, fileIds);
60
59
  expect(addFilesSpy).toHaveBeenCalledTimes(1);
61
- expect(refreshFileListSpy).toHaveBeenCalled();
62
- expect(refreshFileListSpy).toHaveBeenCalledTimes(1);
60
+ expect(revalidateResourcesSpy).toHaveBeenCalled();
61
+ expect(revalidateResourcesSpy).toHaveBeenCalledTimes(1);
63
62
  });
64
63
 
65
64
  it('should handle single file addition', async () => {
@@ -79,17 +78,16 @@ describe('KnowledgeBaseContentActions', () => {
79
78
  },
80
79
  ]);
81
80
 
82
- const refreshFileListSpy = vi.fn().mockResolvedValue(undefined);
83
- vi.spyOn(useFileStore, 'getState').mockReturnValue({
84
- refreshFileList: refreshFileListSpy,
85
- } as any);
81
+ const revalidateResourcesSpy = vi
82
+ .spyOn(resourceHooks, 'revalidateResources')
83
+ .mockResolvedValue(undefined);
86
84
 
87
85
  await act(async () => {
88
86
  await result.current.addFilesToKnowledgeBase(knowledgeBaseId, fileIds);
89
87
  });
90
88
 
91
89
  expect(addFilesSpy).toHaveBeenCalledWith(knowledgeBaseId, fileIds);
92
- expect(refreshFileListSpy).toHaveBeenCalled();
90
+ expect(revalidateResourcesSpy).toHaveBeenCalled();
93
91
  });
94
92
 
95
93
  it('should handle empty file array', async () => {
@@ -102,17 +100,16 @@ describe('KnowledgeBaseContentActions', () => {
102
100
  .spyOn(knowledgeBaseService, 'addFilesToKnowledgeBase')
103
101
  .mockResolvedValue([]);
104
102
 
105
- const refreshFileListSpy = vi.fn().mockResolvedValue(undefined);
106
- vi.spyOn(useFileStore, 'getState').mockReturnValue({
107
- refreshFileList: refreshFileListSpy,
108
- } as any);
103
+ const revalidateResourcesSpy = vi
104
+ .spyOn(resourceHooks, 'revalidateResources')
105
+ .mockResolvedValue(undefined);
109
106
 
110
107
  await act(async () => {
111
108
  await result.current.addFilesToKnowledgeBase(knowledgeBaseId, fileIds);
112
109
  });
113
110
 
114
111
  expect(addFilesSpy).toHaveBeenCalledWith(knowledgeBaseId, fileIds);
115
- expect(refreshFileListSpy).toHaveBeenCalled();
112
+ expect(revalidateResourcesSpy).toHaveBeenCalled();
116
113
  });
117
114
 
118
115
  describe('error handling', () => {
@@ -125,10 +122,9 @@ describe('KnowledgeBaseContentActions', () => {
125
122
 
126
123
  vi.spyOn(knowledgeBaseService, 'addFilesToKnowledgeBase').mockRejectedValue(serviceError);
127
124
 
128
- const refreshFileListSpy = vi.fn().mockResolvedValue(undefined);
129
- vi.spyOn(useFileStore, 'getState').mockReturnValue({
130
- refreshFileList: refreshFileListSpy,
131
- } as any);
125
+ const revalidateResourcesSpy = vi
126
+ .spyOn(resourceHooks, 'revalidateResources')
127
+ .mockResolvedValue(undefined);
132
128
 
133
129
  await expect(async () => {
134
130
  await act(async () => {
@@ -136,7 +132,7 @@ describe('KnowledgeBaseContentActions', () => {
136
132
  });
137
133
  }).rejects.toThrow('Failed to add files to knowledge base');
138
134
 
139
- expect(refreshFileListSpy).not.toHaveBeenCalled();
135
+ expect(revalidateResourcesSpy).not.toHaveBeenCalled();
140
136
  });
141
137
 
142
138
  it('should handle refresh file list errors', async () => {
@@ -155,10 +151,7 @@ describe('KnowledgeBaseContentActions', () => {
155
151
  },
156
152
  ]);
157
153
 
158
- const refreshFileListSpy = vi.fn().mockRejectedValue(refreshError);
159
- vi.spyOn(useFileStore, 'getState').mockReturnValue({
160
- refreshFileList: refreshFileListSpy,
161
- } as any);
154
+ vi.spyOn(resourceHooks, 'revalidateResources').mockRejectedValue(refreshError);
162
155
 
163
156
  await expect(async () => {
164
157
  await act(async () => {
@@ -180,10 +173,9 @@ describe('KnowledgeBaseContentActions', () => {
180
173
  .spyOn(knowledgeBaseService, 'removeFilesFromKnowledgeBase')
181
174
  .mockResolvedValue({} as any);
182
175
 
183
- const refreshFileListSpy = vi.fn().mockResolvedValue(undefined);
184
- vi.spyOn(useFileStore, 'getState').mockReturnValue({
185
- refreshFileList: refreshFileListSpy,
186
- } as any);
176
+ const revalidateResourcesSpy = vi
177
+ .spyOn(resourceHooks, 'revalidateResources')
178
+ .mockResolvedValue(undefined);
187
179
 
188
180
  await act(async () => {
189
181
  await result.current.removeFilesFromKnowledgeBase(knowledgeBaseId, fileIds);
@@ -191,8 +183,8 @@ describe('KnowledgeBaseContentActions', () => {
191
183
 
192
184
  expect(removeFilesSpy).toHaveBeenCalledWith(knowledgeBaseId, fileIds);
193
185
  expect(removeFilesSpy).toHaveBeenCalledTimes(1);
194
- expect(refreshFileListSpy).toHaveBeenCalled();
195
- expect(refreshFileListSpy).toHaveBeenCalledTimes(1);
186
+ expect(revalidateResourcesSpy).toHaveBeenCalled();
187
+ expect(revalidateResourcesSpy).toHaveBeenCalledTimes(1);
196
188
  });
197
189
 
198
190
  it('should handle single file removal', async () => {
@@ -205,17 +197,16 @@ describe('KnowledgeBaseContentActions', () => {
205
197
  .spyOn(knowledgeBaseService, 'removeFilesFromKnowledgeBase')
206
198
  .mockResolvedValue({} as any);
207
199
 
208
- const refreshFileListSpy = vi.fn().mockResolvedValue(undefined);
209
- vi.spyOn(useFileStore, 'getState').mockReturnValue({
210
- refreshFileList: refreshFileListSpy,
211
- } as any);
200
+ const revalidateResourcesSpy = vi
201
+ .spyOn(resourceHooks, 'revalidateResources')
202
+ .mockResolvedValue(undefined);
212
203
 
213
204
  await act(async () => {
214
205
  await result.current.removeFilesFromKnowledgeBase(knowledgeBaseId, fileIds);
215
206
  });
216
207
 
217
208
  expect(removeFilesSpy).toHaveBeenCalledWith(knowledgeBaseId, fileIds);
218
- expect(refreshFileListSpy).toHaveBeenCalled();
209
+ expect(revalidateResourcesSpy).toHaveBeenCalled();
219
210
  });
220
211
 
221
212
  it('should handle empty file array', async () => {
@@ -228,17 +219,16 @@ describe('KnowledgeBaseContentActions', () => {
228
219
  .spyOn(knowledgeBaseService, 'removeFilesFromKnowledgeBase')
229
220
  .mockResolvedValue({} as any);
230
221
 
231
- const refreshFileListSpy = vi.fn().mockResolvedValue(undefined);
232
- vi.spyOn(useFileStore, 'getState').mockReturnValue({
233
- refreshFileList: refreshFileListSpy,
234
- } as any);
222
+ const revalidateResourcesSpy = vi
223
+ .spyOn(resourceHooks, 'revalidateResources')
224
+ .mockResolvedValue(undefined);
235
225
 
236
226
  await act(async () => {
237
227
  await result.current.removeFilesFromKnowledgeBase(knowledgeBaseId, fileIds);
238
228
  });
239
229
 
240
230
  expect(removeFilesSpy).toHaveBeenCalledWith(knowledgeBaseId, fileIds);
241
- expect(refreshFileListSpy).toHaveBeenCalled();
231
+ expect(revalidateResourcesSpy).toHaveBeenCalled();
242
232
  });
243
233
 
244
234
  describe('error handling', () => {
@@ -253,10 +243,9 @@ describe('KnowledgeBaseContentActions', () => {
253
243
  serviceError,
254
244
  );
255
245
 
256
- const refreshFileListSpy = vi.fn().mockResolvedValue(undefined);
257
- vi.spyOn(useFileStore, 'getState').mockReturnValue({
258
- refreshFileList: refreshFileListSpy,
259
- } as any);
246
+ const revalidateResourcesSpy = vi
247
+ .spyOn(resourceHooks, 'revalidateResources')
248
+ .mockResolvedValue(undefined);
260
249
 
261
250
  await expect(async () => {
262
251
  await act(async () => {
@@ -264,7 +253,7 @@ describe('KnowledgeBaseContentActions', () => {
264
253
  });
265
254
  }).rejects.toThrow('Failed to remove files from knowledge base');
266
255
 
267
- expect(refreshFileListSpy).not.toHaveBeenCalled();
256
+ expect(revalidateResourcesSpy).not.toHaveBeenCalled();
268
257
  });
269
258
 
270
259
  it('should handle refresh file list errors', async () => {
@@ -276,10 +265,7 @@ describe('KnowledgeBaseContentActions', () => {
276
265
 
277
266
  vi.spyOn(knowledgeBaseService, 'removeFilesFromKnowledgeBase').mockResolvedValue({} as any);
278
267
 
279
- const refreshFileListSpy = vi.fn().mockRejectedValue(refreshError);
280
- vi.spyOn(useFileStore, 'getState').mockReturnValue({
281
- refreshFileList: refreshFileListSpy,
282
- } as any);
268
+ vi.spyOn(resourceHooks, 'revalidateResources').mockRejectedValue(refreshError);
283
269
 
284
270
  await expect(async () => {
285
271
  await act(async () => {
@@ -1,8 +1,8 @@
1
1
  import { type StateCreator } from 'zustand/vanilla';
2
2
 
3
3
  import { knowledgeBaseService } from '@/services/knowledgeBase';
4
- import { useFileStore } from '@/store/file';
5
- import { type KnowledgeBaseStore } from '@/store/knowledgeBase/store';
4
+ import { revalidateResources } from '@/store/file/slices/resource/hooks';
5
+ import { type KnowledgeBaseStore } from '@/store/library/store';
6
6
 
7
7
  export interface KnowledgeBaseContentAction {
8
8
  addFilesToKnowledgeBase: (knowledgeBaseId: string, ids: string[]) => Promise<void>;
@@ -17,11 +17,15 @@ export const createContentSlice: StateCreator<
17
17
  > = () => ({
18
18
  addFilesToKnowledgeBase: async (knowledgeBaseId, ids) => {
19
19
  await knowledgeBaseService.addFilesToKnowledgeBase(knowledgeBaseId, ids);
20
- await useFileStore.getState().refreshFileList();
20
+
21
+ // Revalidate resource list to show updated KB associations
22
+ await revalidateResources();
21
23
  },
22
24
 
23
25
  removeFilesFromKnowledgeBase: async (knowledgeBaseId, ids) => {
24
26
  await knowledgeBaseService.removeFilesFromKnowledgeBase(knowledgeBaseId, ids);
25
- await useFileStore.getState().refreshFileList();
27
+
28
+ // Revalidate resource list to show updated KB associations
29
+ await revalidateResources();
26
30
  },
27
31
  });
@@ -3,7 +3,7 @@ import { type StateCreator } from 'zustand/vanilla';
3
3
 
4
4
  import { mutate, useClientDataSWR } from '@/libs/swr';
5
5
  import { knowledgeBaseService } from '@/services/knowledgeBase';
6
- import { type KnowledgeBaseStore } from '@/store/knowledgeBase/store';
6
+ import { type KnowledgeBaseStore } from '@/store/library/store';
7
7
  import { type CreateKnowledgeBaseParams, type KnowledgeBaseItem } from '@/types/knowledgeBase';
8
8
 
9
9
  const FETCH_KNOWLEDGE_BASE_LIST_KEY = 'FETCH_KNOWLEDGE_BASE';
@@ -1,4 +1,4 @@
1
- import { type KnowledgeBaseStoreState } from '@/store/knowledgeBase/initialState';
1
+ import { type KnowledgeBaseStoreState } from '@/store/library/initialState';
2
2
 
3
3
  const activeKnowledgeBaseId = (s: KnowledgeBaseStoreState) => s.activeKnowledgeBaseId;
4
4
 
@@ -11,7 +11,7 @@ import { type StateCreator } from 'zustand/vanilla';
11
11
  import { notification } from '@/components/AntdStaticMethods';
12
12
  import { mutate, useClientDataSWR } from '@/libs/swr';
13
13
  import { ragEvalService } from '@/services/ragEval';
14
- import { type KnowledgeBaseStore } from '@/store/knowledgeBase/store';
14
+ import { type KnowledgeBaseStore } from '@/store/library/store';
15
15
 
16
16
  const FETCH_DATASET_LIST_KEY = 'FETCH_DATASET_LIST';
17
17
  const FETCH_DATASET_RECORD_KEY = 'FETCH_DATASET_RECORD_KEY';
@@ -4,7 +4,7 @@ import { type StateCreator } from 'zustand/vanilla';
4
4
 
5
5
  import { mutate, useClientDataSWR } from '@/libs/swr';
6
6
  import { ragEvalService } from '@/services/ragEval';
7
- import { type KnowledgeBaseStore } from '@/store/knowledgeBase/store';
7
+ import { type KnowledgeBaseStore } from '@/store/library/store';
8
8
 
9
9
  const FETCH_EVALUATION_LIST_KEY = 'FETCH_EVALUATION_LIST_KEY';
10
10
 
@@ -1,6 +1,6 @@
1
1
  import { type StateCreator } from 'zustand/vanilla';
2
2
 
3
- import { type KnowledgeBaseStore } from '@/store/knowledgeBase/store';
3
+ import { type KnowledgeBaseStore } from '@/store/library/store';
4
4
 
5
5
  import { type RAGEvalDatasetAction, createRagEvalDatasetSlice } from './dataset';
6
6
  import { type RAGEvalEvaluationAction, createRagEvalEvaluationSlice } from './evaluation';