@lobehub/lobehub 2.0.0-next.267 → 2.0.0-next.269

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 (135) hide show
  1. package/.cursor/rules/microcopy-cn.mdc +75 -63
  2. package/.cursor/rules/microcopy-en.mdc +4 -8
  3. package/CHANGELOG.md +50 -0
  4. package/README.md +8 -8
  5. package/README.zh-CN.md +8 -8
  6. package/apps/desktop/src/main/core/browser/Browser.ts +6 -0
  7. package/apps/desktop/src/main/locales/default/common.ts +2 -2
  8. package/changelog/v1.json +10 -0
  9. package/docs/development/database-schema.dbml +4 -0
  10. package/e2e/CLAUDE.md +9 -8
  11. package/e2e/cucumber.config.js +1 -0
  12. package/e2e/src/features/page/README.md +118 -0
  13. package/e2e/src/features/page/crud.feature +62 -0
  14. package/e2e/src/features/page/editor-content.feature +93 -0
  15. package/e2e/src/features/page/editor-meta.feature +60 -0
  16. package/e2e/src/steps/agent/conversation.steps.ts +4 -4
  17. package/e2e/src/steps/home/sidebarAgent.steps.ts +91 -94
  18. package/e2e/src/steps/home/sidebarGroup.steps.ts +4 -4
  19. package/e2e/src/steps/hooks.ts +2 -0
  20. package/e2e/src/steps/page/editor-content.steps.ts +344 -0
  21. package/e2e/src/steps/page/editor-meta.steps.ts +410 -0
  22. package/e2e/src/steps/page/page-crud.steps.ts +363 -0
  23. package/e2e/src/support/world.ts +12 -0
  24. package/locales/ar/file.json +2 -0
  25. package/locales/bg-BG/file.json +2 -0
  26. package/locales/de-DE/file.json +2 -0
  27. package/locales/en-US/auth.json +1 -1
  28. package/locales/en-US/file.json +2 -0
  29. package/locales/en-US/metadata.json +2 -2
  30. package/locales/es-ES/file.json +2 -0
  31. package/locales/fa-IR/file.json +2 -0
  32. package/locales/fr-FR/file.json +2 -0
  33. package/locales/it-IT/file.json +2 -0
  34. package/locales/ja-JP/file.json +2 -0
  35. package/locales/ko-KR/file.json +2 -0
  36. package/locales/nl-NL/file.json +2 -0
  37. package/locales/pl-PL/file.json +2 -0
  38. package/locales/pt-BR/file.json +2 -0
  39. package/locales/ru-RU/file.json +2 -0
  40. package/locales/tr-TR/file.json +2 -0
  41. package/locales/vi-VN/file.json +2 -0
  42. package/locales/zh-CN/file.json +2 -0
  43. package/locales/zh-TW/file.json +2 -0
  44. package/package.json +1 -1
  45. package/packages/builtin-agents/src/agents/agent-builder/index.ts +1 -1
  46. package/packages/builtin-agents/src/agents/group-agent-builder/index.ts +1 -1
  47. package/packages/builtin-agents/src/agents/page-agent/index.ts +1 -1
  48. package/packages/const/src/settings/group.ts +0 -10
  49. package/packages/database/migrations/0068_update_group_data.sql +4 -0
  50. package/packages/database/migrations/meta/0068_snapshot.json +9588 -0
  51. package/packages/database/migrations/meta/_journal.json +7 -0
  52. package/packages/database/src/models/__tests__/chatGroup.test.ts +5 -7
  53. package/packages/database/src/models/__tests__/knowledgeBase.test.ts +185 -0
  54. package/packages/database/src/models/knowledgeBase.ts +67 -3
  55. package/packages/database/src/repositories/agentGroup/index.test.ts +23 -29
  56. package/packages/database/src/repositories/agentGroup/index.ts +4 -9
  57. package/packages/database/src/repositories/knowledge/index.ts +3 -3
  58. package/packages/database/src/schemas/chatGroup.ts +4 -3
  59. package/packages/database/src/types/chatGroup.ts +0 -7
  60. package/packages/types/src/agentGroup/index.ts +30 -9
  61. package/packages/utils/src/index.ts +1 -0
  62. package/packages/utils/src/platform.ts +35 -3
  63. package/src/app/[variants]/(desktop)/desktop-onboarding/_layout/index.tsx +30 -22
  64. package/src/app/[variants]/(desktop)/desktop-onboarding/_layout/style.ts +8 -5
  65. package/src/app/[variants]/(main)/_layout/DesktopLayoutContainer.tsx +2 -3
  66. package/src/app/[variants]/(main)/home/_layout/Body/Agent/ModalProvider.tsx +9 -32
  67. package/src/app/[variants]/(main)/home/_layout/hooks/useCreateMenuItems.tsx +3 -37
  68. package/src/app/[variants]/(main)/home/_layout/hooks/useSessionGroupMenuItems.tsx +7 -53
  69. package/src/app/[variants]/(main)/home/features/RecentPage/List.tsx +2 -1
  70. package/src/app/[variants]/(main)/resource/features/DndContextWrapper.tsx +1 -1
  71. package/src/app/[variants]/(main)/resource/library/_layout/Sidebar.tsx +2 -2
  72. package/src/app/[variants]/(main)/resource/library/features/LibraryMenu.tsx +2 -2
  73. package/src/app/[variants]/(mobile)/chat/settings/features/SettingButton.tsx +2 -12
  74. package/src/components/ChatGroupWizard/ChatGroupWizard.tsx +5 -27
  75. package/src/components/DragUpload/index.tsx +24 -27
  76. package/src/components/MemberSelectionModal/MemberSelectionModal.tsx +2 -11
  77. package/src/features/CommandMenu/useCommandMenu.ts +4 -14
  78. package/src/features/ElectronTitlebar/SimpleTitleBar.tsx +31 -0
  79. package/src/features/ElectronTitlebar/index.tsx +1 -0
  80. package/src/features/ResourceManager/components/Editor/index.tsx +2 -3
  81. package/src/features/ResourceManager/components/Explorer/Header/index.tsx +13 -17
  82. package/src/features/ResourceManager/components/Explorer/ItemDropdown/useFileItemDropdown.tsx +1 -1
  83. package/src/features/ResourceManager/components/Explorer/ListView/ListItem/TruncatedFileName.tsx +130 -0
  84. package/src/features/ResourceManager/components/Explorer/ListView/ListItem/index.tsx +36 -4
  85. package/src/features/ResourceManager/components/Explorer/ListView/Skeleton.tsx +4 -3
  86. package/src/features/ResourceManager/components/Explorer/ListView/index.tsx +58 -2
  87. package/src/features/ResourceManager/components/Explorer/MasonryView/index.tsx +58 -6
  88. package/src/features/ResourceManager/components/Explorer/MoveToFolderModal.tsx +2 -5
  89. package/src/features/ResourceManager/components/Explorer/ToolBar/BatchActionsDropdown.tsx +9 -5
  90. package/src/features/ResourceManager/components/Explorer/index.tsx +11 -56
  91. package/src/features/ResourceManager/components/Header/AddButton.tsx +5 -6
  92. package/src/features/ResourceManager/components/LibraryHierarchy/HierarchyNode.tsx +382 -0
  93. package/src/features/ResourceManager/components/LibraryHierarchy/index.tsx +396 -0
  94. package/src/features/ResourceManager/components/LibraryHierarchy/styles.ts +19 -0
  95. package/src/features/ResourceManager/components/LibraryHierarchy/treeState.ts +178 -0
  96. package/src/features/ResourceManager/components/LibraryHierarchy/types.ts +10 -0
  97. package/src/features/ResourceManager/index.tsx +3 -0
  98. package/src/layout/GlobalProvider/GroupWizardProvider.tsx +6 -29
  99. package/src/locales/default/auth.ts +1 -1
  100. package/src/locales/default/file.ts +2 -0
  101. package/src/locales/default/metadata.ts +2 -2
  102. package/src/server/modules/AgentRuntime/AgentRuntimeCoordinator.ts +30 -30
  103. package/src/server/modules/AgentRuntime/AgentStateManager.ts +23 -23
  104. package/src/server/modules/AgentRuntime/InMemoryAgentStateManager.ts +16 -16
  105. package/src/server/modules/AgentRuntime/InMemoryStreamEventManager.ts +13 -13
  106. package/src/server/modules/AgentRuntime/RuntimeExecutors.ts +2 -2
  107. package/src/server/modules/AgentRuntime/StreamEventManager.ts +18 -18
  108. package/src/server/modules/AgentRuntime/types.ts +21 -21
  109. package/src/server/routers/lambda/__tests__/agentGroup.test.ts +8 -8
  110. package/src/server/routers/lambda/agentGroup.ts +10 -12
  111. package/src/server/services/document/index.ts +1 -0
  112. package/src/store/agentGroup/slices/curd.test.ts +4 -4
  113. package/src/store/file/slices/fileManager/action.ts +12 -4
  114. package/src/store/home/slices/homeInput/action.ts +0 -3
  115. package/src/store/session/slices/session/action.ts +5 -9
  116. package/src/utils/platform.ts +2 -0
  117. package/src/app/[variants]/(mobile)/chat/settings/features/AgentTeamSettings/index.tsx +0 -95
  118. package/src/features/GroupChatSettings/AgentCard.tsx +0 -154
  119. package/src/features/GroupChatSettings/AgentTeamChatSettings.tsx +0 -179
  120. package/src/features/GroupChatSettings/AgentTeamMembersSettings.tsx +0 -244
  121. package/src/features/GroupChatSettings/AgentTeamMetaSettings.tsx +0 -94
  122. package/src/features/GroupChatSettings/AgentTeamSettings.tsx +0 -54
  123. package/src/features/GroupChatSettings/GroupCategory/index.tsx +0 -30
  124. package/src/features/GroupChatSettings/GroupCategory/useGroupCategory.tsx +0 -42
  125. package/src/features/GroupChatSettings/GroupChatSettingsProvider.tsx +0 -19
  126. package/src/features/GroupChatSettings/HostMemberCard.tsx +0 -113
  127. package/src/features/GroupChatSettings/StoreUpdater.tsx +0 -34
  128. package/src/features/GroupChatSettings/hooks/useGroupChatSettings.ts +0 -25
  129. package/src/features/GroupChatSettings/index.ts +0 -16
  130. package/src/features/GroupChatSettings/store/action.ts +0 -105
  131. package/src/features/GroupChatSettings/store/index.ts +0 -18
  132. package/src/features/GroupChatSettings/store/initialState.ts +0 -23
  133. package/src/features/GroupChatSettings/store/selectors.ts +0 -13
  134. package/src/features/ResourceManager/components/Tree/index.tsx +0 -883
  135. /package/src/features/ResourceManager/components/{Tree → LibraryHierarchy}/TreeSkeleton.tsx +0 -0
@@ -15,6 +15,7 @@ import {
15
15
  } from '@/app/[variants]/(main)/resource/features/store';
16
16
  import { sortFileList } from '@/app/[variants]/(main)/resource/features/store/selectors';
17
17
  import { useFileStore } from '@/store/file';
18
+ import { useFetchResources } from '@/store/file/slices/resource/hooks';
18
19
  import { useGlobalStore } from '@/store/global';
19
20
  import { INITIAL_STATUS } from '@/store/global/initialState';
20
21
  import { type AsyncTaskStatus } from '@/types/asyncTask';
@@ -53,8 +54,11 @@ const styles = createStaticStyles(({ css }) => ({
53
54
  `,
54
55
  }));
55
56
 
56
- const ListView = memo(() => {
57
+ const ListView = memo(function ListView() {
57
58
  const [
59
+ libraryId,
60
+ category,
61
+ searchQuery,
58
62
  selectFileIds,
59
63
  setSelectedFileIds,
60
64
  pendingRenameItemId,
@@ -62,7 +66,11 @@ const ListView = memo(() => {
62
66
  loadMoreKnowledgeItems,
63
67
  sorter,
64
68
  sortType,
69
+ storeIsTransitioning,
65
70
  ] = useResourceManagerStore((s) => [
71
+ s.libraryId,
72
+ s.category,
73
+ s.searchQuery,
66
74
  s.selectedFileIds,
67
75
  s.setSelectedFileIds,
68
76
  s.pendingRenameItemId,
@@ -70,6 +78,7 @@ const ListView = memo(() => {
70
78
  s.loadMoreKnowledgeItems,
71
79
  s.sorter,
72
80
  s.sortType,
81
+ s.isTransitioning,
73
82
  ]);
74
83
 
75
84
  // Access column widths from Global store
@@ -83,6 +92,7 @@ const ListView = memo(() => {
83
92
  const [isLoadingMore, setIsLoadingMore] = useState(false);
84
93
  const isDragActive = useDragActive();
85
94
  const [isDropZoneActive, setIsDropZoneActive] = useState(false);
95
+ const [isAnyRowHovered, setIsAnyRowHovered] = useState(false);
86
96
  const scrollTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
87
97
  const autoScrollIntervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
88
98
  const containerRef = useRef<HTMLDivElement>(null);
@@ -94,6 +104,33 @@ const ListView = memo(() => {
94
104
  // Get current folder ID - either from breadcrumb or null for root
95
105
  const currentFolderId = folderBreadcrumb?.at(-1)?.id || null;
96
106
 
107
+ const queryParams = useMemo(
108
+ () => ({
109
+ category: libraryId ? undefined : category,
110
+ libraryId,
111
+ parentId: currentFolderSlug || null,
112
+ q: searchQuery ?? undefined,
113
+ showFilesInKnowledgeBase: false,
114
+ sortType,
115
+ sorter,
116
+ }),
117
+ [category, currentFolderSlug, libraryId, searchQuery, sorter, sortType],
118
+ );
119
+
120
+ const { isLoading, isValidating } = useFetchResources(queryParams);
121
+ const { queryParams: currentQueryParams } = useFileStore();
122
+
123
+ const isNavigating = useMemo(() => {
124
+ if (!currentQueryParams || !queryParams) return false;
125
+
126
+ return (
127
+ currentQueryParams.libraryId !== queryParams.libraryId ||
128
+ currentQueryParams.parentId !== queryParams.parentId ||
129
+ currentQueryParams.category !== queryParams.category ||
130
+ currentQueryParams.q !== queryParams.q
131
+ );
132
+ }, [currentQueryParams, queryParams]);
133
+
97
134
  const resourceList = useFileStore((s) => s.resourceList);
98
135
 
99
136
  // Map ResourceItem[] to FileListItem[] for compatibility
@@ -112,6 +149,17 @@ const ListView = memo(() => {
112
149
  // Sort data using current sort settings
113
150
  const data = sortFileList(rawData, sorter, sortType) || [];
114
151
 
152
+ const dataLength = data.length;
153
+ const effectiveIsLoading = isLoading ?? false;
154
+ const effectiveIsNavigating = isNavigating ?? false;
155
+ const effectiveIsTransitioning = storeIsTransitioning ?? false;
156
+ const effectiveIsValidating = isValidating ?? false;
157
+
158
+ const showSkeleton =
159
+ (effectiveIsLoading && dataLength === 0) ||
160
+ (effectiveIsNavigating && effectiveIsValidating) ||
161
+ effectiveIsTransitioning;
162
+
115
163
  const dataRef = useRef<FileListItemType[]>(data);
116
164
 
117
165
  useEffect(() => {
@@ -286,6 +334,8 @@ const ListView = memo(() => {
286
334
  return <ListViewSkeleton columnWidths={columnWidths} />;
287
335
  }, [isLoadingMore, fileListHasMore, columnWidths]);
288
336
 
337
+ if (showSkeleton) return <ListViewSkeleton columnWidths={columnWidths} />;
338
+
289
339
  return (
290
340
  <Flexbox height={'100%'}>
291
341
  <div className={styles.scrollContainer}>
@@ -360,7 +410,11 @@ const ListView = memo(() => {
360
410
  </Flexbox>
361
411
  </Flexbox>
362
412
  <div
363
- className={cx(styles.dropZone, isDropZoneActive && styles.dropZoneActive)}
413
+ className={cx(
414
+ styles.dropZone,
415
+ isDropZoneActive && styles.dropZoneActive,
416
+ isAnyRowHovered && 'any-row-hovered',
417
+ )}
364
418
  data-drop-target-id={currentFolderId || undefined}
365
419
  data-is-folder="true"
366
420
  onDragLeave={handleDropZoneDragLeave}
@@ -385,7 +439,9 @@ const ListView = memo(() => {
385
439
  <FileListItem
386
440
  columnWidths={columnWidths}
387
441
  index={index}
442
+ isAnyRowHovered={isAnyRowHovered}
388
443
  key={item.id}
444
+ onHoverChange={setIsAnyRowHovered}
389
445
  onSelectedChange={handleSelectionChange}
390
446
  pendingRenameItemId={pendingRenameItemId}
391
447
  selected={selectFileIds.includes(item.id)}
@@ -9,24 +9,31 @@ import { useTranslation } from 'react-i18next';
9
9
  import { useResourceManagerStore } from '@/app/[variants]/(main)/resource/features/store';
10
10
  import { sortFileList } from '@/app/[variants]/(main)/resource/features/store/selectors';
11
11
  import { useFileStore } from '@/store/file';
12
+ import { useFetchResources } from '@/store/file/slices/resource/hooks';
12
13
  import { type FileListItem } from '@/types/files';
13
14
 
14
15
  import { useMasonryColumnCount } from '../useMasonryColumnCount';
15
16
  import MasonryItemWrapper from './MasonryFileItem/MasonryItemWrapper';
17
+ import MasonryViewSkeleton from './Skeleton';
16
18
 
17
- const MasonryView = memo(() => {
19
+ const MasonryView = memo(function MasonryView() {
18
20
  // Access all state from Resource Manager store
19
21
  const [
20
22
  libraryId,
23
+ category,
24
+ searchQuery,
21
25
  selectedFileIds,
22
26
  setSelectedFileIds,
23
27
  loadMoreKnowledgeItems,
24
28
  fileListHasMore,
25
- isMasonryReady,
29
+ storeIsMasonryReady,
26
30
  sorter,
27
31
  sortType,
32
+ storeIsTransitioning,
28
33
  ] = useResourceManagerStore((s) => [
29
34
  s.libraryId,
35
+ s.category,
36
+ s.searchQuery,
30
37
  s.selectedFileIds,
31
38
  s.setSelectedFileIds,
32
39
  s.loadMoreKnowledgeItems,
@@ -34,6 +41,7 @@ const MasonryView = memo(() => {
34
41
  s.isMasonryReady,
35
42
  s.sorter,
36
43
  s.sortType,
44
+ s.isTransitioning,
37
45
  ]);
38
46
 
39
47
  const { t } = useTranslation('file');
@@ -43,6 +51,33 @@ const MasonryView = memo(() => {
43
51
  // NEW: Read from resource store instead of fetching independently
44
52
  const resourceList = useFileStore((s) => s.resourceList);
45
53
 
54
+ const queryParams = useMemo(
55
+ () => ({
56
+ category: libraryId ? undefined : category,
57
+ libraryId,
58
+ parentId: null,
59
+ q: searchQuery ?? undefined,
60
+ showFilesInKnowledgeBase: false,
61
+ sortType,
62
+ sorter,
63
+ }),
64
+ [category, libraryId, searchQuery, sorter, sortType],
65
+ );
66
+
67
+ const { isLoading, isValidating } = useFetchResources(queryParams);
68
+ const { queryParams: currentQueryParams } = useFileStore();
69
+
70
+ const isNavigating = useMemo(() => {
71
+ if (!currentQueryParams || !queryParams) return false;
72
+
73
+ return (
74
+ currentQueryParams.libraryId !== queryParams.libraryId ||
75
+ currentQueryParams.parentId !== queryParams.parentId ||
76
+ currentQueryParams.category !== queryParams.category ||
77
+ currentQueryParams.q !== queryParams.q
78
+ );
79
+ }, [currentQueryParams, queryParams]);
80
+
46
81
  // Map ResourceItem[] to FileListItem[] for compatibility
47
82
  const rawData = resourceList?.map(
48
83
  (item): FileListItem => ({
@@ -69,7 +104,20 @@ const MasonryView = memo(() => {
69
104
  );
70
105
 
71
106
  // Sort data using current sort settings
72
- const data = sortFileList(rawData, sorter, sortType);
107
+ const data = sortFileList(rawData, sorter, sortType) || [];
108
+
109
+ const dataLength = data.length;
110
+ const effectiveIsLoading = isLoading ?? false;
111
+ const effectiveIsNavigating = isNavigating ?? false;
112
+ const effectiveIsValidating = isValidating ?? false;
113
+ const effectiveIsTransitioning = storeIsTransitioning ?? false;
114
+ const effectiveIsMasonryReady = storeIsMasonryReady;
115
+
116
+ const showSkeleton =
117
+ (effectiveIsLoading && dataLength === 0) ||
118
+ (effectiveIsNavigating && effectiveIsValidating) ||
119
+ effectiveIsTransitioning ||
120
+ !effectiveIsMasonryReady;
73
121
 
74
122
  const masonryContext = useMemo(
75
123
  () => ({
@@ -108,13 +156,15 @@ const MasonryView = memo(() => {
108
156
  [handleLoadMore],
109
157
  );
110
158
 
111
- return (
159
+ return showSkeleton ? (
160
+ <MasonryViewSkeleton columnCount={columnCount} />
161
+ ) : (
112
162
  <div
113
163
  onScroll={handleScroll}
114
164
  style={{
115
165
  flex: 1,
116
166
  height: '100%',
117
- opacity: isMasonryReady ? 1 : 0,
167
+ opacity: effectiveIsMasonryReady ? 1 : 0,
118
168
  overflowY: 'auto',
119
169
  transition: 'opacity 0.2s ease-in-out',
120
170
  }}
@@ -124,7 +174,7 @@ const MasonryView = memo(() => {
124
174
  ItemContent={MasonryItemWrapper}
125
175
  columnCount={columnCount}
126
176
  context={masonryContext}
127
- data={data || []}
177
+ data={data}
128
178
  style={{
129
179
  gap: '16px',
130
180
  overflow: 'hidden',
@@ -147,4 +197,6 @@ const MasonryView = memo(() => {
147
197
  );
148
198
  });
149
199
 
200
+ MasonryView.displayName = 'MasonryView';
201
+
150
202
  export default MasonryView;
@@ -5,7 +5,7 @@ import { memo, useCallback, useEffect, useState } from 'react';
5
5
  import { useTranslation } from 'react-i18next';
6
6
 
7
7
  import FolderTree, { type FolderTreeItem } from '@/features/ResourceManager/components/FolderTree';
8
- import { clearTreeFolderCache } from '@/features/ResourceManager/components/Tree';
8
+ import { clearTreeFolderCache } from '@/features/ResourceManager/components/LibraryHierarchy';
9
9
  import { fileService } from '@/services/file';
10
10
  import { useFileStore } from '@/store/file';
11
11
 
@@ -28,10 +28,7 @@ const MoveToFolderModal = memo<MoveToFolderModalProps>(
28
28
  const [loadedFolders, setLoadedFolders] = useState<Set<string>>(new Set());
29
29
  const [isCreatingFolder, setIsCreatingFolder] = useState(false);
30
30
 
31
- const [moveResource, createFolder] = useFileStore((s) => [
32
- s.moveResource,
33
- s.createFolder,
34
- ]);
31
+ const [moveResource, createFolder] = useFileStore((s) => [s.moveResource, s.createFolder]);
35
32
 
36
33
  // Sort items: folders only
37
34
  const sortItems = useCallback((items: FolderTreeItem[]): FolderTreeItem[] => {
@@ -25,13 +25,12 @@ export type MultiSelectActionType =
25
25
  | 'removeFromKnowledgeBase';
26
26
 
27
27
  interface BatchActionsDropdownProps {
28
- disabled?: boolean;
29
28
  onActionClick: (type: MultiSelectActionType) => Promise<void>;
30
29
  selectCount: number;
31
30
  }
32
31
 
33
32
  const BatchActionsDropdown = memo<BatchActionsDropdownProps>(
34
- ({ selectCount, onActionClick, disabled }) => {
33
+ ({ selectCount, onActionClick }) => {
35
34
  const { t } = useTranslation(['components', 'common', 'file', 'knowledgeBase']);
36
35
  const { modal, message } = App.useApp();
37
36
 
@@ -54,7 +53,7 @@ const BatchActionsDropdown = memo<BatchActionsDropdownProps>(
54
53
  danger: true,
55
54
  icon: <Icon icon={Trash2Icon} />,
56
55
  key: 'deleteLibrary',
57
- label: t('delete', { ns: 'common' }),
56
+ label: t('header.actions.deleteLibrary', { ns: 'file' }),
58
57
  onClick: async () => {
59
58
  modal.confirm({
60
59
  okButtonProps: {
@@ -74,6 +73,7 @@ const BatchActionsDropdown = memo<BatchActionsDropdownProps>(
74
73
  const availableKnowledgeBases = (knowledgeBases || []).filter((kb) => kb.id !== libraryId);
75
74
 
76
75
  const addToKnowledgeBaseSubmenu: DropdownItem[] = availableKnowledgeBases.map((kb) => ({
76
+ disabled: selectCount === 0,
77
77
  icon: <RepoIcon />,
78
78
  key: `add-to-kb-${kb.id}`,
79
79
  label: <span style={{ marginLeft: 8 }}>{kb.name}</span>,
@@ -95,6 +95,7 @@ const BatchActionsDropdown = memo<BatchActionsDropdownProps>(
95
95
 
96
96
  if (libraryId) {
97
97
  items.push({
98
+ disabled: selectCount === 0,
98
99
  icon: <Icon icon={BookMinusIcon} />,
99
100
  key: 'removeFromKnowledgeBase',
100
101
  label: t('FileManager.actions.removeFromKnowledgeBase'),
@@ -117,6 +118,7 @@ const BatchActionsDropdown = memo<BatchActionsDropdownProps>(
117
118
  if (availableKnowledgeBases.length > 0) {
118
119
  items.push({
119
120
  children: addToKnowledgeBaseSubmenu as any,
121
+ disabled: selectCount === 0,
120
122
  icon: <Icon icon={BookPlusIcon} />,
121
123
  key: 'addToOtherKnowledgeBase',
122
124
  label: t('FileManager.actions.addToOtherKnowledgeBase'),
@@ -125,6 +127,7 @@ const BatchActionsDropdown = memo<BatchActionsDropdownProps>(
125
127
  } else if (availableKnowledgeBases.length > 0) {
126
128
  items.push({
127
129
  children: addToKnowledgeBaseSubmenu as any,
130
+ disabled: selectCount === 0,
128
131
  icon: <Icon icon={BookPlusIcon} />,
129
132
  key: 'addToKnowledgeBase',
130
133
  label: t('FileManager.actions.addToKnowledgeBase'),
@@ -133,6 +136,7 @@ const BatchActionsDropdown = memo<BatchActionsDropdownProps>(
133
136
 
134
137
  items.push(
135
138
  {
139
+ disabled: selectCount === 0,
136
140
  icon: <Icon icon={FileBoxIcon} />,
137
141
  key: 'batchChunking',
138
142
  label: t('FileManager.actions.batchChunking'),
@@ -145,6 +149,7 @@ const BatchActionsDropdown = memo<BatchActionsDropdownProps>(
145
149
  },
146
150
  {
147
151
  danger: true,
152
+ disabled: selectCount === 0,
148
153
  icon: <Icon icon={Trash2Icon} />,
149
154
  key: 'delete',
150
155
  label: t('delete', { ns: 'common' }),
@@ -177,9 +182,8 @@ const BatchActionsDropdown = memo<BatchActionsDropdownProps>(
177
182
  ]);
178
183
 
179
184
  return (
180
- <DropdownMenu items={menuItems} placement="bottomLeft" triggerProps={{ disabled }}>
185
+ <DropdownMenu items={menuItems} placement="bottomLeft">
181
186
  <ActionIconWithChevron
182
- disabled={disabled}
183
187
  icon={CircleEllipsisIcon}
184
188
  title={t('FileManager.actions.batchActions', 'Batch actions')}
185
189
  />
@@ -12,11 +12,8 @@ import { useFetchResources, useResourceStore } from '@/store/file/slices/resourc
12
12
  import EmptyPlaceholder from './EmptyPlaceholder';
13
13
  import Header from './Header';
14
14
  import ListView from './ListView';
15
- import ListViewSkeleton from './ListView/Skeleton';
16
15
  import MasonryView from './MasonryView';
17
- import MasonryViewSkeleton from './MasonryView/Skeleton';
18
16
  import { useCheckTaskStatus } from './useCheckTaskStatus';
19
- import { useMasonryColumnCount } from './useMasonryColumnCount';
20
17
  import { useResourceExplorer } from './useResourceExplorer';
21
18
 
22
19
  /**
@@ -32,27 +29,16 @@ const ResourceExplorer = memo(() => {
32
29
  useResourceManagerUrlSync();
33
30
 
34
31
  // Get state from Resource Manager store
35
- const [
36
- libraryId,
37
- category,
38
- viewMode,
39
- isTransitioning,
40
- isMasonryReady,
41
- searchQuery,
42
- setSelectedFileIds,
43
- sorter,
44
- sortType,
45
- ] = useResourceManagerStore((s) => [
46
- s.libraryId,
47
- s.category,
48
- s.viewMode,
49
- s.isTransitioning,
50
- s.isMasonryReady,
51
- s.searchQuery,
52
- s.setSelectedFileIds,
53
- s.sorter,
54
- s.sortType,
55
- ]);
32
+ const [libraryId, category, viewMode, searchQuery, setSelectedFileIds, sorter, sortType] =
33
+ useResourceManagerStore((s) => [
34
+ s.libraryId,
35
+ s.category,
36
+ s.viewMode,
37
+ s.searchQuery,
38
+ s.setSelectedFileIds,
39
+ s.sorter,
40
+ s.sortType,
41
+ ]);
56
42
 
57
43
  // Get folder path for empty state check
58
44
  const { currentFolderSlug } = useFolderPath();
@@ -77,19 +63,7 @@ const ResourceExplorer = memo(() => {
77
63
  const { isLoading, isValidating } = useFetchResources(queryParams);
78
64
 
79
65
  // Get resource data from store (updated by SWR hook)
80
- const { resourceList, queryParams: currentQueryParams } = useResourceStore();
81
-
82
- // Check if we're navigating to a different view (different query params)
83
- const isNavigating = useMemo(() => {
84
- if (!currentQueryParams || !queryParams) return false;
85
-
86
- return (
87
- currentQueryParams.libraryId !== queryParams.libraryId ||
88
- currentQueryParams.parentId !== queryParams.parentId ||
89
- currentQueryParams.category !== queryParams.category ||
90
- currentQueryParams.q !== queryParams.q
91
- );
92
- }, [currentQueryParams, queryParams]);
66
+ const { resourceList } = useResourceStore();
93
67
 
94
68
  // Map ResourceItem[] to FileListItem[] for compatibility
95
69
  // TODO: Eventually update all consumers to use ResourceItem directly
@@ -119,19 +93,6 @@ const ResourceExplorer = memo(() => {
119
93
  setSelectedFileIds([]);
120
94
  }, [category, libraryId, searchQuery, setSelectedFileIds]);
121
95
 
122
- // Computed values
123
- const columnCount = useMasonryColumnCount();
124
-
125
- // Show skeleton when:
126
- // 1. Initial load with no data (isLoading && no data)
127
- // 2. Navigating to different folder/category (isNavigating && isValidating)
128
- // 3. View mode transitions
129
- const showSkeleton =
130
- (isLoading && (!data || data.length >= 5)) ||
131
- (isNavigating && isValidating) ||
132
- (viewMode === 'list' && isTransitioning) ||
133
- (viewMode === 'masonry' && (isTransitioning || !isMasonryReady));
134
-
135
96
  const showEmptyStatus = !isLoading && !isValidating && data?.length === 0 && !currentFolderSlug;
136
97
 
137
98
  return (
@@ -139,12 +100,6 @@ const ResourceExplorer = memo(() => {
139
100
  <Header />
140
101
  {showEmptyStatus ? (
141
102
  <EmptyPlaceholder />
142
- ) : showSkeleton ? (
143
- viewMode === 'list' ? (
144
- <ListViewSkeleton />
145
- ) : (
146
- <MasonryViewSkeleton columnCount={columnCount} />
147
- )
148
103
  ) : viewMode === 'list' ? (
149
104
  <ListView />
150
105
  ) : (
@@ -22,7 +22,6 @@ const AddButton = () => {
22
22
  const { t } = useTranslation('file');
23
23
  const pushDockFileList = useFileStore((s) => s.pushDockFileList);
24
24
  const uploadFolderWithStructure = useFileStore((s) => s.uploadFolderWithStructure);
25
- const createResource = useFileStore((s) => s.createResource);
26
25
  const createResourceAndSync = useFileStore((s) => s.createResourceAndSync);
27
26
 
28
27
  // TODO: Migrate Notion import to use createResource
@@ -39,9 +38,9 @@ const AddButton = () => {
39
38
  ]);
40
39
 
41
40
  const handleOpenPageEditor = useCallback(async () => {
42
- // Create a new page with optimistic update - instant UI feedback
41
+ // Create a new page and wait for server sync - ensures page editor can load the document
43
42
  const untitledTitle = t('pageList.untitled');
44
- const tempId = await createResource({
43
+ const realId = await createResourceAndSync({
45
44
  content: '',
46
45
  fileType: 'custom/document',
47
46
  knowledgeBaseId: libraryId,
@@ -50,10 +49,10 @@ const AddButton = () => {
50
49
  title: untitledTitle,
51
50
  });
52
51
 
53
- // Switch to page view mode immediately (temp ID works)
54
- setCurrentViewItemId(tempId);
52
+ // Switch to page view mode with real ID
53
+ setCurrentViewItemId(realId);
55
54
  setMode('page');
56
- }, [createResource, currentFolderId, libraryId, setCurrentViewItemId, setMode, t]);
55
+ }, [createResourceAndSync, currentFolderId, libraryId, setCurrentViewItemId, setMode, t]);
57
56
 
58
57
  const handleCreateFolder = useCallback(async () => {
59
58
  // Create folder and wait for sync to complete before triggering rename