@lobehub/lobehub 2.0.0-next.249 → 2.0.0-next.250
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/release.yml +4 -0
- package/CHANGELOG.md +25 -0
- package/changelog/v1.json +5 -0
- package/locales/en-US/file.json +2 -0
- package/locales/zh-CN/discover.json +3 -0
- package/locales/zh-CN/file.json +2 -0
- package/package.json +3 -3
- package/packages/types/package.json +2 -2
- package/packages/types/src/discover/mcp.ts +3 -1
- package/src/app/[variants]/(main)/community/(list)/(home)/index.tsx +2 -0
- package/src/app/[variants]/(main)/community/(list)/features/SortButton/index.tsx +4 -0
- package/src/app/[variants]/(main)/community/(list)/mcp/features/Category/index.tsx +7 -3
- package/src/app/[variants]/(main)/community/(list)/mcp/index.tsx +2 -2
- package/src/app/[variants]/(main)/home/_layout/Body/Project/List/Editing.tsx +1 -1
- package/src/app/[variants]/(main)/home/_layout/Body/Project/List/Item.tsx +1 -1
- package/src/app/[variants]/(main)/home/_layout/Body/Project/List/index.tsx +1 -1
- package/src/app/[variants]/(main)/home/_layout/Body/Project/List/useDropdownMenu.tsx +1 -1
- package/src/app/[variants]/(main)/resource/(home)/_layout/Body/LibraryList/{List/Item → Item}/Editing.tsx +1 -1
- package/src/app/[variants]/(main)/resource/(home)/_layout/Body/LibraryList/{List/Item → Item}/index.tsx +1 -1
- package/src/app/[variants]/(main)/resource/(home)/_layout/Body/LibraryList/{List/Item → Item}/useDropdownMenu.tsx +1 -1
- package/src/app/[variants]/(main)/resource/(home)/_layout/Body/LibraryList/index.tsx +16 -6
- package/src/app/[variants]/(main)/resource/(home)/_layout/Body/{KnowledgeBase.tsx → index.tsx} +2 -3
- package/src/app/[variants]/(main)/resource/(home)/_layout/Sidebar.tsx +2 -2
- package/src/app/[variants]/(main)/resource/(home)/index.tsx +23 -10
- package/src/app/[variants]/(main)/resource/features/DndContextWrapper.tsx +12 -37
- package/src/app/[variants]/(main)/resource/features/hooks/useKnowledgeItem.ts +1 -1
- package/src/app/[variants]/(main)/resource/features/store/action.ts +9 -39
- package/src/app/[variants]/(main)/resource/library/_layout/Header/LibraryHead.tsx +1 -1
- package/src/app/[variants]/(main)/resource/library/index.tsx +13 -6
- package/src/features/LibraryModal/AddFilesToKnowledgeBase/SelectForm.tsx +1 -1
- package/src/features/LibraryModal/CreateNew/CreateForm.tsx +1 -1
- package/src/features/PageEditor/Header/Breadcrumb.tsx +1 -1
- package/src/features/PageEditor/store/action.ts +5 -2
- package/src/features/PageExplorer/PageExplorerPlaceholder.tsx +5 -7
- package/src/features/ResourceManager/components/Explorer/Header/Breadcrumb.tsx +1 -1
- package/src/features/ResourceManager/components/Explorer/ItemDropdown/useFileItemDropdown.tsx +57 -26
- package/src/features/ResourceManager/components/Explorer/ListView/ListItem/index.tsx +35 -6
- package/src/features/ResourceManager/components/Explorer/ListView/Skeleton.tsx +20 -14
- package/src/features/ResourceManager/components/Explorer/ListView/index.tsx +41 -31
- package/src/features/ResourceManager/components/Explorer/MasonryView/MasonryFileItem/index.tsx +1 -1
- package/src/features/ResourceManager/components/Explorer/MasonryView/Skeleton.tsx +6 -2
- package/src/features/ResourceManager/components/Explorer/MasonryView/index.tsx +29 -18
- package/src/features/ResourceManager/components/Explorer/MoveToFolderModal.tsx +7 -34
- package/src/features/ResourceManager/components/Explorer/ToolBar/BatchActionsDropdown.tsx +1 -1
- package/src/features/ResourceManager/components/Explorer/index.tsx +58 -18
- package/src/features/ResourceManager/components/Explorer/useCheckTaskStatus.ts +6 -4
- package/src/features/ResourceManager/components/Header/AddButton.tsx +58 -35
- package/src/features/ResourceManager/components/Header/hooks/useNotionImport.ts +5 -5
- package/src/features/ResourceManager/components/Tree/TreeSkeleton.tsx +19 -9
- package/src/features/ResourceManager/components/Tree/index.tsx +110 -5
- package/src/features/ResourceManager/components/UploadDock/index.tsx +2 -1
- package/src/features/ResourceManager/constants.ts +3 -0
- package/src/hooks/useMCPCategory.tsx +7 -0
- package/src/locales/default/discover.ts +3 -0
- package/src/locales/default/file.ts +2 -0
- package/src/services/file/index.ts +34 -1
- package/src/services/resource/index.ts +249 -0
- package/src/store/discover/slices/mcp/action.ts +1 -1
- package/src/store/file/slices/chat/action.ts +2 -1
- package/src/store/file/slices/document/action.ts +10 -7
- package/src/store/file/slices/fileManager/action.ts +14 -4
- package/src/store/file/slices/fileManager/initialState.ts +2 -0
- package/src/store/file/slices/resource/action.ts +432 -0
- package/src/store/file/slices/resource/hooks.ts +82 -0
- package/src/store/file/slices/resource/initialState.ts +67 -0
- package/src/store/file/slices/resource/syncEngine.ts +326 -0
- package/src/store/file/store.ts +6 -1
- package/src/store/{knowledgeBase → library}/initialState.ts +2 -2
- package/src/store/{knowledgeBase → library}/slices/content/action.test.ts +37 -51
- package/src/store/{knowledgeBase → library}/slices/content/action.ts +8 -4
- package/src/store/{knowledgeBase → library}/slices/crud/action.ts +1 -1
- package/src/store/{knowledgeBase → library}/slices/crud/selectors.ts +1 -1
- package/src/store/{knowledgeBase → library}/slices/ragEval/actions/dataset.ts +1 -1
- package/src/store/{knowledgeBase → library}/slices/ragEval/actions/evaluation.ts +1 -1
- package/src/store/{knowledgeBase → library}/slices/ragEval/actions/index.ts +1 -1
- package/src/types/resource.ts +133 -0
- package/src/app/[variants]/(main)/resource/(home)/_layout/Body/LibraryList/List/index.tsx +0 -25
- /package/src/app/[variants]/(main)/resource/(home)/_layout/Body/LibraryList/{List/Item → Item}/Actions.tsx +0 -0
- /package/src/store/{knowledgeBase → library}/index.ts +0 -0
- /package/src/store/{knowledgeBase → library}/selectors.ts +0 -0
- /package/src/store/{knowledgeBase → library}/slices/content/index.ts +0 -0
- /package/src/store/{knowledgeBase → library}/slices/crud/action.test.ts +0 -0
- /package/src/store/{knowledgeBase → library}/slices/crud/index.ts +0 -0
- /package/src/store/{knowledgeBase → library}/slices/crud/initialState.ts +0 -0
- /package/src/store/{knowledgeBase → library}/slices/ragEval/index.ts +0 -0
- /package/src/store/{knowledgeBase → library}/slices/ragEval/initialState.ts +0 -0
- /package/src/store/{knowledgeBase → library}/store.ts +0 -0
|
@@ -34,8 +34,11 @@ const MasonryViewSkeleton = memo<MasonrySkeletonProps>(({ columnCount }) => {
|
|
|
34
34
|
// Generate varying heights for more natural masonry look
|
|
35
35
|
const heights = [180, 220, 200, 190, 240, 210, 200, 230, 180, 220, 210, 190];
|
|
36
36
|
|
|
37
|
-
// Calculate number of items based on viewport and column count
|
|
38
|
-
const itemCount = Math.min(columnCount * 3, 12);
|
|
37
|
+
// Calculate number of items based on viewport and column count, minimum 6 items
|
|
38
|
+
const itemCount = Math.max(Math.min(columnCount * 3, 12), 6);
|
|
39
|
+
|
|
40
|
+
// Calculate opacity gradient from 100% to 20%
|
|
41
|
+
const getOpacity = (index: number) => 1 - (index / (itemCount - 1)) * 0.8;
|
|
39
42
|
|
|
40
43
|
return (
|
|
41
44
|
<div
|
|
@@ -50,6 +53,7 @@ const MasonryViewSkeleton = memo<MasonrySkeletonProps>(({ columnCount }) => {
|
|
|
50
53
|
key={index}
|
|
51
54
|
style={{
|
|
52
55
|
height: heights[index % heights.length],
|
|
56
|
+
opacity: getOpacity(index),
|
|
53
57
|
}}
|
|
54
58
|
/>
|
|
55
59
|
))}
|
|
@@ -6,12 +6,10 @@ import { cssVar } from 'antd-style';
|
|
|
6
6
|
import { type UIEvent, memo, useCallback, useMemo, useState } from 'react';
|
|
7
7
|
import { useTranslation } from 'react-i18next';
|
|
8
8
|
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
useResourceManagerFetchKnowledgeItems,
|
|
12
|
-
useResourceManagerStore,
|
|
13
|
-
} from '@/app/[variants]/(main)/resource/features/store';
|
|
9
|
+
import { useResourceManagerStore } from '@/app/[variants]/(main)/resource/features/store';
|
|
14
10
|
import { sortFileList } from '@/app/[variants]/(main)/resource/features/store/selectors';
|
|
11
|
+
import { useFileStore } from '@/store/file';
|
|
12
|
+
import { type FileListItem } from '@/types/files';
|
|
15
13
|
|
|
16
14
|
import { useMasonryColumnCount } from '../useMasonryColumnCount';
|
|
17
15
|
import MasonryItemWrapper from './MasonryFileItem/MasonryItemWrapper';
|
|
@@ -20,8 +18,6 @@ const MasonryView = memo(() => {
|
|
|
20
18
|
// Access all state from Resource Manager store
|
|
21
19
|
const [
|
|
22
20
|
libraryId,
|
|
23
|
-
category,
|
|
24
|
-
searchQuery,
|
|
25
21
|
selectedFileIds,
|
|
26
22
|
setSelectedFileIds,
|
|
27
23
|
loadMoreKnowledgeItems,
|
|
@@ -31,8 +27,6 @@ const MasonryView = memo(() => {
|
|
|
31
27
|
sortType,
|
|
32
28
|
] = useResourceManagerStore((s) => [
|
|
33
29
|
s.libraryId,
|
|
34
|
-
s.category,
|
|
35
|
-
s.searchQuery,
|
|
36
30
|
s.selectedFileIds,
|
|
37
31
|
s.setSelectedFileIds,
|
|
38
32
|
s.loadMoreKnowledgeItems,
|
|
@@ -46,16 +40,33 @@ const MasonryView = memo(() => {
|
|
|
46
40
|
const columnCount = useMasonryColumnCount();
|
|
47
41
|
const [isLoadingMore, setIsLoadingMore] = useState(false);
|
|
48
42
|
|
|
49
|
-
|
|
43
|
+
// NEW: Read from resource store instead of fetching independently
|
|
44
|
+
const resourceList = useFileStore((s) => s.resourceList);
|
|
50
45
|
|
|
51
|
-
//
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
46
|
+
// Map ResourceItem[] to FileListItem[] for compatibility
|
|
47
|
+
const rawData = resourceList?.map(
|
|
48
|
+
(item): FileListItem => ({
|
|
49
|
+
chunkCount: item.chunkCount ?? null,
|
|
50
|
+
chunkingError: item.chunkingError ?? null,
|
|
51
|
+
chunkingStatus: (item.chunkingStatus as any) ?? null,
|
|
52
|
+
content: item.content,
|
|
53
|
+
createdAt: item.createdAt,
|
|
54
|
+
editorData: item.editorData,
|
|
55
|
+
embeddingError: item.embeddingError ?? null,
|
|
56
|
+
embeddingStatus: (item.embeddingStatus as any) ?? null,
|
|
57
|
+
fileType: item.fileType,
|
|
58
|
+
finishEmbedding: item.finishEmbedding ?? false,
|
|
59
|
+
id: item.id,
|
|
60
|
+
metadata: item.metadata,
|
|
61
|
+
name: item.name,
|
|
62
|
+
parentId: item.parentId,
|
|
63
|
+
size: item.size,
|
|
64
|
+
slug: item.slug,
|
|
65
|
+
sourceType: item.sourceType,
|
|
66
|
+
updatedAt: item.updatedAt,
|
|
67
|
+
url: item.url ?? '',
|
|
68
|
+
}),
|
|
69
|
+
);
|
|
59
70
|
|
|
60
71
|
// Sort data using current sort settings
|
|
61
72
|
const data = sortFileList(rawData, sorter, sortType);
|
|
@@ -11,14 +11,13 @@ import { useFileStore } from '@/store/file';
|
|
|
11
11
|
|
|
12
12
|
interface MoveToFolderModalProps {
|
|
13
13
|
fileId: string;
|
|
14
|
-
fileType?: string;
|
|
15
14
|
knowledgeBaseId?: string;
|
|
16
15
|
onClose: () => void;
|
|
17
16
|
open: boolean;
|
|
18
17
|
}
|
|
19
18
|
|
|
20
19
|
const MoveToFolderModal = memo<MoveToFolderModalProps>(
|
|
21
|
-
({ open, onClose, fileId,
|
|
20
|
+
({ open, onClose, fileId, knowledgeBaseId }) => {
|
|
22
21
|
const { t } = useTranslation('components');
|
|
23
22
|
const { message } = App.useApp();
|
|
24
23
|
|
|
@@ -29,11 +28,9 @@ const MoveToFolderModal = memo<MoveToFolderModalProps>(
|
|
|
29
28
|
const [loadedFolders, setLoadedFolders] = useState<Set<string>>(new Set());
|
|
30
29
|
const [isCreatingFolder, setIsCreatingFolder] = useState(false);
|
|
31
30
|
|
|
32
|
-
const [
|
|
33
|
-
s.
|
|
34
|
-
s.refreshFileList,
|
|
31
|
+
const [moveResource, createFolder] = useFileStore((s) => [
|
|
32
|
+
s.moveResource,
|
|
35
33
|
s.createFolder,
|
|
36
|
-
s.updateDocument,
|
|
37
34
|
]);
|
|
38
35
|
|
|
39
36
|
// Sort items: folders only
|
|
@@ -226,20 +223,8 @@ const MoveToFolderModal = memo<MoveToFolderModalProps>(
|
|
|
226
223
|
|
|
227
224
|
const handleMove = async () => {
|
|
228
225
|
try {
|
|
229
|
-
//
|
|
230
|
-
|
|
231
|
-
fileType === 'custom/document' || fileType === 'custom/folder';
|
|
232
|
-
|
|
233
|
-
if (isDocument) {
|
|
234
|
-
// Use updateDocument for Pages and folders
|
|
235
|
-
await updateDocument(fileId, { parentId: selectedFolderId });
|
|
236
|
-
} else {
|
|
237
|
-
// Use moveFileToFolder for regular files
|
|
238
|
-
await moveFileToFolder(fileId, selectedFolderId);
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
// Refresh file list to invalidate SWR cache for both Explorer and Tree
|
|
242
|
-
await refreshFileList();
|
|
226
|
+
// Use optimistic moveResource for instant UI update
|
|
227
|
+
await moveResource(fileId, selectedFolderId);
|
|
243
228
|
|
|
244
229
|
// Clear and reload all expanded folders in Tree's module-level cache
|
|
245
230
|
if (knowledgeBaseId) {
|
|
@@ -256,20 +241,8 @@ const MoveToFolderModal = memo<MoveToFolderModalProps>(
|
|
|
256
241
|
|
|
257
242
|
const handleMoveToRoot = async () => {
|
|
258
243
|
try {
|
|
259
|
-
//
|
|
260
|
-
|
|
261
|
-
fileType === 'custom/document' || fileType === 'custom/folder';
|
|
262
|
-
|
|
263
|
-
if (isDocument) {
|
|
264
|
-
// Use updateDocument for Pages and folders
|
|
265
|
-
await updateDocument(fileId, { parentId: null });
|
|
266
|
-
} else {
|
|
267
|
-
// Use moveFileToFolder for regular files
|
|
268
|
-
await moveFileToFolder(fileId, null);
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
// Refresh file list to invalidate SWR cache for both Explorer and Tree
|
|
272
|
-
await refreshFileList();
|
|
244
|
+
// Use optimistic moveResource for instant UI update
|
|
245
|
+
await moveResource(fileId, null);
|
|
273
246
|
|
|
274
247
|
// Clear and reload all expanded folders in Tree's module-level cache
|
|
275
248
|
if (knowledgeBaseId) {
|
|
@@ -12,7 +12,7 @@ import { useTranslation } from 'react-i18next';
|
|
|
12
12
|
|
|
13
13
|
import { useResourceManagerStore } from '@/app/[variants]/(main)/resource/features/store';
|
|
14
14
|
import RepoIcon from '@/components/LibIcon';
|
|
15
|
-
import { useKnowledgeBaseStore } from '@/store/
|
|
15
|
+
import { useKnowledgeBaseStore } from '@/store/library';
|
|
16
16
|
|
|
17
17
|
import ActionIconWithChevron from './ActionIconWithChevron';
|
|
18
18
|
|
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { Flexbox } from '@lobehub/ui';
|
|
4
|
-
import { memo, useEffect } from 'react';
|
|
4
|
+
import { memo, useEffect, useMemo } from 'react';
|
|
5
5
|
|
|
6
6
|
import { useFolderPath } from '@/app/[variants]/(main)/resource/features/hooks/useFolderPath';
|
|
7
7
|
import { useResourceManagerUrlSync } from '@/app/[variants]/(main)/resource/features/hooks/useResourceManagerUrlSync';
|
|
8
|
-
import {
|
|
9
|
-
useResourceManagerFetchKnowledgeItems,
|
|
10
|
-
useResourceManagerStore,
|
|
11
|
-
} from '@/app/[variants]/(main)/resource/features/store';
|
|
8
|
+
import { useResourceManagerStore } from '@/app/[variants]/(main)/resource/features/store';
|
|
12
9
|
import { sortFileList } from '@/app/[variants]/(main)/resource/features/store/selectors';
|
|
10
|
+
import { useFetchResources, useResourceStore } from '@/store/file/slices/resource/hooks';
|
|
13
11
|
|
|
14
12
|
import EmptyPlaceholder from './EmptyPlaceholder';
|
|
15
13
|
import Header from './Header';
|
|
@@ -59,14 +57,53 @@ const ResourceExplorer = memo(() => {
|
|
|
59
57
|
// Get folder path for empty state check
|
|
60
58
|
const { currentFolderSlug } = useFolderPath();
|
|
61
59
|
|
|
62
|
-
//
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
60
|
+
// Build query params for SWR
|
|
61
|
+
const queryParams = useMemo(
|
|
62
|
+
() => ({
|
|
63
|
+
// Only use category filter when NOT in a specific library
|
|
64
|
+
// When viewing a library, show all items regardless of category
|
|
65
|
+
category: libraryId ? undefined : category,
|
|
66
|
+
libraryId,
|
|
67
|
+
parentId: currentFolderSlug || null,
|
|
68
|
+
q: searchQuery ?? undefined,
|
|
69
|
+
showFilesInKnowledgeBase: false,
|
|
70
|
+
sortType,
|
|
71
|
+
sorter,
|
|
72
|
+
}),
|
|
73
|
+
[category, libraryId, currentFolderSlug, searchQuery, sortType, sorter],
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
// Use SWR for data fetching with automatic caching and revalidation
|
|
77
|
+
const { isLoading, isValidating } = useFetchResources(queryParams);
|
|
78
|
+
|
|
79
|
+
// 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]);
|
|
93
|
+
|
|
94
|
+
// Map ResourceItem[] to FileListItem[] for compatibility
|
|
95
|
+
// TODO: Eventually update all consumers to use ResourceItem directly
|
|
96
|
+
const rawData = resourceList?.map((item) => ({
|
|
97
|
+
...item,
|
|
98
|
+
// Ensure all FileListItem fields are present with proper types
|
|
99
|
+
chunkCount: item.chunkCount ?? null,
|
|
100
|
+
chunkingError: item.chunkingError ?? null,
|
|
101
|
+
chunkingStatus: (item.chunkingStatus ?? null) as any,
|
|
102
|
+
embeddingError: item.embeddingError ?? null,
|
|
103
|
+
embeddingStatus: (item.embeddingStatus ?? null) as any,
|
|
104
|
+
finishEmbedding: item.finishEmbedding ?? false,
|
|
105
|
+
url: item.url ?? '',
|
|
106
|
+
}));
|
|
70
107
|
|
|
71
108
|
// Sort data using current sort settings
|
|
72
109
|
const data = sortFileList(rawData, sorter, sortType);
|
|
@@ -83,17 +120,20 @@ const ResourceExplorer = memo(() => {
|
|
|
83
120
|
}, [category, libraryId, searchQuery, setSelectedFileIds]);
|
|
84
121
|
|
|
85
122
|
// Computed values
|
|
86
|
-
const showEmptyStatus = !isLoading && data?.length === 0 && !currentFolderSlug;
|
|
87
|
-
|
|
88
123
|
const columnCount = useMasonryColumnCount();
|
|
89
124
|
|
|
90
|
-
//
|
|
91
|
-
//
|
|
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
|
|
92
129
|
const showSkeleton =
|
|
93
|
-
(isLoading && !data
|
|
130
|
+
(isLoading && (!data || data.length >= 5)) ||
|
|
131
|
+
(isNavigating && isValidating) ||
|
|
94
132
|
(viewMode === 'list' && isTransitioning) ||
|
|
95
133
|
(viewMode === 'masonry' && (isTransitioning || !isMasonryReady));
|
|
96
134
|
|
|
135
|
+
const showEmptyStatus = !isLoading && !isValidating && data?.length === 0 && !currentFolderSlug;
|
|
136
|
+
|
|
97
137
|
return (
|
|
98
138
|
<Flexbox height={'100%'}>
|
|
99
139
|
<Header />
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { useEffect } from 'react';
|
|
2
2
|
|
|
3
|
-
import { useFileStore } from '@/store/file';
|
|
4
3
|
import { AsyncTaskStatus } from '@/types/asyncTask';
|
|
4
|
+
import { revalidateResources } from '@/store/file/slices/resource/hooks';
|
|
5
5
|
import { type FileListItem } from '@/types/files';
|
|
6
6
|
|
|
7
7
|
export const useCheckTaskStatus = (data: FileListItem[] | undefined) => {
|
|
8
|
-
const [refreshFileList] = useFileStore((s) => [s.refreshFileList]);
|
|
9
8
|
const hasProcessingChunkTask = data?.some(
|
|
10
9
|
(item) => item.chunkingStatus === AsyncTaskStatus.Processing,
|
|
11
10
|
);
|
|
@@ -15,11 +14,14 @@ export const useCheckTaskStatus = (data: FileListItem[] | undefined) => {
|
|
|
15
14
|
|
|
16
15
|
const isProcessing = hasProcessingChunkTask || hasProcessingEmbeddingTask;
|
|
17
16
|
|
|
18
|
-
// every
|
|
17
|
+
// Poll every 5s to check if chunking/embedding status has changed
|
|
19
18
|
useEffect(() => {
|
|
20
19
|
if (!isProcessing) return;
|
|
21
20
|
|
|
22
|
-
const interval = setInterval(
|
|
21
|
+
const interval = setInterval(() => {
|
|
22
|
+
// Re-fetch with the same query params used for initial load
|
|
23
|
+
revalidateResources();
|
|
24
|
+
}, 5000);
|
|
23
25
|
return () => {
|
|
24
26
|
clearInterval(interval);
|
|
25
27
|
};
|
|
@@ -9,11 +9,11 @@ import { useCallback, useMemo } from 'react';
|
|
|
9
9
|
import { useTranslation } from 'react-i18next';
|
|
10
10
|
|
|
11
11
|
import { useResourceManagerStore } from '@/app/[variants]/(main)/resource/features/store';
|
|
12
|
+
import { message } from '@/components/AntdStaticMethods';
|
|
12
13
|
import DragUpload from '@/components/DragUpload';
|
|
13
14
|
import GuideModal from '@/components/GuideModal';
|
|
14
15
|
import GuideVideo from '@/components/GuideVideo';
|
|
15
16
|
import { useFileStore } from '@/store/file';
|
|
16
|
-
import { DocumentSourceType } from '@/types/document';
|
|
17
17
|
|
|
18
18
|
import useNotionImport from './hooks/useNotionImport';
|
|
19
19
|
import useUploadFolder from './hooks/useUploadFolder';
|
|
@@ -22,9 +22,12 @@ 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
|
|
25
|
+
const createResource = useFileStore((s) => s.createResource);
|
|
26
|
+
const createResourceAndSync = useFileStore((s) => s.createResourceAndSync);
|
|
27
|
+
|
|
28
|
+
// TODO: Migrate Notion import to use createResource
|
|
29
|
+
// Keep old functions temporarily for components not yet migrated
|
|
26
30
|
const createDocument = useFileStore((s) => s.createDocument);
|
|
27
|
-
const refreshFileList = useFileStore((s) => s.refreshFileList);
|
|
28
31
|
|
|
29
32
|
const [libraryId, currentFolderId, setCurrentViewItemId, setMode, setPendingRenameItemId] =
|
|
30
33
|
useResourceManagerStore((s) => [
|
|
@@ -36,47 +39,64 @@ const AddButton = () => {
|
|
|
36
39
|
]);
|
|
37
40
|
|
|
38
41
|
const handleOpenPageEditor = useCallback(async () => {
|
|
39
|
-
// Create a new page
|
|
42
|
+
// Create a new page with optimistic update - instant UI feedback
|
|
40
43
|
const untitledTitle = t('pageList.untitled');
|
|
41
|
-
const
|
|
44
|
+
const tempId = await createResource({
|
|
42
45
|
content: '',
|
|
46
|
+
fileType: 'custom/document',
|
|
43
47
|
knowledgeBaseId: libraryId,
|
|
44
48
|
parentId: currentFolderId ?? undefined,
|
|
49
|
+
sourceType: 'document',
|
|
45
50
|
title: untitledTitle,
|
|
46
51
|
});
|
|
47
52
|
|
|
48
|
-
//
|
|
49
|
-
|
|
50
|
-
newDocumentMap.set(newPage.id, {
|
|
51
|
-
content: newPage.content || '',
|
|
52
|
-
createdAt: newPage.createdAt ? new Date(newPage.createdAt) : new Date(),
|
|
53
|
-
editorData:
|
|
54
|
-
typeof newPage.editorData === 'string'
|
|
55
|
-
? JSON.parse(newPage.editorData)
|
|
56
|
-
: newPage.editorData || null,
|
|
57
|
-
fileType: 'custom/document',
|
|
58
|
-
filename: newPage.title || untitledTitle,
|
|
59
|
-
id: newPage.id,
|
|
60
|
-
metadata: newPage.metadata || {},
|
|
61
|
-
source: 'document',
|
|
62
|
-
sourceType: DocumentSourceType.EDITOR,
|
|
63
|
-
title: newPage.title || untitledTitle,
|
|
64
|
-
totalCharCount: newPage.content?.length || 0,
|
|
65
|
-
totalLineCount: 0,
|
|
66
|
-
updatedAt: newPage.updatedAt ? new Date(newPage.updatedAt) : new Date(),
|
|
67
|
-
});
|
|
68
|
-
useFileStore.setState({ localDocumentMap: newDocumentMap });
|
|
69
|
-
|
|
70
|
-
// Switch to page view mode
|
|
71
|
-
setCurrentViewItemId(newPage.id);
|
|
53
|
+
// Switch to page view mode immediately (temp ID works)
|
|
54
|
+
setCurrentViewItemId(tempId);
|
|
72
55
|
setMode('page');
|
|
73
|
-
}, [
|
|
56
|
+
}, [createResource, currentFolderId, libraryId, setCurrentViewItemId, setMode, t]);
|
|
74
57
|
|
|
75
58
|
const handleCreateFolder = useCallback(async () => {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
59
|
+
// Create folder and wait for sync to complete before triggering rename
|
|
60
|
+
try {
|
|
61
|
+
// Get current resource list to check for duplicate folder names
|
|
62
|
+
const resourceList = useFileStore.getState().resourceList || [];
|
|
63
|
+
|
|
64
|
+
// Filter for folders at the same level
|
|
65
|
+
const foldersAtSameLevel = resourceList.filter(
|
|
66
|
+
(item) =>
|
|
67
|
+
item.fileType === 'custom/folder' &&
|
|
68
|
+
(item.parentId ?? null) === (currentFolderId ?? null),
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
// Generate unique folder name
|
|
72
|
+
const baseName = 'Untitled';
|
|
73
|
+
const existingNames = new Set(foldersAtSameLevel.map((folder) => folder.name));
|
|
74
|
+
|
|
75
|
+
let uniqueName = baseName;
|
|
76
|
+
let counter = 1;
|
|
77
|
+
|
|
78
|
+
while (existingNames.has(uniqueName)) {
|
|
79
|
+
uniqueName = `${baseName} ${counter}`;
|
|
80
|
+
counter++;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Wait for sync to complete to get the real ID
|
|
84
|
+
const realId = await createResourceAndSync({
|
|
85
|
+
content: '',
|
|
86
|
+
fileType: 'custom/folder',
|
|
87
|
+
knowledgeBaseId: libraryId,
|
|
88
|
+
parentId: currentFolderId ?? undefined,
|
|
89
|
+
sourceType: 'document',
|
|
90
|
+
title: uniqueName,
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// Trigger auto-rename with the real ID (after sync completes)
|
|
94
|
+
setPendingRenameItemId(realId);
|
|
95
|
+
} catch (error) {
|
|
96
|
+
message.error(t('header.actions.createFolderError'));
|
|
97
|
+
console.error('Failed to create folder:', error);
|
|
98
|
+
}
|
|
99
|
+
}, [createResourceAndSync, currentFolderId, libraryId, setPendingRenameItemId, t]);
|
|
80
100
|
|
|
81
101
|
const {
|
|
82
102
|
handleCloseNotionGuide,
|
|
@@ -89,7 +109,10 @@ const AddButton = () => {
|
|
|
89
109
|
createDocument,
|
|
90
110
|
currentFolderId,
|
|
91
111
|
libraryId,
|
|
92
|
-
|
|
112
|
+
refetchResources: async () => {
|
|
113
|
+
const { revalidateResources } = await import('@/store/file/slices/resource/hooks');
|
|
114
|
+
await revalidateResources();
|
|
115
|
+
},
|
|
93
116
|
t,
|
|
94
117
|
});
|
|
95
118
|
|
|
@@ -11,7 +11,7 @@ interface UseNotionImportOptions {
|
|
|
11
11
|
createDocument: DocumentAction['createDocument'];
|
|
12
12
|
currentFolderId?: string | null;
|
|
13
13
|
libraryId?: string | null;
|
|
14
|
-
|
|
14
|
+
refetchResources: () => Promise<void>;
|
|
15
15
|
t: TFunction<'file'>;
|
|
16
16
|
}
|
|
17
17
|
|
|
@@ -19,7 +19,7 @@ const useNotionImport = ({
|
|
|
19
19
|
createDocument,
|
|
20
20
|
currentFolderId,
|
|
21
21
|
libraryId,
|
|
22
|
-
|
|
22
|
+
refetchResources,
|
|
23
23
|
t,
|
|
24
24
|
}: UseNotionImportOptions) => {
|
|
25
25
|
const notionInputRef = useRef<HTMLInputElement>(null);
|
|
@@ -171,8 +171,8 @@ const useNotionImport = ({
|
|
|
171
171
|
);
|
|
172
172
|
}
|
|
173
173
|
|
|
174
|
-
//
|
|
175
|
-
await
|
|
174
|
+
// Refetch resources to show imported documents
|
|
175
|
+
await refetchResources();
|
|
176
176
|
} catch (error) {
|
|
177
177
|
console.error('Failed to import Notion export:', error);
|
|
178
178
|
const { message } = await import('antd');
|
|
@@ -182,7 +182,7 @@ const useNotionImport = ({
|
|
|
182
182
|
// Reset input to allow re-uploading
|
|
183
183
|
event.target.value = '';
|
|
184
184
|
},
|
|
185
|
-
[createDocument, currentFolderId, libraryId,
|
|
185
|
+
[createDocument, currentFolderId, libraryId, refetchResources, t],
|
|
186
186
|
);
|
|
187
187
|
|
|
188
188
|
return {
|
|
@@ -17,9 +17,13 @@ const styles = createStaticStyles(({ css, cssVar }) => ({
|
|
|
17
17
|
`,
|
|
18
18
|
}));
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
interface TreeSkeletonItemProps {
|
|
21
|
+
opacity?: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const TreeSkeletonItem = memo<TreeSkeletonItemProps>(({ opacity = 1 }) => {
|
|
21
25
|
return (
|
|
22
|
-
<Flexbox className={styles.container} horizontal>
|
|
26
|
+
<Flexbox className={styles.container} horizontal style={{ opacity }}>
|
|
23
27
|
<Skeleton.Button
|
|
24
28
|
active
|
|
25
29
|
size={'small'}
|
|
@@ -43,13 +47,19 @@ const TreeSkeletonItem = memo(() => {
|
|
|
43
47
|
|
|
44
48
|
TreeSkeletonItem.displayName = 'TreeSkeletonItem';
|
|
45
49
|
|
|
46
|
-
const TreeSkeleton = memo(() =>
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
const TreeSkeleton = memo(() => {
|
|
51
|
+
const count = 6;
|
|
52
|
+
// Calculate opacity gradient from 100% to 20%
|
|
53
|
+
const getOpacity = (index: number) => 1 - (index / (count - 1)) * 0.8;
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<Flexbox gap={2}>
|
|
57
|
+
{Array.from({ length: count }).map((_, i) => (
|
|
58
|
+
<TreeSkeletonItem key={i} opacity={getOpacity(i)} />
|
|
59
|
+
))}
|
|
60
|
+
</Flexbox>
|
|
61
|
+
);
|
|
62
|
+
});
|
|
53
63
|
|
|
54
64
|
TreeSkeleton.displayName = 'TreeSkeleton';
|
|
55
65
|
|