@lobehub/lobehub 2.0.0-next.54 → 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.
Files changed (152) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/changelog/v1.json +9 -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 +68 -16
  93. package/src/features/KnowledgeManager/Header/AddButton.tsx +107 -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 +83 -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 +85 -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
@@ -13,11 +13,19 @@ import KnowledgeBaseList from '../../../components/KnowledgeBaseList';
13
13
 
14
14
  const useStyles = createStyles(({ css, token }) => ({
15
15
  header: css`
16
- color: ${token.colorTextDescription};
16
+ cursor: pointer;
17
+ border-radius: ${token.borderRadius}px;
18
+ color: ${token.colorTextSecondary};
19
+ transition: background-color 0.2s;
20
+
21
+ &:hover {
22
+ background-color: ${token.colorFillTertiary};
23
+ }
17
24
  `,
18
25
  }));
19
26
 
20
- const KnowledgeBase = () => {
27
+ // TODO: Rename to Collection
28
+ const Collection = () => {
21
29
  const { t } = useTranslation('file');
22
30
  const { styles } = useStyles();
23
31
  const navigate = useNavigate();
@@ -35,13 +43,14 @@ const KnowledgeBase = () => {
35
43
  };
36
44
 
37
45
  return (
38
- <Flexbox flex={1} gap={8}>
46
+ <Flexbox flex={1} gap={8} paddingInline={8}>
39
47
  <Flexbox
40
48
  align={'center'}
41
49
  className={styles.header}
42
50
  horizontal
43
51
  justify={'space-between'}
44
- paddingInline={'16px 12px'}
52
+ paddingBlock={6}
53
+ paddingInline={8}
45
54
  >
46
55
  <Flexbox align={'center'} gap={8} horizontal>
47
56
  <ActionIcon
@@ -51,7 +60,7 @@ const KnowledgeBase = () => {
51
60
  }}
52
61
  size={'small'}
53
62
  />
54
- <div style={{ lineHeight: '14px' }}>{t('knowledgeBase.title')}</div>
63
+ <div style={{ flex: 1, lineHeight: '14px' }}>{t('knowledgeBase.title')}</div>
55
64
  </Flexbox>
56
65
  <ActionIcon
57
66
  icon={PlusIcon}
@@ -61,9 +70,13 @@ const KnowledgeBase = () => {
61
70
  />
62
71
  </Flexbox>
63
72
 
64
- {showList && <KnowledgeBaseList />}
73
+ {showList && (
74
+ <Flexbox flex={1}>
75
+ <KnowledgeBaseList />
76
+ </Flexbox>
77
+ )}
65
78
  </Flexbox>
66
79
  );
67
80
  };
68
81
 
69
- export default KnowledgeBase;
82
+ export default Collection;
@@ -11,7 +11,7 @@ interface FileListProps {
11
11
  variant?: 'raw' | 'file' | 'folder';
12
12
  }
13
13
 
14
- const FileIcon = memo<FileListProps>(({ fileName, size, variant = 'file', isDirectory }) => {
14
+ const FileIcon = memo<FileListProps>(({ fileName, size, variant = 'raw', isDirectory }) => {
15
15
  if (isDirectory)
16
16
  return <FileTypeIcon color={'gold'} size={size} type={'folder'} variant={'color'} />;
17
17
 
@@ -28,6 +28,8 @@ const FileIcon = memo<FileListProps>(({ fileName, size, variant = 'file', isDire
28
28
  );
29
29
  }
30
30
 
31
+ console.log('FileIcon', fileName);
32
+
31
33
  return <MaterialFileTypeIcon filename={fileName} size={size} type={'file'} variant={variant} />;
32
34
  });
33
35
 
@@ -5,7 +5,7 @@ import { useTranslation } from 'react-i18next';
5
5
  import TipGuide from '@/components/TipGuide';
6
6
  import { LOBE_CHAT_CLOUD } from '@/const/branding';
7
7
  import { isServerMode } from '@/const/version';
8
- import { AssignKnowledgeBaseModal } from '@/features/KnowledgeBaseModal';
8
+ import { AttachKnowledgeModal } from '@/features/KnowledgeBaseModal';
9
9
  import { featureFlagsSelectors, useServerConfigStore } from '@/store/serverConfig';
10
10
  import { useUserStore } from '@/store/user';
11
11
  import { preferenceSelectors } from '@/store/user/selectors';
@@ -69,7 +69,7 @@ const Knowledge = memo(() => {
69
69
  ) : (
70
70
  content
71
71
  )}
72
- <AssignKnowledgeBaseModal open={modalOpen} setOpen={setModalOpen} />
72
+ <AttachKnowledgeModal open={modalOpen} setOpen={setModalOpen} />
73
73
  </Suspense>
74
74
  );
75
75
  });
@@ -55,7 +55,7 @@ const FileSidePanel = memo<PropsWithChildren>(({ children }) => {
55
55
  className={styles.panel}
56
56
  defaultSize={{ width: tmpWidth }}
57
57
  expand={showFilePanel}
58
- maxWidth={320}
58
+ maxWidth={280}
59
59
  minWidth={FOLDER_WIDTH}
60
60
  mode={md ? 'fixed' : 'float'}
61
61
  onExpandChange={handleExpand}
@@ -0,0 +1,80 @@
1
+ import { Text } from '@lobehub/ui';
2
+ import { createStyles } from 'antd-style';
3
+ import { memo } from 'react';
4
+ import { Flexbox } from 'react-layout-kit';
5
+
6
+ import KnowledgeIcon from '@/components/KnowledgeIcon';
7
+ import { KnowledgeItem } from '@/types/knowledgeBase';
8
+
9
+ import Actions from './Action';
10
+
11
+ const useStyles = createStyles(({ css, token }) => ({
12
+ card: css`
13
+ cursor: pointer;
14
+
15
+ position: relative;
16
+
17
+ overflow: hidden;
18
+
19
+ padding: 12px;
20
+ border: 1px solid ${token.colorBorderSecondary};
21
+ border-radius: ${token.borderRadiusLG}px;
22
+
23
+ background: ${token.colorBgContainer};
24
+
25
+ transition: all ${token.motionDurationMid};
26
+
27
+ &:hover {
28
+ border-color: ${token.colorPrimary};
29
+ box-shadow: ${token.boxShadowTertiary};
30
+ }
31
+ `,
32
+ desc: css`
33
+ margin: 0 !important;
34
+ font-size: 12px;
35
+ line-height: 1.4;
36
+ color: ${token.colorTextDescription};
37
+ `,
38
+ title: css`
39
+ margin: 0 !important;
40
+ font-size: 14px;
41
+ font-weight: 500;
42
+ line-height: 1.4;
43
+ `,
44
+ }));
45
+
46
+ const MasonryItem = memo<KnowledgeItem>(({ id, fileType, name, type, description, enabled }) => {
47
+ const { styles } = useStyles();
48
+
49
+ return (
50
+ <div className={styles.card}>
51
+ <Flexbox gap={12} style={{ position: 'relative' }}>
52
+ <Flexbox align={'center'} gap={12} horizontal>
53
+ <KnowledgeIcon
54
+ fileType={fileType}
55
+ name={name}
56
+ size={{ file: 48, repo: 48 }}
57
+ type={type}
58
+ />
59
+ <Flexbox flex={1} gap={6} style={{ overflow: 'hidden', position: 'relative' }}>
60
+ <Text className={styles.title} ellipsis={{ rows: 2 }}>
61
+ {name}
62
+ </Text>
63
+ </Flexbox>
64
+ </Flexbox>
65
+ {description && (
66
+ <Text className={styles.desc} ellipsis={{ rows: 3 }}>
67
+ {description}
68
+ </Text>
69
+ )}
70
+ <Flexbox align={'center'} justify={'flex-end'}>
71
+ <Actions enabled={enabled} id={id} type={type} />
72
+ </Flexbox>
73
+ </Flexbox>
74
+ </div>
75
+ );
76
+ });
77
+
78
+ MasonryItem.displayName = 'MasonryItem';
79
+
80
+ export default MasonryItem;
@@ -0,0 +1,27 @@
1
+ import { memo } from 'react';
2
+
3
+ import { KnowledgeItem } from '@/types/knowledgeBase';
4
+
5
+ import MasonryItem from './MasonryItem';
6
+
7
+ interface MasonryItemWrapperProps {
8
+ data: KnowledgeItem;
9
+ index: number;
10
+ }
11
+
12
+ const MasonryItemWrapper = memo<MasonryItemWrapperProps>(({ data: item }) => {
13
+ // Safety check: return null if item is undefined
14
+ if (!item || !item.id) {
15
+ return null;
16
+ }
17
+
18
+ return (
19
+ <div style={{ padding: '8px 4px' }}>
20
+ <MasonryItem {...item} />
21
+ </div>
22
+ );
23
+ });
24
+
25
+ MasonryItemWrapper.displayName = 'MasonryItemWrapper';
26
+
27
+ export default MasonryItemWrapper;
@@ -1,15 +1,20 @@
1
1
  import { Icon } from '@lobehub/ui';
2
+ import { VirtuosoMasonry } from '@virtuoso.dev/masonry';
2
3
  import { Empty } from 'antd';
3
4
  import { ServerCrash } from 'lucide-react';
4
- import { memo } from 'react';
5
+ import React, { memo, useMemo, useState } from 'react';
5
6
  import { useTranslation } from 'react-i18next';
6
- import { Center } from 'react-layout-kit';
7
+ import { Center, Flexbox } from 'react-layout-kit';
7
8
  import { Virtuoso } from 'react-virtuoso';
8
9
 
9
10
  import { useAgentStore } from '@/store/agent';
11
+ import { useGlobalStore } from '@/store/global';
10
12
 
11
13
  import Item from './Item';
14
+ import MasonryItemWrapper from './Item/MasonryItemWrapper';
12
15
  import Loading from './Loading';
16
+ import MasonrySkeleton from './MasonrySkeleton';
17
+ import ViewSwitcher, { ViewMode } from './ViewSwitcher';
13
18
 
14
19
  export const List = memo(() => {
15
20
  const { t } = useTranslation('file');
@@ -18,31 +23,107 @@ export const List = memo(() => {
18
23
 
19
24
  const { isLoading, error, data } = useFetchFilesAndKnowledgeBases();
20
25
 
26
+ const [columnCount, setColumnCount] = useState(2);
27
+ const [isTransitioning, setIsTransitioning] = useState(false);
28
+
29
+ const viewMode = useGlobalStore((s) => s.status.knowledgeBaseModalViewMode || 'list') as ViewMode;
30
+ const updateSystemStatus = useGlobalStore((s) => s.updateSystemStatus);
31
+ const setViewMode = (mode: ViewMode) => {
32
+ setIsTransitioning(true);
33
+ updateSystemStatus({ knowledgeBaseModalViewMode: mode });
34
+ };
35
+
36
+ // Update column count based on window size (max 2 columns for modal)
37
+ const updateColumnCount = React.useCallback(() => {
38
+ const width = window.innerWidth;
39
+ if (width < 480) {
40
+ setColumnCount(1);
41
+ } else {
42
+ setColumnCount(2);
43
+ }
44
+ }, []);
45
+
46
+ // Initialize column count on mount
47
+ React.useEffect(() => {
48
+ updateColumnCount();
49
+ }, [updateColumnCount]);
50
+
51
+ // Set up resize listener when in masonry mode
52
+ React.useEffect(() => {
53
+ if (viewMode === 'masonry') {
54
+ window.addEventListener('resize', updateColumnCount);
55
+ return () => window.removeEventListener('resize', updateColumnCount);
56
+ }
57
+ }, [viewMode, updateColumnCount]);
58
+
59
+ // Handle view transition with a brief delay to show skeleton
60
+ React.useEffect(() => {
61
+ if (isTransitioning && data) {
62
+ requestAnimationFrame(() => {
63
+ const timer = setTimeout(() => {
64
+ setIsTransitioning(false);
65
+ }, 100);
66
+ return () => clearTimeout(timer);
67
+ });
68
+ }
69
+ }, [isTransitioning, viewMode, data]);
70
+
21
71
  const isEmpty = data && data.length === 0;
22
72
 
23
- return isLoading ? (
24
- <Loading />
25
- ) : isEmpty ? (
26
- <Center gap={12} padding={40}>
27
- {error ? (
28
- <>
29
- <Icon icon={ServerCrash} size={80} />
30
- {t('networkError')}
31
- </>
73
+ const masonryContext = useMemo(() => ({}), []);
74
+
75
+ return (
76
+ <Flexbox height={500}>
77
+ <Flexbox paddingInline={16} style={{ paddingBlockEnd: 12 }}>
78
+ <Flexbox align={'center'} horizontal justify={'flex-end'}>
79
+ <ViewSwitcher onViewChange={setViewMode} view={viewMode} />
80
+ </Flexbox>
81
+ </Flexbox>
82
+ {isLoading || isTransitioning ? (
83
+ viewMode === 'masonry' ? (
84
+ <MasonrySkeleton columnCount={columnCount} />
85
+ ) : (
86
+ <Loading />
87
+ )
88
+ ) : isEmpty ? (
89
+ <Center gap={12} padding={40}>
90
+ {error ? (
91
+ <>
92
+ <Icon icon={ServerCrash} size={80} />
93
+ {t('networkError')}
94
+ </>
95
+ ) : (
96
+ <Empty description={t('empty')} image={Empty.PRESENTED_IMAGE_SIMPLE} />
97
+ )}
98
+ </Center>
99
+ ) : viewMode === 'list' ? (
100
+ <Virtuoso
101
+ itemContent={(index) => {
102
+ const item = data![index];
103
+ return <Item key={item.id} {...item} />;
104
+ }}
105
+ overscan={400}
106
+ style={{ flex: 1, marginInline: -16 }}
107
+ totalCount={data!.length}
108
+ />
32
109
  ) : (
33
- <Empty description={t('empty')} image={Empty.PRESENTED_IMAGE_SIMPLE} />
110
+ <div style={{ flex: 1, overflow: 'hidden' }}>
111
+ <div style={{ height: '100%', overflowY: 'auto' }}>
112
+ <div style={{ paddingInline: 16 }}>
113
+ <VirtuosoMasonry
114
+ ItemContent={MasonryItemWrapper}
115
+ columnCount={columnCount}
116
+ context={masonryContext}
117
+ data={data || []}
118
+ style={{
119
+ gap: '16px',
120
+ }}
121
+ />
122
+ </div>
123
+ </div>
124
+ </div>
34
125
  )}
35
- </Center>
36
- ) : (
37
- <Virtuoso
38
- itemContent={(index) => {
39
- const item = data![index];
40
- return <Item key={item.id} {...item} />;
41
- }}
42
- overscan={400}
43
- style={{ height: 500, marginInline: -16 }}
44
- totalCount={data!.length}
45
- />
126
+ </Flexbox>
46
127
  );
47
128
  });
48
129
 
@@ -0,0 +1,62 @@
1
+ import { Skeleton } from 'antd';
2
+ import { createStyles } from 'antd-style';
3
+ import { memo } from 'react';
4
+
5
+ const useStyles = createStyles(({ css, token }) => ({
6
+ card: css`
7
+ padding: 12px;
8
+ border: 1px solid ${token.colorBorderSecondary};
9
+ border-radius: ${token.borderRadiusLG}px;
10
+ background: ${token.colorBgContainer};
11
+ `,
12
+ grid: css`
13
+ display: grid;
14
+ gap: 16px;
15
+ padding-block: 12px;
16
+ `,
17
+ }));
18
+
19
+ interface MasonrySkeletonProps {
20
+ columnCount: number;
21
+ }
22
+
23
+ const MasonrySkeleton = memo<MasonrySkeletonProps>(({ columnCount }) => {
24
+ const { styles } = useStyles();
25
+ // Generate varying heights for more natural masonry look
26
+ const heights = [160, 180, 170, 160, 190, 170, 160, 180];
27
+
28
+ // Calculate number of items based on column count (max 2 columns for modal)
29
+ const itemCount = Math.min(columnCount * 3, 8);
30
+
31
+ return (
32
+ <div
33
+ className={styles.grid}
34
+ style={{
35
+ gridTemplateColumns: `repeat(${columnCount}, 1fr)`,
36
+ }}
37
+ >
38
+ {Array.from({ length: itemCount }).map((_, index) => (
39
+ <div className={styles.card} key={index}>
40
+ <Skeleton
41
+ active
42
+ avatar={{ shape: 'square', size: 48 }}
43
+ paragraph={{
44
+ rows: 3,
45
+ width: ['100%', '90%', '70%'],
46
+ }}
47
+ style={{
48
+ height: heights[index % heights.length],
49
+ }}
50
+ title={{
51
+ width: '80%',
52
+ }}
53
+ />
54
+ </div>
55
+ ))}
56
+ </div>
57
+ );
58
+ });
59
+
60
+ MasonrySkeleton.displayName = 'MasonrySkeleton';
61
+
62
+ export default MasonrySkeleton;
@@ -7,11 +7,12 @@ import { useServerConfigStore } from '@/store/serverConfig';
7
7
 
8
8
  import List from './List';
9
9
 
10
- interface AssignKnowledgeBaseProps {
10
+ interface AttachKnowledgeModalProps {
11
11
  open?: boolean;
12
12
  setOpen: (open: boolean) => void;
13
13
  }
14
- export const AssignKnowledgeBaseModal = memo<AssignKnowledgeBaseProps>(({ setOpen, open }) => {
14
+
15
+ export const AttachKnowledgeModal = memo<AttachKnowledgeModalProps>(({ setOpen, open }) => {
15
16
  const { t } = useTranslation('chat');
16
17
  const mobile = useServerConfigStore((s) => s.isMobile);
17
18
 
@@ -21,11 +21,11 @@ const CreateForm = memo<CreateFormProps>(({ onClose, onSuccess }) => {
21
21
  try {
22
22
  const id = await createNewKnowledgeBase(values);
23
23
  setLoading(false);
24
- onClose?.();
25
24
 
26
25
  // Call onSuccess callback if provided, otherwise navigate directly
27
26
  if (onSuccess) {
28
27
  onSuccess(id);
28
+ onClose?.();
29
29
  } else {
30
30
  window.location.href = `/knowledge/bases/${id}`;
31
31
  }
@@ -0,0 +1,111 @@
1
+ import { ActionIcon, Dropdown, Icon } from '@lobehub/ui';
2
+ import { App } from 'antd';
3
+ import { Copy, CopyPlus, MoreHorizontal, Pencil, Trash2 } from 'lucide-react';
4
+ import { memo, useState } from 'react';
5
+ import { useTranslation } from 'react-i18next';
6
+ import { Flexbox } from 'react-layout-kit';
7
+
8
+ import { useFileStore } from '@/store/file';
9
+
10
+ interface DocumentActionsProps {
11
+ documentContent?: string;
12
+ documentId: string;
13
+ onDelete?: () => void;
14
+ onRename?: () => void;
15
+ }
16
+
17
+ const DocumentActions = memo<DocumentActionsProps>(
18
+ ({ documentId, documentContent, onDelete, onRename }) => {
19
+ const { t } = useTranslation(['common', 'file']);
20
+ const { message, modal } = App.useApp();
21
+ const [loading, setLoading] = useState(false);
22
+ const removeDocument = useFileStore((s) => s.removeDocument);
23
+ const duplicateDocument = useFileStore((s) => s.duplicateDocument);
24
+
25
+ const handleDelete = () => {
26
+ modal.confirm({
27
+ cancelText: t('cancel'),
28
+ content: t('documentEditor.deleteConfirm.content', { ns: 'file' }),
29
+ okButtonProps: { danger: true },
30
+ okText: t('delete'),
31
+ onOk: async () => {
32
+ setLoading(true);
33
+ try {
34
+ await removeDocument(documentId);
35
+ message.success(t('documentEditor.deleteSuccess', { ns: 'file' }));
36
+ onDelete?.();
37
+ } catch (error) {
38
+ console.error('Failed to delete document:', error);
39
+ message.error(t('documentEditor.deleteError', { ns: 'file' }));
40
+ } finally {
41
+ setLoading(false);
42
+ }
43
+ },
44
+ title: t('documentEditor.deleteConfirm.title', { ns: 'file' }),
45
+ });
46
+ };
47
+
48
+ const handleCopy = async () => {
49
+ if (documentContent) {
50
+ try {
51
+ await navigator.clipboard.writeText(documentContent);
52
+ } catch (error) {
53
+ console.error('Failed to copy document:', error);
54
+ }
55
+ }
56
+ };
57
+
58
+ const handleDuplicate = async () => {
59
+ setLoading(true);
60
+ try {
61
+ await duplicateDocument(documentId);
62
+ } catch (error) {
63
+ console.error('Failed to duplicate document:', error);
64
+ } finally {
65
+ setLoading(false);
66
+ }
67
+ };
68
+
69
+ return (
70
+ <Flexbox align={'center'} horizontal onClick={(e) => e.stopPropagation()}>
71
+ <Dropdown
72
+ menu={{
73
+ items: [
74
+ {
75
+ icon: <Icon icon={Pencil} />,
76
+ key: 'rename',
77
+ label: t('rename'),
78
+ onClick: () => onRename?.(),
79
+ },
80
+ {
81
+ icon: <Icon icon={Copy} />,
82
+ key: 'copy',
83
+ label: t('documentList.copyContent', { ns: 'file' }),
84
+ onClick: handleCopy,
85
+ },
86
+ {
87
+ icon: <Icon icon={CopyPlus} />,
88
+ key: 'duplicate',
89
+ label: t('documentList.duplicate', { ns: 'file' }),
90
+ onClick: handleDuplicate,
91
+ },
92
+ {
93
+ danger: true,
94
+ icon: <Icon icon={Trash2} />,
95
+ key: 'delete',
96
+ label: t('delete'),
97
+ onClick: handleDelete,
98
+ },
99
+ ],
100
+ }}
101
+ placement="bottomRight"
102
+ trigger={['click']}
103
+ >
104
+ <ActionIcon icon={MoreHorizontal} loading={loading} size="small" variant="borderless" />
105
+ </Dropdown>
106
+ </Flexbox>
107
+ );
108
+ },
109
+ );
110
+
111
+ export default DocumentActions;