@lobehub/lobehub 2.0.0-next.233 → 2.0.0-next.234

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 (86) hide show
  1. package/.github/workflows/e2e.yml +6 -12
  2. package/.github/workflows/test.yml +3 -3
  3. package/CHANGELOG.md +34 -0
  4. package/CLAUDE.md +1 -1
  5. package/changelog/v1.json +9 -0
  6. package/docs/development/basic/feature-development.mdx +4 -5
  7. package/docs/development/basic/feature-development.zh-CN.mdx +4 -5
  8. package/e2e/README.md +6 -6
  9. package/e2e/src/features/community/detail-pages.feature +9 -9
  10. package/e2e/src/features/community/interactions.feature +13 -13
  11. package/e2e/src/features/community/smoke.feature +6 -6
  12. package/e2e/src/steps/agent/conversation-mgmt.steps.ts +196 -25
  13. package/e2e/src/steps/agent/conversation.steps.ts +58 -0
  14. package/e2e/src/steps/agent/message-ops.steps.ts +20 -15
  15. package/e2e/src/steps/community/detail-pages.steps.ts +60 -19
  16. package/e2e/src/steps/community/interactions.steps.ts +145 -32
  17. package/e2e/src/steps/hooks.ts +12 -2
  18. package/locales/en-US/setting.json +3 -0
  19. package/locales/zh-CN/file.json +4 -0
  20. package/locales/zh-CN/setting.json +3 -0
  21. package/package.json +5 -5
  22. package/packages/const/src/index.ts +1 -0
  23. package/packages/const/src/lobehubSkill.ts +55 -0
  24. package/packages/types/package.json +1 -1
  25. package/packages/types/src/files/upload.ts +11 -1
  26. package/packages/types/src/message/common/tools.ts +1 -1
  27. package/packages/types/src/serverConfig.ts +1 -0
  28. package/public/not-compatible.html +1296 -0
  29. package/src/app/[variants]/(main)/resource/features/FileDetail.tsx +20 -12
  30. package/src/app/[variants]/(main)/resource/features/modal/FullscreenModal.tsx +2 -4
  31. package/src/app/[variants]/layout.tsx +50 -1
  32. package/src/features/ChatInput/ActionBar/Tools/LobehubSkillServerItem.tsx +304 -0
  33. package/src/features/ChatInput/ActionBar/Tools/useControls.tsx +74 -10
  34. package/src/features/Conversation/Messages/AssistantGroup/Tool/Inspector/ToolTitle.tsx +9 -0
  35. package/src/features/FileViewer/Renderer/Code/index.tsx +224 -0
  36. package/src/features/FileViewer/Renderer/Image/index.tsx +8 -1
  37. package/src/features/FileViewer/Renderer/PDF/index.tsx +3 -1
  38. package/src/features/FileViewer/Renderer/PDF/style.ts +2 -1
  39. package/src/features/FileViewer/index.tsx +135 -24
  40. package/src/features/PageEditor/EditorCanvas/useSlashItems.tsx +7 -4
  41. package/src/features/PageEditor/store/initialState.ts +2 -1
  42. package/src/features/ResourceManager/components/Editor/FileContent.tsx +1 -4
  43. package/src/features/ResourceManager/components/Editor/FileCopilot.tsx +64 -0
  44. package/src/features/ResourceManager/components/Editor/index.tsx +98 -31
  45. package/src/features/ResourceManager/components/Explorer/ItemDropdown/useFileItemDropdown.tsx +3 -2
  46. package/src/features/ResourceManager/components/Explorer/ListView/ColumnResizeHandle.tsx +119 -0
  47. package/src/features/ResourceManager/components/Explorer/ListView/ListItem/index.tsx +67 -22
  48. package/src/features/ResourceManager/components/Explorer/ListView/Skeleton.tsx +46 -11
  49. package/src/features/ResourceManager/components/Explorer/ListView/index.tsx +140 -81
  50. package/src/features/ResourceManager/components/Explorer/ToolBar/SortDropdown.tsx +20 -12
  51. package/src/features/ResourceManager/components/Explorer/ToolBar/ViewSwitcher.tsx +18 -10
  52. package/src/features/ResourceManager/components/UploadDock/Item.tsx +38 -6
  53. package/src/features/ResourceManager/components/UploadDock/index.tsx +62 -41
  54. package/src/features/ResourceManager/index.tsx +1 -0
  55. package/src/helpers/toolEngineering/index.test.ts +3 -0
  56. package/src/helpers/toolEngineering/index.ts +12 -1
  57. package/src/locales/default/file.ts +4 -0
  58. package/src/locales/default/setting.ts +3 -0
  59. package/src/server/globalConfig/index.ts +1 -0
  60. package/src/server/modules/ModelRuntime/index.test.ts +214 -1
  61. package/src/server/modules/ModelRuntime/index.ts +43 -7
  62. package/src/server/routers/lambda/document.ts +44 -0
  63. package/src/server/routers/tools/market.ts +261 -0
  64. package/src/server/services/document/index.ts +22 -0
  65. package/src/services/document/index.ts +4 -0
  66. package/src/services/upload.ts +22 -2
  67. package/src/store/chat/slices/plugin/actions/internals.ts +15 -2
  68. package/src/store/chat/slices/plugin/actions/pluginTypes.ts +104 -0
  69. package/src/store/file/slices/fileManager/action.test.ts +9 -3
  70. package/src/store/file/slices/fileManager/action.ts +165 -70
  71. package/src/store/file/slices/upload/action.ts +3 -0
  72. package/src/store/global/actions/general.ts +15 -0
  73. package/src/store/global/initialState.ts +13 -0
  74. package/src/store/serverConfig/selectors.ts +1 -0
  75. package/src/store/tool/initialState.ts +11 -2
  76. package/src/store/tool/selectors/index.ts +1 -0
  77. package/src/store/tool/selectors/tool.ts +3 -1
  78. package/src/store/tool/slices/lobehubSkillStore/action.ts +361 -0
  79. package/src/store/tool/slices/lobehubSkillStore/index.ts +4 -0
  80. package/src/store/tool/slices/lobehubSkillStore/initialState.ts +24 -0
  81. package/src/store/tool/slices/lobehubSkillStore/selectors.ts +145 -0
  82. package/src/store/tool/slices/lobehubSkillStore/types.ts +100 -0
  83. package/src/store/tool/store.ts +8 -2
  84. package/vitest.config.mts +1 -0
  85. package/src/features/FileViewer/Renderer/JavaScript/index.tsx +0 -66
  86. package/src/features/FileViewer/Renderer/TXT/index.tsx +0 -50
@@ -1,22 +1,107 @@
1
1
  'use client';
2
2
 
3
+ import { BUILTIN_AGENT_SLUGS } from '@lobechat/builtin-agents';
3
4
  import { ActionIcon, Flexbox } from '@lobehub/ui';
4
- import { cssVar } from 'antd-style';
5
- import { ArrowLeftIcon } from 'lucide-react';
6
- import { memo } from 'react';
5
+ import { Modal } from 'antd';
6
+ import { cssVar, useTheme } from 'antd-style';
7
+ import { ArrowLeftIcon, BotMessageSquareIcon, DownloadIcon, InfoIcon } from 'lucide-react';
8
+ import { memo, useState } from 'react';
7
9
  import { useTranslation } from 'react-i18next';
8
10
 
11
+ import FileDetailComponent from '@/app/[variants]/(main)/resource/features/FileDetail';
9
12
  import { useResourceManagerStore } from '@/app/[variants]/(main)/resource/features/store';
13
+ import Loading from '@/components/Loading/BrandTextLoading';
10
14
  import NavHeader from '@/features/NavHeader';
15
+ import PageAgentProvider from '@/features/PageEditor/PageAgentProvider';
16
+ import ToggleRightPanelButton from '@/features/RightPanel/ToggleRightPanelButton';
17
+ import { useAgentStore } from '@/store/agent';
18
+ import { builtinAgentSelectors } from '@/store/agent/selectors';
11
19
  import { fileManagerSelectors, useFileStore } from '@/store/file';
20
+ import { downloadFile } from '@/utils/client/downloadFile';
12
21
 
13
- import Breadcrumb from '../Explorer/Header/Breadcrumb';
14
22
  import FileContent from './FileContent';
23
+ import FileCopilot from './FileCopilot';
15
24
 
16
25
  interface FileEditorProps {
17
26
  onBack?: () => void;
18
27
  }
19
28
 
29
+ const FileEditorCanvas = memo<FileEditorProps>(({ onBack }) => {
30
+ const { t } = useTranslation(['common', 'file']);
31
+ const theme = useTheme();
32
+ const [isDetailModalOpen, setIsDetailModalOpen] = useState(false);
33
+
34
+ const currentViewItemId = useResourceManagerStore((s) => s.currentViewItemId);
35
+
36
+ const fileDetail = useFileStore(fileManagerSelectors.getFileById(currentViewItemId));
37
+
38
+ return (
39
+ <>
40
+ <Flexbox height={'100%'} horizontal width={'100%'}>
41
+ <Flexbox flex={1} height={'100%'}>
42
+ <NavHeader
43
+ left={
44
+ <Flexbox align={'center'} gap={12} horizontal style={{ minHeight: 32 }}>
45
+ <ActionIcon icon={ArrowLeftIcon} onClick={onBack} title={t('back')} />
46
+ <span
47
+ style={{
48
+ color: theme.colorText,
49
+ fontSize: 14,
50
+ fontWeight: 500,
51
+ }}
52
+ >
53
+ {fileDetail?.name}
54
+ </span>
55
+ </Flexbox>
56
+ }
57
+ right={
58
+ <Flexbox gap={8} horizontal>
59
+ <ToggleRightPanelButton icon={BotMessageSquareIcon} showActive={true} size={20} />
60
+ {fileDetail?.url && (
61
+ <ActionIcon
62
+ icon={DownloadIcon}
63
+ onClick={() => {
64
+ if (fileDetail?.url && fileDetail?.name) {
65
+ downloadFile(fileDetail.url, fileDetail.name);
66
+ }
67
+ }}
68
+ title={t('download', { ns: 'common' })}
69
+ />
70
+ )}
71
+ <ActionIcon icon={InfoIcon} onClick={() => setIsDetailModalOpen(true)} />
72
+ </Flexbox>
73
+ }
74
+ style={{
75
+ borderBottom: `1px solid ${cssVar.colorBorderSecondary}`,
76
+ }}
77
+ styles={{
78
+ left: { padding: 0 },
79
+ }}
80
+ />
81
+ <Flexbox flex={1} style={{ overflow: 'hidden' }}>
82
+ <FileContent fileId={currentViewItemId} />
83
+ </Flexbox>
84
+ </Flexbox>
85
+ <FileCopilot />
86
+ </Flexbox>
87
+
88
+ <Modal
89
+ footer={null}
90
+ onCancel={() => setIsDetailModalOpen(false)}
91
+ open={isDetailModalOpen}
92
+ title={t('detail.basic.title', { ns: 'file' })}
93
+ width={400}
94
+ >
95
+ {fileDetail && (
96
+ <FileDetailComponent {...fileDetail} showDownloadButton={false} showTitle={false} />
97
+ )}
98
+ </Modal>
99
+ </>
100
+ );
101
+ });
102
+
103
+ FileEditorCanvas.displayName = 'FileEditorCanvas';
104
+
20
105
  /**
21
106
  * View or Edit a file
22
107
  *
@@ -24,38 +109,20 @@ interface FileEditorProps {
24
109
  * So we depend on context, not props.
25
110
  */
26
111
  const FileEditor = memo<FileEditorProps>(({ onBack }) => {
27
- const { t } = useTranslation('common');
112
+ const useInitBuiltinAgent = useAgentStore((s) => s.useInitBuiltinAgent);
113
+ const pageAgentId = useAgentStore(builtinAgentSelectors.pageAgentId);
28
114
 
29
- const [currentViewItemId, category] = useResourceManagerStore((s) => [
30
- s.currentViewItemId,
31
- s.category,
32
- ]);
115
+ useInitBuiltinAgent(BUILTIN_AGENT_SLUGS.pageAgent);
33
116
 
34
- const fileDetail = useFileStore(fileManagerSelectors.getFileById(currentViewItemId));
117
+ if (!pageAgentId) return <Loading debugId="FileEditor > PageAgent Init" />;
35
118
 
36
119
  return (
37
- <Flexbox height={'100%'}>
38
- <NavHeader
39
- left={
40
- <Flexbox align={'center'} gap={4} horizontal style={{ minHeight: 32 }}>
41
- <ActionIcon icon={ArrowLeftIcon} onClick={onBack} title={t('back')} />
42
- <Flexbox align={'center'} style={{ marginLeft: 8 }}>
43
- <Breadcrumb category={category} fileName={fileDetail?.name} />
44
- </Flexbox>
45
- </Flexbox>
46
- }
47
- style={{
48
- borderBottom: `1px solid ${cssVar.colorBorderSecondary}`,
49
- }}
50
- styles={{
51
- left: { padding: 0 },
52
- }}
53
- />
54
- <Flexbox flex={1} style={{ overflow: 'hidden' }}>
55
- <FileContent fileId={currentViewItemId} />
56
- </Flexbox>
57
- </Flexbox>
120
+ <PageAgentProvider pageAgentId={pageAgentId}>
121
+ <FileEditorCanvas onBack={onBack} />
122
+ </PageAgentProvider>
58
123
  );
59
124
  });
60
125
 
126
+ FileEditor.displayName = 'FileEditor';
127
+
61
128
  export default FileEditor;
@@ -56,8 +56,9 @@ export const useFileItemDropdown = ({
56
56
  s.useFetchKnowledgeBaseList,
57
57
  ]);
58
58
 
59
- // Only fetch knowledge bases when dropdown is enabled (open)
60
- // This prevents the expensive SWR call for all 20-25 visible items
59
+ // Fetch knowledge bases - SWR caches this across all dropdown instances
60
+ // Only the first call fetches from server, subsequent calls use cache
61
+ // The expensive menu computation is deferred until dropdown opens (menuItems is a function)
61
62
  const { data: knowledgeBases } = useFetchKnowledgeBaseList();
62
63
 
63
64
  const inKnowledgeBase = !!knowledgeBaseId;
@@ -0,0 +1,119 @@
1
+ 'use client';
2
+
3
+ import { createStaticStyles, cssVar } from 'antd-style';
4
+ import React, { memo, useCallback, useEffect, useRef, useState } from 'react';
5
+
6
+ const styles = createStaticStyles(({ css }) => ({
7
+ handle: css`
8
+ cursor: col-resize;
9
+ user-select: none;
10
+
11
+ position: absolute;
12
+ z-index: 1;
13
+ inset-block: 0 0;
14
+ inset-inline-end: 0;
15
+ transform: translateX(-4px);
16
+
17
+ display: flex;
18
+ align-items: center;
19
+ justify-content: center;
20
+
21
+ width: 16px;
22
+
23
+ &::after {
24
+ content: '';
25
+
26
+ width: 1.5px;
27
+ height: calc(100% - 16px);
28
+ border-radius: 1px;
29
+
30
+ background-color: ${cssVar.colorBorder};
31
+
32
+ transition: all 0.2s;
33
+ }
34
+
35
+ &:hover::after {
36
+ width: 3px;
37
+ background-color: ${cssVar.colorPrimary};
38
+ }
39
+ `,
40
+ handleDragging: css`
41
+ &::after {
42
+ width: 3px !important;
43
+ background-color: ${cssVar.colorPrimary} !important;
44
+ }
45
+ `,
46
+ }));
47
+
48
+ interface ColumnResizeHandleProps {
49
+ column: 'name' | 'date' | 'size';
50
+ currentWidth: number;
51
+ maxWidth: number;
52
+ minWidth: number;
53
+ onResize: (width: number) => void;
54
+ }
55
+
56
+ const ColumnResizeHandle = memo<ColumnResizeHandleProps>(
57
+ ({ currentWidth, minWidth, maxWidth, onResize }) => {
58
+ const [isDragging, setIsDragging] = useState(false);
59
+ const startXRef = useRef(0);
60
+ const startWidthRef = useRef(0);
61
+
62
+ const handleMouseMove = useCallback(
63
+ (e: MouseEvent) => {
64
+ const delta = e.clientX - startXRef.current;
65
+ const newWidth = Math.max(minWidth, Math.min(maxWidth, startWidthRef.current + delta));
66
+
67
+ // Update width in real-time during drag
68
+ onResize(newWidth);
69
+ },
70
+ [minWidth, maxWidth, onResize],
71
+ );
72
+
73
+ const handleMouseUp = useCallback(() => {
74
+ setIsDragging(false);
75
+ }, []);
76
+
77
+ const handleMouseDown = useCallback(
78
+ (e: React.MouseEvent) => {
79
+ e.preventDefault();
80
+ e.stopPropagation();
81
+
82
+ setIsDragging(true);
83
+ startXRef.current = e.clientX;
84
+ startWidthRef.current = currentWidth;
85
+ },
86
+ [currentWidth],
87
+ );
88
+
89
+ // Attach document-level event listeners when dragging
90
+ useEffect(() => {
91
+ if (isDragging) {
92
+ document.addEventListener('mousemove', handleMouseMove);
93
+ document.addEventListener('mouseup', handleMouseUp);
94
+
95
+ // Disable text selection and lock cursor during drag
96
+ document.body.style.userSelect = 'none';
97
+ document.body.style.cursor = 'col-resize';
98
+
99
+ return () => {
100
+ document.removeEventListener('mousemove', handleMouseMove);
101
+ document.removeEventListener('mouseup', handleMouseUp);
102
+ document.body.style.userSelect = '';
103
+ document.body.style.cursor = '';
104
+ };
105
+ }
106
+ }, [isDragging, handleMouseMove, handleMouseUp]);
107
+
108
+ return (
109
+ <div
110
+ className={`${styles.handle} ${isDragging ? styles.handleDragging : ''}`}
111
+ onMouseDown={handleMouseDown}
112
+ />
113
+ );
114
+ },
115
+ );
116
+
117
+ ColumnResizeHandle.displayName = 'ColumnResizeHandle';
118
+
119
+ export default ColumnResizeHandle;
@@ -26,6 +26,7 @@ import DropdownMenu from '../../ItemDropdown/DropdownMenu';
26
26
  import { useFileItemDropdown } from '../../ItemDropdown/useFileItemDropdown';
27
27
  import ChunksBadge from './ChunkTag';
28
28
 
29
+ // Initialize dayjs plugin once at module level
29
30
  dayjs.extend(relativeTime);
30
31
 
31
32
  export const FILE_DATE_WIDTH = 160;
@@ -35,6 +36,7 @@ const styles = createStaticStyles(({ css }) => {
35
36
  return {
36
37
  container: css`
37
38
  cursor: pointer;
39
+ min-width: 800px;
38
40
 
39
41
  &:hover {
40
42
  background: ${cssVar.colorFillTertiary};
@@ -83,7 +85,6 @@ const styles = createStaticStyles(({ css }) => {
83
85
  overflow: hidden;
84
86
  flex: 1;
85
87
  min-width: 0;
86
- max-width: 600px;
87
88
  `,
88
89
  selected: css`
89
90
  background: ${cssVar.colorFillTertiary};
@@ -96,6 +97,11 @@ const styles = createStaticStyles(({ css }) => {
96
97
  });
97
98
 
98
99
  interface FileListItemProps extends FileListItemType {
100
+ columnWidths: {
101
+ date: number;
102
+ name: number;
103
+ size: number;
104
+ };
99
105
  index: number;
100
106
  onSelectedChange: (id: string, selected: boolean, shiftKey: boolean, index: number) => void;
101
107
  pendingRenameItemId?: string | null;
@@ -107,6 +113,7 @@ const FileListItem = memo<FileListItemProps>(
107
113
  ({
108
114
  size,
109
115
  chunkingError,
116
+ columnWidths,
110
117
  embeddingError,
111
118
  embeddingStatus,
112
119
  finishEmbedding,
@@ -242,7 +249,7 @@ const FileListItem = memo<FileListItemProps>(
242
249
  [createdAt],
243
250
  );
244
251
 
245
- const handleRenameStart = () => {
252
+ const handleRenameStart = useCallback(() => {
246
253
  setIsRenaming(true);
247
254
  setRenamingValue(name);
248
255
  // Focus input after render
@@ -250,9 +257,9 @@ const FileListItem = memo<FileListItemProps>(
250
257
  inputRef.current?.focus();
251
258
  inputRef.current?.select();
252
259
  }, 0);
253
- };
260
+ }, [name]);
254
261
 
255
- const handleRenameConfirm = async () => {
262
+ const handleRenameConfirm = useCallback(async () => {
256
263
  if (!renamingValue.trim()) {
257
264
  message.error(t('FileManager.actions.renameError'));
258
265
  return;
@@ -271,12 +278,12 @@ const FileListItem = memo<FileListItemProps>(
271
278
  console.error('Rename error:', error);
272
279
  message.error(t('FileManager.actions.renameError'));
273
280
  }
274
- };
281
+ }, [renamingValue, name, fileStoreState.renameFolder, id, message, t]);
275
282
 
276
- const handleRenameCancel = () => {
283
+ const handleRenameCancel = useCallback(() => {
277
284
  setIsRenaming(false);
278
285
  setRenamingValue(name);
279
- };
286
+ }, [name]);
280
287
 
281
288
  // Memoize click handler to prevent recreation on every render
282
289
  const handleItemClick = useCallback(() => {
@@ -357,29 +364,42 @@ const FileListItem = memo<FileListItemProps>(
357
364
  paddingInline={8}
358
365
  style={{
359
366
  borderBlockEnd: `1px solid ${cssVar.colorBorderSecondary}`,
367
+ userSelect: 'none',
360
368
  }}
361
369
  >
370
+ <Center
371
+ height={40}
372
+ onClick={(e) => {
373
+ e.stopPropagation();
374
+
375
+ onSelectedChange(id, !selected, e.shiftKey, index);
376
+ }}
377
+ onPointerDown={(e) => {
378
+ e.stopPropagation();
379
+ // Prevent text selection when shift-clicking for batch selection
380
+ if (e.shiftKey) {
381
+ e.preventDefault();
382
+ }
383
+ }}
384
+ style={{ paddingInline: 4 }}
385
+ >
386
+ <Checkbox checked={selected} />
387
+ </Center>
362
388
  <Flexbox
363
389
  align={'center'}
364
390
  className={styles.item}
365
391
  distribution={'space-between'}
366
- flex={1}
367
392
  horizontal
368
393
  onClick={handleItemClick}
394
+ style={{
395
+ flexShrink: 0,
396
+ maxWidth: columnWidths.name,
397
+ minWidth: columnWidths.name,
398
+ paddingInline: 8,
399
+ width: columnWidths.name,
400
+ }}
369
401
  >
370
402
  <Flexbox align={'center'} className={styles.nameContainer} horizontal>
371
- <Center
372
- height={48}
373
- onClick={(e) => {
374
- e.stopPropagation();
375
-
376
- onSelectedChange(id, !selected, e.shiftKey, index);
377
- }}
378
- onPointerDown={(e) => e.stopPropagation()}
379
- style={{ paddingInline: 4 }}
380
- >
381
- <Checkbox checked={selected} />
382
- </Center>
383
403
  <Flexbox
384
404
  align={'center'}
385
405
  justify={'center'}
@@ -479,10 +499,10 @@ const FileListItem = memo<FileListItemProps>(
479
499
  </Flexbox>
480
500
  {!isDragging && (
481
501
  <>
482
- <Flexbox className={styles.item} width={FILE_DATE_WIDTH}>
502
+ <Flexbox className={styles.item} style={{ flexShrink: 0 }} width={columnWidths.date}>
483
503
  {displayTime}
484
504
  </Flexbox>
485
- <Flexbox className={styles.item} width={FILE_SIZE_WIDTH}>
505
+ <Flexbox className={styles.item} style={{ flexShrink: 0 }} width={columnWidths.size}>
486
506
  {isFolder || isPage ? '-' : formatSize(size)}
487
507
  </Flexbox>
488
508
  </>
@@ -491,6 +511,31 @@ const FileListItem = memo<FileListItemProps>(
491
511
  </ContextMenuTrigger>
492
512
  );
493
513
  },
514
+ // Custom comparison function to prevent unnecessary re-renders
515
+ (prevProps, nextProps) => {
516
+ // Only re-render if these critical props change
517
+ return (
518
+ prevProps.id === nextProps.id &&
519
+ prevProps.name === nextProps.name &&
520
+ prevProps.selected === nextProps.selected &&
521
+ prevProps.chunkingStatus === nextProps.chunkingStatus &&
522
+ prevProps.embeddingStatus === nextProps.embeddingStatus &&
523
+ prevProps.chunkCount === nextProps.chunkCount &&
524
+ prevProps.chunkingError === nextProps.chunkingError &&
525
+ prevProps.embeddingError === nextProps.embeddingError &&
526
+ prevProps.finishEmbedding === nextProps.finishEmbedding &&
527
+ prevProps.pendingRenameItemId === nextProps.pendingRenameItemId &&
528
+ prevProps.size === nextProps.size &&
529
+ prevProps.createdAt === nextProps.createdAt &&
530
+ prevProps.fileType === nextProps.fileType &&
531
+ prevProps.sourceType === nextProps.sourceType &&
532
+ prevProps.slug === nextProps.slug &&
533
+ prevProps.url === nextProps.url &&
534
+ prevProps.columnWidths.name === nextProps.columnWidths.name &&
535
+ prevProps.columnWidths.date === nextProps.columnWidths.date &&
536
+ prevProps.columnWidths.size === nextProps.columnWidths.size
537
+ );
538
+ },
494
539
  );
495
540
 
496
541
  FileListItem.displayName = 'FileListItem';
@@ -1,20 +1,55 @@
1
- import { Flexbox, Skeleton } from '@lobehub/ui';
1
+ import { Center, Checkbox, Flexbox, Skeleton } from '@lobehub/ui';
2
+ import { cssVar } from 'antd-style';
2
3
 
3
4
  import { FILE_DATE_WIDTH, FILE_SIZE_WIDTH } from './ListItem';
4
5
 
5
- const ListViewSkeleton = () => (
6
- <Flexbox style={{ marginInline: 16 }}>
7
- {Array.from({ length: 4 }).map((_, index) => (
8
- <Flexbox align={'center'} distribution={'space-between'} height={48} horizontal key={index}>
9
- <Flexbox align={'center'} flex={1} gap={8} horizontal paddingInline={8}>
6
+ interface ListViewSkeletonProps {
7
+ columnWidths?: {
8
+ date: number;
9
+ name: number;
10
+ size: number;
11
+ };
12
+ count?: number;
13
+ }
14
+
15
+ const ListViewSkeleton = ({
16
+ columnWidths = { date: FILE_DATE_WIDTH, name: 400, size: FILE_SIZE_WIDTH },
17
+ count = 3,
18
+ }: ListViewSkeletonProps) => (
19
+ <Flexbox>
20
+ {Array.from({ length: count }).map((_, index) => (
21
+ <Flexbox
22
+ align={'center'}
23
+ height={48}
24
+ horizontal
25
+ key={index}
26
+ paddingInline={8}
27
+ style={{
28
+ borderBlockEnd: `1px solid ${cssVar.colorBorderSecondary}`,
29
+ }}
30
+ >
31
+ <Center height={40} style={{ paddingInline: 4 }}>
32
+ <Checkbox disabled />
33
+ </Center>
34
+ <Flexbox
35
+ align={'center'}
36
+ horizontal
37
+ paddingInline={8}
38
+ style={{
39
+ flexShrink: 0,
40
+ maxWidth: columnWidths.name,
41
+ minWidth: columnWidths.name,
42
+ width: columnWidths.name,
43
+ }}
44
+ >
10
45
  <Skeleton.Avatar active shape={'square'} size={24} style={{ marginInline: 8 }} />
11
- <Skeleton.Button active style={{ height: 16, width: 300 }} />
46
+ <Skeleton.Button active style={{ height: 16, width: '60%' }} />
12
47
  </Flexbox>
13
- <Flexbox paddingInline={24} width={FILE_DATE_WIDTH}>
14
- <Skeleton.Button active style={{ height: 16 }} />
48
+ <Flexbox paddingInline={24} style={{ flexShrink: 0 }} width={columnWidths.date}>
49
+ <Skeleton.Button active style={{ height: 16, width: '80%' }} />
15
50
  </Flexbox>
16
- <Flexbox paddingInline={24} width={FILE_SIZE_WIDTH}>
17
- <Skeleton.Button active style={{ height: 16 }} />
51
+ <Flexbox paddingInline={24} style={{ flexShrink: 0 }} width={columnWidths.size}>
52
+ <Skeleton.Button active style={{ height: 16, width: '60%' }} />
18
53
  </Flexbox>
19
54
  </Flexbox>
20
55
  ))}