@lobehub/lobehub 2.0.0-next.266 → 2.0.0-next.268
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/.cursor/rules/microcopy-cn.mdc +75 -63
- package/.cursor/rules/microcopy-en.mdc +4 -8
- package/CHANGELOG.md +50 -0
- package/README.md +8 -8
- package/README.zh-CN.md +8 -8
- package/apps/desktop/src/main/locales/default/common.ts +2 -2
- package/changelog/v1.json +10 -0
- package/docs/development/database-schema.dbml +4 -0
- package/e2e/CLAUDE.md +43 -81
- package/e2e/cucumber.config.js +1 -0
- package/e2e/docs/local-setup.md +67 -219
- package/e2e/scripts/setup.ts +529 -0
- package/e2e/src/features/home/sidebarAgent.feature +62 -0
- package/e2e/src/features/home/sidebarGroup.feature +62 -0
- package/e2e/src/features/page/README.md +118 -0
- package/e2e/src/features/page/crud.feature +62 -0
- package/e2e/src/features/page/editor-content.feature +93 -0
- package/e2e/src/features/page/editor-meta.feature +60 -0
- package/e2e/src/steps/agent/conversation.steps.ts +4 -4
- package/e2e/src/steps/home/sidebarAgent.steps.ts +370 -0
- package/e2e/src/steps/home/sidebarGroup.steps.ts +168 -0
- package/e2e/src/steps/hooks.ts +4 -0
- package/e2e/src/steps/page/editor-content.steps.ts +344 -0
- package/e2e/src/steps/page/editor-meta.steps.ts +410 -0
- package/e2e/src/steps/page/page-crud.steps.ts +363 -0
- package/e2e/src/support/world.ts +12 -0
- package/locales/ar/file.json +2 -0
- package/locales/bg-BG/file.json +2 -0
- package/locales/de-DE/file.json +2 -0
- package/locales/en-US/auth.json +1 -1
- package/locales/en-US/file.json +2 -0
- package/locales/en-US/metadata.json +2 -2
- package/locales/es-ES/file.json +2 -0
- package/locales/fa-IR/file.json +2 -0
- package/locales/fr-FR/file.json +2 -0
- package/locales/it-IT/file.json +2 -0
- package/locales/ja-JP/file.json +2 -0
- package/locales/ko-KR/file.json +2 -0
- package/locales/nl-NL/file.json +2 -0
- package/locales/pl-PL/file.json +2 -0
- package/locales/pt-BR/file.json +2 -0
- package/locales/ru-RU/file.json +2 -0
- package/locales/tr-TR/file.json +2 -0
- package/locales/vi-VN/file.json +2 -0
- package/locales/zh-CN/file.json +2 -0
- package/locales/zh-TW/file.json +2 -0
- package/package.json +3 -3
- package/packages/builtin-agents/src/agents/agent-builder/index.ts +1 -1
- package/packages/builtin-agents/src/agents/group-agent-builder/index.ts +1 -1
- package/packages/builtin-agents/src/agents/page-agent/index.ts +1 -1
- package/packages/const/src/settings/group.ts +0 -10
- package/packages/database/migrations/0068_update_group_data.sql +4 -0
- package/packages/database/migrations/meta/0068_snapshot.json +9588 -0
- package/packages/database/migrations/meta/_journal.json +7 -0
- package/packages/database/src/models/__tests__/chatGroup.test.ts +5 -7
- package/packages/database/src/models/__tests__/knowledgeBase.test.ts +185 -0
- package/packages/database/src/models/knowledgeBase.ts +67 -3
- package/packages/database/src/repositories/agentGroup/index.test.ts +23 -29
- package/packages/database/src/repositories/agentGroup/index.ts +4 -9
- package/packages/database/src/repositories/knowledge/index.ts +3 -3
- package/packages/database/src/schemas/chatGroup.ts +4 -3
- package/packages/database/src/types/chatGroup.ts +0 -7
- package/packages/types/src/agentGroup/index.ts +30 -9
- package/packages/utils/src/multimodalContent.test.ts +302 -0
- package/packages/utils/src/server/__tests__/sse.test.ts +353 -0
- package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/AgentGroupItem/Editing.tsx +4 -11
- package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/AgentGroupItem/index.tsx +3 -3
- package/src/app/[variants]/(main)/home/_layout/Body/Agent/ModalProvider.tsx +9 -32
- package/src/app/[variants]/(main)/home/_layout/hooks/useCreateMenuItems.tsx +3 -37
- package/src/app/[variants]/(main)/home/_layout/hooks/useSessionGroupMenuItems.tsx +7 -53
- package/src/app/[variants]/(main)/home/features/RecentPage/List.tsx +2 -1
- package/src/app/[variants]/(main)/resource/features/DndContextWrapper.tsx +1 -1
- package/src/app/[variants]/(main)/resource/library/_layout/Sidebar.tsx +2 -2
- package/src/app/[variants]/(main)/resource/library/features/LibraryMenu.tsx +2 -2
- package/src/app/[variants]/(mobile)/chat/settings/features/SettingButton.tsx +2 -12
- package/src/components/ChatGroupWizard/ChatGroupWizard.tsx +5 -27
- package/src/components/DragUpload/index.tsx +24 -27
- package/src/components/MemberSelectionModal/MemberSelectionModal.tsx +2 -11
- package/src/features/ChatInput/ActionBar/Params/Controls.tsx +42 -7
- package/src/features/CommandMenu/useCommandMenu.ts +4 -14
- package/src/features/ResourceManager/components/Editor/index.tsx +2 -3
- package/src/features/ResourceManager/components/Explorer/Header/index.tsx +13 -17
- package/src/features/ResourceManager/components/Explorer/ItemDropdown/useFileItemDropdown.tsx +1 -1
- package/src/features/ResourceManager/components/Explorer/ListView/ListItem/TruncatedFileName.tsx +130 -0
- package/src/features/ResourceManager/components/Explorer/ListView/ListItem/index.tsx +36 -4
- package/src/features/ResourceManager/components/Explorer/ListView/Skeleton.tsx +4 -3
- package/src/features/ResourceManager/components/Explorer/ListView/index.tsx +58 -2
- package/src/features/ResourceManager/components/Explorer/MasonryView/index.tsx +58 -6
- package/src/features/ResourceManager/components/Explorer/MoveToFolderModal.tsx +2 -5
- package/src/features/ResourceManager/components/Explorer/ToolBar/BatchActionsDropdown.tsx +9 -5
- package/src/features/ResourceManager/components/Explorer/index.tsx +11 -56
- package/src/features/ResourceManager/components/Header/AddButton.tsx +5 -6
- package/src/features/ResourceManager/components/LibraryHierarchy/HierarchyNode.tsx +382 -0
- package/src/features/ResourceManager/components/LibraryHierarchy/index.tsx +396 -0
- package/src/features/ResourceManager/components/LibraryHierarchy/styles.ts +19 -0
- package/src/features/ResourceManager/components/LibraryHierarchy/treeState.ts +178 -0
- package/src/features/ResourceManager/components/LibraryHierarchy/types.ts +10 -0
- package/src/features/ResourceManager/index.tsx +3 -0
- package/src/layout/GlobalProvider/GroupWizardProvider.tsx +6 -29
- package/src/locales/default/auth.ts +1 -1
- package/src/locales/default/file.ts +2 -0
- package/src/locales/default/metadata.ts +2 -2
- package/src/server/modules/AgentRuntime/AgentRuntimeCoordinator.ts +30 -30
- package/src/server/modules/AgentRuntime/AgentStateManager.ts +23 -23
- package/src/server/modules/AgentRuntime/InMemoryAgentStateManager.ts +16 -16
- package/src/server/modules/AgentRuntime/InMemoryStreamEventManager.ts +13 -13
- package/src/server/modules/AgentRuntime/RuntimeExecutors.ts +2 -2
- package/src/server/modules/AgentRuntime/StreamEventManager.ts +18 -18
- package/src/server/modules/AgentRuntime/types.ts +21 -21
- package/src/server/routers/lambda/__tests__/agentGroup.test.ts +8 -8
- package/src/server/routers/lambda/agentGroup.ts +10 -12
- package/src/server/services/document/index.ts +1 -0
- package/src/store/agentGroup/slices/curd.test.ts +4 -4
- package/src/store/file/slices/fileManager/action.ts +12 -4
- package/src/store/home/slices/homeInput/action.ts +0 -3
- package/src/store/home/slices/sidebarUI/action.ts +9 -0
- package/src/store/session/slices/session/action.ts +5 -9
- package/src/app/[variants]/(mobile)/chat/settings/features/AgentTeamSettings/index.tsx +0 -95
- package/src/features/GroupChatSettings/AgentCard.tsx +0 -154
- package/src/features/GroupChatSettings/AgentTeamChatSettings.tsx +0 -179
- package/src/features/GroupChatSettings/AgentTeamMembersSettings.tsx +0 -244
- package/src/features/GroupChatSettings/AgentTeamMetaSettings.tsx +0 -94
- package/src/features/GroupChatSettings/AgentTeamSettings.tsx +0 -54
- package/src/features/GroupChatSettings/GroupCategory/index.tsx +0 -30
- package/src/features/GroupChatSettings/GroupCategory/useGroupCategory.tsx +0 -42
- package/src/features/GroupChatSettings/GroupChatSettingsProvider.tsx +0 -19
- package/src/features/GroupChatSettings/HostMemberCard.tsx +0 -113
- package/src/features/GroupChatSettings/StoreUpdater.tsx +0 -34
- package/src/features/GroupChatSettings/hooks/useGroupChatSettings.ts +0 -25
- package/src/features/GroupChatSettings/index.ts +0 -16
- package/src/features/GroupChatSettings/store/action.ts +0 -105
- package/src/features/GroupChatSettings/store/index.ts +0 -18
- package/src/features/GroupChatSettings/store/initialState.ts +0 -23
- package/src/features/GroupChatSettings/store/selectors.ts +0 -13
- package/src/features/ResourceManager/components/Tree/index.tsx +0 -883
- /package/src/features/ResourceManager/components/{Tree → LibraryHierarchy}/TreeSkeleton.tsx +0 -0
|
@@ -4,7 +4,7 @@ import { BUILTIN_AGENT_SLUGS } from '@lobechat/builtin-agents';
|
|
|
4
4
|
import { ActionIcon, Flexbox } from '@lobehub/ui';
|
|
5
5
|
import { Modal } from 'antd';
|
|
6
6
|
import { cssVar, useTheme } from 'antd-style';
|
|
7
|
-
import { ArrowLeftIcon,
|
|
7
|
+
import { ArrowLeftIcon, DownloadIcon, InfoIcon } from 'lucide-react';
|
|
8
8
|
import { memo, useState } from 'react';
|
|
9
9
|
import { useTranslation } from 'react-i18next';
|
|
10
10
|
|
|
@@ -13,7 +13,6 @@ import { useResourceManagerStore } from '@/app/[variants]/(main)/resource/featur
|
|
|
13
13
|
import Loading from '@/components/Loading/BrandTextLoading';
|
|
14
14
|
import NavHeader from '@/features/NavHeader';
|
|
15
15
|
import PageAgentProvider from '@/features/PageEditor/PageAgentProvider';
|
|
16
|
-
import ToggleRightPanelButton from '@/features/RightPanel/ToggleRightPanelButton';
|
|
17
16
|
import { useAgentStore } from '@/store/agent';
|
|
18
17
|
import { builtinAgentSelectors } from '@/store/agent/selectors';
|
|
19
18
|
import { fileManagerSelectors, useFileStore } from '@/store/file';
|
|
@@ -56,7 +55,7 @@ const FileEditorCanvas = memo<FileEditorProps>(({ onBack }) => {
|
|
|
56
55
|
}
|
|
57
56
|
right={
|
|
58
57
|
<Flexbox gap={8} horizontal>
|
|
59
|
-
<ToggleRightPanelButton icon={BotMessageSquareIcon} showActive={true} size={20} />
|
|
58
|
+
{/* <ToggleRightPanelButton icon={BotMessageSquareIcon} showActive={true} size={20} /> */}
|
|
60
59
|
{fileDetail?.url && (
|
|
61
60
|
<ActionIcon
|
|
62
61
|
icon={DownloadIcon}
|
|
@@ -29,18 +29,18 @@ const Header = memo(() => {
|
|
|
29
29
|
]);
|
|
30
30
|
const toggleCommandMenu = useGlobalStore((s) => s.toggleCommandMenu);
|
|
31
31
|
|
|
32
|
-
//
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
32
|
+
// If no libraryId, show category name or "Resource" for All
|
|
33
|
+
const leftContent = !libraryId ? (
|
|
34
|
+
<Flexbox style={{ marginLeft: 8 }}>
|
|
35
|
+
{category === FilesTabs.All
|
|
36
|
+
? t('resource', { defaultValue: 'Resource' })
|
|
37
|
+
: t(`tab.${category as FilesTabs}` as any)}
|
|
38
|
+
</Flexbox>
|
|
39
|
+
) : (
|
|
40
|
+
<Flexbox style={{ marginLeft: 8 }}>
|
|
41
|
+
<Breadcrumb category={category} knowledgeBaseId={libraryId} />
|
|
42
|
+
</Flexbox>
|
|
43
|
+
);
|
|
44
44
|
|
|
45
45
|
return (
|
|
46
46
|
<NavHeader
|
|
@@ -49,11 +49,7 @@ const Header = memo(() => {
|
|
|
49
49
|
<>
|
|
50
50
|
<ActionIcon icon={SearchIcon} onClick={() => toggleCommandMenu(true)} />
|
|
51
51
|
<SortDropdown />
|
|
52
|
-
<BatchActionsDropdown
|
|
53
|
-
disabled={isBatchActionsDisabled}
|
|
54
|
-
onActionClick={onActionClick}
|
|
55
|
-
selectCount={selectFileIds.length}
|
|
56
|
-
/>
|
|
52
|
+
<BatchActionsDropdown onActionClick={onActionClick} selectCount={selectFileIds.length} />
|
|
57
53
|
<ViewSwitcher />
|
|
58
54
|
<Flexbox style={{ marginLeft: 8 }}>
|
|
59
55
|
<AddButton />
|
package/src/features/ResourceManager/components/Explorer/ItemDropdown/useFileItemDropdown.tsx
CHANGED
|
@@ -15,7 +15,7 @@ import { useTranslation } from 'react-i18next';
|
|
|
15
15
|
import { shallow } from 'zustand/shallow';
|
|
16
16
|
|
|
17
17
|
import RepoIcon from '@/components/LibIcon';
|
|
18
|
-
import { clearTreeFolderCache } from '@/features/ResourceManager/components/
|
|
18
|
+
import { clearTreeFolderCache } from '@/features/ResourceManager/components/LibraryHierarchy';
|
|
19
19
|
import { PAGE_FILE_TYPE } from '@/features/ResourceManager/constants';
|
|
20
20
|
import { documentService } from '@/services/document';
|
|
21
21
|
import { useFileStore } from '@/store/file';
|
package/src/features/ResourceManager/components/Explorer/ListView/ListItem/TruncatedFileName.tsx
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { memo, useEffect, useRef, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
interface TruncatedFileNameProps {
|
|
4
|
+
className?: string;
|
|
5
|
+
name: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Truncates file name from the center, preserving the extension at the end
|
|
10
|
+
* Similar to macOS Finder behavior
|
|
11
|
+
*/
|
|
12
|
+
const TruncatedFileName = memo<TruncatedFileNameProps>(({ name, className }) => {
|
|
13
|
+
const containerRef = useRef<HTMLSpanElement>(null);
|
|
14
|
+
const [displayName, setDisplayName] = useState(name);
|
|
15
|
+
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
const container = containerRef.current;
|
|
18
|
+
if (!container) return;
|
|
19
|
+
|
|
20
|
+
const updateTruncation = () => {
|
|
21
|
+
const containerWidth = container.offsetWidth;
|
|
22
|
+
if (containerWidth === 0) return;
|
|
23
|
+
|
|
24
|
+
// Create a temporary span to measure text width
|
|
25
|
+
const measureSpan = document.createElement('span');
|
|
26
|
+
measureSpan.style.visibility = 'hidden';
|
|
27
|
+
measureSpan.style.position = 'absolute';
|
|
28
|
+
measureSpan.style.whiteSpace = 'nowrap';
|
|
29
|
+
measureSpan.style.font = window.getComputedStyle(container).font;
|
|
30
|
+
document.body.append(measureSpan);
|
|
31
|
+
|
|
32
|
+
// Measure full name
|
|
33
|
+
measureSpan.textContent = name;
|
|
34
|
+
const fullWidth = measureSpan.offsetWidth;
|
|
35
|
+
|
|
36
|
+
// If it fits, show the full name
|
|
37
|
+
if (fullWidth <= containerWidth) {
|
|
38
|
+
setDisplayName(name);
|
|
39
|
+
measureSpan.remove();
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Split filename and extension
|
|
44
|
+
const lastDotIndex = name.lastIndexOf('.');
|
|
45
|
+
let baseName = name;
|
|
46
|
+
let extension = '';
|
|
47
|
+
|
|
48
|
+
// Only treat as extension if dot is not at the start and there's content after it
|
|
49
|
+
if (lastDotIndex > 0 && lastDotIndex < name.length - 1) {
|
|
50
|
+
baseName = name.slice(0, lastDotIndex);
|
|
51
|
+
extension = name.slice(lastDotIndex); // includes the dot
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Measure ellipsis width
|
|
55
|
+
measureSpan.textContent = '...';
|
|
56
|
+
const ellipsisWidth = measureSpan.offsetWidth;
|
|
57
|
+
|
|
58
|
+
// Measure extension width
|
|
59
|
+
measureSpan.textContent = extension;
|
|
60
|
+
const extensionWidth = measureSpan.offsetWidth;
|
|
61
|
+
|
|
62
|
+
// Calculate available width for base name
|
|
63
|
+
const availableWidth = containerWidth - ellipsisWidth - extensionWidth;
|
|
64
|
+
|
|
65
|
+
if (availableWidth <= 0) {
|
|
66
|
+
// Not enough space, just show ellipsis + extension
|
|
67
|
+
setDisplayName(`...${extension}`);
|
|
68
|
+
measureSpan.remove();
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Binary search to find the optimal split point
|
|
73
|
+
let left = 0;
|
|
74
|
+
let right = baseName.length;
|
|
75
|
+
let bestFit = '';
|
|
76
|
+
|
|
77
|
+
while (left <= right) {
|
|
78
|
+
const mid = Math.floor((left + right) / 2);
|
|
79
|
+
const startChars = Math.ceil(mid / 2);
|
|
80
|
+
const endChars = Math.floor(mid / 2);
|
|
81
|
+
|
|
82
|
+
const truncated =
|
|
83
|
+
baseName.slice(0, startChars) + (mid > 0 ? baseName.slice(-endChars) : '');
|
|
84
|
+
|
|
85
|
+
measureSpan.textContent = truncated;
|
|
86
|
+
const truncatedWidth = measureSpan.offsetWidth;
|
|
87
|
+
|
|
88
|
+
if (truncatedWidth <= availableWidth) {
|
|
89
|
+
bestFit = truncated;
|
|
90
|
+
left = mid + 1;
|
|
91
|
+
} else {
|
|
92
|
+
right = mid - 1;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
measureSpan.remove();
|
|
97
|
+
|
|
98
|
+
// Construct final truncated name
|
|
99
|
+
if (bestFit.length === 0) {
|
|
100
|
+
setDisplayName(`...${extension}`);
|
|
101
|
+
} else {
|
|
102
|
+
const startChars = Math.ceil(bestFit.length / 2);
|
|
103
|
+
const endChars = Math.floor(bestFit.length / 2);
|
|
104
|
+
setDisplayName(
|
|
105
|
+
`${baseName.slice(0, startChars)}...${baseName.slice(-endChars)}${extension}`,
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
updateTruncation();
|
|
111
|
+
|
|
112
|
+
// Use ResizeObserver to handle container size changes
|
|
113
|
+
const resizeObserver = new ResizeObserver(updateTruncation);
|
|
114
|
+
resizeObserver.observe(container);
|
|
115
|
+
|
|
116
|
+
return () => {
|
|
117
|
+
resizeObserver.disconnect();
|
|
118
|
+
};
|
|
119
|
+
}, [name]);
|
|
120
|
+
|
|
121
|
+
return (
|
|
122
|
+
<span className={className} ref={containerRef} title={name}>
|
|
123
|
+
{displayName}
|
|
124
|
+
</span>
|
|
125
|
+
);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
TruncatedFileName.displayName = 'TruncatedFileName';
|
|
129
|
+
|
|
130
|
+
export default TruncatedFileName;
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
} from '@/app/[variants]/(main)/resource/features/DndContextWrapper';
|
|
18
18
|
import { useResourceManagerStore } from '@/app/[variants]/(main)/resource/features/store';
|
|
19
19
|
import FileIcon from '@/components/FileIcon';
|
|
20
|
-
import { clearTreeFolderCache } from '@/features/ResourceManager/components/
|
|
20
|
+
import { clearTreeFolderCache } from '@/features/ResourceManager/components/LibraryHierarchy';
|
|
21
21
|
import { PAGE_FILE_TYPE } from '@/features/ResourceManager/constants';
|
|
22
22
|
import { fileManagerSelectors, useFileStore } from '@/store/file';
|
|
23
23
|
import { type FileListItem as FileListItemType } from '@/types/files';
|
|
@@ -27,6 +27,7 @@ import { isChunkingUnsupported } from '@/utils/isChunkingUnsupported';
|
|
|
27
27
|
import DropdownMenu from '../../ItemDropdown/DropdownMenu';
|
|
28
28
|
import { useFileItemDropdown } from '../../ItemDropdown/useFileItemDropdown';
|
|
29
29
|
import ChunksBadge from './ChunkTag';
|
|
30
|
+
import TruncatedFileName from './TruncatedFileName';
|
|
30
31
|
|
|
31
32
|
// Initialize dayjs plugin once at module level
|
|
32
33
|
dayjs.extend(relativeTime);
|
|
@@ -40,6 +41,7 @@ const styles = createStaticStyles(({ css }) => {
|
|
|
40
41
|
cursor: pointer;
|
|
41
42
|
min-width: 800px;
|
|
42
43
|
|
|
44
|
+
/* Hover effect for individual rows */
|
|
43
45
|
&:hover {
|
|
44
46
|
background: ${cssVar.colorFillTertiary};
|
|
45
47
|
}
|
|
@@ -59,6 +61,25 @@ const styles = createStaticStyles(({ css }) => {
|
|
|
59
61
|
opacity: 0.5;
|
|
60
62
|
`,
|
|
61
63
|
|
|
64
|
+
evenRow: css`
|
|
65
|
+
background: ${cssVar.colorFillQuaternary};
|
|
66
|
+
|
|
67
|
+
/* Hover effect overrides zebra striping on the hovered row only */
|
|
68
|
+
&:hover {
|
|
69
|
+
background: ${cssVar.colorFillTertiary};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/* Hide zebra striping when any row is hovered */
|
|
73
|
+
.any-row-hovered & {
|
|
74
|
+
background: transparent;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/* But keep hover effect on the actual hovered row */
|
|
78
|
+
.any-row-hovered &:hover {
|
|
79
|
+
background: ${cssVar.colorFillTertiary};
|
|
80
|
+
}
|
|
81
|
+
`,
|
|
82
|
+
|
|
62
83
|
hover: css`
|
|
63
84
|
opacity: 0;
|
|
64
85
|
|
|
@@ -80,7 +101,6 @@ const styles = createStaticStyles(({ css }) => {
|
|
|
80
101
|
margin-inline-start: 12px;
|
|
81
102
|
|
|
82
103
|
color: ${cssVar.colorText};
|
|
83
|
-
text-overflow: ellipsis;
|
|
84
104
|
white-space: nowrap;
|
|
85
105
|
`,
|
|
86
106
|
nameContainer: css`
|
|
@@ -105,6 +125,8 @@ interface FileListItemProps extends FileListItemType {
|
|
|
105
125
|
size: number;
|
|
106
126
|
};
|
|
107
127
|
index: number;
|
|
128
|
+
isAnyRowHovered: boolean;
|
|
129
|
+
onHoverChange: (isHovered: boolean) => void;
|
|
108
130
|
onSelectedChange: (id: string, selected: boolean, shiftKey: boolean, index: number) => void;
|
|
109
131
|
pendingRenameItemId?: string | null;
|
|
110
132
|
selected?: boolean;
|
|
@@ -133,6 +155,7 @@ const FileListItem = memo<FileListItemProps>(
|
|
|
133
155
|
sourceType,
|
|
134
156
|
slug,
|
|
135
157
|
pendingRenameItemId,
|
|
158
|
+
onHoverChange,
|
|
136
159
|
}) => {
|
|
137
160
|
const { t } = useTranslation(['components', 'file']);
|
|
138
161
|
const { message } = App.useApp();
|
|
@@ -376,12 +399,14 @@ const FileListItem = memo<FileListItemProps>(
|
|
|
376
399
|
className={cx(
|
|
377
400
|
styles.container,
|
|
378
401
|
'file-list-item-group',
|
|
402
|
+
index % 2 === 0 && styles.evenRow,
|
|
379
403
|
selected && styles.selected,
|
|
380
404
|
isDragging && styles.dragging,
|
|
381
405
|
isOver && styles.dragOver,
|
|
382
406
|
)}
|
|
383
407
|
data-drop-target-id={id}
|
|
384
408
|
data-is-folder={String(isFolder)}
|
|
409
|
+
data-row-index={index}
|
|
385
410
|
draggable={!!resourceManagerState.libraryId}
|
|
386
411
|
height={48}
|
|
387
412
|
horizontal
|
|
@@ -390,6 +415,8 @@ const FileListItem = memo<FileListItemProps>(
|
|
|
390
415
|
onDragOver={handleDragOver}
|
|
391
416
|
onDragStart={handleDragStart}
|
|
392
417
|
onDrop={handleDrop}
|
|
418
|
+
onMouseEnter={() => onHoverChange(true)}
|
|
419
|
+
onMouseLeave={() => onHoverChange(false)}
|
|
393
420
|
paddingInline={8}
|
|
394
421
|
style={{
|
|
395
422
|
borderBlockEnd: `1px solid ${cssVar.colorBorderSecondary}`,
|
|
@@ -469,7 +496,10 @@ const FileListItem = memo<FileListItemProps>(
|
|
|
469
496
|
value={renamingValue}
|
|
470
497
|
/>
|
|
471
498
|
) : (
|
|
472
|
-
<
|
|
499
|
+
<TruncatedFileName
|
|
500
|
+
className={styles.name}
|
|
501
|
+
name={name || t('file:pageList.untitled')}
|
|
502
|
+
/>
|
|
473
503
|
)}
|
|
474
504
|
</Flexbox>
|
|
475
505
|
<Flexbox
|
|
@@ -482,6 +512,7 @@ const FileListItem = memo<FileListItemProps>(
|
|
|
482
512
|
onPointerDown={(e) => e.stopPropagation()}
|
|
483
513
|
>
|
|
484
514
|
{!isFolder &&
|
|
515
|
+
!isPage &&
|
|
485
516
|
(fileStoreState.isCreatingFileParseTask ||
|
|
486
517
|
isNull(chunkingStatus) ||
|
|
487
518
|
!chunkingStatus ? (
|
|
@@ -562,7 +593,8 @@ const FileListItem = memo<FileListItemProps>(
|
|
|
562
593
|
prevProps.url === nextProps.url &&
|
|
563
594
|
prevProps.columnWidths.name === nextProps.columnWidths.name &&
|
|
564
595
|
prevProps.columnWidths.date === nextProps.columnWidths.date &&
|
|
565
|
-
prevProps.columnWidths.size === nextProps.columnWidths.size
|
|
596
|
+
prevProps.columnWidths.size === nextProps.columnWidths.size &&
|
|
597
|
+
prevProps.isAnyRowHovered === nextProps.isAnyRowHovered
|
|
566
598
|
);
|
|
567
599
|
},
|
|
568
600
|
);
|
|
@@ -29,6 +29,7 @@ const ListViewSkeleton = ({
|
|
|
29
29
|
key={index}
|
|
30
30
|
paddingInline={8}
|
|
31
31
|
style={{
|
|
32
|
+
background: index % 2 === 0 ? cssVar.colorFillQuaternary : 'transparent',
|
|
32
33
|
borderBlockEnd: `1px solid ${cssVar.colorBorderSecondary}`,
|
|
33
34
|
opacity: getOpacity(index),
|
|
34
35
|
}}
|
|
@@ -39,21 +40,21 @@ const ListViewSkeleton = ({
|
|
|
39
40
|
<Flexbox
|
|
40
41
|
align={'center'}
|
|
41
42
|
horizontal
|
|
42
|
-
paddingInline={8}
|
|
43
43
|
style={{
|
|
44
44
|
flexShrink: 0,
|
|
45
45
|
maxWidth: columnWidths.name,
|
|
46
46
|
minWidth: columnWidths.name,
|
|
47
|
+
paddingInline: 8,
|
|
47
48
|
width: columnWidths.name,
|
|
48
49
|
}}
|
|
49
50
|
>
|
|
50
51
|
<Skeleton.Avatar active shape={'square'} size={24} style={{ marginInline: 8 }} />
|
|
51
52
|
<Skeleton.Button active style={{ height: 16, width: '60%' }} />
|
|
52
53
|
</Flexbox>
|
|
53
|
-
<Flexbox
|
|
54
|
+
<Flexbox style={{ flexShrink: 0, paddingInline: '0 24px' }} width={columnWidths.date}>
|
|
54
55
|
<Skeleton.Button active style={{ height: 16, width: '80%' }} />
|
|
55
56
|
</Flexbox>
|
|
56
|
-
<Flexbox
|
|
57
|
+
<Flexbox style={{ flexShrink: 0, paddingInline: '0 24px' }} width={columnWidths.size}>
|
|
57
58
|
<Skeleton.Button active style={{ height: 16, width: '60%' }} />
|
|
58
59
|
</Flexbox>
|
|
59
60
|
</Flexbox>
|
|
@@ -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(
|
|
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
|
-
|
|
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:
|
|
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/
|
|
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[] => {
|