@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
@@ -15,8 +15,12 @@ import {
15
15
  useResourceManagerStore,
16
16
  } from '@/app/[variants]/(main)/resource/features/store';
17
17
  import { sortFileList } from '@/app/[variants]/(main)/resource/features/store/selectors';
18
+ import { useGlobalStore } from '@/store/global';
19
+ import { INITIAL_STATUS } from '@/store/global/initialState';
18
20
 
19
- import FileListItem, { FILE_DATE_WIDTH, FILE_SIZE_WIDTH } from './ListItem';
21
+ import ColumnResizeHandle from './ColumnResizeHandle';
22
+ import FileListItem from './ListItem';
23
+ import ListViewSkeleton from './Skeleton';
20
24
 
21
25
  const log = debug('resource-manager:list-view');
22
26
 
@@ -31,18 +35,19 @@ const styles = createStaticStyles(({ css }) => ({
31
35
  outline-offset: -4px;
32
36
  `,
33
37
  header: css`
38
+ min-width: 800px;
34
39
  height: 40px;
35
40
  min-height: 40px;
36
41
  color: ${cssVar.colorTextDescription};
37
42
  `,
38
43
  headerItem: css`
39
- padding-block: 0;
44
+ height: 100%;
45
+ padding-block: 6px;
40
46
  padding-inline: 0 24px;
41
47
  `,
42
- loadingIndicator: css`
43
- padding: 16px;
44
- font-size: 14px;
45
- color: ${cssVar.colorTextDescription};
48
+ scrollContainer: css`
49
+ overflow: auto hidden;
50
+ flex: 1;
46
51
  `,
47
52
  }));
48
53
 
@@ -72,6 +77,12 @@ const ListView = memo(() => {
72
77
  s.sortType,
73
78
  ]);
74
79
 
80
+ // Access column widths from Global store
81
+ const columnWidths = useGlobalStore(
82
+ (s) => s.status.resourceManagerColumnWidths || INITIAL_STATUS.resourceManagerColumnWidths,
83
+ );
84
+ const updateColumnWidth = useGlobalStore((s) => s.updateResourceManagerColumnWidth);
85
+
75
86
  const { t } = useTranslation(['components', 'file']);
76
87
  const virtuosoRef = useRef<VirtuosoHandle>(null);
77
88
  const [isLoadingMore, setIsLoadingMore] = useState(false);
@@ -88,7 +99,6 @@ const ListView = memo(() => {
88
99
  // Get current folder ID - either from breadcrumb or null for root
89
100
  const currentFolderId = folderBreadcrumb?.at(-1)?.id || null;
90
101
 
91
- // Fetch data with SWR
92
102
  const { data: rawData } = useResourceManagerFetchKnowledgeItems({
93
103
  category,
94
104
  knowledgeBaseId: libraryId,
@@ -103,7 +113,11 @@ const ListView = memo(() => {
103
113
  // Handle selection change with shift-click support for range selection
104
114
  const handleSelectionChange = useCallback(
105
115
  (id: string, checked: boolean, shiftKey: boolean, clickedIndex: number) => {
106
- if (shiftKey && lastSelectedIndex !== null && selectFileIds.length > 0 && data) {
116
+ // Always get the latest state from the store to avoid stale closure issues
117
+ const currentSelected = useResourceManagerStore.getState().selectedFileIds;
118
+
119
+ if (shiftKey && lastSelectedIndex !== null && data) {
120
+ // Shift-click: select range from lastSelectedIndex to current index
107
121
  const start = Math.min(lastSelectedIndex, clickedIndex);
108
122
  const end = Math.max(lastSelectedIndex, clickedIndex);
109
123
  const rangeIds = data
@@ -111,19 +125,21 @@ const ListView = memo(() => {
111
125
  .filter(Boolean)
112
126
  .map((item) => item.id);
113
127
 
114
- const prevSet = new Set(selectFileIds);
128
+ // Merge with existing selection
129
+ const prevSet = new Set(currentSelected);
115
130
  rangeIds.forEach((rangeId) => prevSet.add(rangeId));
116
131
  setSelectedFileIds(Array.from(prevSet));
117
132
  } else {
133
+ // Regular click: toggle single item
118
134
  if (checked) {
119
- setSelectedFileIds([...selectFileIds, id]);
135
+ setSelectedFileIds([...currentSelected, id]);
120
136
  } else {
121
- setSelectedFileIds(selectFileIds.filter((item) => item !== id));
137
+ setSelectedFileIds(currentSelected.filter((item) => item !== id));
122
138
  }
123
139
  }
124
140
  setLastSelectedIndex(clickedIndex);
125
141
  },
126
- [lastSelectedIndex, selectFileIds, data, setSelectedFileIds],
142
+ [lastSelectedIndex, data, setSelectedFileIds],
127
143
  );
128
144
 
129
145
  // Clean up invalid selections when data changes
@@ -254,81 +270,124 @@ const ListView = memo(() => {
254
270
  };
255
271
  }, [clearScrollTimers]);
256
272
 
273
+ // Memoize footer component to show skeleton loaders when loading more
274
+ const Footer = useCallback(() => {
275
+ if (!isLoadingMore || !fileListHasMore) return null;
276
+ return <ListViewSkeleton columnWidths={columnWidths} />;
277
+ }, [isLoadingMore, fileListHasMore, columnWidths]);
278
+
257
279
  return (
258
280
  <Flexbox height={'100%'}>
259
- <Flexbox
260
- align={'center'}
261
- className={styles.header}
262
- horizontal
263
- paddingInline={8}
264
- style={{
265
- borderBlockEnd: `1px solid ${cssVar.colorBorderSecondary}`,
266
- fontSize: 12,
267
- }}
268
- >
269
- <Center height={40} style={{ paddingInline: 4 }}>
270
- <Checkbox
271
- checked={allSelected}
272
- indeterminate={indeterminate}
273
- onChange={handleSelectAll}
274
- />
275
- </Center>
276
- <Flexbox className={styles.headerItem} flex={1} style={{ paddingInline: 8 }}>
277
- {t('FileManager.title.title')}
278
- </Flexbox>
279
- <Flexbox className={styles.headerItem} width={FILE_DATE_WIDTH}>
280
- {t('FileManager.title.createdAt')}
281
- </Flexbox>
282
- <Flexbox className={styles.headerItem} width={FILE_SIZE_WIDTH}>
283
- {t('FileManager.title.size')}
284
- </Flexbox>
285
- </Flexbox>
286
- <div
287
- className={cx(styles.dropZone, isDropZoneActive && styles.dropZoneActive)}
288
- data-drop-target-id={currentFolderId || undefined}
289
- data-is-folder="true"
290
- onDragLeave={handleDropZoneDragLeave}
291
- onDragOver={(e) => {
292
- handleDropZoneDragOver(e);
293
- handleDragMove(e);
294
- }}
295
- onDrop={handleDropZoneDrop}
296
- ref={containerRef}
297
- style={{ flex: 1, overflow: 'hidden', position: 'relative' }}
298
- >
299
- <Virtuoso
300
- data={data || []}
301
- defaultItemHeight={48}
302
- endReached={handleEndReached}
303
- increaseViewportBy={{ bottom: 800, top: 1200 }}
304
- initialItemCount={30}
305
- itemContent={(index, item) => {
306
- if (!item) return null;
307
- return (
308
- <FileListItem
309
- index={index}
310
- key={item.id}
311
- onSelectedChange={handleSelectionChange}
312
- pendingRenameItemId={pendingRenameItemId}
313
- selected={selectFileIds.includes(item.id)}
314
- {...item}
315
- />
316
- );
281
+ <div className={styles.scrollContainer}>
282
+ <Flexbox
283
+ align={'center'}
284
+ className={styles.header}
285
+ horizontal
286
+ paddingInline={8}
287
+ style={{
288
+ borderBlockEnd: `1px solid ${cssVar.colorBorderSecondary}`,
289
+ fontSize: 12,
317
290
  }}
318
- overscan={48 * 5}
319
- ref={virtuosoRef}
320
- style={{ height: '100%' }}
321
- />
322
- {isLoadingMore && (
323
- <Center
324
- className={styles.loadingIndicator}
291
+ >
292
+ <Center height={40} style={{ paddingInline: 4 }}>
293
+ <Checkbox
294
+ checked={allSelected}
295
+ indeterminate={indeterminate}
296
+ onChange={handleSelectAll}
297
+ />
298
+ </Center>
299
+ <Flexbox
300
+ className={styles.headerItem}
301
+ justify={'center'}
325
302
  style={{
326
- borderBlockStart: `1px solid ${cssVar.colorBorderSecondary}`,
303
+ flexShrink: 0,
304
+ maxWidth: columnWidths.name,
305
+ minWidth: columnWidths.name,
306
+ paddingInline: 8,
307
+ paddingInlineEnd: 16,
308
+ position: 'relative',
309
+ width: columnWidths.name,
327
310
  }}
328
311
  >
329
- {t('loading', { defaultValue: 'Loading...', ns: 'file' })}
330
- </Center>
331
- )}
312
+ {t('FileManager.title.title')}
313
+ <ColumnResizeHandle
314
+ column="name"
315
+ currentWidth={columnWidths.name}
316
+ maxWidth={1200}
317
+ minWidth={200}
318
+ onResize={(width) => updateColumnWidth('name', width)}
319
+ />
320
+ </Flexbox>
321
+ <Flexbox
322
+ className={styles.headerItem}
323
+ justify={'center'}
324
+ style={{ flexShrink: 0, paddingInlineEnd: 16, position: 'relative' }}
325
+ width={columnWidths.date}
326
+ >
327
+ {t('FileManager.title.createdAt')}
328
+ <ColumnResizeHandle
329
+ column="date"
330
+ currentWidth={columnWidths.date}
331
+ maxWidth={300}
332
+ minWidth={120}
333
+ onResize={(width) => updateColumnWidth('date', width)}
334
+ />
335
+ </Flexbox>
336
+ <Flexbox
337
+ className={styles.headerItem}
338
+ justify={'center'}
339
+ style={{ flexShrink: 0, paddingInlineEnd: 16, position: 'relative' }}
340
+ width={columnWidths.size}
341
+ >
342
+ {t('FileManager.title.size')}
343
+ <ColumnResizeHandle
344
+ column="size"
345
+ currentWidth={columnWidths.size}
346
+ maxWidth={200}
347
+ minWidth={80}
348
+ onResize={(width) => updateColumnWidth('size', width)}
349
+ />
350
+ </Flexbox>
351
+ </Flexbox>
352
+ <div
353
+ className={cx(styles.dropZone, isDropZoneActive && styles.dropZoneActive)}
354
+ data-drop-target-id={currentFolderId || undefined}
355
+ data-is-folder="true"
356
+ onDragLeave={handleDropZoneDragLeave}
357
+ onDragOver={(e) => {
358
+ handleDropZoneDragOver(e);
359
+ handleDragMove(e);
360
+ }}
361
+ onDrop={handleDropZoneDrop}
362
+ ref={containerRef}
363
+ style={{ overflow: 'hidden', position: 'relative' }}
364
+ >
365
+ <Virtuoso
366
+ components={{ Footer }}
367
+ data={data || []}
368
+ defaultItemHeight={48}
369
+ endReached={handleEndReached}
370
+ increaseViewportBy={{ bottom: 800, top: 1200 }}
371
+ initialItemCount={30}
372
+ itemContent={(index, item) => {
373
+ if (!item) return null;
374
+ return (
375
+ <FileListItem
376
+ columnWidths={columnWidths}
377
+ index={index}
378
+ key={item.id}
379
+ onSelectedChange={handleSelectionChange}
380
+ pendingRenameItemId={pendingRenameItemId}
381
+ selected={selectFileIds.includes(item.id)}
382
+ {...item}
383
+ />
384
+ );
385
+ }}
386
+ overscan={48 * 5}
387
+ ref={virtuosoRef}
388
+ style={{ height: 'calc(100vh - 100px)' }}
389
+ />
390
+ </div>
332
391
  </div>
333
392
  </Flexbox>
334
393
  );
@@ -1,8 +1,9 @@
1
- import { type DropdownItem, DropdownMenu } from '@lobehub/ui';
1
+ import { Dropdown } from '@lobehub/ui';
2
2
  import { ArrowDownAZ } from 'lucide-react';
3
3
  import { memo, useMemo } from 'react';
4
4
  import { useTranslation } from 'react-i18next';
5
5
 
6
+ import { type MenuProps } from '@/components/Menu';
6
7
  import { useResourceManagerStore } from '@/app/[variants]/(main)/resource/features/store';
7
8
 
8
9
  import ActionIconWithChevron from './ActionIconWithChevron';
@@ -21,23 +22,30 @@ const SortDropdown = memo(() => {
21
22
  [t],
22
23
  );
23
24
 
24
- const menuItems: DropdownItem[] = sortOptions.map((option) => ({
25
- key: option.key,
26
- label: option.label,
27
- onClick: () => setSorter(option.key as 'name' | 'createdAt' | 'size'),
28
- style:
29
- option.key === (sorter || 'createdAt')
30
- ? { backgroundColor: 'var(--ant-control-item-bg-active)' }
31
- : {},
32
- }));
25
+ const menuItems: MenuProps['items'] = useMemo(
26
+ () =>
27
+ sortOptions.map((option) => ({
28
+ key: option.key,
29
+ label: option.label,
30
+ onClick: () => setSorter(option.key as 'name' | 'createdAt' | 'size'),
31
+ })),
32
+ [setSorter, sortOptions],
33
+ );
33
34
 
34
35
  const currentSortLabel =
35
36
  sortOptions.find((option) => option.key === sorter)?.label || t('FileManager.sort.dateAdded');
36
37
 
37
38
  return (
38
- <DropdownMenu items={menuItems}>
39
+ <Dropdown
40
+ arrow={false}
41
+ menu={{
42
+ items: menuItems,
43
+ selectable: true,
44
+ selectedKeys: [sorter || 'createdAt'],
45
+ }}
46
+ >
39
47
  <ActionIconWithChevron icon={ArrowDownAZ} title={currentSortLabel} />
40
- </DropdownMenu>
48
+ </Dropdown>
41
49
  );
42
50
  });
43
51
 
@@ -1,8 +1,10 @@
1
- import { type DropdownItem, DropdownMenu, Icon } from '@lobehub/ui';
1
+ import { Dropdown, Icon } from '@lobehub/ui';
2
2
  import { Grid3x3Icon, ListIcon } from 'lucide-react';
3
3
  import { memo, useMemo } from 'react';
4
4
  import { useTranslation } from 'react-i18next';
5
5
 
6
+ import { type MenuProps } from '@/components/Menu';
7
+
6
8
  import { useViewMode } from '../hooks/useViewMode';
7
9
  import ActionIconWithChevron from './ActionIconWithChevron';
8
10
 
@@ -18,30 +20,36 @@ const ViewSwitcher = memo(() => {
18
20
  const currentViewLabel =
19
21
  viewMode === 'list' ? t('FileManager.view.list') : t('FileManager.view.masonry');
20
22
 
21
- const menuItems = useMemo<DropdownItem[]>(() => {
22
- return [
23
+ const menuItems: MenuProps['items'] = useMemo(
24
+ () => [
23
25
  {
24
26
  icon: <Icon icon={ListIcon} />,
25
27
  key: 'list',
26
28
  label: t('FileManager.view.list'),
27
29
  onClick: () => setViewMode('list'),
28
- style: viewMode === 'list' ? { backgroundColor: 'var(--ant-control-item-bg-active)' } : {},
29
30
  },
30
31
  {
31
32
  icon: <Icon icon={Grid3x3Icon} />,
32
33
  key: 'masonry',
33
34
  label: t('FileManager.view.masonry'),
34
35
  onClick: () => setViewMode('masonry'),
35
- style:
36
- viewMode === 'masonry' ? { backgroundColor: 'var(--ant-control-item-bg-active)' } : {},
37
36
  },
38
- ];
39
- }, [setViewMode, t, viewMode]);
37
+ ],
38
+ [setViewMode, t],
39
+ );
40
40
 
41
41
  return (
42
- <DropdownMenu items={menuItems} placement="bottomRight">
42
+ <Dropdown
43
+ arrow={false}
44
+ menu={{
45
+ items: menuItems,
46
+ selectable: true,
47
+ selectedKeys: [viewMode],
48
+ }}
49
+ placement="bottomRight"
50
+ >
43
51
  <ActionIconWithChevron icon={currentViewIcon} title={currentViewLabel} />
44
- </DropdownMenu>
52
+ </Dropdown>
45
53
  );
46
54
  });
47
55
 
@@ -1,14 +1,25 @@
1
- import { Flexbox, Text } from '@lobehub/ui';
1
+ import { ActionIcon, Flexbox, Text } from '@lobehub/ui';
2
2
  import { createStaticStyles } from 'antd-style';
3
+ import { XIcon } from 'lucide-react';
3
4
  import { type ReactNode, memo, useMemo } from 'react';
4
5
  import { useTranslation } from 'react-i18next';
5
6
 
6
7
  import FileIcon from '@/components/FileIcon';
8
+ import { useFileStore } from '@/store/file';
7
9
  import { type UploadFileItem } from '@/types/files/upload';
8
10
  import { formatSize, formatSpeed, formatTime } from '@/utils/format';
9
11
 
10
12
  const styles = createStaticStyles(({ css, cssVar }) => {
11
13
  return {
14
+ cancelButton: css`
15
+ opacity: 0;
16
+ transition: opacity 0.2s ease;
17
+ `,
18
+ container: css`
19
+ &:hover .cancel-button {
20
+ opacity: 1;
21
+ }
22
+ `,
12
23
  progress: css`
13
24
  position: absolute;
14
25
  inset-block: 0 0;
@@ -33,9 +44,10 @@ const styles = createStaticStyles(({ css, cssVar }) => {
33
44
 
34
45
  type UploadItemProps = UploadFileItem;
35
46
 
36
- const UploadItem = memo<UploadItemProps>(({ file, status, uploadState }) => {
47
+ const UploadItem = memo<UploadItemProps>(({ id, file, status, uploadState }) => {
37
48
  const { t } = useTranslation('file');
38
49
  const { type, name, size } = file;
50
+ const cancelUpload = useFileStore((s) => s.cancelUpload);
39
51
 
40
52
  const desc: ReactNode = useMemo(() => {
41
53
  switch (status) {
@@ -87,28 +99,48 @@ const UploadItem = memo<UploadItemProps>(({ file, status, uploadState }) => {
87
99
  </Text>
88
100
  );
89
101
  }
102
+ case 'cancelled': {
103
+ return (
104
+ <Text style={{ fontSize: 12 }} type={'warning'}>
105
+ {formatSize(size)} · {t('uploadDock.body.item.cancelled')}
106
+ </Text>
107
+ );
108
+ }
90
109
  default: {
91
110
  return '';
92
111
  }
93
112
  }
94
- }, [status, uploadState]);
113
+ }, [status, uploadState, size, t]);
95
114
 
96
115
  return (
97
116
  <Flexbox
98
117
  align={'center'}
99
- gap={4}
118
+ className={styles.container}
119
+ gap={12}
100
120
  horizontal
101
121
  key={name}
102
122
  paddingBlock={8}
103
123
  paddingInline={12}
104
124
  style={{ position: 'relative' }}
105
125
  >
106
- <FileIcon fileName={name} fileType={type} />
107
- <Flexbox style={{ overflow: 'hidden' }}>
126
+ <FileIcon fileName={name} fileType={type} size={36} />
127
+ <Flexbox flex={1} style={{ overflow: 'hidden' }}>
108
128
  <div className={styles.title}>{name}</div>
109
129
  {desc}
110
130
  </Flexbox>
111
131
 
132
+ {(status === 'uploading' || status === 'pending') && (
133
+ <ActionIcon
134
+ className={`${styles.cancelButton} cancel-button`}
135
+ icon={XIcon}
136
+ onClick={() => {
137
+ cancelUpload(id);
138
+ }}
139
+ size="small"
140
+ title={t('uploadDock.body.item.cancel')}
141
+ />
142
+ )}
143
+
112
144
  {status === 'uploading' && !!uploadState && (
113
145
  <div
114
146
  className={styles.progress}
@@ -3,6 +3,7 @@ import { ActionIcon, Center, Flexbox, Icon, Text } from '@lobehub/ui';
3
3
  import { createStaticStyles, cssVar } from 'antd-style';
4
4
  import isEqual from 'fast-deep-equal';
5
5
  import { UploadIcon, XIcon } from 'lucide-react';
6
+ import { AnimatePresence, motion } from 'motion/react';
6
7
  import { memo, useEffect, useMemo, useState } from 'react';
7
8
  import { useTranslation } from 'react-i18next';
8
9
 
@@ -132,49 +133,69 @@ const UploadDock = memo(() => {
132
133
  )}
133
134
  </Flexbox>
134
135
 
135
- {expand ? (
136
- <Flexbox
137
- justify={'space-between'}
138
- style={{
139
- background: `color-mix(in srgb, ${cssVar.colorBgLayout} 95%, white)`,
140
- borderBottomLeftRadius: 8,
141
- borderBottomRightRadius: 8,
142
- height: 400,
143
- }}
144
- >
145
- <Flexbox gap={8} paddingBlock={16} style={{ overflowY: 'scroll' }}>
146
- {fileList.map((item) => (
147
- <Item key={item.id} {...item} />
148
- ))}
149
- </Flexbox>
150
- <Center style={{ height: 40, minHeight: 40 }}>
151
- <Text
152
- onClick={() => {
153
- setExpand(false);
136
+ <AnimatePresence mode="wait">
137
+ {expand ? (
138
+ <motion.div
139
+ animate={{ height: 400, opacity: 1 }}
140
+ exit={{ height: 0, opacity: 0 }}
141
+ initial={{ height: 0, opacity: 0 }}
142
+ key="expanded"
143
+ style={{ overflow: 'hidden' }}
144
+ transition={{ duration: 0.3, ease: 'easeInOut' }}
145
+ >
146
+ <Flexbox
147
+ justify={'space-between'}
148
+ style={{
149
+ background: cssVar.colorBgContainer,
150
+ borderBottomLeftRadius: 8,
151
+ borderBottomRightRadius: 8,
152
+ height: 400,
154
153
  }}
155
- style={{ cursor: 'pointer' }}
156
- type={'secondary'}
157
154
  >
158
- {t('uploadDock.body.collapse')}
159
- </Text>
160
- </Center>
161
- </Flexbox>
162
- ) : (
163
- overviewUploadingStatus !== 'pending' && (
164
- <div
165
- className={styles.progress}
166
- style={{
167
- borderColor:
168
- overviewUploadingStatus === 'success'
169
- ? cssVar.colorSuccess
170
- : overviewUploadingStatus === 'error'
171
- ? cssVar.colorError
172
- : undefined,
173
- insetInlineEnd: `${100 - totalUploadingProgress}%`,
174
- }}
175
- />
176
- )
177
- )}
155
+ <Flexbox gap={8} paddingBlock={8} style={{ overflowY: 'scroll' }}>
156
+ {fileList.map((item) => (
157
+ <Item key={item.id} {...item} />
158
+ ))}
159
+ </Flexbox>
160
+ <Center style={{ height: 40, minHeight: 40 }}>
161
+ <Text
162
+ onClick={() => {
163
+ setExpand(false);
164
+ }}
165
+ style={{ cursor: 'pointer' }}
166
+ type={'secondary'}
167
+ >
168
+ {t('uploadDock.body.collapse')}
169
+ </Text>
170
+ </Center>
171
+ </Flexbox>
172
+ </motion.div>
173
+ ) : (
174
+ overviewUploadingStatus !== 'pending' && (
175
+ <motion.div
176
+ animate={{ opacity: 1, scaleY: 1 }}
177
+ exit={{ opacity: 0, scaleY: 0 }}
178
+ initial={{ opacity: 0, scaleY: 0 }}
179
+ key="collapsed"
180
+ style={{ originY: 0 }}
181
+ transition={{ duration: 0.2, ease: 'easeOut' }}
182
+ >
183
+ <div
184
+ className={styles.progress}
185
+ style={{
186
+ borderColor:
187
+ overviewUploadingStatus === 'success'
188
+ ? cssVar.colorSuccess
189
+ : overviewUploadingStatus === 'error'
190
+ ? cssVar.colorError
191
+ : undefined,
192
+ insetInlineEnd: `${100 - totalUploadingProgress}%`,
193
+ }}
194
+ />
195
+ </motion.div>
196
+ )
197
+ )}
198
+ </AnimatePresence>
178
199
  </Flexbox>
179
200
  );
180
201
  });
@@ -22,6 +22,7 @@ const useStyles = createStyles(({ css, token }) => {
22
22
  return {
23
23
  container: css`
24
24
  position: relative;
25
+ overflow: hidden;
25
26
  `,
26
27
  editorOverlay: css`
27
28
  position: absolute;
@@ -70,6 +70,9 @@ vi.mock('@/store/tool/selectors', () => ({
70
70
  klavisStoreSelectors: {
71
71
  klavisAsLobeTools: () => [],
72
72
  },
73
+ lobehubSkillStoreSelectors: {
74
+ lobehubSkillAsLobeTools: () => [],
75
+ },
73
76
  }));
74
77
 
75
78
  vi.mock('../isCanUseFC', () => ({
@@ -11,7 +11,11 @@ import type { LobeChatPluginManifest } from '@lobehub/chat-plugin-sdk';
11
11
  import { getAgentStoreState } from '@/store/agent';
12
12
  import { agentSelectors } from '@/store/agent/selectors';
13
13
  import { getToolStoreState } from '@/store/tool';
14
- import { klavisStoreSelectors, pluginSelectors } from '@/store/tool/selectors';
14
+ import {
15
+ klavisStoreSelectors,
16
+ lobehubSkillStoreSelectors,
17
+ pluginSelectors,
18
+ } from '@/store/tool/selectors';
15
19
 
16
20
  import { getSearchConfig } from '../getSearchConfig';
17
21
  import { isCanUseFC } from '../isCanUseFC';
@@ -51,11 +55,18 @@ export const createToolsEngine = (config: ToolsEngineConfig = {}): ToolsEngine =
51
55
  .map((tool) => tool.manifest as LobeChatPluginManifest)
52
56
  .filter(Boolean);
53
57
 
58
+ // Get LobeHub Skill tool manifests
59
+ const lobehubSkillTools = lobehubSkillStoreSelectors.lobehubSkillAsLobeTools(toolStoreState);
60
+ const lobehubSkillManifests = lobehubSkillTools
61
+ .map((tool) => tool.manifest as LobeChatPluginManifest)
62
+ .filter(Boolean);
63
+
54
64
  // Combine all manifests
55
65
  const allManifests = [
56
66
  ...pluginManifests,
57
67
  ...builtinManifests,
58
68
  ...klavisManifests,
69
+ ...lobehubSkillManifests,
59
70
  ...additionalManifests,
60
71
  ];
61
72