@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.
- package/.github/workflows/release.yml +4 -0
- package/CHANGELOG.md +25 -0
- package/changelog/v1.json +5 -0
- package/locales/en-US/file.json +2 -0
- package/locales/zh-CN/discover.json +3 -0
- package/locales/zh-CN/file.json +2 -0
- package/package.json +3 -3
- package/packages/types/package.json +2 -2
- package/packages/types/src/discover/mcp.ts +3 -1
- package/src/app/[variants]/(main)/community/(list)/(home)/index.tsx +2 -0
- package/src/app/[variants]/(main)/community/(list)/features/SortButton/index.tsx +4 -0
- package/src/app/[variants]/(main)/community/(list)/mcp/features/Category/index.tsx +7 -3
- package/src/app/[variants]/(main)/community/(list)/mcp/index.tsx +2 -2
- package/src/app/[variants]/(main)/home/_layout/Body/Project/List/Editing.tsx +1 -1
- package/src/app/[variants]/(main)/home/_layout/Body/Project/List/Item.tsx +1 -1
- package/src/app/[variants]/(main)/home/_layout/Body/Project/List/index.tsx +1 -1
- package/src/app/[variants]/(main)/home/_layout/Body/Project/List/useDropdownMenu.tsx +1 -1
- package/src/app/[variants]/(main)/resource/(home)/_layout/Body/LibraryList/{List/Item → Item}/Editing.tsx +1 -1
- package/src/app/[variants]/(main)/resource/(home)/_layout/Body/LibraryList/{List/Item → Item}/index.tsx +1 -1
- package/src/app/[variants]/(main)/resource/(home)/_layout/Body/LibraryList/{List/Item → Item}/useDropdownMenu.tsx +1 -1
- package/src/app/[variants]/(main)/resource/(home)/_layout/Body/LibraryList/index.tsx +16 -6
- package/src/app/[variants]/(main)/resource/(home)/_layout/Body/{KnowledgeBase.tsx → index.tsx} +2 -3
- package/src/app/[variants]/(main)/resource/(home)/_layout/Sidebar.tsx +2 -2
- package/src/app/[variants]/(main)/resource/(home)/index.tsx +23 -10
- package/src/app/[variants]/(main)/resource/features/DndContextWrapper.tsx +12 -37
- package/src/app/[variants]/(main)/resource/features/hooks/useKnowledgeItem.ts +1 -1
- package/src/app/[variants]/(main)/resource/features/store/action.ts +9 -39
- package/src/app/[variants]/(main)/resource/library/_layout/Header/LibraryHead.tsx +1 -1
- package/src/app/[variants]/(main)/resource/library/index.tsx +13 -6
- package/src/features/LibraryModal/AddFilesToKnowledgeBase/SelectForm.tsx +1 -1
- package/src/features/LibraryModal/CreateNew/CreateForm.tsx +1 -1
- package/src/features/PageEditor/Header/Breadcrumb.tsx +1 -1
- package/src/features/PageEditor/store/action.ts +5 -2
- package/src/features/PageExplorer/PageExplorerPlaceholder.tsx +5 -7
- package/src/features/ResourceManager/components/Explorer/Header/Breadcrumb.tsx +1 -1
- package/src/features/ResourceManager/components/Explorer/ItemDropdown/useFileItemDropdown.tsx +57 -26
- package/src/features/ResourceManager/components/Explorer/ListView/ListItem/index.tsx +35 -6
- package/src/features/ResourceManager/components/Explorer/ListView/Skeleton.tsx +20 -14
- package/src/features/ResourceManager/components/Explorer/ListView/index.tsx +41 -31
- package/src/features/ResourceManager/components/Explorer/MasonryView/MasonryFileItem/index.tsx +1 -1
- package/src/features/ResourceManager/components/Explorer/MasonryView/Skeleton.tsx +6 -2
- package/src/features/ResourceManager/components/Explorer/MasonryView/index.tsx +29 -18
- package/src/features/ResourceManager/components/Explorer/MoveToFolderModal.tsx +7 -34
- package/src/features/ResourceManager/components/Explorer/ToolBar/BatchActionsDropdown.tsx +1 -1
- package/src/features/ResourceManager/components/Explorer/index.tsx +58 -18
- package/src/features/ResourceManager/components/Explorer/useCheckTaskStatus.ts +6 -4
- package/src/features/ResourceManager/components/Header/AddButton.tsx +58 -35
- package/src/features/ResourceManager/components/Header/hooks/useNotionImport.ts +5 -5
- package/src/features/ResourceManager/components/Tree/TreeSkeleton.tsx +19 -9
- package/src/features/ResourceManager/components/Tree/index.tsx +110 -5
- package/src/features/ResourceManager/components/UploadDock/index.tsx +2 -1
- package/src/features/ResourceManager/constants.ts +3 -0
- package/src/hooks/useMCPCategory.tsx +7 -0
- package/src/locales/default/discover.ts +3 -0
- package/src/locales/default/file.ts +2 -0
- package/src/services/file/index.ts +34 -1
- package/src/services/resource/index.ts +249 -0
- package/src/store/discover/slices/mcp/action.ts +1 -1
- package/src/store/file/slices/chat/action.ts +2 -1
- package/src/store/file/slices/document/action.ts +10 -7
- package/src/store/file/slices/fileManager/action.ts +14 -4
- package/src/store/file/slices/fileManager/initialState.ts +2 -0
- package/src/store/file/slices/resource/action.ts +432 -0
- package/src/store/file/slices/resource/hooks.ts +82 -0
- package/src/store/file/slices/resource/initialState.ts +67 -0
- package/src/store/file/slices/resource/syncEngine.ts +326 -0
- package/src/store/file/store.ts +6 -1
- package/src/store/{knowledgeBase → library}/initialState.ts +2 -2
- package/src/store/{knowledgeBase → library}/slices/content/action.test.ts +37 -51
- package/src/store/{knowledgeBase → library}/slices/content/action.ts +8 -4
- package/src/store/{knowledgeBase → library}/slices/crud/action.ts +1 -1
- package/src/store/{knowledgeBase → library}/slices/crud/selectors.ts +1 -1
- package/src/store/{knowledgeBase → library}/slices/ragEval/actions/dataset.ts +1 -1
- package/src/store/{knowledgeBase → library}/slices/ragEval/actions/evaluation.ts +1 -1
- package/src/store/{knowledgeBase → library}/slices/ragEval/actions/index.ts +1 -1
- package/src/types/resource.ts +133 -0
- package/src/app/[variants]/(main)/resource/(home)/_layout/Body/LibraryList/List/index.tsx +0 -25
- /package/src/app/[variants]/(main)/resource/(home)/_layout/Body/LibraryList/{List/Item → Item}/Actions.tsx +0 -0
- /package/src/store/{knowledgeBase → library}/index.ts +0 -0
- /package/src/store/{knowledgeBase → library}/selectors.ts +0 -0
- /package/src/store/{knowledgeBase → library}/slices/content/index.ts +0 -0
- /package/src/store/{knowledgeBase → library}/slices/crud/action.test.ts +0 -0
- /package/src/store/{knowledgeBase → library}/slices/crud/index.ts +0 -0
- /package/src/store/{knowledgeBase → library}/slices/crud/initialState.ts +0 -0
- /package/src/store/{knowledgeBase → library}/slices/ragEval/index.ts +0 -0
- /package/src/store/{knowledgeBase → library}/slices/ragEval/initialState.ts +0 -0
- /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
|
+
}
|
package/src/store/file/store.ts
CHANGED
|
@@ -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 '../
|
|
2
|
-
import { type RAGEvalState, initialDatasetState } from '../
|
|
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
|
|
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
|
|
51
|
-
|
|
52
|
-
|
|
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(
|
|
62
|
-
expect(
|
|
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
|
|
83
|
-
|
|
84
|
-
|
|
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(
|
|
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
|
|
106
|
-
|
|
107
|
-
|
|
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(
|
|
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
|
|
129
|
-
|
|
130
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
|
184
|
-
|
|
185
|
-
|
|
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(
|
|
195
|
-
expect(
|
|
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
|
|
209
|
-
|
|
210
|
-
|
|
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(
|
|
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
|
|
232
|
-
|
|
233
|
-
|
|
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(
|
|
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
|
|
257
|
-
|
|
258
|
-
|
|
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(
|
|
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
|
-
|
|
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 {
|
|
5
|
-
import { type KnowledgeBaseStore } from '@/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
|
-
|
|
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
|
-
|
|
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/
|
|
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';
|
|
@@ -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/
|
|
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/
|
|
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/
|
|
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';
|