@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.
Files changed (165) 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/Conversation/Messages/Assistant/index.tsx +7 -1
  71. package/src/features/FileSidePanel/index.tsx +1 -1
  72. package/src/features/KnowledgeBaseModal/AssignKnowledgeBase/Item/MasonryItem.tsx +80 -0
  73. package/src/features/KnowledgeBaseModal/AssignKnowledgeBase/Item/MasonryItemWrapper.tsx +27 -0
  74. package/src/features/KnowledgeBaseModal/AssignKnowledgeBase/List.tsx +104 -23
  75. package/src/features/KnowledgeBaseModal/AssignKnowledgeBase/MasonrySkeleton.tsx +62 -0
  76. package/src/features/KnowledgeBaseModal/AssignKnowledgeBase/index.tsx +3 -2
  77. package/src/features/KnowledgeBaseModal/CreateNew/CreateForm.tsx +1 -1
  78. package/src/features/KnowledgeManager/DocumentExplorer/DocumentActions.tsx +111 -0
  79. package/src/features/KnowledgeManager/DocumentExplorer/DocumentEditor.tsx +723 -0
  80. package/src/features/KnowledgeManager/DocumentExplorer/DocumentEditorPlaceholder.tsx +169 -0
  81. package/src/features/KnowledgeManager/DocumentExplorer/DocumentListItem.tsx +148 -0
  82. package/src/features/KnowledgeManager/DocumentExplorer/DocumentListSkeleton.tsx +39 -0
  83. package/src/features/KnowledgeManager/DocumentExplorer/NoteEditorModal.tsx +348 -0
  84. package/src/features/KnowledgeManager/DocumentExplorer/RenamePopover.tsx +163 -0
  85. package/src/features/KnowledgeManager/DocumentExplorer/index.tsx +318 -0
  86. package/src/features/{FileManager/FileList → KnowledgeManager/FileExplorer}/FileListItem/index.tsx +48 -9
  87. package/src/features/KnowledgeManager/FileExplorer/MasonryFileItem/DefaultFileItem.tsx +149 -0
  88. package/src/features/KnowledgeManager/FileExplorer/MasonryFileItem/ImageFileItem.tsx +245 -0
  89. package/src/features/KnowledgeManager/FileExplorer/MasonryFileItem/MarkdownFileItem.tsx +232 -0
  90. package/src/features/KnowledgeManager/FileExplorer/MasonryFileItem/NoteFileItem.tsx +230 -0
  91. package/src/features/KnowledgeManager/FileExplorer/MasonryFileItem/index.tsx +398 -0
  92. package/src/features/KnowledgeManager/FileExplorer/ToolBar/ViewSwitcher.tsx +45 -0
  93. package/src/features/{FileManager/FileList → KnowledgeManager/FileExplorer}/index.tsx +68 -16
  94. package/src/features/KnowledgeManager/Header/AddButton.tsx +107 -0
  95. package/src/features/KnowledgeManager/Header/NewNoteButton.tsx +33 -0
  96. package/src/features/{FileManager → KnowledgeManager}/Header/index.tsx +3 -9
  97. package/src/features/KnowledgeManager/Home/RecentDocumentCard.tsx +116 -0
  98. package/src/features/KnowledgeManager/Home/RecentDocuments.tsx +77 -0
  99. package/src/features/KnowledgeManager/Home/RecentFileCard.tsx +121 -0
  100. package/src/features/KnowledgeManager/Home/RecentFiles.tsx +73 -0
  101. package/src/features/KnowledgeManager/Home/RecentFilesSkeleton.tsx +83 -0
  102. package/src/features/KnowledgeManager/Home/UploadEntries.tsx +208 -0
  103. package/src/features/KnowledgeManager/Home/index.tsx +221 -0
  104. package/src/features/KnowledgeManager/index.tsx +75 -0
  105. package/src/features/Portal/FilePreview/Body/index.tsx +1 -1
  106. package/src/features/Portal/FilePreview/Header.tsx +1 -1
  107. package/src/locales/default/common.ts +1 -0
  108. package/src/locales/default/file.ts +85 -2
  109. package/src/locales/default/tool.ts +8 -0
  110. package/src/server/routers/lambda/__tests__/file.test.ts +85 -6
  111. package/src/server/routers/lambda/document.ts +57 -0
  112. package/src/server/routers/lambda/file.ts +72 -0
  113. package/src/server/routers/lambda/knowledge.ts +94 -0
  114. package/src/server/services/document/index.ts +103 -0
  115. package/src/services/document/index.ts +44 -0
  116. package/src/services/file/index.ts +5 -3
  117. package/src/store/aiInfra/slices/aiProvider/__tests__/action.test.ts +125 -229
  118. package/src/store/aiInfra/slices/aiProvider/action.ts +113 -33
  119. package/src/store/chat/slices/builtinTool/actions/localSystem.ts +1 -1
  120. package/src/store/file/initialState.ts +6 -1
  121. package/src/store/file/slices/chat/action.ts +3 -3
  122. package/src/store/file/slices/document/action.ts +359 -0
  123. package/src/store/file/slices/document/index.ts +3 -0
  124. package/src/store/file/slices/document/initialState.ts +22 -0
  125. package/src/store/file/slices/document/selectors.ts +25 -0
  126. package/src/store/file/slices/fileManager/action.test.ts +16 -9
  127. package/src/store/file/slices/fileManager/action.ts +11 -11
  128. package/src/store/file/store.ts +3 -0
  129. package/src/store/global/initialState.ts +3 -1
  130. package/src/tools/interventions.ts +3 -5
  131. package/src/tools/local-system/Intervention/MoveLocalFiles/MoveFileItem.tsx +56 -0
  132. package/src/tools/local-system/Intervention/MoveLocalFiles/index.tsx +26 -0
  133. package/src/tools/local-system/Intervention/RunCommand/index.tsx +1 -2
  134. package/src/tools/local-system/Intervention/index.ts +11 -0
  135. package/src/tools/local-system/Render/MoveLocalFiles/MoveFileItem.tsx +56 -0
  136. package/src/tools/local-system/Render/MoveLocalFiles/index.tsx +26 -0
  137. package/src/tools/local-system/Render/index.ts +21 -0
  138. package/src/tools/renders.ts +6 -24
  139. package/src/tools/web-browsing/Render/index.ts +13 -0
  140. package/src/app/[variants]/(main)/knowledge/routes/KnowledgeHome/menu/FileMenu.tsx +0 -75
  141. package/src/features/FileManager/FileList/MasonryFileItem/index.tsx +0 -582
  142. package/src/features/FileManager/index.tsx +0 -36
  143. /package/src/features/{FileManager/FileList/ToolBar → KnowledgeBaseModal/AssignKnowledgeBase}/ViewSwitcher.tsx +0 -0
  144. /package/src/features/{FileManager → KnowledgeManager}/ChunkDrawer/ChunkList/ChunkItem.tsx +0 -0
  145. /package/src/features/{FileManager → KnowledgeManager}/ChunkDrawer/ChunkList/index.tsx +0 -0
  146. /package/src/features/{FileManager → KnowledgeManager}/ChunkDrawer/Content.tsx +0 -0
  147. /package/src/features/{FileManager → KnowledgeManager}/ChunkDrawer/Loading/index.tsx +0 -0
  148. /package/src/features/{FileManager → KnowledgeManager}/ChunkDrawer/SimilaritySearchList/Item.tsx +0 -0
  149. /package/src/features/{FileManager → KnowledgeManager}/ChunkDrawer/SimilaritySearchList/index.tsx +0 -0
  150. /package/src/features/{FileManager → KnowledgeManager}/ChunkDrawer/index.tsx +0 -0
  151. /package/src/features/{FileManager/FileList → KnowledgeManager/FileExplorer}/EmptyStatus.tsx +0 -0
  152. /package/src/features/{FileManager/FileList → KnowledgeManager/FileExplorer}/FileListItem/ChunkTag.tsx +0 -0
  153. /package/src/features/{FileManager/FileList → KnowledgeManager/FileExplorer}/FileListItem/DropdownMenu.tsx +0 -0
  154. /package/src/features/{FileManager/FileList → KnowledgeManager/FileExplorer}/FileSkeleton.tsx +0 -0
  155. /package/src/features/{FileManager/FileList → KnowledgeManager/FileExplorer}/MasonryFileItem/MasonryItemWrapper.tsx +0 -0
  156. /package/src/features/{FileManager/FileList → KnowledgeManager/FileExplorer}/MasonrySkeleton.tsx +0 -0
  157. /package/src/features/{FileManager/FileList → KnowledgeManager/FileExplorer}/ToolBar/Config.tsx +0 -0
  158. /package/src/features/{FileManager/FileList → KnowledgeManager/FileExplorer}/ToolBar/MultiSelectActions.tsx +0 -0
  159. /package/src/features/{FileManager/FileList → KnowledgeManager/FileExplorer}/ToolBar/index.tsx +0 -0
  160. /package/src/features/{FileManager/FileList → KnowledgeManager/FileExplorer}/useCheckTaskStatus.ts +0 -0
  161. /package/src/features/{FileManager → KnowledgeManager}/Header/FilesSearchBar.tsx +0 -0
  162. /package/src/features/{FileManager → KnowledgeManager}/Header/TogglePanelButton.tsx +0 -0
  163. /package/src/features/{FileManager → KnowledgeManager}/Header/UploadFileButton.tsx +0 -0
  164. /package/src/features/{FileManager → KnowledgeManager}/UploadDock/Item.tsx +0 -0
  165. /package/src/features/{FileManager → KnowledgeManager}/UploadDock/index.tsx +0 -0
@@ -11,6 +11,8 @@ export interface LobeDocument {
11
11
  */
12
12
  createdAt: Date;
13
13
 
14
+ editorData: Record<string, any> | null;
15
+
14
16
  /**
15
17
  * File type or extension
16
18
  */
@@ -54,7 +56,12 @@ export interface LobeDocument {
54
56
  source: string;
55
57
 
56
58
  /**
57
- * Document title (if available)
59
+ * 文档来源类型
60
+ */
61
+ sourceType: DocumentSourceType;
62
+
63
+ /**
64
+ * 文档标题 (如果可用)。
58
65
  */
59
66
  title?: string;
60
67
 
@@ -161,7 +168,12 @@ export enum DocumentSourceType {
161
168
  API = 'api',
162
169
 
163
170
  /**
164
- * Local or uploaded files
171
+ * 编辑器创建的文档
172
+ */
173
+ EDITOR = 'editor',
174
+
175
+ /**
176
+ * 本地或上传的文件
165
177
  */
166
178
  FILE = 'file',
167
179
 
@@ -2,7 +2,9 @@ export enum FilesTabs {
2
2
  All = 'all',
3
3
  Audios = 'audios',
4
4
  Documents = 'documents',
5
+ Home = 'home',
5
6
  Images = 'images',
7
+ Pages = 'pages',
6
8
  Videos = 'videos',
7
9
  Websites = 'websites',
8
10
  }
@@ -6,14 +6,24 @@ export interface FileListItem {
6
6
  chunkCount: number | null;
7
7
  chunkingError: any | null;
8
8
  chunkingStatus?: AsyncTaskStatus | null;
9
+ /**
10
+ * Text content of the document (for notes/documents)
11
+ */
12
+ content?: string | null;
9
13
  createdAt: Date;
14
+ editorData?: Record<string, any> | null;
10
15
  embeddingError: any | null;
11
16
  embeddingStatus?: AsyncTaskStatus | null;
12
17
  fileType: string;
13
18
  finishEmbedding: boolean;
14
19
  id: string;
20
+ /**
21
+ * Metadata (for notes/documents)
22
+ */
23
+ metadata?: Record<string, any> | null;
15
24
  name: string;
16
25
  size: number;
26
+ sourceType: string;
17
27
  updatedAt: Date;
18
28
  url: string;
19
29
  }
@@ -1,4 +1,4 @@
1
- import { ModelParamsSchema , AiModelType, Pricing } from 'model-bank';
1
+ import { AiModelType, ModelParamsSchema, Pricing } from 'model-bank';
2
2
  import { ReactNode } from 'react';
3
3
 
4
4
  import { AiProviderSettings } from './aiProvider';
@@ -0,0 +1,93 @@
1
+ import { ModelIcon } from '@lobehub/icons';
2
+ import { Text } from '@lobehub/ui';
3
+ import { Popover } from 'antd';
4
+ import { createStyles } from 'antd-style';
5
+ import { AiModelForSelect } from 'model-bank';
6
+ import numeral from 'numeral';
7
+ import { memo, useMemo } from 'react';
8
+ import { Flexbox } from 'react-layout-kit';
9
+
10
+ const POPOVER_MAX_WIDTH = 320;
11
+
12
+ const useStyles = createStyles(({ css, token, isDarkMode }) => ({
13
+ descriptionText: css`
14
+ color: ${isDarkMode ? token.colorText : token.colorTextSecondary};
15
+ `,
16
+ popover: css`
17
+ .ant-popover-inner {
18
+ background: ${isDarkMode ? token.colorBgSpotlight : token.colorBgElevated};
19
+ }
20
+ `,
21
+ priceText: css`
22
+ font-weight: 500;
23
+ color: ${isDarkMode ? token.colorTextLightSolid : token.colorTextTertiary};
24
+ `,
25
+ }));
26
+
27
+ type ImageModelItemProps = AiModelForSelect & {
28
+ /**
29
+ * Whether to show popover on hover
30
+ * @default true
31
+ */
32
+ showPopover?: boolean;
33
+ };
34
+
35
+ const ImageModelItem = memo<ImageModelItemProps>(
36
+ ({ approximatePricePerImage, description, pricePerImage, showPopover = true, ...model }) => {
37
+ const { styles } = useStyles();
38
+
39
+ const priceLabel = useMemo(() => {
40
+ // Priority 1: Use exact price
41
+ if (typeof pricePerImage === 'number') {
42
+ return `${numeral(pricePerImage).format('$0,0.00[000]')} / image`;
43
+ }
44
+
45
+ // Priority 2: Use approximate price with prefix
46
+ if (typeof approximatePricePerImage === 'number') {
47
+ return `~ ${numeral(approximatePricePerImage).format('$0,0.00[000]')} / image`;
48
+ }
49
+
50
+ return undefined;
51
+ }, [approximatePricePerImage, pricePerImage]);
52
+
53
+ const popoverContent = useMemo(() => {
54
+ if (!description && !priceLabel) return null;
55
+
56
+ return (
57
+ <Flexbox gap={8} style={{ maxWidth: POPOVER_MAX_WIDTH }}>
58
+ {description && <Text className={styles.descriptionText}>{description}</Text>}
59
+ {priceLabel && <Text className={styles.priceText}>{priceLabel}</Text>}
60
+ </Flexbox>
61
+ );
62
+ }, [description, priceLabel, styles.descriptionText, styles.priceText]);
63
+
64
+ const content = (
65
+ <Flexbox align={'center'} gap={8} horizontal style={{ overflow: 'hidden' }}>
66
+ <ModelIcon model={model.id} size={20} />
67
+ <Text ellipsis title={model.displayName || model.id}>
68
+ {model.displayName || model.id}
69
+ </Text>
70
+ </Flexbox>
71
+ );
72
+
73
+ if (!showPopover || !popoverContent) return content;
74
+
75
+ return (
76
+ <Popover
77
+ align={{
78
+ offset: [24, -10],
79
+ }}
80
+ arrow={false}
81
+ classNames={{ root: styles.popover }}
82
+ content={popoverContent}
83
+ placement="rightTop"
84
+ >
85
+ {content}
86
+ </Popover>
87
+ );
88
+ },
89
+ );
90
+
91
+ ImageModelItem.displayName = 'ImageModelItem';
92
+
93
+ export default ImageModelItem;
@@ -7,7 +7,7 @@ import { memo, useMemo } from 'react';
7
7
  import { useTranslation } from 'react-i18next';
8
8
  import { Flexbox } from 'react-layout-kit';
9
9
 
10
- import { ModelItemRender, ProviderItemRender } from '@/components/ModelSelect';
10
+ import { ProviderItemRender } from '@/components/ModelSelect';
11
11
  import { isDeprecatedEdition } from '@/const/version';
12
12
  import { useAiInfraStore } from '@/store/aiInfra';
13
13
  import { aiProviderSelectors } from '@/store/aiInfra/slices/aiProvider/selectors';
@@ -16,6 +16,8 @@ import { imageGenerationConfigSelectors } from '@/store/image/slices/generationC
16
16
  import { featureFlagsSelectors, useServerConfigStore } from '@/store/serverConfig';
17
17
  import { EnabledProviderWithModels } from '@/types/aiProvider';
18
18
 
19
+ import ImageModelItem from './ImageModelItem';
20
+
19
21
  const useStyles = createStyles(({ css, prefixCls }) => ({
20
22
  popup: css`
21
23
  &.${prefixCls}-select-dropdown .${prefixCls}-select-item-option-grouped {
@@ -48,7 +50,7 @@ const ModelSelect = memo(() => {
48
50
  const options = useMemo<SelectProps['options']>(() => {
49
51
  const getImageModels = (provider: EnabledProviderWithModels) => {
50
52
  const modelOptions = provider.children.map((model) => ({
51
- label: <ModelItemRender {...model} {...model.abilities} showInfoTag={false} />,
53
+ label: <ImageModelItem {...model} />,
52
54
  provider: provider.id,
53
55
  value: `${provider.id}/${model.id}`,
54
56
  }));
@@ -133,11 +135,24 @@ const ModelSelect = memo(() => {
133
135
  }));
134
136
  }, [enabledImageModelList, showLLM, t, theme.colorTextTertiary, router]);
135
137
 
138
+ const labelRender: SelectProps['labelRender'] = (props) => {
139
+ const modelInfo = enabledImageModelList
140
+ .flatMap((provider) =>
141
+ provider.children.map((model) => ({ ...model, providerId: provider.id })),
142
+ )
143
+ .find((model) => props.value === `${model.providerId}/${model.id}`);
144
+
145
+ if (!modelInfo) return props.label;
146
+
147
+ return <ImageModelItem {...modelInfo} showPopover={false} />;
148
+ };
149
+
136
150
  return (
137
151
  <Select
138
152
  classNames={{
139
153
  root: styles.popup,
140
154
  }}
155
+ labelRender={labelRender}
141
156
  onChange={(value, option) => {
142
157
  // Skip onChange for disabled options (empty states)
143
158
  if (value === 'no-provider' || value.includes('/empty')) return;
@@ -53,8 +53,9 @@ const KnowledgeRouter = memo(() => {
53
53
  <MemoryRouter initialEntries={[getInitialPath()]} initialIndex={0}>
54
54
  <UrlSynchronizer />
55
55
  <Routes>
56
- {/* Knowledge home - file list page */}
56
+ {/* Knowledge home */}
57
57
  <Route element={<KnowledgeHomePage />} path="/" />
58
+ <Route element={<KnowledgeHomePage />} path="/:id" />
58
59
 
59
60
  {/* Knowledge bases routes */}
60
61
  <Route element={<KnowledgeBasesListPage />} path="/bases" />
@@ -16,8 +16,6 @@ const useStyles = createStyles(({ css, token, isDarkMode }) => ({
16
16
  `,
17
17
  container: css`
18
18
  cursor: pointer;
19
-
20
- margin-inline: 8px;
21
19
  padding-block: 4px;
22
20
  padding-inline: 8px;
23
21
  border-radius: ${token.borderRadius}px;
@@ -1,4 +1,4 @@
1
- import { useSearchParams } from 'react-router-dom';
1
+ import { useParams, useSearchParams } from 'react-router-dom';
2
2
 
3
3
  import { FilesTabs } from '@/types/files';
4
4
 
@@ -8,15 +8,18 @@ import { FilesTabs } from '@/types/files';
8
8
  */
9
9
  export const useFileCategory = (): [string, (value: string) => void] => {
10
10
  const [searchParams, setSearchParams] = useSearchParams();
11
+ const { id } = useParams<{ id: string }>();
11
12
 
12
- const category = searchParams.get('category') ?? FilesTabs.All;
13
+ // If there's an ID in the path, default to Pages instead of Home
14
+ const defaultCategory = id ? FilesTabs.Pages : FilesTabs.Home;
15
+ const category = searchParams.get('category') ?? defaultCategory;
13
16
 
14
17
  const setCategory = (value: string) => {
15
18
  setSearchParams(
16
19
  (prev) => {
17
20
  const newParams = new URLSearchParams(prev);
18
21
 
19
- if (value === FilesTabs.All) {
22
+ if (value === FilesTabs.Home) {
20
23
  // Clear on default
21
24
  newParams.delete('category');
22
25
  } else {
@@ -6,8 +6,8 @@ import { useParams } from 'react-router-dom';
6
6
 
7
7
  import FileModalQueryRoute from '@/app/[variants]/(main)/knowledge/shared/FileModalQueryRoute';
8
8
  import { useSetFileModalId } from '@/app/[variants]/(main)/knowledge/shared/useFileQueryParam';
9
- import FileManager from '@/features/FileManager';
10
9
  import FilePanel from '@/features/FileSidePanel';
10
+ import KnowledgeItemManager from '@/features/KnowledgeManager';
11
11
  import { knowledgeBaseSelectors, useKnowledgeBaseStore } from '@/store/knowledgeBase';
12
12
 
13
13
  import { useKnowledgeBaseItem } from '../../hooks/useKnowledgeItem';
@@ -35,7 +35,7 @@ const KnowledgeBaseDetailPage = memo(() => {
35
35
  <Menu id={id} />
36
36
  </FilePanel>
37
37
  <Flexbox flex={1} style={{ overflow: 'hidden', position: 'relative' }}>
38
- <FileManager knowledgeBaseId={id} onOpenFile={setFileModalId} title={name} />
38
+ <KnowledgeItemManager knowledgeBaseId={id} onOpenFile={setFileModalId} title={name} />
39
39
  </Flexbox>
40
40
  <FileModalQueryRoute />
41
41
  </>
@@ -9,7 +9,7 @@ import { Flexbox } from 'react-layout-kit';
9
9
  import Menu from '@/components/Menu';
10
10
  import type { MenuProps } from '@/components/Menu';
11
11
 
12
- const MenuItems = () => {
12
+ const CategoryMenu = () => {
13
13
  const { t } = useTranslation('knowledgeBase');
14
14
 
15
15
  const items: MenuProps['items'] = useMemo(
@@ -30,6 +30,6 @@ const MenuItems = () => {
30
30
  );
31
31
  };
32
32
 
33
- MenuItems.displayName = 'MenuItems';
33
+ CategoryMenu.displayName = 'MenuItems';
34
34
 
35
- export default MenuItems;
35
+ export default CategoryMenu;
@@ -3,14 +3,14 @@
3
3
  import { memo } from 'react';
4
4
  import { Flexbox } from 'react-layout-kit';
5
5
 
6
+ import CategoryMenu from './CategoryMenu';
6
7
  import Head from './Head';
7
- import MenuItems from './MenuItems';
8
8
 
9
9
  const Menu = memo<{ id: string }>(({ id }) => {
10
10
  return (
11
11
  <Flexbox gap={16} height={'100%'} paddingInline={12} style={{ paddingTop: 12 }}>
12
12
  <Head id={id} />
13
- <MenuItems />
13
+ <CategoryMenu />
14
14
  </Flexbox>
15
15
  );
16
16
  });
@@ -5,11 +5,13 @@ import { memo } from 'react';
5
5
  import { useTranslation } from 'react-i18next';
6
6
  import { Flexbox } from 'react-layout-kit';
7
7
  import { useMediaQuery } from 'react-responsive';
8
+ import { useParams } from 'react-router-dom';
8
9
 
9
10
  import NProgress from '@/components/NProgress';
10
11
  import PanelTitle from '@/components/PanelTitle';
11
- import FileManager from '@/features/FileManager';
12
12
  import FilePanel from '@/features/FileSidePanel';
13
+ import KnowledgeItemManager from '@/features/KnowledgeManager';
14
+ import TogglePanelButton from '@/features/KnowledgeManager/Header/TogglePanelButton';
13
15
  import { useShowMobileWorkspace } from '@/hooks/useShowMobileWorkspace';
14
16
  import { FilesTabs } from '@/types/files';
15
17
 
@@ -18,8 +20,8 @@ import FileModalQueryRoute from '../../shared/FileModalQueryRoute';
18
20
  import { useSetFileModalId } from '../../shared/useFileQueryParam';
19
21
  import Container from './layout/Container';
20
22
  import RegisterHotkeys from './layout/RegisterHotkeys';
21
- import FileMenu from './menu/FileMenu';
22
- import KnowledgeBase from './menu/KnowledgeBase';
23
+ import CategoryMenu from './menu/CategoryMenu';
24
+ import Collection from './menu/KnowledgeBase';
23
25
 
24
26
  const useStyles = createStyles(({ css, token }) => ({
25
27
  main: css`
@@ -27,42 +29,63 @@ const useStyles = createStyles(({ css, token }) => ({
27
29
  overflow: hidden;
28
30
  background: ${token.colorBgLayout};
29
31
  `,
32
+ sidebar: css`
33
+ position: relative;
34
+
35
+ &:hover .toggle-button {
36
+ opacity: 1;
37
+ }
38
+ `,
39
+ toggleButton: css`
40
+ position: absolute;
41
+ z-index: 10;
42
+ inset-block-start: 8px;
43
+ inset-inline-end: 8px;
44
+
45
+ opacity: 0;
46
+
47
+ transition: opacity ${token.motionDurationSlow};
48
+ `,
30
49
  }));
31
50
 
32
- // Menu content component
33
- const MenuContent = memo(() => {
51
+ const Sidebar = memo(() => {
34
52
  const { t } = useTranslation('file');
53
+ const { styles } = useStyles();
35
54
 
36
55
  return (
37
- <Flexbox gap={16} height={'100%'}>
56
+ <Flexbox className={styles.sidebar} gap={16} height={'100%'}>
57
+ <div className={`${styles.toggleButton} toggle-button`}>
58
+ <TogglePanelButton />
59
+ </div>
38
60
  <Flexbox paddingInline={8}>
39
61
  <PanelTitle desc={t('desc')} title={t('title')} />
40
- <FileMenu />
62
+ <CategoryMenu />
41
63
  </Flexbox>
42
- <KnowledgeBase />
64
+ <Collection />
43
65
  </Flexbox>
44
66
  );
45
67
  });
46
68
 
47
- MenuContent.displayName = 'MenuContent';
69
+ Sidebar.displayName = 'Sidebar';
48
70
 
49
71
  // Main files list component
50
- const FilesListPage = memo(() => {
72
+ const MainContent = memo(() => {
73
+ const { id } = useParams<{ id: string }>();
51
74
  const [category] = useFileCategory();
52
75
  const setFileModalId = useSetFileModalId();
53
76
 
54
77
  return (
55
- <FileManager
78
+ <KnowledgeItemManager
56
79
  category={category}
80
+ documentId={id}
57
81
  onOpenFile={setFileModalId}
58
82
  title={`${category as FilesTabs}`}
59
83
  />
60
84
  );
61
85
  });
62
86
 
63
- FilesListPage.displayName = 'FilesListPage';
87
+ MainContent.displayName = 'FilesListPage';
64
88
 
65
- // Desktop layout
66
89
  const DesktopLayout = memo(() => {
67
90
  return (
68
91
  <>
@@ -74,10 +97,10 @@ const DesktopLayout = memo(() => {
74
97
  width={'100%'}
75
98
  >
76
99
  <FilePanel>
77
- <MenuContent />
100
+ <Sidebar />
78
101
  </FilePanel>
79
102
  <Container>
80
- <FilesListPage />
103
+ <MainContent />
81
104
  </Container>
82
105
  </Flexbox>
83
106
  <RegisterHotkeys />
@@ -88,7 +111,6 @@ const DesktopLayout = memo(() => {
88
111
 
89
112
  DesktopLayout.displayName = 'DesktopLayout';
90
113
 
91
- // Mobile layout
92
114
  const MobileLayout = memo(() => {
93
115
  const showMobileWorkspace = useShowMobileWorkspace();
94
116
  const { styles } = useStyles();
@@ -102,7 +124,7 @@ const MobileLayout = memo(() => {
102
124
  style={showMobileWorkspace ? { display: 'none' } : undefined}
103
125
  width="100%"
104
126
  >
105
- <MenuContent />
127
+ <Sidebar />
106
128
  </Flexbox>
107
129
  <Flexbox
108
130
  className={styles.main}
@@ -110,7 +132,7 @@ const MobileLayout = memo(() => {
110
132
  style={showMobileWorkspace ? undefined : { display: 'none' }}
111
133
  width="100%"
112
134
  >
113
- <FilesListPage />
135
+ <MainContent />
114
136
  </Flexbox>
115
137
  <FileModalQueryRoute />
116
138
  </>
@@ -12,7 +12,7 @@ const Container = memo<PropsWithChildren>(({ children }) => {
12
12
  flex={1}
13
13
  style={{
14
14
  background: theme.colorBgContainerSecondary,
15
- overflow: 'hidden',
15
+ overflowY: 'auto',
16
16
  position: 'relative',
17
17
  }}
18
18
  >
@@ -0,0 +1,148 @@
1
+ 'use client';
2
+
3
+ import { CaretDownFilled } from '@ant-design/icons';
4
+ import { ActionIcon, Icon } from '@lobehub/ui';
5
+ import { createStyles } from 'antd-style';
6
+ import { motion } from 'framer-motion';
7
+ import { FilePenIcon, FileText, FolderOpen, ImageIcon, Mic2, SquarePlay } from 'lucide-react';
8
+ import { memo, useMemo, useState } from 'react';
9
+ import { useTranslation } from 'react-i18next';
10
+ import { Flexbox } from 'react-layout-kit';
11
+ import { useNavigate } from 'react-router-dom';
12
+
13
+ import Menu from '@/components/Menu';
14
+ import type { MenuProps } from '@/components/Menu';
15
+ import { FilesTabs } from '@/types/files';
16
+
17
+ import { useFileCategory } from '../../../hooks/useFileCategory';
18
+
19
+ const useStyles = createStyles(({ css, token }) => ({
20
+ header: css`
21
+ cursor: pointer;
22
+ border-radius: ${token.borderRadius}px;
23
+ color: ${token.colorTextSecondary};
24
+ transition: background-color 0.2s;
25
+
26
+ &:hover {
27
+ background-color: ${token.colorFillTertiary};
28
+ }
29
+ `,
30
+ headerActive: css`
31
+ color: ${token.colorText};
32
+ background-color: ${token.colorFillSecondary};
33
+ `,
34
+ indentedMenu: css`
35
+ padding-inline-start: 24px;
36
+ `,
37
+ }));
38
+
39
+ const CategoryMenu = memo(() => {
40
+ const { t } = useTranslation('file');
41
+ const { styles, cx } = useStyles();
42
+ const [activeKey] = useFileCategory();
43
+ const [showCollapsed, setShowCollapsed] = useState(activeKey !== FilesTabs.Home);
44
+ const navigate = useNavigate();
45
+
46
+ const collapsedItems: MenuProps['items'] = useMemo(
47
+ () => [
48
+ {
49
+ icon: <Icon icon={FolderOpen} />,
50
+ key: FilesTabs.All,
51
+ label: t('tab.all'),
52
+ },
53
+ {
54
+ icon: <Icon icon={FileText} />,
55
+ key: FilesTabs.Documents,
56
+ label: t('tab.documents'),
57
+ },
58
+ {
59
+ icon: <Icon icon={FilePenIcon} />,
60
+ key: FilesTabs.Pages,
61
+ label: t('tab.pages'),
62
+ },
63
+ {
64
+ icon: <Icon icon={ImageIcon} />,
65
+ key: FilesTabs.Images,
66
+ label: t('tab.images'),
67
+ },
68
+ {
69
+ icon: <Icon icon={Mic2} />,
70
+ key: FilesTabs.Audios,
71
+ label: t('tab.audios'),
72
+ },
73
+ {
74
+ icon: <Icon icon={SquarePlay} />,
75
+ key: FilesTabs.Videos,
76
+ label: t('tab.videos'),
77
+ },
78
+ // {
79
+ // icon: <Icon icon={Globe} />,
80
+ // key: FilesTabs.Websites,
81
+ // label: t('tab.websites'),
82
+ // },
83
+ ],
84
+ [t],
85
+ );
86
+
87
+ const isHomeActive = activeKey === FilesTabs.Home;
88
+
89
+ return (
90
+ <Flexbox gap={4}>
91
+ <Flexbox
92
+ align={'center'}
93
+ className={cx(styles.header, isHomeActive && styles.headerActive)}
94
+ horizontal
95
+ onClick={() => {
96
+ navigate('/', { replace: true });
97
+ }}
98
+ paddingBlock={6}
99
+ paddingInline={8}
100
+ >
101
+ <Flexbox align={'center'} flex={1} gap={8} horizontal>
102
+ <motion.div
103
+ animate={{ rotate: showCollapsed ? 0 : -90 }}
104
+ transition={{ duration: 0.2, ease: 'easeInOut' }}
105
+ >
106
+ <ActionIcon
107
+ icon={CaretDownFilled as any}
108
+ onClick={(e) => {
109
+ e.stopPropagation();
110
+ setShowCollapsed(!showCollapsed);
111
+ }}
112
+ size={'small'}
113
+ />
114
+ </motion.div>
115
+ <div style={{ flex: 1, lineHeight: '14px' }}>{t('tab.home')}</div>
116
+ </Flexbox>
117
+ </Flexbox>
118
+
119
+ <motion.div
120
+ animate={{
121
+ height: showCollapsed ? 'auto' : 0,
122
+ opacity: showCollapsed ? 1 : 0,
123
+ }}
124
+ initial={false}
125
+ style={{ overflow: 'hidden' }}
126
+ transition={{ duration: 0.2, ease: 'easeInOut' }}
127
+ >
128
+ <Flexbox>
129
+ <Menu
130
+ compact
131
+ items={collapsedItems}
132
+ onClick={({ key }) => {
133
+ // Navigate to home route and set category
134
+ const categoryParam = key === FilesTabs.Home ? '' : `?category=${key}`;
135
+ navigate(`/${categoryParam}`, { replace: true });
136
+ }}
137
+ selectable
138
+ selectedKeys={[activeKey]}
139
+ />
140
+ </Flexbox>
141
+ </motion.div>
142
+ </Flexbox>
143
+ );
144
+ });
145
+
146
+ CategoryMenu.displayName = 'CategoryMenu';
147
+
148
+ export default CategoryMenu;