@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.
Files changed (152) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/changelog/v1.json +18 -0
  3. package/locales/ar/common.json +1 -0
  4. package/locales/ar/file.json +85 -2
  5. package/locales/bg-BG/common.json +1 -0
  6. package/locales/bg-BG/file.json +85 -2
  7. package/locales/de-DE/common.json +1 -0
  8. package/locales/de-DE/file.json +85 -2
  9. package/locales/en-US/common.json +1 -0
  10. package/locales/en-US/file.json +85 -2
  11. package/locales/es-ES/common.json +1 -0
  12. package/locales/es-ES/file.json +85 -2
  13. package/locales/fa-IR/common.json +1 -0
  14. package/locales/fa-IR/file.json +85 -2
  15. package/locales/fr-FR/common.json +1 -0
  16. package/locales/fr-FR/file.json +85 -2
  17. package/locales/it-IT/common.json +1 -0
  18. package/locales/it-IT/file.json +85 -2
  19. package/locales/ja-JP/common.json +1 -0
  20. package/locales/ja-JP/file.json +85 -2
  21. package/locales/ko-KR/common.json +1 -0
  22. package/locales/ko-KR/file.json +85 -2
  23. package/locales/nl-NL/common.json +1 -0
  24. package/locales/nl-NL/file.json +85 -2
  25. package/locales/pl-PL/common.json +1 -0
  26. package/locales/pl-PL/file.json +85 -2
  27. package/locales/pt-BR/common.json +1 -0
  28. package/locales/pt-BR/file.json +85 -2
  29. package/locales/ru-RU/common.json +1 -0
  30. package/locales/ru-RU/file.json +85 -2
  31. package/locales/tr-TR/common.json +1 -0
  32. package/locales/tr-TR/file.json +85 -2
  33. package/locales/vi-VN/common.json +1 -0
  34. package/locales/vi-VN/file.json +85 -2
  35. package/locales/zh-CN/common.json +1 -0
  36. package/locales/zh-CN/file.json +85 -2
  37. package/locales/zh-TW/common.json +1 -0
  38. package/locales/zh-TW/file.json +85 -2
  39. package/package.json +1 -1
  40. package/packages/database/src/models/__tests__/file.test.ts +94 -29
  41. package/packages/database/src/models/file.ts +15 -4
  42. package/packages/database/src/repositories/knowledge/index.test.ts +300 -0
  43. package/packages/database/src/repositories/knowledge/index.ts +420 -0
  44. package/packages/model-bank/src/aiModels/aihubmix.ts +1 -0
  45. package/packages/model-bank/src/aiModels/google.ts +9 -5
  46. package/packages/model-bank/src/aiModels/openai.ts +2 -35
  47. package/packages/model-bank/src/aiModels/openrouter.ts +1 -0
  48. package/packages/model-bank/src/aiModels/vertexai.ts +2 -0
  49. package/packages/model-bank/src/types/aiModel.ts +15 -2
  50. package/packages/model-runtime/src/core/usageConverters/index.ts +1 -0
  51. package/packages/model-runtime/src/core/usageConverters/utils/resolveImageSinglePrice.ts +34 -0
  52. package/packages/types/src/document/index.ts +14 -2
  53. package/packages/types/src/files/index.ts +2 -0
  54. package/packages/types/src/files/list.ts +10 -0
  55. package/packages/types/src/llm.ts +1 -1
  56. package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/ModelSelect/ImageModelItem.tsx +93 -0
  57. package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/{ModelSelect.tsx → ModelSelect/index.tsx} +17 -2
  58. package/src/app/[variants]/(main)/knowledge/KnowledgeRouter.tsx +2 -1
  59. package/src/app/[variants]/(main)/knowledge/components/KnowledgeBaseItem/index.tsx +0 -2
  60. package/src/app/[variants]/(main)/knowledge/hooks/useFileCategory.ts +6 -3
  61. package/src/app/[variants]/(main)/knowledge/routes/KnowledgeBaseDetail/index.tsx +2 -2
  62. package/src/app/[variants]/(main)/knowledge/routes/KnowledgeBaseDetail/menu/{MenuItems.tsx → CategoryMenu.tsx} +3 -3
  63. package/src/app/[variants]/(main)/knowledge/routes/KnowledgeBaseDetail/menu/Menu.tsx +2 -2
  64. package/src/app/[variants]/(main)/knowledge/routes/KnowledgeHome/index.tsx +40 -18
  65. package/src/app/[variants]/(main)/knowledge/routes/KnowledgeHome/layout/Container.tsx +1 -1
  66. package/src/app/[variants]/(main)/knowledge/routes/KnowledgeHome/menu/CategoryMenu.tsx +148 -0
  67. package/src/app/[variants]/(main)/knowledge/routes/KnowledgeHome/menu/KnowledgeBase.tsx +20 -7
  68. package/src/components/FileIcon/index.tsx +3 -1
  69. package/src/features/ChatInput/ActionBar/Knowledge/index.tsx +2 -2
  70. package/src/features/FileSidePanel/index.tsx +1 -1
  71. package/src/features/KnowledgeBaseModal/AssignKnowledgeBase/Item/MasonryItem.tsx +80 -0
  72. package/src/features/KnowledgeBaseModal/AssignKnowledgeBase/Item/MasonryItemWrapper.tsx +27 -0
  73. package/src/features/KnowledgeBaseModal/AssignKnowledgeBase/List.tsx +104 -23
  74. package/src/features/KnowledgeBaseModal/AssignKnowledgeBase/MasonrySkeleton.tsx +62 -0
  75. package/src/features/KnowledgeBaseModal/AssignKnowledgeBase/index.tsx +3 -2
  76. package/src/features/KnowledgeBaseModal/CreateNew/CreateForm.tsx +1 -1
  77. package/src/features/KnowledgeManager/DocumentExplorer/DocumentActions.tsx +111 -0
  78. package/src/features/KnowledgeManager/DocumentExplorer/DocumentEditor.tsx +723 -0
  79. package/src/features/KnowledgeManager/DocumentExplorer/DocumentEditorPlaceholder.tsx +169 -0
  80. package/src/features/KnowledgeManager/DocumentExplorer/DocumentListItem.tsx +148 -0
  81. package/src/features/KnowledgeManager/DocumentExplorer/DocumentListSkeleton.tsx +39 -0
  82. package/src/features/KnowledgeManager/DocumentExplorer/NoteEditorModal.tsx +348 -0
  83. package/src/features/KnowledgeManager/DocumentExplorer/RenamePopover.tsx +163 -0
  84. package/src/features/KnowledgeManager/DocumentExplorer/index.tsx +318 -0
  85. package/src/features/{FileManager/FileList → KnowledgeManager/FileExplorer}/FileListItem/index.tsx +48 -9
  86. package/src/features/KnowledgeManager/FileExplorer/MasonryFileItem/DefaultFileItem.tsx +149 -0
  87. package/src/features/KnowledgeManager/FileExplorer/MasonryFileItem/ImageFileItem.tsx +245 -0
  88. package/src/features/KnowledgeManager/FileExplorer/MasonryFileItem/MarkdownFileItem.tsx +232 -0
  89. package/src/features/KnowledgeManager/FileExplorer/MasonryFileItem/NoteFileItem.tsx +230 -0
  90. package/src/features/KnowledgeManager/FileExplorer/MasonryFileItem/index.tsx +398 -0
  91. package/src/features/KnowledgeManager/FileExplorer/ToolBar/ViewSwitcher.tsx +45 -0
  92. package/src/features/{FileManager/FileList → KnowledgeManager/FileExplorer}/index.tsx +55 -16
  93. package/src/features/KnowledgeManager/Header/AddButton.tsx +118 -0
  94. package/src/features/KnowledgeManager/Header/NewNoteButton.tsx +33 -0
  95. package/src/features/{FileManager → KnowledgeManager}/Header/index.tsx +3 -9
  96. package/src/features/KnowledgeManager/Home/RecentDocumentCard.tsx +116 -0
  97. package/src/features/KnowledgeManager/Home/RecentDocuments.tsx +77 -0
  98. package/src/features/KnowledgeManager/Home/RecentFileCard.tsx +121 -0
  99. package/src/features/KnowledgeManager/Home/RecentFiles.tsx +73 -0
  100. package/src/features/KnowledgeManager/Home/RecentFilesSkeleton.tsx +81 -0
  101. package/src/features/KnowledgeManager/Home/UploadEntries.tsx +208 -0
  102. package/src/features/KnowledgeManager/Home/index.tsx +221 -0
  103. package/src/features/KnowledgeManager/index.tsx +75 -0
  104. package/src/features/Portal/FilePreview/Body/index.tsx +1 -1
  105. package/src/features/Portal/FilePreview/Header.tsx +1 -1
  106. package/src/locales/default/common.ts +1 -0
  107. package/src/locales/default/file.ts +87 -2
  108. package/src/server/routers/lambda/__tests__/file.test.ts +85 -6
  109. package/src/server/routers/lambda/document.ts +57 -0
  110. package/src/server/routers/lambda/file.ts +72 -0
  111. package/src/server/routers/lambda/knowledge.ts +94 -0
  112. package/src/server/services/document/index.ts +103 -0
  113. package/src/services/document/index.ts +44 -0
  114. package/src/services/file/index.ts +5 -3
  115. package/src/store/aiInfra/slices/aiProvider/__tests__/action.test.ts +125 -229
  116. package/src/store/aiInfra/slices/aiProvider/action.ts +113 -33
  117. package/src/store/file/initialState.ts +6 -1
  118. package/src/store/file/slices/chat/action.ts +3 -3
  119. package/src/store/file/slices/document/action.ts +359 -0
  120. package/src/store/file/slices/document/index.ts +3 -0
  121. package/src/store/file/slices/document/initialState.ts +22 -0
  122. package/src/store/file/slices/document/selectors.ts +25 -0
  123. package/src/store/file/slices/fileManager/action.test.ts +16 -9
  124. package/src/store/file/slices/fileManager/action.ts +11 -11
  125. package/src/store/file/store.ts +3 -0
  126. package/src/store/global/initialState.ts +3 -1
  127. package/src/app/[variants]/(main)/knowledge/routes/KnowledgeHome/menu/FileMenu.tsx +0 -75
  128. package/src/features/FileManager/FileList/MasonryFileItem/index.tsx +0 -582
  129. package/src/features/FileManager/index.tsx +0 -36
  130. /package/src/features/{FileManager/FileList/ToolBar → KnowledgeBaseModal/AssignKnowledgeBase}/ViewSwitcher.tsx +0 -0
  131. /package/src/features/{FileManager → KnowledgeManager}/ChunkDrawer/ChunkList/ChunkItem.tsx +0 -0
  132. /package/src/features/{FileManager → KnowledgeManager}/ChunkDrawer/ChunkList/index.tsx +0 -0
  133. /package/src/features/{FileManager → KnowledgeManager}/ChunkDrawer/Content.tsx +0 -0
  134. /package/src/features/{FileManager → KnowledgeManager}/ChunkDrawer/Loading/index.tsx +0 -0
  135. /package/src/features/{FileManager → KnowledgeManager}/ChunkDrawer/SimilaritySearchList/Item.tsx +0 -0
  136. /package/src/features/{FileManager → KnowledgeManager}/ChunkDrawer/SimilaritySearchList/index.tsx +0 -0
  137. /package/src/features/{FileManager → KnowledgeManager}/ChunkDrawer/index.tsx +0 -0
  138. /package/src/features/{FileManager/FileList → KnowledgeManager/FileExplorer}/EmptyStatus.tsx +0 -0
  139. /package/src/features/{FileManager/FileList → KnowledgeManager/FileExplorer}/FileListItem/ChunkTag.tsx +0 -0
  140. /package/src/features/{FileManager/FileList → KnowledgeManager/FileExplorer}/FileListItem/DropdownMenu.tsx +0 -0
  141. /package/src/features/{FileManager/FileList → KnowledgeManager/FileExplorer}/FileSkeleton.tsx +0 -0
  142. /package/src/features/{FileManager/FileList → KnowledgeManager/FileExplorer}/MasonryFileItem/MasonryItemWrapper.tsx +0 -0
  143. /package/src/features/{FileManager/FileList → KnowledgeManager/FileExplorer}/MasonrySkeleton.tsx +0 -0
  144. /package/src/features/{FileManager/FileList → KnowledgeManager/FileExplorer}/ToolBar/Config.tsx +0 -0
  145. /package/src/features/{FileManager/FileList → KnowledgeManager/FileExplorer}/ToolBar/MultiSelectActions.tsx +0 -0
  146. /package/src/features/{FileManager/FileList → KnowledgeManager/FileExplorer}/ToolBar/index.tsx +0 -0
  147. /package/src/features/{FileManager/FileList → KnowledgeManager/FileExplorer}/useCheckTaskStatus.ts +0 -0
  148. /package/src/features/{FileManager → KnowledgeManager}/Header/FilesSearchBar.tsx +0 -0
  149. /package/src/features/{FileManager → KnowledgeManager}/Header/TogglePanelButton.tsx +0 -0
  150. /package/src/features/{FileManager → KnowledgeManager}/Header/UploadFileButton.tsx +0 -0
  151. /package/src/features/{FileManager → KnowledgeManager}/UploadDock/Item.tsx +0 -0
  152. /package/src/features/{FileManager → KnowledgeManager}/UploadDock/index.tsx +0 -0
@@ -1,75 +0,0 @@
1
- 'use client';
2
-
3
- import { Icon } from '@lobehub/ui';
4
- import { FileText, Globe, ImageIcon, LayoutGrid, Mic2, SquarePlay } from 'lucide-react';
5
- import { memo, useMemo } from 'react';
6
- import { useTranslation } from 'react-i18next';
7
- import { Flexbox } from 'react-layout-kit';
8
-
9
- import Menu from '@/components/Menu';
10
- import type { MenuProps } from '@/components/Menu';
11
- import { FilesTabs } from '@/types/files';
12
-
13
- import { useFileCategory } from '../../../hooks/useFileCategory';
14
-
15
- const FileMenu = memo(() => {
16
- const { t } = useTranslation('file');
17
- const [activeKey, setActiveKey] = useFileCategory();
18
-
19
- const items: MenuProps['items'] = useMemo(
20
- () =>
21
- [
22
- {
23
- icon: <Icon icon={LayoutGrid} />,
24
- key: FilesTabs.All,
25
- label: t('tab.all'),
26
- },
27
- {
28
- icon: <Icon icon={FileText} />,
29
- key: FilesTabs.Documents,
30
- label: t('tab.documents'),
31
- },
32
- {
33
- icon: <Icon icon={ImageIcon} />,
34
- key: FilesTabs.Images,
35
- label: t('tab.images'),
36
- },
37
- {
38
- icon: <Icon icon={Mic2} />,
39
- key: FilesTabs.Audios,
40
- label: t('tab.audios'),
41
- },
42
- {
43
- icon: <Icon icon={SquarePlay} />,
44
- key: FilesTabs.Videos,
45
- label: t('tab.videos'),
46
- },
47
- {
48
- icon: <Icon icon={Globe} />,
49
- key: FilesTabs.Websites,
50
- label: t('tab.websites'),
51
- },
52
- ]
53
- .filter(Boolean)
54
- .slice(0, 5) as MenuProps['items'],
55
- [t],
56
- );
57
-
58
- return (
59
- <Flexbox>
60
- <Menu
61
- compact
62
- items={items}
63
- onClick={({ key }) => {
64
- setActiveKey(key);
65
- }}
66
- selectable
67
- selectedKeys={[activeKey]}
68
- />
69
- </Flexbox>
70
- );
71
- });
72
-
73
- FileMenu.displayName = 'FileMenu';
74
-
75
- export default FileMenu;
@@ -1,582 +0,0 @@
1
- import { Button, Tooltip } from '@lobehub/ui';
2
- import { Checkbox, Image } from 'antd';
3
- import { createStyles } from 'antd-style';
4
- import { isNull } from 'lodash-es';
5
- import { FileBoxIcon } from 'lucide-react';
6
- import { memo, useEffect, useRef, useState } from 'react';
7
- import { useTranslation } from 'react-i18next';
8
- import { Flexbox } from 'react-layout-kit';
9
-
10
- import FileIcon from '@/components/FileIcon';
11
- import { fileManagerSelectors, useFileStore } from '@/store/file';
12
- import { FileListItem } from '@/types/files';
13
- import { formatSize } from '@/utils/format';
14
- import { isChunkingUnsupported } from '@/utils/isChunkingUnsupported';
15
-
16
- import ChunksBadge from '../FileListItem/ChunkTag';
17
- import DropdownMenu from '../FileListItem/DropdownMenu';
18
-
19
- // Image file types
20
- const IMAGE_TYPES = new Set([
21
- 'image/png',
22
- 'image/jpeg',
23
- 'image/jpg',
24
- 'image/gif',
25
- 'image/webp',
26
- 'image/svg+xml',
27
- ]);
28
-
29
- // Markdown file types
30
- const MARKDOWN_TYPES = new Set(['text/markdown', 'text/x-markdown']);
31
-
32
- // Helper to check if filename ends with .md
33
- const isMarkdownFile = (name: string, fileType?: string) => {
34
- return (
35
- name.toLowerCase().endsWith('.md') ||
36
- name.toLowerCase().endsWith('.markdown') ||
37
- (fileType && MARKDOWN_TYPES.has(fileType))
38
- );
39
- };
40
-
41
- const useStyles = createStyles(({ css, token }) => ({
42
- actions: css`
43
- opacity: 0;
44
- transition: opacity ${token.motionDurationMid};
45
- `,
46
- card: css`
47
- cursor: pointer;
48
-
49
- position: relative;
50
-
51
- overflow: visible;
52
-
53
- border: 1px solid ${token.colorBorderSecondary};
54
- border-radius: ${token.borderRadiusLG}px;
55
-
56
- background: ${token.colorBgContainer};
57
-
58
- transition: all ${token.motionDurationMid};
59
-
60
- &:hover {
61
- border-color: ${token.colorPrimary};
62
- box-shadow: ${token.boxShadowTertiary};
63
-
64
- .actions {
65
- opacity: 1;
66
- }
67
-
68
- .checkbox {
69
- opacity: 1;
70
- }
71
-
72
- .dropdown {
73
- opacity: 1;
74
- }
75
-
76
- .floatingChunkBadge {
77
- opacity: 1;
78
- }
79
- }
80
- `,
81
- checkbox: css`
82
- position: absolute;
83
- z-index: 2;
84
- inset-block-start: 8px;
85
- inset-inline-start: 8px;
86
-
87
- opacity: 0;
88
-
89
- transition: opacity ${token.motionDurationMid};
90
- `,
91
- content: css`
92
- position: relative;
93
- `,
94
- contentWithPadding: css`
95
- padding: 12px;
96
- `,
97
- dropdown: css`
98
- position: absolute;
99
- z-index: 2;
100
- inset-block-start: 8px;
101
- inset-inline-end: 8px;
102
-
103
- opacity: 0;
104
-
105
- transition: opacity ${token.motionDurationMid};
106
- `,
107
- floatingChunkBadge: css`
108
- position: absolute;
109
- z-index: 3;
110
- inset-block-end: 8px;
111
- inset-inline-end: 8px;
112
-
113
- border-radius: ${token.borderRadius}px;
114
-
115
- opacity: 0;
116
- background: ${token.colorBgContainer};
117
- box-shadow: ${token.boxShadow};
118
-
119
- transition: opacity ${token.motionDurationMid};
120
- `,
121
- hoverOverlay: css`
122
- position: absolute;
123
- z-index: 1;
124
- inset: 0;
125
-
126
- display: flex;
127
- flex-direction: column;
128
- align-items: center;
129
- justify-content: center;
130
-
131
- padding: 16px;
132
- border-radius: ${token.borderRadiusLG}px;
133
-
134
- opacity: 0;
135
- background: ${token.colorBgMask};
136
-
137
- transition: opacity ${token.motionDurationMid};
138
-
139
- &:hover {
140
- opacity: 1;
141
- }
142
- `,
143
- iconWrapper: css`
144
- display: flex;
145
- align-items: center;
146
- justify-content: center;
147
-
148
- height: 120px;
149
- margin-block-end: 12px;
150
- border-radius: ${token.borderRadius}px;
151
-
152
- background: ${token.colorFillQuaternary};
153
- `,
154
- imagePlaceholder: css`
155
- display: flex;
156
- align-items: center;
157
- justify-content: center;
158
-
159
- min-height: 120px;
160
-
161
- background: ${token.colorFillQuaternary};
162
- `,
163
- imageWrapper: css`
164
- position: relative;
165
-
166
- overflow: hidden;
167
-
168
- width: 100%;
169
- border-radius: ${token.borderRadiusLG}px;
170
-
171
- background: ${token.colorFillQuaternary};
172
-
173
- img {
174
- display: block;
175
- width: 100%;
176
- height: auto;
177
- }
178
- `,
179
- markdownLoading: css`
180
- display: flex;
181
- align-items: center;
182
- justify-content: center;
183
-
184
- min-height: 120px;
185
- border-radius: ${token.borderRadiusLG}px;
186
-
187
- font-size: 12px;
188
- color: ${token.colorTextTertiary};
189
-
190
- background: ${token.colorFillQuaternary};
191
- `,
192
- markdownPreview: css`
193
- position: relative;
194
-
195
- overflow: hidden;
196
-
197
- width: 100%;
198
- min-height: 120px;
199
- max-height: 300px;
200
- padding: 16px;
201
- border-radius: ${token.borderRadiusLG}px;
202
-
203
- font-size: 13px;
204
- line-height: 1.6;
205
- color: ${token.colorTextSecondary};
206
- word-wrap: break-word;
207
- white-space: pre-wrap;
208
-
209
- background: ${token.colorFillQuaternary};
210
-
211
- &::after {
212
- pointer-events: none;
213
- content: '';
214
-
215
- position: absolute;
216
- inset-block-end: 0;
217
- inset-inline: 0;
218
-
219
- height: 60px;
220
-
221
- background: linear-gradient(to bottom, transparent, ${token.colorFillQuaternary});
222
- }
223
- `,
224
- name: css`
225
- overflow: hidden;
226
- display: -webkit-box;
227
- -webkit-box-orient: vertical;
228
- -webkit-line-clamp: 2;
229
-
230
- margin-block-end: 12px;
231
-
232
- font-weight: ${token.fontWeightStrong};
233
- color: ${token.colorText};
234
- word-break: break-word;
235
- `,
236
- overlaySize: css`
237
- font-size: 12px;
238
- color: ${token.colorTextLightSolid};
239
- opacity: 0.9;
240
- `,
241
- overlayTitle: css`
242
- overflow: hidden;
243
- display: -webkit-box;
244
- -webkit-box-orient: vertical;
245
- -webkit-line-clamp: 3;
246
-
247
- max-width: 100%;
248
- margin-block-end: 8px;
249
-
250
- font-size: 14px;
251
- font-weight: ${token.fontWeightStrong};
252
- color: ${token.colorTextLightSolid};
253
- text-align: center;
254
- word-break: break-word;
255
- `,
256
- selected: css`
257
- border-color: ${token.colorPrimary};
258
- background: ${token.colorPrimaryBg};
259
-
260
- .checkbox {
261
- opacity: 1;
262
- }
263
- `,
264
- }));
265
-
266
- interface MasonryFileItemProps extends FileListItem {
267
- knowledgeBaseId?: string;
268
- onOpen: (id: string) => void;
269
- onSelectedChange: (id: string, selected: boolean) => void;
270
- selected?: boolean;
271
- }
272
-
273
- const MasonryFileItem = memo<MasonryFileItemProps>(
274
- ({
275
- chunkingError,
276
- embeddingError,
277
- embeddingStatus,
278
- finishEmbedding,
279
- chunkCount,
280
- url,
281
- name,
282
- fileType,
283
- id,
284
- selected,
285
- chunkingStatus,
286
- onSelectedChange,
287
- knowledgeBaseId,
288
- size,
289
- onOpen,
290
- }) => {
291
- const { t } = useTranslation('components');
292
- const { styles, cx } = useStyles();
293
- const [imageLoaded, setImageLoaded] = useState(false);
294
- const [markdownContent, setMarkdownContent] = useState<string>('');
295
- const [isLoadingMarkdown, setIsLoadingMarkdown] = useState(false);
296
- const [isCreatingFileParseTask, parseFiles] = useFileStore((s) => [
297
- fileManagerSelectors.isCreatingFileParseTask(id)(s),
298
- s.parseFilesToChunks,
299
- ]);
300
-
301
- const isSupportedForChunking = !isChunkingUnsupported(fileType);
302
- const isImage = fileType && IMAGE_TYPES.has(fileType);
303
- const isMarkdown = isMarkdownFile(name, fileType);
304
-
305
- const cardRef = useRef<HTMLDivElement>(null);
306
- const [isInView, setIsInView] = useState(false);
307
-
308
- // Use Intersection Observer to detect when card enters viewport
309
- useEffect(() => {
310
- if (!cardRef.current) return;
311
-
312
- const observer = new IntersectionObserver(
313
- (entries) => {
314
- entries.forEach((entry) => {
315
- if (entry.isIntersecting && !isInView) {
316
- setIsInView(true);
317
- }
318
- });
319
- },
320
- {
321
- rootMargin: '200px', // Increased margin to load content earlier
322
- threshold: 0.01, // Lower threshold for earlier triggering
323
- },
324
- );
325
-
326
- observer.observe(cardRef.current);
327
-
328
- return () => {
329
- observer.disconnect();
330
- };
331
- }, [isInView]);
332
-
333
- // Fetch markdown content only when in viewport
334
- useEffect(() => {
335
- if (isMarkdown && url && isInView && !markdownContent) {
336
- setIsLoadingMarkdown(true);
337
- fetch(url)
338
- .then((res) => res.text())
339
- .then((text) => {
340
- // Take first 500 characters for preview
341
- const preview = text.slice(0, 500);
342
- setMarkdownContent(preview);
343
- })
344
- .catch((error) => {
345
- console.error('Failed to fetch markdown content:', error);
346
- setMarkdownContent('');
347
- })
348
- .finally(() => {
349
- setIsLoadingMarkdown(false);
350
- });
351
- }
352
- }, [isMarkdown, url, isInView, markdownContent]);
353
-
354
- return (
355
- <div className={cx(styles.card, selected && styles.selected)} ref={cardRef}>
356
- <div
357
- className={cx('checkbox', styles.checkbox)}
358
- onClick={(e) => {
359
- e.stopPropagation();
360
- onSelectedChange(id, !selected);
361
- }}
362
- >
363
- <Checkbox checked={selected} />
364
- </div>
365
-
366
- <div className={cx('dropdown', styles.dropdown)} onClick={(e) => e.stopPropagation()}>
367
- <DropdownMenu filename={name} id={id} knowledgeBaseId={knowledgeBaseId} url={url} />
368
- </div>
369
-
370
- <div
371
- className={cx(styles.content, !isImage && !isMarkdown && styles.contentWithPadding)}
372
- onClick={() => {
373
- onOpen(id);
374
- }}
375
- >
376
- {isImage && url ? (
377
- <>
378
- <div className={styles.imageWrapper}>
379
- {!imageLoaded && (
380
- <div className={styles.imagePlaceholder}>
381
- <FileIcon fileName={name} fileType={fileType} size={64} />
382
- </div>
383
- )}
384
- {isInView && (
385
- <Image
386
- alt={name}
387
- loading="lazy"
388
- onError={() => setImageLoaded(false)}
389
- onLoad={() => setImageLoaded(true)}
390
- preview={{
391
- src: url,
392
- }}
393
- src={url}
394
- style={{
395
- display: 'block',
396
- height: 'auto',
397
- opacity: imageLoaded ? 1 : 0,
398
- transition: 'opacity 0.3s',
399
- width: '100%',
400
- }}
401
- wrapperStyle={{
402
- display: 'block',
403
- width: '100%',
404
- }}
405
- />
406
- )}
407
- {/* Hover overlay */}
408
- <div className={styles.hoverOverlay}>
409
- <div className={styles.overlayTitle}>{name}</div>
410
- <div className={styles.overlaySize}>{formatSize(size)}</div>
411
- </div>
412
- </div>
413
- {/* Floating chunk badge or action button */}
414
- {!isNull(chunkingStatus) && chunkingStatus ? (
415
- <div
416
- className={cx('floatingChunkBadge', styles.floatingChunkBadge)}
417
- onClick={(e) => e.stopPropagation()}
418
- >
419
- <ChunksBadge
420
- chunkCount={chunkCount}
421
- chunkingError={chunkingError}
422
- chunkingStatus={chunkingStatus}
423
- embeddingError={embeddingError}
424
- embeddingStatus={embeddingStatus}
425
- finishEmbedding={finishEmbedding}
426
- id={id}
427
- />
428
- </div>
429
- ) : (
430
- isSupportedForChunking && (
431
- <Tooltip title={t('FileManager.actions.chunkingTooltip')}>
432
- <div
433
- className={cx('floatingChunkBadge', styles.floatingChunkBadge)}
434
- onClick={(e) => {
435
- e.stopPropagation();
436
- if (!isCreatingFileParseTask) {
437
- parseFiles([id]);
438
- }
439
- }}
440
- style={{ cursor: 'pointer' }}
441
- >
442
- <Button
443
- icon={FileBoxIcon}
444
- loading={isCreatingFileParseTask}
445
- size={'small'}
446
- type={'text'}
447
- />
448
- </div>
449
- </Tooltip>
450
- )
451
- )}
452
- </>
453
- ) : isMarkdown ? (
454
- <>
455
- <div style={{ position: 'relative' }}>
456
- {isLoadingMarkdown ? (
457
- <div className={styles.markdownLoading}>Loading preview...</div>
458
- ) : markdownContent ? (
459
- <div className={styles.markdownPreview}>{markdownContent}</div>
460
- ) : (
461
- <div className={styles.iconWrapper}>
462
- <FileIcon fileName={name} fileType={fileType} size={64} />
463
- </div>
464
- )}
465
- {/* Hover overlay */}
466
- <div className={styles.hoverOverlay}>
467
- <div className={styles.overlayTitle}>{name}</div>
468
- <div className={styles.overlaySize}>{formatSize(size)}</div>
469
- </div>
470
- </div>
471
- {/* Floating chunk badge or action button */}
472
- {!isNull(chunkingStatus) && chunkingStatus ? (
473
- <div
474
- className={cx('floatingChunkBadge', styles.floatingChunkBadge)}
475
- onClick={(e) => e.stopPropagation()}
476
- >
477
- <ChunksBadge
478
- chunkCount={chunkCount}
479
- chunkingError={chunkingError}
480
- chunkingStatus={chunkingStatus}
481
- embeddingError={embeddingError}
482
- embeddingStatus={embeddingStatus}
483
- finishEmbedding={finishEmbedding}
484
- id={id}
485
- />
486
- </div>
487
- ) : (
488
- isSupportedForChunking && (
489
- <Tooltip title={t('FileManager.actions.chunkingTooltip')}>
490
- <div
491
- className={cx('floatingChunkBadge', styles.floatingChunkBadge)}
492
- onClick={(e) => {
493
- e.stopPropagation();
494
- if (!isCreatingFileParseTask) {
495
- parseFiles([id]);
496
- }
497
- }}
498
- style={{ cursor: 'pointer' }}
499
- >
500
- <Button
501
- icon={FileBoxIcon}
502
- loading={isCreatingFileParseTask}
503
- size={'small'}
504
- type={'text'}
505
- />
506
- </div>
507
- </Tooltip>
508
- )
509
- )}
510
- </>
511
- ) : (
512
- <>
513
- <Flexbox
514
- align={'center'}
515
- gap={12}
516
- justify={'center'}
517
- paddingBlock={24}
518
- paddingInline={12}
519
- style={{ minHeight: 180 }}
520
- >
521
- <FileIcon fileName={name} fileType={fileType} size={64} />
522
- <div className={styles.name} style={{ textAlign: 'center' }}>
523
- {name}
524
- </div>
525
- <div
526
- style={{
527
- color: 'var(--lobe-chat-text-tertiary)',
528
- fontSize: 12,
529
- textAlign: 'center',
530
- }}
531
- >
532
- {formatSize(size)}
533
- </div>
534
- </Flexbox>
535
- {/* Floating chunk badge or action button */}
536
- {!isNull(chunkingStatus) && chunkingStatus ? (
537
- <div
538
- className={cx('floatingChunkBadge', styles.floatingChunkBadge)}
539
- onClick={(e) => e.stopPropagation()}
540
- >
541
- <ChunksBadge
542
- chunkCount={chunkCount}
543
- chunkingError={chunkingError}
544
- chunkingStatus={chunkingStatus}
545
- embeddingError={embeddingError}
546
- embeddingStatus={embeddingStatus}
547
- finishEmbedding={finishEmbedding}
548
- id={id}
549
- />
550
- </div>
551
- ) : (
552
- isSupportedForChunking && (
553
- <Tooltip title={t('FileManager.actions.chunkingTooltip')}>
554
- <div
555
- className={cx('floatingChunkBadge', styles.floatingChunkBadge)}
556
- onClick={(e) => {
557
- e.stopPropagation();
558
- if (!isCreatingFileParseTask) {
559
- parseFiles([id]);
560
- }
561
- }}
562
- style={{ cursor: 'pointer' }}
563
- >
564
- <Button
565
- icon={FileBoxIcon}
566
- loading={isCreatingFileParseTask}
567
- size={'small'}
568
- type={'text'}
569
- />
570
- </div>
571
- </Tooltip>
572
- )
573
- )}
574
- </>
575
- )}
576
- </div>
577
- </div>
578
- );
579
- },
580
- );
581
-
582
- export default MasonryFileItem;
@@ -1,36 +0,0 @@
1
- 'use client';
2
-
3
- import { Text } from '@lobehub/ui';
4
- import dynamic from 'next/dynamic';
5
- import { memo } from 'react';
6
- import { Flexbox } from 'react-layout-kit';
7
-
8
- import FileList from './FileList';
9
- import Header from './Header';
10
- import UploadDock from './UploadDock';
11
-
12
- const ChunkDrawer = dynamic(() => import('./ChunkDrawer'), { ssr: false });
13
-
14
- interface FileManagerProps {
15
- category?: string;
16
- knowledgeBaseId?: string;
17
- onOpenFile: (id: string) => void;
18
- title: string;
19
- }
20
- const FileManager = memo<FileManagerProps>(({ title, knowledgeBaseId, category, onOpenFile }) => {
21
- return (
22
- <>
23
- <Header knowledgeBaseId={knowledgeBaseId} />
24
- <Flexbox gap={12} height={'100%'}>
25
- <Text strong style={{ fontSize: 16, marginBlock: 16, marginInline: 24 }}>
26
- {title}
27
- </Text>
28
- <FileList category={category} knowledgeBaseId={knowledgeBaseId} onOpenFile={onOpenFile} />
29
- </Flexbox>
30
- <UploadDock />
31
- <ChunkDrawer />
32
- </>
33
- );
34
- });
35
-
36
- export default FileManager;