@lobehub/lobehub 2.0.0-next.54 → 2.0.0-next.56
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/CHANGELOG.md +52 -0
- package/changelog/v1.json +18 -0
- package/locales/ar/common.json +1 -0
- package/locales/ar/file.json +85 -2
- package/locales/bg-BG/common.json +1 -0
- package/locales/bg-BG/file.json +85 -2
- package/locales/de-DE/common.json +1 -0
- package/locales/de-DE/file.json +85 -2
- package/locales/en-US/common.json +1 -0
- package/locales/en-US/file.json +85 -2
- package/locales/es-ES/common.json +1 -0
- package/locales/es-ES/file.json +85 -2
- package/locales/fa-IR/common.json +1 -0
- package/locales/fa-IR/file.json +85 -2
- package/locales/fr-FR/common.json +1 -0
- package/locales/fr-FR/file.json +85 -2
- package/locales/it-IT/common.json +1 -0
- package/locales/it-IT/file.json +85 -2
- package/locales/ja-JP/common.json +1 -0
- package/locales/ja-JP/file.json +85 -2
- package/locales/ko-KR/common.json +1 -0
- package/locales/ko-KR/file.json +85 -2
- package/locales/nl-NL/common.json +1 -0
- package/locales/nl-NL/file.json +85 -2
- package/locales/pl-PL/common.json +1 -0
- package/locales/pl-PL/file.json +85 -2
- package/locales/pt-BR/common.json +1 -0
- package/locales/pt-BR/file.json +85 -2
- package/locales/ru-RU/common.json +1 -0
- package/locales/ru-RU/file.json +85 -2
- package/locales/tr-TR/common.json +1 -0
- package/locales/tr-TR/file.json +85 -2
- package/locales/vi-VN/common.json +1 -0
- package/locales/vi-VN/file.json +85 -2
- package/locales/zh-CN/common.json +1 -0
- package/locales/zh-CN/file.json +85 -2
- package/locales/zh-TW/common.json +1 -0
- package/locales/zh-TW/file.json +85 -2
- package/package.json +1 -1
- package/packages/database/src/models/__tests__/file.test.ts +94 -29
- package/packages/database/src/models/file.ts +15 -4
- package/packages/database/src/repositories/knowledge/index.test.ts +300 -0
- package/packages/database/src/repositories/knowledge/index.ts +420 -0
- package/packages/model-bank/src/aiModels/aihubmix.ts +1 -0
- package/packages/model-bank/src/aiModels/google.ts +9 -5
- package/packages/model-bank/src/aiModels/openai.ts +2 -35
- package/packages/model-bank/src/aiModels/openrouter.ts +1 -0
- package/packages/model-bank/src/aiModels/vertexai.ts +2 -0
- package/packages/model-bank/src/types/aiModel.ts +15 -2
- package/packages/model-runtime/src/core/usageConverters/index.ts +1 -0
- package/packages/model-runtime/src/core/usageConverters/utils/resolveImageSinglePrice.ts +34 -0
- package/packages/types/src/document/index.ts +14 -2
- package/packages/types/src/files/index.ts +2 -0
- package/packages/types/src/files/list.ts +10 -0
- package/packages/types/src/llm.ts +1 -1
- package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/ModelSelect/ImageModelItem.tsx +93 -0
- package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/{ModelSelect.tsx → ModelSelect/index.tsx} +17 -2
- package/src/app/[variants]/(main)/knowledge/KnowledgeRouter.tsx +2 -1
- package/src/app/[variants]/(main)/knowledge/components/KnowledgeBaseItem/index.tsx +0 -2
- package/src/app/[variants]/(main)/knowledge/hooks/useFileCategory.ts +6 -3
- package/src/app/[variants]/(main)/knowledge/routes/KnowledgeBaseDetail/index.tsx +2 -2
- package/src/app/[variants]/(main)/knowledge/routes/KnowledgeBaseDetail/menu/{MenuItems.tsx → CategoryMenu.tsx} +3 -3
- package/src/app/[variants]/(main)/knowledge/routes/KnowledgeBaseDetail/menu/Menu.tsx +2 -2
- package/src/app/[variants]/(main)/knowledge/routes/KnowledgeHome/index.tsx +40 -18
- package/src/app/[variants]/(main)/knowledge/routes/KnowledgeHome/layout/Container.tsx +1 -1
- package/src/app/[variants]/(main)/knowledge/routes/KnowledgeHome/menu/CategoryMenu.tsx +148 -0
- package/src/app/[variants]/(main)/knowledge/routes/KnowledgeHome/menu/KnowledgeBase.tsx +20 -7
- package/src/components/FileIcon/index.tsx +3 -1
- package/src/features/ChatInput/ActionBar/Knowledge/index.tsx +2 -2
- package/src/features/FileSidePanel/index.tsx +1 -1
- package/src/features/KnowledgeBaseModal/AssignKnowledgeBase/Item/MasonryItem.tsx +80 -0
- package/src/features/KnowledgeBaseModal/AssignKnowledgeBase/Item/MasonryItemWrapper.tsx +27 -0
- package/src/features/KnowledgeBaseModal/AssignKnowledgeBase/List.tsx +104 -23
- package/src/features/KnowledgeBaseModal/AssignKnowledgeBase/MasonrySkeleton.tsx +62 -0
- package/src/features/KnowledgeBaseModal/AssignKnowledgeBase/index.tsx +3 -2
- package/src/features/KnowledgeBaseModal/CreateNew/CreateForm.tsx +1 -1
- package/src/features/KnowledgeManager/DocumentExplorer/DocumentActions.tsx +111 -0
- package/src/features/KnowledgeManager/DocumentExplorer/DocumentEditor.tsx +723 -0
- package/src/features/KnowledgeManager/DocumentExplorer/DocumentEditorPlaceholder.tsx +169 -0
- package/src/features/KnowledgeManager/DocumentExplorer/DocumentListItem.tsx +148 -0
- package/src/features/KnowledgeManager/DocumentExplorer/DocumentListSkeleton.tsx +39 -0
- package/src/features/KnowledgeManager/DocumentExplorer/NoteEditorModal.tsx +348 -0
- package/src/features/KnowledgeManager/DocumentExplorer/RenamePopover.tsx +163 -0
- package/src/features/KnowledgeManager/DocumentExplorer/index.tsx +318 -0
- package/src/features/{FileManager/FileList → KnowledgeManager/FileExplorer}/FileListItem/index.tsx +48 -9
- package/src/features/KnowledgeManager/FileExplorer/MasonryFileItem/DefaultFileItem.tsx +149 -0
- package/src/features/KnowledgeManager/FileExplorer/MasonryFileItem/ImageFileItem.tsx +245 -0
- package/src/features/KnowledgeManager/FileExplorer/MasonryFileItem/MarkdownFileItem.tsx +232 -0
- package/src/features/KnowledgeManager/FileExplorer/MasonryFileItem/NoteFileItem.tsx +230 -0
- package/src/features/KnowledgeManager/FileExplorer/MasonryFileItem/index.tsx +398 -0
- package/src/features/KnowledgeManager/FileExplorer/ToolBar/ViewSwitcher.tsx +45 -0
- package/src/features/{FileManager/FileList → KnowledgeManager/FileExplorer}/index.tsx +55 -16
- package/src/features/KnowledgeManager/Header/AddButton.tsx +118 -0
- package/src/features/KnowledgeManager/Header/NewNoteButton.tsx +33 -0
- package/src/features/{FileManager → KnowledgeManager}/Header/index.tsx +3 -9
- package/src/features/KnowledgeManager/Home/RecentDocumentCard.tsx +116 -0
- package/src/features/KnowledgeManager/Home/RecentDocuments.tsx +77 -0
- package/src/features/KnowledgeManager/Home/RecentFileCard.tsx +121 -0
- package/src/features/KnowledgeManager/Home/RecentFiles.tsx +73 -0
- package/src/features/KnowledgeManager/Home/RecentFilesSkeleton.tsx +81 -0
- package/src/features/KnowledgeManager/Home/UploadEntries.tsx +208 -0
- package/src/features/KnowledgeManager/Home/index.tsx +221 -0
- package/src/features/KnowledgeManager/index.tsx +75 -0
- package/src/features/Portal/FilePreview/Body/index.tsx +1 -1
- package/src/features/Portal/FilePreview/Header.tsx +1 -1
- package/src/locales/default/common.ts +1 -0
- package/src/locales/default/file.ts +87 -2
- package/src/server/routers/lambda/__tests__/file.test.ts +85 -6
- package/src/server/routers/lambda/document.ts +57 -0
- package/src/server/routers/lambda/file.ts +72 -0
- package/src/server/routers/lambda/knowledge.ts +94 -0
- package/src/server/services/document/index.ts +103 -0
- package/src/services/document/index.ts +44 -0
- package/src/services/file/index.ts +5 -3
- package/src/store/aiInfra/slices/aiProvider/__tests__/action.test.ts +125 -229
- package/src/store/aiInfra/slices/aiProvider/action.ts +113 -33
- package/src/store/file/initialState.ts +6 -1
- package/src/store/file/slices/chat/action.ts +3 -3
- package/src/store/file/slices/document/action.ts +359 -0
- package/src/store/file/slices/document/index.ts +3 -0
- package/src/store/file/slices/document/initialState.ts +22 -0
- package/src/store/file/slices/document/selectors.ts +25 -0
- package/src/store/file/slices/fileManager/action.test.ts +16 -9
- package/src/store/file/slices/fileManager/action.ts +11 -11
- package/src/store/file/store.ts +3 -0
- package/src/store/global/initialState.ts +3 -1
- package/src/app/[variants]/(main)/knowledge/routes/KnowledgeHome/menu/FileMenu.tsx +0 -75
- package/src/features/FileManager/FileList/MasonryFileItem/index.tsx +0 -582
- package/src/features/FileManager/index.tsx +0 -36
- /package/src/features/{FileManager/FileList/ToolBar → KnowledgeBaseModal/AssignKnowledgeBase}/ViewSwitcher.tsx +0 -0
- /package/src/features/{FileManager → KnowledgeManager}/ChunkDrawer/ChunkList/ChunkItem.tsx +0 -0
- /package/src/features/{FileManager → KnowledgeManager}/ChunkDrawer/ChunkList/index.tsx +0 -0
- /package/src/features/{FileManager → KnowledgeManager}/ChunkDrawer/Content.tsx +0 -0
- /package/src/features/{FileManager → KnowledgeManager}/ChunkDrawer/Loading/index.tsx +0 -0
- /package/src/features/{FileManager → KnowledgeManager}/ChunkDrawer/SimilaritySearchList/Item.tsx +0 -0
- /package/src/features/{FileManager → KnowledgeManager}/ChunkDrawer/SimilaritySearchList/index.tsx +0 -0
- /package/src/features/{FileManager → KnowledgeManager}/ChunkDrawer/index.tsx +0 -0
- /package/src/features/{FileManager/FileList → KnowledgeManager/FileExplorer}/EmptyStatus.tsx +0 -0
- /package/src/features/{FileManager/FileList → KnowledgeManager/FileExplorer}/FileListItem/ChunkTag.tsx +0 -0
- /package/src/features/{FileManager/FileList → KnowledgeManager/FileExplorer}/FileListItem/DropdownMenu.tsx +0 -0
- /package/src/features/{FileManager/FileList → KnowledgeManager/FileExplorer}/FileSkeleton.tsx +0 -0
- /package/src/features/{FileManager/FileList → KnowledgeManager/FileExplorer}/MasonryFileItem/MasonryItemWrapper.tsx +0 -0
- /package/src/features/{FileManager/FileList → KnowledgeManager/FileExplorer}/MasonrySkeleton.tsx +0 -0
- /package/src/features/{FileManager/FileList → KnowledgeManager/FileExplorer}/ToolBar/Config.tsx +0 -0
- /package/src/features/{FileManager/FileList → KnowledgeManager/FileExplorer}/ToolBar/MultiSelectActions.tsx +0 -0
- /package/src/features/{FileManager/FileList → KnowledgeManager/FileExplorer}/ToolBar/index.tsx +0 -0
- /package/src/features/{FileManager/FileList → KnowledgeManager/FileExplorer}/useCheckTaskStatus.ts +0 -0
- /package/src/features/{FileManager → KnowledgeManager}/Header/FilesSearchBar.tsx +0 -0
- /package/src/features/{FileManager → KnowledgeManager}/Header/TogglePanelButton.tsx +0 -0
- /package/src/features/{FileManager → KnowledgeManager}/Header/UploadFileButton.tsx +0 -0
- /package/src/features/{FileManager → KnowledgeManager}/UploadDock/Item.tsx +0 -0
- /package/src/features/{FileManager → KnowledgeManager}/UploadDock/index.tsx +0 -0
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import { ImageFileState, initialImageFileState } from './slices/chat';
|
|
2
2
|
import { FileChunkState, initialFileChunkState } from './slices/chunk';
|
|
3
|
+
import { DocumentState, initialDocumentState } from './slices/document';
|
|
3
4
|
import { FileManagerState, initialFileManagerState } from './slices/fileManager';
|
|
4
5
|
|
|
5
|
-
export type FilesStoreState = ImageFileState &
|
|
6
|
+
export type FilesStoreState = ImageFileState &
|
|
7
|
+
DocumentState &
|
|
8
|
+
FileManagerState &
|
|
9
|
+
FileChunkState;
|
|
6
10
|
|
|
7
11
|
export const initialState: FilesStoreState = {
|
|
8
12
|
...initialImageFileState,
|
|
13
|
+
...initialDocumentState,
|
|
9
14
|
...initialFileManagerState,
|
|
10
15
|
...initialFileChunkState,
|
|
11
16
|
};
|
|
@@ -23,14 +23,12 @@ const n = setNamespace('chat');
|
|
|
23
23
|
export interface FileAction {
|
|
24
24
|
clearChatUploadFileList: () => void;
|
|
25
25
|
dispatchChatUploadFileList: (payload: UploadFileListDispatch) => void;
|
|
26
|
-
|
|
27
26
|
removeChatUploadFile: (id: string) => Promise<void>;
|
|
28
27
|
startAsyncTask: (
|
|
29
28
|
fileId: string,
|
|
30
29
|
runner: (id: string) => Promise<string>,
|
|
31
30
|
onFileItemChange: (fileItem: FileListItem) => void,
|
|
32
31
|
) => Promise<void>;
|
|
33
|
-
|
|
34
32
|
uploadChatFiles: (files: File[]) => Promise<void>;
|
|
35
33
|
}
|
|
36
34
|
|
|
@@ -43,12 +41,14 @@ export const createFileSlice: StateCreator<
|
|
|
43
41
|
clearChatUploadFileList: () => {
|
|
44
42
|
set({ chatUploadFileList: [] }, false, n('clearChatUploadFileList'));
|
|
45
43
|
},
|
|
44
|
+
|
|
46
45
|
dispatchChatUploadFileList: (payload) => {
|
|
47
46
|
const nextValue = uploadFileListReducer(get().chatUploadFileList, payload);
|
|
48
47
|
if (nextValue === get().chatUploadFileList) return;
|
|
49
48
|
|
|
50
49
|
set({ chatUploadFileList: nextValue }, false, `dispatchChatFileList/${payload.type}`);
|
|
51
50
|
},
|
|
51
|
+
|
|
52
52
|
removeChatUploadFile: async (id) => {
|
|
53
53
|
const { dispatchChatUploadFileList } = get();
|
|
54
54
|
|
|
@@ -68,7 +68,7 @@ export const createFileSlice: StateCreator<
|
|
|
68
68
|
let fileItem: FileListItem | undefined = undefined;
|
|
69
69
|
|
|
70
70
|
try {
|
|
71
|
-
fileItem = await fileService.
|
|
71
|
+
fileItem = await fileService.getKnowledgeItem(id);
|
|
72
72
|
} catch (e) {
|
|
73
73
|
console.error('getFileItem Error:', e);
|
|
74
74
|
continue;
|
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
import { StateCreator } from 'zustand/vanilla';
|
|
2
|
+
|
|
3
|
+
import { documentService } from '@/services/document';
|
|
4
|
+
import { DocumentSourceType, LobeDocument } from '@/types/document';
|
|
5
|
+
import { setNamespace } from '@/utils/storeDebug';
|
|
6
|
+
|
|
7
|
+
import { FileStore } from '../../store';
|
|
8
|
+
|
|
9
|
+
const n = setNamespace('document');
|
|
10
|
+
|
|
11
|
+
const ALLOWED_DOCUMENT_SOURCE_TYPES = new Set(['editor', 'file', 'api']);
|
|
12
|
+
const ALLOWED_DOCUMENT_FILE_TYPES = new Set(['custom/document', 'application/pdf']);
|
|
13
|
+
const EDITOR_DOCUMENT_FILE_TYPE = 'custom/document';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Check if a document should be displayed in the document list
|
|
17
|
+
*/
|
|
18
|
+
const isAllowedDocument = (document: { fileType: string; sourceType: string }) => {
|
|
19
|
+
return (
|
|
20
|
+
ALLOWED_DOCUMENT_SOURCE_TYPES.has(document.sourceType) &&
|
|
21
|
+
ALLOWED_DOCUMENT_FILE_TYPES.has(document.fileType)
|
|
22
|
+
);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export interface DocumentAction {
|
|
26
|
+
/**
|
|
27
|
+
* Create a new document with markdown content (not optimistic, waits for server response)
|
|
28
|
+
* Returns the created document
|
|
29
|
+
*/
|
|
30
|
+
createDocument: (params: {
|
|
31
|
+
content: string;
|
|
32
|
+
knowledgeBaseId?: string;
|
|
33
|
+
title: string;
|
|
34
|
+
}) => Promise<{ [key: string]: any; id: string }>;
|
|
35
|
+
/**
|
|
36
|
+
* Create a new optimistic document immediately in local map
|
|
37
|
+
* Returns the temporary ID for the new document
|
|
38
|
+
*/
|
|
39
|
+
createOptimisticDocument: (title?: string) => string;
|
|
40
|
+
/**
|
|
41
|
+
* Duplicate an existing document
|
|
42
|
+
* Returns the created document
|
|
43
|
+
*/
|
|
44
|
+
duplicateDocument: (documentId: string) => Promise<{ [key: string]: any; id: string }>;
|
|
45
|
+
/**
|
|
46
|
+
* Fetch all documents from the server
|
|
47
|
+
*/
|
|
48
|
+
fetchDocuments: () => Promise<void>;
|
|
49
|
+
/**
|
|
50
|
+
* Get documents from local optimistic map merged with server data
|
|
51
|
+
*/
|
|
52
|
+
getOptimisticDocuments: () => LobeDocument[];
|
|
53
|
+
/**
|
|
54
|
+
* Remove a document (deletes from documents table)
|
|
55
|
+
*/
|
|
56
|
+
removeDocument: (documentId: string) => Promise<void>;
|
|
57
|
+
/**
|
|
58
|
+
* Remove a temp document from local map
|
|
59
|
+
*/
|
|
60
|
+
removeTempDocument: (tempId: string) => void;
|
|
61
|
+
/**
|
|
62
|
+
* Replace a temp document with real document data (for smooth UX when creating documents)
|
|
63
|
+
*/
|
|
64
|
+
replaceTempDocumentWithReal: (tempId: string, realDocument: LobeDocument) => void;
|
|
65
|
+
/**
|
|
66
|
+
* Optimistically update document in local map and queue for DB sync
|
|
67
|
+
*/
|
|
68
|
+
updateDocumentOptimistically: (
|
|
69
|
+
documentId: string,
|
|
70
|
+
updates: Partial<LobeDocument>,
|
|
71
|
+
) => Promise<void>;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export const createDocumentSlice: StateCreator<
|
|
75
|
+
FileStore,
|
|
76
|
+
[['zustand/devtools', never]],
|
|
77
|
+
[],
|
|
78
|
+
DocumentAction
|
|
79
|
+
> = (set, get) => ({
|
|
80
|
+
createDocument: async ({ title, content, knowledgeBaseId }) => {
|
|
81
|
+
const now = Date.now();
|
|
82
|
+
|
|
83
|
+
// Create document with markdown content, leave editorData as empty JSON object
|
|
84
|
+
const newDoc = await documentService.createDocument({
|
|
85
|
+
content,
|
|
86
|
+
editorData: '{}', // Empty JSON object instead of empty string
|
|
87
|
+
fileType: EDITOR_DOCUMENT_FILE_TYPE,
|
|
88
|
+
knowledgeBaseId,
|
|
89
|
+
metadata: {
|
|
90
|
+
createdAt: now,
|
|
91
|
+
},
|
|
92
|
+
title,
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// Don't refresh documents here - the caller will handle replacing the temp document
|
|
96
|
+
// with the real one via replaceTempDocumentWithReal, which provides a smooth UX
|
|
97
|
+
// without triggering the loading skeleton
|
|
98
|
+
|
|
99
|
+
return newDoc;
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
createOptimisticDocument: (title = 'Untitled') => {
|
|
103
|
+
const { localDocumentMap } = get();
|
|
104
|
+
|
|
105
|
+
// Generate temporary ID with prefix to identify optimistic documents
|
|
106
|
+
const tempId = `temp-document-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
107
|
+
const now = new Date();
|
|
108
|
+
|
|
109
|
+
const newDocument: LobeDocument = {
|
|
110
|
+
content: null,
|
|
111
|
+
createdAt: now,
|
|
112
|
+
editorData: null,
|
|
113
|
+
fileType: EDITOR_DOCUMENT_FILE_TYPE,
|
|
114
|
+
filename: title,
|
|
115
|
+
id: tempId,
|
|
116
|
+
metadata: {},
|
|
117
|
+
source: 'document',
|
|
118
|
+
sourceType: DocumentSourceType.EDITOR,
|
|
119
|
+
title: title,
|
|
120
|
+
totalCharCount: 0,
|
|
121
|
+
totalLineCount: 0,
|
|
122
|
+
updatedAt: now,
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
// Add to local map
|
|
126
|
+
const newMap = new Map(localDocumentMap);
|
|
127
|
+
newMap.set(tempId, newDocument);
|
|
128
|
+
set({ localDocumentMap: newMap }, false, n('createOptimisticDocument'));
|
|
129
|
+
|
|
130
|
+
return tempId;
|
|
131
|
+
},
|
|
132
|
+
|
|
133
|
+
duplicateDocument: async (documentId) => {
|
|
134
|
+
// Fetch the source document
|
|
135
|
+
const sourceDoc = await documentService.getDocumentById(documentId);
|
|
136
|
+
|
|
137
|
+
if (!sourceDoc) {
|
|
138
|
+
throw new Error(`Document with ID ${documentId} not found`);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Create a new document with copied properties
|
|
142
|
+
const newDoc = await documentService.createDocument({
|
|
143
|
+
content: sourceDoc.content || '',
|
|
144
|
+
editorData: sourceDoc.editorData
|
|
145
|
+
? typeof sourceDoc.editorData === 'string'
|
|
146
|
+
? sourceDoc.editorData
|
|
147
|
+
: JSON.stringify(sourceDoc.editorData)
|
|
148
|
+
: '{}',
|
|
149
|
+
fileType: sourceDoc.fileType,
|
|
150
|
+
metadata: {
|
|
151
|
+
...sourceDoc.metadata,
|
|
152
|
+
createdAt: Date.now(),
|
|
153
|
+
duplicatedFrom: documentId,
|
|
154
|
+
},
|
|
155
|
+
title: `${sourceDoc.title} (Copy)`,
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// Add the new document to local map immediately for instant UI update
|
|
159
|
+
const { localDocumentMap } = get();
|
|
160
|
+
const newMap = new Map(localDocumentMap);
|
|
161
|
+
const editorDoc: LobeDocument = {
|
|
162
|
+
content: newDoc.content || null,
|
|
163
|
+
createdAt: newDoc.createdAt ? new Date(newDoc.createdAt) : new Date(),
|
|
164
|
+
editorData:
|
|
165
|
+
typeof newDoc.editorData === 'string'
|
|
166
|
+
? JSON.parse(newDoc.editorData)
|
|
167
|
+
: newDoc.editorData || null,
|
|
168
|
+
fileType: newDoc.fileType,
|
|
169
|
+
filename: newDoc.title || newDoc.filename || '',
|
|
170
|
+
id: newDoc.id,
|
|
171
|
+
metadata: newDoc.metadata || {},
|
|
172
|
+
source: 'document',
|
|
173
|
+
sourceType: DocumentSourceType.EDITOR,
|
|
174
|
+
title: newDoc.title || '',
|
|
175
|
+
totalCharCount: newDoc.content?.length || 0,
|
|
176
|
+
totalLineCount: 0,
|
|
177
|
+
updatedAt: newDoc.updatedAt ? new Date(newDoc.updatedAt) : new Date(),
|
|
178
|
+
};
|
|
179
|
+
newMap.set(newDoc.id, editorDoc);
|
|
180
|
+
set({ localDocumentMap: newMap }, false, n('duplicateDocument'));
|
|
181
|
+
|
|
182
|
+
// Don't refresh documents here - we've already added it to the local map
|
|
183
|
+
// This prevents the loading skeleton from appearing
|
|
184
|
+
|
|
185
|
+
return newDoc;
|
|
186
|
+
},
|
|
187
|
+
|
|
188
|
+
fetchDocuments: async () => {
|
|
189
|
+
set({ isDocumentListLoading: true }, false, n('fetchDocuments/start'));
|
|
190
|
+
|
|
191
|
+
try {
|
|
192
|
+
const documentItems = await documentService.queryDocuments();
|
|
193
|
+
const documents = documentItems.filter(isAllowedDocument).map((doc) => ({
|
|
194
|
+
...doc,
|
|
195
|
+
filename: doc.filename ?? doc.title ?? 'Untitled',
|
|
196
|
+
})) as LobeDocument[];
|
|
197
|
+
set({ documents, isDocumentListLoading: false }, false, n('fetchDocuments/success'));
|
|
198
|
+
|
|
199
|
+
// Sync with local map: remove temp documents that now exist on server
|
|
200
|
+
const { localDocumentMap } = get();
|
|
201
|
+
const newMap = new Map(localDocumentMap);
|
|
202
|
+
|
|
203
|
+
for (const [id] of localDocumentMap.entries()) {
|
|
204
|
+
if (id.startsWith('temp-document-')) {
|
|
205
|
+
newMap.delete(id);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
set({ localDocumentMap: newMap }, false, n('fetchDocuments/syncLocalMap'));
|
|
210
|
+
} catch (error) {
|
|
211
|
+
console.error('Failed to fetch documents:', error);
|
|
212
|
+
set({ isDocumentListLoading: false }, false, n('fetchDocuments/error'));
|
|
213
|
+
throw error;
|
|
214
|
+
}
|
|
215
|
+
},
|
|
216
|
+
|
|
217
|
+
getOptimisticDocuments: () => {
|
|
218
|
+
const { localDocumentMap, documents } = get();
|
|
219
|
+
|
|
220
|
+
// Track which documents we've added
|
|
221
|
+
const addedIds = new Set<string>();
|
|
222
|
+
|
|
223
|
+
// Create result array - start with server documents
|
|
224
|
+
const result: LobeDocument[] = documents.map((document) => {
|
|
225
|
+
addedIds.add(document.id);
|
|
226
|
+
// Check if we have a local optimistic update for this document
|
|
227
|
+
const localUpdate = localDocumentMap.get(document.id);
|
|
228
|
+
// If local update exists and is newer, use it; otherwise use server version
|
|
229
|
+
if (localUpdate && new Date(localUpdate.updatedAt) >= new Date(document.updatedAt)) {
|
|
230
|
+
return localUpdate;
|
|
231
|
+
}
|
|
232
|
+
return document;
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
// Add any optimistic documents that aren't in server list yet (e.g., newly created temp documents)
|
|
236
|
+
for (const [id, document] of localDocumentMap.entries()) {
|
|
237
|
+
if (!addedIds.has(id)) {
|
|
238
|
+
result.unshift(document); // Add new documents to the beginning
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return result;
|
|
243
|
+
},
|
|
244
|
+
|
|
245
|
+
removeDocument: async (documentId) => {
|
|
246
|
+
// Remove from local optimistic map first (optimistic update)
|
|
247
|
+
const { localDocumentMap, documents } = get();
|
|
248
|
+
const newMap = new Map(localDocumentMap);
|
|
249
|
+
newMap.delete(documentId);
|
|
250
|
+
|
|
251
|
+
// Also remove from documents array to update the list immediately
|
|
252
|
+
const newDocuments = documents.filter((doc) => doc.id !== documentId);
|
|
253
|
+
|
|
254
|
+
set(
|
|
255
|
+
{ documents: newDocuments, localDocumentMap: newMap },
|
|
256
|
+
false,
|
|
257
|
+
n('removeDocument/optimistic'),
|
|
258
|
+
);
|
|
259
|
+
|
|
260
|
+
try {
|
|
261
|
+
// Delete from documents table
|
|
262
|
+
await documentService.deleteDocument(documentId);
|
|
263
|
+
// No need to call fetchDocuments() - optimistic update is enough
|
|
264
|
+
} catch (error) {
|
|
265
|
+
console.error('Failed to delete document:', error);
|
|
266
|
+
// Restore the document in local map and documents array on error
|
|
267
|
+
const restoredMap = new Map(localDocumentMap);
|
|
268
|
+
set({ documents, localDocumentMap: restoredMap }, false, n('removeDocument/restore'));
|
|
269
|
+
throw error;
|
|
270
|
+
}
|
|
271
|
+
},
|
|
272
|
+
|
|
273
|
+
removeTempDocument: (tempId) => {
|
|
274
|
+
const { localDocumentMap } = get();
|
|
275
|
+
const newMap = new Map(localDocumentMap);
|
|
276
|
+
newMap.delete(tempId);
|
|
277
|
+
set({ localDocumentMap: newMap }, false, n('removeTempDocument'));
|
|
278
|
+
},
|
|
279
|
+
|
|
280
|
+
replaceTempDocumentWithReal: (tempId, realDocument) => {
|
|
281
|
+
const { localDocumentMap } = get();
|
|
282
|
+
const newMap = new Map(localDocumentMap);
|
|
283
|
+
|
|
284
|
+
// Remove temp document
|
|
285
|
+
newMap.delete(tempId);
|
|
286
|
+
|
|
287
|
+
// Add real document with same position
|
|
288
|
+
newMap.set(realDocument.id, realDocument);
|
|
289
|
+
|
|
290
|
+
set({ localDocumentMap: newMap }, false, n('replaceTempDocumentWithReal'));
|
|
291
|
+
},
|
|
292
|
+
|
|
293
|
+
updateDocumentOptimistically: async (documentId, updates) => {
|
|
294
|
+
const { localDocumentMap, documents } = get();
|
|
295
|
+
|
|
296
|
+
// Find the document either in local map or documents state
|
|
297
|
+
let existingDocument = localDocumentMap.get(documentId);
|
|
298
|
+
if (!existingDocument) {
|
|
299
|
+
existingDocument = documents.find((doc) => doc.id === documentId);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if (!existingDocument) {
|
|
303
|
+
console.warn('[updateDocumentOptimistically] Document not found:', documentId);
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Create updated document with new timestamp
|
|
308
|
+
// Merge metadata if both exist, otherwise use the update's metadata or preserve existing
|
|
309
|
+
const mergedMetadata =
|
|
310
|
+
updates.metadata !== undefined
|
|
311
|
+
? { ...existingDocument.metadata, ...updates.metadata }
|
|
312
|
+
: existingDocument.metadata;
|
|
313
|
+
|
|
314
|
+
// Clean up undefined values from metadata
|
|
315
|
+
const cleanedMetadata = mergedMetadata
|
|
316
|
+
? Object.fromEntries(Object.entries(mergedMetadata).filter(([, v]) => v !== undefined))
|
|
317
|
+
: {};
|
|
318
|
+
|
|
319
|
+
const updatedDocument: LobeDocument = {
|
|
320
|
+
...existingDocument,
|
|
321
|
+
...updates,
|
|
322
|
+
metadata: cleanedMetadata,
|
|
323
|
+
title: updates.title || existingDocument.title,
|
|
324
|
+
updatedAt: new Date(),
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
// Update local map immediately for optimistic UI
|
|
328
|
+
const newMap = new Map(localDocumentMap);
|
|
329
|
+
newMap.set(documentId, updatedDocument);
|
|
330
|
+
set({ localDocumentMap: newMap }, false, n('updateDocumentOptimistically'));
|
|
331
|
+
|
|
332
|
+
// Queue background sync to DB
|
|
333
|
+
try {
|
|
334
|
+
await documentService.updateDocument({
|
|
335
|
+
content: updatedDocument.content || '',
|
|
336
|
+
editorData:
|
|
337
|
+
typeof updatedDocument.editorData === 'string'
|
|
338
|
+
? updatedDocument.editorData
|
|
339
|
+
: JSON.stringify(updatedDocument.editorData || {}),
|
|
340
|
+
id: documentId,
|
|
341
|
+
metadata: updatedDocument.metadata || {},
|
|
342
|
+
title: updatedDocument.title || updatedDocument.filename,
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
// After successful sync, refresh file list to get server state
|
|
346
|
+
// This will eventually sync back to the map via syncDocumentMapWithServer
|
|
347
|
+
} catch (error) {
|
|
348
|
+
console.error('[updateDocumentOptimistically] Failed to sync to DB:', error);
|
|
349
|
+
// On error, revert the optimistic update
|
|
350
|
+
const revertMap = new Map(localDocumentMap);
|
|
351
|
+
if (existingDocument) {
|
|
352
|
+
revertMap.set(documentId, existingDocument);
|
|
353
|
+
} else {
|
|
354
|
+
revertMap.delete(documentId);
|
|
355
|
+
}
|
|
356
|
+
set({ localDocumentMap: revertMap }, false, n('revertOptimisticUpdate'));
|
|
357
|
+
}
|
|
358
|
+
},
|
|
359
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { LobeDocument } from '@/types/document';
|
|
2
|
+
|
|
3
|
+
export interface DocumentState {
|
|
4
|
+
/**
|
|
5
|
+
* Server documents fetched from document service
|
|
6
|
+
*/
|
|
7
|
+
documents: LobeDocument[];
|
|
8
|
+
/**
|
|
9
|
+
* Loading state for document fetching
|
|
10
|
+
*/
|
|
11
|
+
isDocumentListLoading: boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Local optimistic document map for immediate UI updates
|
|
14
|
+
*/
|
|
15
|
+
localDocumentMap: Map<string, LobeDocument>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const initialDocumentState: DocumentState = {
|
|
19
|
+
documents: [],
|
|
20
|
+
isDocumentListLoading: false,
|
|
21
|
+
localDocumentMap: new Map(),
|
|
22
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { FilesStoreState } from '../../initialState';
|
|
2
|
+
|
|
3
|
+
const getDocumentById = (documentId: string | undefined) => (s: FilesStoreState) => {
|
|
4
|
+
if (!documentId) return undefined;
|
|
5
|
+
|
|
6
|
+
// First check local optimistic map
|
|
7
|
+
const localDocument = s.localDocumentMap.get(documentId);
|
|
8
|
+
|
|
9
|
+
// Then check server documents
|
|
10
|
+
const serverDocument = s.documents.find((doc) => doc.id === documentId);
|
|
11
|
+
|
|
12
|
+
// If both exist, prefer the local update if it's newer
|
|
13
|
+
if (localDocument && serverDocument) {
|
|
14
|
+
return new Date(localDocument.updatedAt) >= new Date(serverDocument.updatedAt)
|
|
15
|
+
? localDocument
|
|
16
|
+
: serverDocument;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Return whichever exists, or undefined if neither exists
|
|
20
|
+
return localDocument || serverDocument;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const documentSelectors = {
|
|
24
|
+
getDocumentById,
|
|
25
|
+
};
|
|
@@ -61,6 +61,7 @@ vi.mock('@/libs/trpc/client', () => ({
|
|
|
61
61
|
file: {
|
|
62
62
|
getFileItemById: { query: vi.fn() },
|
|
63
63
|
getFiles: { query: vi.fn() },
|
|
64
|
+
getKnowledgeItems: { query: vi.fn() },
|
|
64
65
|
removeFileAsyncTask: { mutate: vi.fn() },
|
|
65
66
|
},
|
|
66
67
|
},
|
|
@@ -610,7 +611,7 @@ describe('FileManagerActions', () => {
|
|
|
610
611
|
await result.current.refreshFileList();
|
|
611
612
|
});
|
|
612
613
|
|
|
613
|
-
expect(mutate).toHaveBeenCalledWith(['
|
|
614
|
+
expect(mutate).toHaveBeenCalledWith(['useFetchKnowledgeItems', params]);
|
|
614
615
|
});
|
|
615
616
|
|
|
616
617
|
it('should call mutate with undefined params', async () => {
|
|
@@ -620,7 +621,7 @@ describe('FileManagerActions', () => {
|
|
|
620
621
|
await result.current.refreshFileList();
|
|
621
622
|
});
|
|
622
623
|
|
|
623
|
-
expect(mutate).toHaveBeenCalledWith(['
|
|
624
|
+
expect(mutate).toHaveBeenCalledWith(['useFetchKnowledgeItems', undefined]);
|
|
624
625
|
});
|
|
625
626
|
});
|
|
626
627
|
|
|
@@ -802,7 +803,7 @@ describe('FileManagerActions', () => {
|
|
|
802
803
|
it('should not fetch when id is undefined', () => {
|
|
803
804
|
const { result } = renderHook(() => useStore());
|
|
804
805
|
|
|
805
|
-
renderHook(() => result.current.
|
|
806
|
+
renderHook(() => result.current.useFetchKnowledgeItem(undefined));
|
|
806
807
|
|
|
807
808
|
expect(lambdaClient.file.getFileItemById.query).not.toHaveBeenCalled();
|
|
808
809
|
});
|
|
@@ -820,13 +821,16 @@ describe('FileManagerActions', () => {
|
|
|
820
821
|
id: 'file-1',
|
|
821
822
|
name: 'test.txt',
|
|
822
823
|
size: 100,
|
|
824
|
+
sourceType: 'file',
|
|
823
825
|
updatedAt: new Date(),
|
|
824
826
|
url: 'http://example.com/test.txt',
|
|
825
827
|
};
|
|
826
828
|
|
|
827
829
|
vi.mocked(lambdaClient.file.getFileItemById.query).mockResolvedValue(mockFile);
|
|
828
830
|
|
|
829
|
-
const { result: swrResult } = renderHook(() =>
|
|
831
|
+
const { result: swrResult } = renderHook(() =>
|
|
832
|
+
result.current.useFetchKnowledgeItem('file-1'),
|
|
833
|
+
);
|
|
830
834
|
|
|
831
835
|
await waitFor(() => {
|
|
832
836
|
expect(swrResult.current.data).toEqual(mockFile);
|
|
@@ -834,7 +838,7 @@ describe('FileManagerActions', () => {
|
|
|
834
838
|
});
|
|
835
839
|
});
|
|
836
840
|
|
|
837
|
-
describe('
|
|
841
|
+
describe('useFetchKnowledgeItems', () => {
|
|
838
842
|
it('should fetch file list with params', async () => {
|
|
839
843
|
const { result } = renderHook(() => useStore());
|
|
840
844
|
|
|
@@ -849,6 +853,7 @@ describe('FileManagerActions', () => {
|
|
|
849
853
|
id: 'file-1',
|
|
850
854
|
name: 'test1.txt',
|
|
851
855
|
size: 100,
|
|
856
|
+
sourceType: 'file',
|
|
852
857
|
updatedAt: new Date(),
|
|
853
858
|
url: 'http://example.com/test1.txt',
|
|
854
859
|
},
|
|
@@ -862,15 +867,16 @@ describe('FileManagerActions', () => {
|
|
|
862
867
|
id: 'file-2',
|
|
863
868
|
name: 'test2.txt',
|
|
864
869
|
size: 200,
|
|
870
|
+
sourceType: 'file',
|
|
865
871
|
updatedAt: new Date(),
|
|
866
872
|
url: 'http://example.com/test2.txt',
|
|
867
873
|
},
|
|
868
874
|
];
|
|
869
875
|
|
|
870
|
-
vi.mocked(lambdaClient.file.
|
|
876
|
+
vi.mocked(lambdaClient.file.getKnowledgeItems.query).mockResolvedValue(mockFiles);
|
|
871
877
|
|
|
872
878
|
const params = { category: 'all' as any };
|
|
873
|
-
const { result: swrResult } = renderHook(() => result.current.
|
|
879
|
+
const { result: swrResult } = renderHook(() => result.current.useFetchKnowledgeItems(params));
|
|
874
880
|
|
|
875
881
|
await waitFor(() => {
|
|
876
882
|
expect(swrResult.current.data).toEqual(mockFiles);
|
|
@@ -891,15 +897,16 @@ describe('FileManagerActions', () => {
|
|
|
891
897
|
id: 'file-1',
|
|
892
898
|
name: 'test.txt',
|
|
893
899
|
size: 100,
|
|
900
|
+
sourceType: 'file',
|
|
894
901
|
updatedAt: new Date(),
|
|
895
902
|
url: 'http://example.com/test.txt',
|
|
896
903
|
},
|
|
897
904
|
];
|
|
898
905
|
|
|
899
|
-
vi.mocked(lambdaClient.file.
|
|
906
|
+
vi.mocked(lambdaClient.file.getKnowledgeItems.query).mockResolvedValue(mockFiles);
|
|
900
907
|
|
|
901
908
|
const params = { category: 'all' as any };
|
|
902
|
-
renderHook(() => result.current.
|
|
909
|
+
renderHook(() => result.current.useFetchKnowledgeItems(params));
|
|
903
910
|
|
|
904
911
|
await waitFor(() => {
|
|
905
912
|
expect(result.current.fileList).toEqual(mockFiles);
|
|
@@ -4,7 +4,7 @@ import { StateCreator } from 'zustand/vanilla';
|
|
|
4
4
|
|
|
5
5
|
import { FILE_UPLOAD_BLACKLIST, MAX_UPLOAD_FILE_COUNT } from '@/const/file';
|
|
6
6
|
import { useClientDataSWR } from '@/libs/swr';
|
|
7
|
-
import {
|
|
7
|
+
import { FileService, fileService } from '@/services/file';
|
|
8
8
|
import { ragService } from '@/services/rag';
|
|
9
9
|
import {
|
|
10
10
|
UploadFileListDispatch,
|
|
@@ -35,11 +35,11 @@ export interface FileManageAction {
|
|
|
35
35
|
toggleEmbeddingIds: (ids: string[], loading?: boolean) => void;
|
|
36
36
|
toggleParsingIds: (ids: string[], loading?: boolean) => void;
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
useFetchKnowledgeItem: (id?: string) => SWRResponse<FileListItem | undefined>;
|
|
39
|
+
useFetchKnowledgeItems: (params: QueryFileListParams) => SWRResponse<FileListItem[]>;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
const
|
|
42
|
+
const FETCH_ALL_KNOWLEDGE_KEY = 'useFetchKnowledgeItems';
|
|
43
43
|
|
|
44
44
|
export const createFileManageSlice: StateCreator<
|
|
45
45
|
FileStore,
|
|
@@ -171,7 +171,7 @@ export const createFileManageSlice: StateCreator<
|
|
|
171
171
|
get().toggleParsingIds([id], false);
|
|
172
172
|
},
|
|
173
173
|
refreshFileList: async () => {
|
|
174
|
-
await mutate([
|
|
174
|
+
await mutate([FETCH_ALL_KNOWLEDGE_KEY, get().queryListParams]);
|
|
175
175
|
},
|
|
176
176
|
removeAllFiles: async () => {
|
|
177
177
|
await fileService.removeAllFiles();
|
|
@@ -220,15 +220,15 @@ export const createFileManageSlice: StateCreator<
|
|
|
220
220
|
});
|
|
221
221
|
},
|
|
222
222
|
|
|
223
|
-
|
|
224
|
-
useClientDataSWR<FileListItem | undefined>(!id ? null : ['
|
|
225
|
-
serverFileService.
|
|
223
|
+
useFetchKnowledgeItem: (id) =>
|
|
224
|
+
useClientDataSWR<FileListItem | undefined>(!id ? null : ['useFetchKnowledgeItem', id], () =>
|
|
225
|
+
serverFileService.getKnowledgeItem(id!),
|
|
226
226
|
),
|
|
227
227
|
|
|
228
|
-
|
|
228
|
+
useFetchKnowledgeItems: (params) =>
|
|
229
229
|
useClientDataSWR<FileListItem[]>(
|
|
230
|
-
[
|
|
231
|
-
() => serverFileService.
|
|
230
|
+
[FETCH_ALL_KNOWLEDGE_KEY, params],
|
|
231
|
+
() => serverFileService.getKnowledgeItems(params),
|
|
232
232
|
{
|
|
233
233
|
onSuccess: (data) => {
|
|
234
234
|
set({ fileList: data, queryListParams: params });
|
package/src/store/file/store.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { createDevtools } from '../middleware/createDevtools';
|
|
|
6
6
|
import { FilesStoreState, initialState } from './initialState';
|
|
7
7
|
import { FileAction, createFileSlice } from './slices/chat';
|
|
8
8
|
import { FileChunkAction, createFileChunkSlice } from './slices/chunk';
|
|
9
|
+
import { DocumentAction, createDocumentSlice } from './slices/document';
|
|
9
10
|
import { FileManageAction, createFileManageSlice } from './slices/fileManager';
|
|
10
11
|
import { TTSFileAction, createTTSFileSlice } from './slices/tts';
|
|
11
12
|
import { FileUploadAction, createFileUploadSlice } from './slices/upload/action';
|
|
@@ -14,6 +15,7 @@ import { FileUploadAction, createFileUploadSlice } from './slices/upload/action'
|
|
|
14
15
|
|
|
15
16
|
export type FileStore = FilesStoreState &
|
|
16
17
|
FileAction &
|
|
18
|
+
DocumentAction &
|
|
17
19
|
TTSFileAction &
|
|
18
20
|
FileManageAction &
|
|
19
21
|
FileChunkAction &
|
|
@@ -22,6 +24,7 @@ export type FileStore = FilesStoreState &
|
|
|
22
24
|
const createStore: StateCreator<FileStore, [['zustand/devtools', never]]> = (...parameters) => ({
|
|
23
25
|
...initialState,
|
|
24
26
|
...createFileSlice(...parameters),
|
|
27
|
+
...createDocumentSlice(...parameters),
|
|
25
28
|
...createFileManageSlice(...parameters),
|
|
26
29
|
...createTTSFileSlice(...parameters),
|
|
27
30
|
...createFileChunkSlice(...parameters),
|
|
@@ -9,7 +9,7 @@ import { AsyncLocalStorage } from '@/utils/localStorage';
|
|
|
9
9
|
export enum SidebarTabKey {
|
|
10
10
|
Chat = 'chat',
|
|
11
11
|
Discover = 'discover',
|
|
12
|
-
Files = '
|
|
12
|
+
Files = 'knowledge',
|
|
13
13
|
Image = 'image',
|
|
14
14
|
Me = 'me',
|
|
15
15
|
Setting = 'settings',
|
|
@@ -72,6 +72,7 @@ export interface SystemStatus {
|
|
|
72
72
|
*/
|
|
73
73
|
isEnablePglite?: boolean;
|
|
74
74
|
isShowCredit?: boolean;
|
|
75
|
+
knowledgeBaseModalViewMode?: 'list' | 'masonry';
|
|
75
76
|
language?: LocaleMode;
|
|
76
77
|
/**
|
|
77
78
|
* 记住用户最后选择的图像生成模型
|
|
@@ -142,6 +143,7 @@ export const INITIAL_STATUS = {
|
|
|
142
143
|
hideThreadLimitAlert: false,
|
|
143
144
|
imagePanelWidth: 320,
|
|
144
145
|
imageTopicPanelWidth: 80,
|
|
146
|
+
knowledgeBaseModalViewMode: 'list' as const,
|
|
145
147
|
mobileShowTopic: false,
|
|
146
148
|
noWideScreen: true,
|
|
147
149
|
portalWidth: 400,
|