@lobehub/lobehub 2.0.0-next.53 → 2.0.0-next.55
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/Conversation/Messages/Assistant/index.tsx +7 -1
- 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 +68 -16
- package/src/features/KnowledgeManager/Header/AddButton.tsx +107 -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 +83 -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 +85 -2
- package/src/locales/default/tool.ts +8 -0
- 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/chat/slices/builtinTool/actions/localSystem.ts +1 -1
- 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/tools/interventions.ts +3 -5
- package/src/tools/local-system/Intervention/MoveLocalFiles/MoveFileItem.tsx +56 -0
- package/src/tools/local-system/Intervention/MoveLocalFiles/index.tsx +26 -0
- package/src/tools/local-system/Intervention/RunCommand/index.tsx +1 -2
- package/src/tools/local-system/Intervention/index.ts +11 -0
- package/src/tools/local-system/Render/MoveLocalFiles/MoveFileItem.tsx +56 -0
- package/src/tools/local-system/Render/MoveLocalFiles/index.tsx +26 -0
- package/src/tools/local-system/Render/index.ts +21 -0
- package/src/tools/renders.ts +6 -24
- package/src/tools/web-browsing/Render/index.ts +13 -0
- 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
|
@@ -12,7 +12,7 @@ import { Virtuoso } from 'react-virtuoso';
|
|
|
12
12
|
|
|
13
13
|
import { useFileStore } from '@/store/file';
|
|
14
14
|
import { useGlobalStore } from '@/store/global';
|
|
15
|
-
import { SortType } from '@/types/files';
|
|
15
|
+
import { FilesTabs, SortType } from '@/types/files';
|
|
16
16
|
|
|
17
17
|
import EmptyStatus from './EmptyStatus';
|
|
18
18
|
import FileListItem, { FILE_DATE_WIDTH, FILE_SIZE_WIDTH } from './FileListItem';
|
|
@@ -40,13 +40,13 @@ const useStyles = createStyles(({ css, token, isDarkMode }) => ({
|
|
|
40
40
|
`,
|
|
41
41
|
}));
|
|
42
42
|
|
|
43
|
-
interface
|
|
43
|
+
interface FileExplorerProps {
|
|
44
44
|
category?: string;
|
|
45
45
|
knowledgeBaseId?: string;
|
|
46
46
|
onOpenFile: (id: string) => void;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
const
|
|
49
|
+
const FileExplorer = memo<FileExplorerProps>(({ knowledgeBaseId, category, onOpenFile }) => {
|
|
50
50
|
const { t } = useTranslation('components');
|
|
51
51
|
const { styles } = useStyles();
|
|
52
52
|
|
|
@@ -54,11 +54,19 @@ const FileList = memo<FileListProps>(({ knowledgeBaseId, category, onOpenFile })
|
|
|
54
54
|
const [viewConfig, setViewConfig] = useState({ showFilesInKnowledgeBase: false });
|
|
55
55
|
const [lastSelectedIndex, setLastSelectedIndex] = useState<number | null>(null);
|
|
56
56
|
const [isTransitioning, setIsTransitioning] = useState(false);
|
|
57
|
+
const [isMasonryReady, setIsMasonryReady] = useState(false);
|
|
57
58
|
|
|
58
|
-
|
|
59
|
+
// Always use masonry view for Images category, ignore stored preference
|
|
60
|
+
const storedViewMode = useGlobalStore((s) => s.status.fileManagerViewMode);
|
|
61
|
+
const viewMode = (
|
|
62
|
+
category === FilesTabs.Images ? 'masonry' : storedViewMode || 'list'
|
|
63
|
+
) as ViewMode;
|
|
59
64
|
const updateSystemStatus = useGlobalStore((s) => s.updateSystemStatus);
|
|
60
65
|
const setViewMode = (mode: ViewMode) => {
|
|
61
66
|
setIsTransitioning(true);
|
|
67
|
+
if (mode === 'masonry') {
|
|
68
|
+
setIsMasonryReady(false);
|
|
69
|
+
}
|
|
62
70
|
updateSystemStatus({ fileManagerViewMode: mode });
|
|
63
71
|
};
|
|
64
72
|
|
|
@@ -71,7 +79,7 @@ const FileList = memo<FileListProps>(({ knowledgeBaseId, category, onOpenFile })
|
|
|
71
79
|
setColumnCount(2);
|
|
72
80
|
} else if (width < 1024) {
|
|
73
81
|
setColumnCount(3);
|
|
74
|
-
} else if (width <
|
|
82
|
+
} else if (width < 1536) {
|
|
75
83
|
setColumnCount(4);
|
|
76
84
|
} else {
|
|
77
85
|
setColumnCount(5);
|
|
@@ -100,9 +108,9 @@ const FileList = memo<FileListProps>(({ knowledgeBaseId, category, onOpenFile })
|
|
|
100
108
|
defaultValue: SortType.Desc,
|
|
101
109
|
});
|
|
102
110
|
|
|
103
|
-
const
|
|
111
|
+
const useFetchKnowledgeItems = useFileStore((s) => s.useFetchKnowledgeItems);
|
|
104
112
|
|
|
105
|
-
const { data, isLoading } =
|
|
113
|
+
const { data, isLoading } = useFetchKnowledgeItems({
|
|
106
114
|
category,
|
|
107
115
|
knowledgeBaseId,
|
|
108
116
|
q: query,
|
|
@@ -111,6 +119,19 @@ const FileList = memo<FileListProps>(({ knowledgeBaseId, category, onOpenFile })
|
|
|
111
119
|
...viewConfig,
|
|
112
120
|
});
|
|
113
121
|
|
|
122
|
+
// Debug: Log received data
|
|
123
|
+
React.useEffect(() => {
|
|
124
|
+
if (data) {
|
|
125
|
+
console.log('[FileList] Received data:', {
|
|
126
|
+
count: data.length,
|
|
127
|
+
documents: data.filter((item) => item.sourceType === 'document'),
|
|
128
|
+
sampleDocumentWithEditorData: data.find(
|
|
129
|
+
(item) => item.sourceType === 'document' && item.editorData,
|
|
130
|
+
),
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}, [data]);
|
|
134
|
+
|
|
114
135
|
// Handle view transition with a brief delay to show skeleton
|
|
115
136
|
React.useEffect(() => {
|
|
116
137
|
if (isTransitioning && data) {
|
|
@@ -124,6 +145,20 @@ const FileList = memo<FileListProps>(({ knowledgeBaseId, category, onOpenFile })
|
|
|
124
145
|
}
|
|
125
146
|
}, [isTransitioning, viewMode, data]);
|
|
126
147
|
|
|
148
|
+
// Mark masonry as ready after it has time to mount and render
|
|
149
|
+
React.useEffect(() => {
|
|
150
|
+
if (viewMode === 'masonry' && data && !isLoading && !isTransitioning) {
|
|
151
|
+
// Give VirtuosoMasonry enough time to fully render and calculate layout
|
|
152
|
+
const timer = setTimeout(() => {
|
|
153
|
+
setIsMasonryReady(true);
|
|
154
|
+
}, 300);
|
|
155
|
+
return () => clearTimeout(timer);
|
|
156
|
+
} else if (viewMode === 'list') {
|
|
157
|
+
// Reset when switching to list view
|
|
158
|
+
setIsMasonryReady(false);
|
|
159
|
+
}
|
|
160
|
+
}, [viewMode, data, isLoading, isTransitioning]);
|
|
161
|
+
|
|
127
162
|
useCheckTaskStatus(data);
|
|
128
163
|
|
|
129
164
|
// Clean up selected files that no longer exist in the data
|
|
@@ -188,12 +223,8 @@ const FileList = memo<FileListProps>(({ knowledgeBaseId, category, onOpenFile })
|
|
|
188
223
|
</Flexbox>
|
|
189
224
|
)}
|
|
190
225
|
</Flexbox>
|
|
191
|
-
{isLoading || isTransitioning ? (
|
|
192
|
-
|
|
193
|
-
<MasonrySkeleton columnCount={columnCount} />
|
|
194
|
-
) : (
|
|
195
|
-
<FileSkeleton />
|
|
196
|
-
)
|
|
226
|
+
{isLoading || (viewMode === 'list' && isTransitioning) ? (
|
|
227
|
+
<FileSkeleton />
|
|
197
228
|
) : viewMode === 'list' ? (
|
|
198
229
|
<Virtuoso
|
|
199
230
|
components={{
|
|
@@ -243,8 +274,29 @@ const FileList = memo<FileListProps>(({ knowledgeBaseId, category, onOpenFile })
|
|
|
243
274
|
style={{ flex: 1 }}
|
|
244
275
|
/>
|
|
245
276
|
) : (
|
|
246
|
-
<div style={{ flex: 1, overflow: 'hidden' }}>
|
|
247
|
-
|
|
277
|
+
<div style={{ flex: 1, overflow: 'hidden', position: 'relative' }}>
|
|
278
|
+
{/* Skeleton overlay */}
|
|
279
|
+
{(isTransitioning || !isMasonryReady) && (
|
|
280
|
+
<div
|
|
281
|
+
style={{
|
|
282
|
+
background: 'inherit',
|
|
283
|
+
inset: 0,
|
|
284
|
+
position: 'absolute',
|
|
285
|
+
zIndex: 10,
|
|
286
|
+
}}
|
|
287
|
+
>
|
|
288
|
+
<MasonrySkeleton columnCount={columnCount} />
|
|
289
|
+
</div>
|
|
290
|
+
)}
|
|
291
|
+
{/* Masonry content - always rendered but hidden until ready */}
|
|
292
|
+
<div
|
|
293
|
+
style={{
|
|
294
|
+
height: '100%',
|
|
295
|
+
opacity: isMasonryReady ? 1 : 0,
|
|
296
|
+
overflowY: 'auto',
|
|
297
|
+
transition: 'opacity 0.2s ease-in-out',
|
|
298
|
+
}}
|
|
299
|
+
>
|
|
248
300
|
<div style={{ paddingBlockEnd: 64, paddingBlockStart: 12, paddingInline: 24 }}>
|
|
249
301
|
<VirtuosoMasonry
|
|
250
302
|
ItemContent={MasonryItemWrapper}
|
|
@@ -263,4 +315,4 @@ const FileList = memo<FileListProps>(({ knowledgeBaseId, category, onOpenFile })
|
|
|
263
315
|
);
|
|
264
316
|
});
|
|
265
317
|
|
|
266
|
-
export default
|
|
318
|
+
export default FileExplorer;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { Button, Dropdown, Icon, MenuProps } from '@lobehub/ui';
|
|
4
|
+
import { Upload } from 'antd';
|
|
5
|
+
import { css, cx } from 'antd-style';
|
|
6
|
+
import { FilePenLine, FileUp, FolderUp, Plus } from 'lucide-react';
|
|
7
|
+
import { useMemo, useState } from 'react';
|
|
8
|
+
import { useTranslation } from 'react-i18next';
|
|
9
|
+
|
|
10
|
+
import DragUpload from '@/components/DragUpload';
|
|
11
|
+
import { useFileStore } from '@/store/file';
|
|
12
|
+
|
|
13
|
+
import NoteEditorModal from '../DocumentExplorer/NoteEditorModal';
|
|
14
|
+
|
|
15
|
+
const hotArea = css`
|
|
16
|
+
&::before {
|
|
17
|
+
content: '';
|
|
18
|
+
position: absolute;
|
|
19
|
+
inset: 0;
|
|
20
|
+
background-color: transparent;
|
|
21
|
+
}
|
|
22
|
+
`;
|
|
23
|
+
|
|
24
|
+
const AddButton = ({ knowledgeBaseId }: { knowledgeBaseId?: string }) => {
|
|
25
|
+
const { t } = useTranslation('file');
|
|
26
|
+
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
27
|
+
const pushDockFileList = useFileStore((s) => s.pushDockFileList);
|
|
28
|
+
|
|
29
|
+
const handleOpenNoteEditor = () => {
|
|
30
|
+
setIsModalOpen(true);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const handleCloseNoteEditor = () => {
|
|
34
|
+
setIsModalOpen(false);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const items = useMemo<MenuProps['items']>(
|
|
38
|
+
() => [
|
|
39
|
+
{
|
|
40
|
+
icon: <Icon icon={FilePenLine} />,
|
|
41
|
+
key: 'create-note',
|
|
42
|
+
label: t('addPage'),
|
|
43
|
+
onClick: handleOpenNoteEditor,
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
type: 'divider',
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
icon: <Icon icon={FileUp} />,
|
|
50
|
+
key: 'upload-file',
|
|
51
|
+
label: (
|
|
52
|
+
<Upload
|
|
53
|
+
beforeUpload={async (file) => {
|
|
54
|
+
await pushDockFileList([file], knowledgeBaseId);
|
|
55
|
+
|
|
56
|
+
return false;
|
|
57
|
+
}}
|
|
58
|
+
multiple={true}
|
|
59
|
+
showUploadList={false}
|
|
60
|
+
>
|
|
61
|
+
<div className={cx(hotArea)}>Upload File</div>
|
|
62
|
+
</Upload>
|
|
63
|
+
),
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
icon: <Icon icon={FolderUp} />,
|
|
67
|
+
key: 'upload-folder',
|
|
68
|
+
label: (
|
|
69
|
+
<Upload
|
|
70
|
+
beforeUpload={async (file) => {
|
|
71
|
+
await pushDockFileList([file], knowledgeBaseId);
|
|
72
|
+
|
|
73
|
+
return false;
|
|
74
|
+
}}
|
|
75
|
+
directory
|
|
76
|
+
multiple={true}
|
|
77
|
+
showUploadList={false}
|
|
78
|
+
>
|
|
79
|
+
<div className={cx(hotArea)}>Upload Folder</div>
|
|
80
|
+
</Upload>
|
|
81
|
+
),
|
|
82
|
+
},
|
|
83
|
+
],
|
|
84
|
+
[knowledgeBaseId, pushDockFileList],
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
<>
|
|
89
|
+
<Dropdown menu={{ items }} placement="bottomRight">
|
|
90
|
+
<Button icon={Plus} type="primary">
|
|
91
|
+
{t('addKnowledge')}
|
|
92
|
+
</Button>
|
|
93
|
+
</Dropdown>
|
|
94
|
+
<DragUpload
|
|
95
|
+
enabledFiles
|
|
96
|
+
onUploadFiles={(files) => pushDockFileList(files, knowledgeBaseId)}
|
|
97
|
+
/>
|
|
98
|
+
<NoteEditorModal
|
|
99
|
+
knowledgeBaseId={knowledgeBaseId}
|
|
100
|
+
onClose={handleCloseNoteEditor}
|
|
101
|
+
open={isModalOpen}
|
|
102
|
+
/>
|
|
103
|
+
</>
|
|
104
|
+
);
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
export default AddButton;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { Button } from '@lobehub/ui';
|
|
4
|
+
import { FilePenLine } from 'lucide-react';
|
|
5
|
+
import { useState } from 'react';
|
|
6
|
+
import { useTranslation } from 'react-i18next';
|
|
7
|
+
|
|
8
|
+
import NoteEditorModal from '../DocumentExplorer/NoteEditorModal';
|
|
9
|
+
|
|
10
|
+
const NewNoteButton = ({ knowledgeBaseId }: { knowledgeBaseId?: string }) => {
|
|
11
|
+
const { t } = useTranslation('file');
|
|
12
|
+
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
13
|
+
|
|
14
|
+
const handleOpen = () => {
|
|
15
|
+
setIsModalOpen(true);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const handleClose = () => {
|
|
19
|
+
setIsModalOpen(false);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<>
|
|
24
|
+
<Button icon={FilePenLine} onClick={handleOpen} type="primary">
|
|
25
|
+
{t('header.newDocumentButton')}
|
|
26
|
+
</Button>
|
|
27
|
+
|
|
28
|
+
<NoteEditorModal knowledgeBaseId={knowledgeBaseId} onClose={handleClose} open={isModalOpen} />
|
|
29
|
+
</>
|
|
30
|
+
);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export default NewNoteButton;
|
|
@@ -3,20 +3,14 @@
|
|
|
3
3
|
import { ChatHeader } from '@lobehub/ui/chat';
|
|
4
4
|
import { memo } from 'react';
|
|
5
5
|
|
|
6
|
+
import AddButton from './AddButton';
|
|
6
7
|
import FilesSearchBar from './FilesSearchBar';
|
|
7
|
-
import TogglePanelButton from './TogglePanelButton';
|
|
8
|
-
import UploadFileButton from './UploadFileButton';
|
|
9
8
|
|
|
10
9
|
const Header = memo<{ knowledgeBaseId?: string }>(({ knowledgeBaseId }) => {
|
|
11
10
|
return (
|
|
12
11
|
<ChatHeader
|
|
13
|
-
left={
|
|
14
|
-
|
|
15
|
-
<TogglePanelButton />
|
|
16
|
-
<FilesSearchBar />
|
|
17
|
-
</>
|
|
18
|
-
}
|
|
19
|
-
right={<UploadFileButton knowledgeBaseId={knowledgeBaseId} />}
|
|
12
|
+
left={<FilesSearchBar />}
|
|
13
|
+
right={<AddButton knowledgeBaseId={knowledgeBaseId} />}
|
|
20
14
|
styles={{
|
|
21
15
|
left: { padding: 0 },
|
|
22
16
|
}}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { createStyles } from 'antd-style';
|
|
4
|
+
import markdownToTxt from 'markdown-to-txt';
|
|
5
|
+
import { memo } from 'react';
|
|
6
|
+
|
|
7
|
+
import { FileListItem } from '@/types/files';
|
|
8
|
+
|
|
9
|
+
// Helper to extract title from markdown content
|
|
10
|
+
const extractTitle = (content: string): string | null => {
|
|
11
|
+
if (!content) return null;
|
|
12
|
+
|
|
13
|
+
// Find first markdown header (# title)
|
|
14
|
+
const match = content.match(/^#\s+(.+)$/m);
|
|
15
|
+
return match ? match[1].trim() : null;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
// Helper to extract preview text from note content
|
|
19
|
+
const getPreviewText = (item: FileListItem): string => {
|
|
20
|
+
if (!item.content) return '';
|
|
21
|
+
|
|
22
|
+
// Convert markdown to plain text
|
|
23
|
+
let plainText = markdownToTxt(item.content);
|
|
24
|
+
|
|
25
|
+
// Remove the title line if it exists
|
|
26
|
+
const title = extractTitle(item.content);
|
|
27
|
+
if (title) {
|
|
28
|
+
plainText = plainText.replace(title, '').trim();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Limit to first 200 characters for preview
|
|
32
|
+
return plainText.slice(0, 200);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const useStyles = createStyles(({ css, token }) => ({
|
|
36
|
+
card: css`
|
|
37
|
+
cursor: pointer;
|
|
38
|
+
user-select: none;
|
|
39
|
+
|
|
40
|
+
position: relative;
|
|
41
|
+
|
|
42
|
+
overflow: hidden;
|
|
43
|
+
flex-shrink: 0;
|
|
44
|
+
|
|
45
|
+
width: 280px;
|
|
46
|
+
height: 180px;
|
|
47
|
+
border: 1px solid ${token.colorBorderSecondary};
|
|
48
|
+
border-radius: ${token.borderRadiusLG}px;
|
|
49
|
+
|
|
50
|
+
background: ${token.colorBgContainer};
|
|
51
|
+
|
|
52
|
+
transition: all ${token.motionDurationMid};
|
|
53
|
+
|
|
54
|
+
&:hover {
|
|
55
|
+
border-color: ${token.colorPrimary};
|
|
56
|
+
box-shadow: ${token.boxShadowTertiary};
|
|
57
|
+
}
|
|
58
|
+
`,
|
|
59
|
+
noteContent: css`
|
|
60
|
+
overflow: hidden;
|
|
61
|
+
display: flex;
|
|
62
|
+
flex-direction: column;
|
|
63
|
+
gap: 8px;
|
|
64
|
+
|
|
65
|
+
height: 100%;
|
|
66
|
+
padding: 12px;
|
|
67
|
+
`,
|
|
68
|
+
notePreview: css`
|
|
69
|
+
overflow: hidden;
|
|
70
|
+
display: -webkit-box;
|
|
71
|
+
flex: 1;
|
|
72
|
+
-webkit-box-orient: vertical;
|
|
73
|
+
-webkit-line-clamp: 3;
|
|
74
|
+
|
|
75
|
+
font-size: 13px;
|
|
76
|
+
line-height: 1.6;
|
|
77
|
+
color: ${token.colorTextSecondary};
|
|
78
|
+
`,
|
|
79
|
+
noteTitle: css`
|
|
80
|
+
overflow: hidden;
|
|
81
|
+
|
|
82
|
+
font-size: 14px;
|
|
83
|
+
font-weight: ${token.fontWeightStrong};
|
|
84
|
+
line-height: 1.4;
|
|
85
|
+
color: ${token.colorText};
|
|
86
|
+
text-overflow: ellipsis;
|
|
87
|
+
white-space: nowrap;
|
|
88
|
+
`,
|
|
89
|
+
}));
|
|
90
|
+
|
|
91
|
+
interface RecentDocumentCardProps {
|
|
92
|
+
document: FileListItem;
|
|
93
|
+
onClick: () => void;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const RecentDocumentCard = memo<RecentDocumentCardProps>(({ document, onClick }) => {
|
|
97
|
+
const { styles } = useStyles();
|
|
98
|
+
|
|
99
|
+
const title = document.name || '';
|
|
100
|
+
const previewText = getPreviewText(document);
|
|
101
|
+
const emoji = document.metadata?.emoji;
|
|
102
|
+
|
|
103
|
+
return (
|
|
104
|
+
<div className={styles.card} onClick={onClick} role="button" tabIndex={0}>
|
|
105
|
+
<div className={styles.noteContent}>
|
|
106
|
+
<div style={{ alignItems: 'center', display: 'flex', gap: 8 }}>
|
|
107
|
+
{emoji && <span style={{ fontSize: 20 }}>{emoji}</span>}
|
|
108
|
+
<div className={styles.noteTitle}>{title}</div>
|
|
109
|
+
</div>
|
|
110
|
+
{previewText && <div className={styles.notePreview}>{previewText}</div>}
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
export default RecentDocumentCard;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { createStyles } from 'antd-style';
|
|
4
|
+
import { memo } from 'react';
|
|
5
|
+
|
|
6
|
+
import { FileListItem } from '@/types/files';
|
|
7
|
+
|
|
8
|
+
import RecentDocumentCard from './RecentDocumentCard';
|
|
9
|
+
import RecentFilesSkeleton from './RecentFilesSkeleton';
|
|
10
|
+
|
|
11
|
+
const useStyles = createStyles(({ css, token }) => ({
|
|
12
|
+
container: css`
|
|
13
|
+
position: relative;
|
|
14
|
+
overflow: hidden;
|
|
15
|
+
`,
|
|
16
|
+
fadeEdge: css`
|
|
17
|
+
pointer-events: none;
|
|
18
|
+
|
|
19
|
+
position: absolute;
|
|
20
|
+
inset-block: 0 0;
|
|
21
|
+
inset-inline-end: 0;
|
|
22
|
+
|
|
23
|
+
width: 80px;
|
|
24
|
+
|
|
25
|
+
background: linear-gradient(to left, ${token.colorBgContainerSecondary}, transparent);
|
|
26
|
+
`,
|
|
27
|
+
scrollContainer: css`
|
|
28
|
+
scroll-behavior: smooth;
|
|
29
|
+
|
|
30
|
+
/* Hide scrollbar */
|
|
31
|
+
scrollbar-width: none;
|
|
32
|
+
|
|
33
|
+
overflow: auto hidden;
|
|
34
|
+
display: flex;
|
|
35
|
+
gap: 16px;
|
|
36
|
+
|
|
37
|
+
padding-block-end: 8px;
|
|
38
|
+
padding-inline-end: 80px;
|
|
39
|
+
|
|
40
|
+
-ms-overflow-style: none;
|
|
41
|
+
|
|
42
|
+
&::-webkit-scrollbar {
|
|
43
|
+
display: none;
|
|
44
|
+
}
|
|
45
|
+
`,
|
|
46
|
+
}));
|
|
47
|
+
|
|
48
|
+
interface RecentDocumentsProps {
|
|
49
|
+
documents: FileListItem[];
|
|
50
|
+
isLoading?: boolean;
|
|
51
|
+
onOpenDocument: (id: string) => void;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const RecentDocuments = memo<RecentDocumentsProps>(({ documents, isLoading, onOpenDocument }) => {
|
|
55
|
+
const { styles } = useStyles();
|
|
56
|
+
|
|
57
|
+
if (isLoading) {
|
|
58
|
+
return <RecentFilesSkeleton />;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<div className={styles.container}>
|
|
63
|
+
<div className={styles.scrollContainer}>
|
|
64
|
+
{documents.map((document) => (
|
|
65
|
+
<RecentDocumentCard
|
|
66
|
+
document={document}
|
|
67
|
+
key={document.id}
|
|
68
|
+
onClick={() => onOpenDocument(document.id)}
|
|
69
|
+
/>
|
|
70
|
+
))}
|
|
71
|
+
</div>
|
|
72
|
+
<div className={styles.fadeEdge} />
|
|
73
|
+
</div>
|
|
74
|
+
);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
export default RecentDocuments;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { formatSize } from '@lobechat/utils/format';
|
|
4
|
+
import { Image as LobeImage, Text } from '@lobehub/ui';
|
|
5
|
+
import { createStyles } from 'antd-style';
|
|
6
|
+
import dayjs from 'dayjs';
|
|
7
|
+
import { memo } from 'react';
|
|
8
|
+
import { Flexbox } from 'react-layout-kit';
|
|
9
|
+
|
|
10
|
+
import FileIcon from '@/components/FileIcon';
|
|
11
|
+
import { FileListItem } from '@/types/files';
|
|
12
|
+
|
|
13
|
+
const IMAGE_FILE_TYPES = new Set([
|
|
14
|
+
'image/png',
|
|
15
|
+
'image/jpeg',
|
|
16
|
+
'image/jpg',
|
|
17
|
+
'image/gif',
|
|
18
|
+
'image/webp',
|
|
19
|
+
'image/svg+xml',
|
|
20
|
+
]);
|
|
21
|
+
|
|
22
|
+
const useStyles = createStyles(({ css, token }) => ({
|
|
23
|
+
card: css`
|
|
24
|
+
cursor: pointer;
|
|
25
|
+
|
|
26
|
+
position: relative;
|
|
27
|
+
|
|
28
|
+
overflow: hidden;
|
|
29
|
+
flex-shrink: 0;
|
|
30
|
+
|
|
31
|
+
width: 280px;
|
|
32
|
+
padding: 12px;
|
|
33
|
+
border: 1px solid ${token.colorBorderSecondary};
|
|
34
|
+
border-radius: ${token.borderRadiusLG}px;
|
|
35
|
+
|
|
36
|
+
background: ${token.colorBgContainer};
|
|
37
|
+
|
|
38
|
+
transition: all ${token.motionDurationMid};
|
|
39
|
+
|
|
40
|
+
&:hover {
|
|
41
|
+
border-color: ${token.colorPrimary};
|
|
42
|
+
box-shadow: ${token.boxShadowTertiary};
|
|
43
|
+
}
|
|
44
|
+
`,
|
|
45
|
+
iconWrapper: css`
|
|
46
|
+
display: flex;
|
|
47
|
+
align-items: center;
|
|
48
|
+
justify-content: center;
|
|
49
|
+
|
|
50
|
+
height: 160px;
|
|
51
|
+
margin-block-end: 12px;
|
|
52
|
+
border-radius: ${token.borderRadius}px;
|
|
53
|
+
|
|
54
|
+
background: ${token.colorBgLayout};
|
|
55
|
+
`,
|
|
56
|
+
imagePreview: css`
|
|
57
|
+
width: 100%;
|
|
58
|
+
height: 160px;
|
|
59
|
+
margin-block-end: 12px;
|
|
60
|
+
border-radius: ${token.borderRadius}px;
|
|
61
|
+
|
|
62
|
+
object-fit: cover;
|
|
63
|
+
background: ${token.colorBgLayout};
|
|
64
|
+
`,
|
|
65
|
+
info: css`
|
|
66
|
+
font-size: 12px;
|
|
67
|
+
color: ${token.colorTextDescription};
|
|
68
|
+
`,
|
|
69
|
+
title: css`
|
|
70
|
+
margin: 0 !important;
|
|
71
|
+
font-size: 14px;
|
|
72
|
+
font-weight: 500;
|
|
73
|
+
line-height: 1.4;
|
|
74
|
+
`,
|
|
75
|
+
}));
|
|
76
|
+
|
|
77
|
+
interface RecentFileCardProps {
|
|
78
|
+
file: FileListItem;
|
|
79
|
+
onClick: () => void;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const RecentFileCard = memo<RecentFileCardProps>(({ file, onClick }) => {
|
|
83
|
+
const { styles } = useStyles();
|
|
84
|
+
|
|
85
|
+
const isImage = IMAGE_FILE_TYPES.has(file.fileType);
|
|
86
|
+
const relativeTime = dayjs(file.updatedAt).fromNow();
|
|
87
|
+
|
|
88
|
+
return (
|
|
89
|
+
<div className={styles.card} onClick={onClick} role="button" tabIndex={0}>
|
|
90
|
+
<Flexbox gap={12} style={{ position: 'relative' }}>
|
|
91
|
+
{/* Preview or Icon */}
|
|
92
|
+
{isImage && file.url ? (
|
|
93
|
+
<LobeImage
|
|
94
|
+
alt={file.name}
|
|
95
|
+
className={styles.imagePreview}
|
|
96
|
+
preview={false}
|
|
97
|
+
src={file.url}
|
|
98
|
+
/>
|
|
99
|
+
) : (
|
|
100
|
+
<div className={styles.iconWrapper}>
|
|
101
|
+
<FileIcon fileName={file.name} fileType={file.fileType} size={48} />
|
|
102
|
+
</div>
|
|
103
|
+
)}
|
|
104
|
+
|
|
105
|
+
{/* File Info */}
|
|
106
|
+
<Flexbox gap={6} style={{ overflow: 'hidden', position: 'relative' }}>
|
|
107
|
+
<Text className={styles.title} ellipsis={{ rows: 2 }}>
|
|
108
|
+
{file.name}
|
|
109
|
+
</Text>
|
|
110
|
+
<Flexbox className={styles.info} gap={8} horizontal>
|
|
111
|
+
<span>{relativeTime}</span>
|
|
112
|
+
<span>•</span>
|
|
113
|
+
<span>{formatSize(file.size)}</span>
|
|
114
|
+
</Flexbox>
|
|
115
|
+
</Flexbox>
|
|
116
|
+
</Flexbox>
|
|
117
|
+
</div>
|
|
118
|
+
);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
export default RecentFileCard;
|