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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. package/.cursor/rules/microcopy-cn.mdc +75 -63
  2. package/.cursor/rules/microcopy-en.mdc +4 -8
  3. package/CHANGELOG.md +50 -0
  4. package/README.md +8 -8
  5. package/README.zh-CN.md +8 -8
  6. package/apps/desktop/src/main/core/browser/Browser.ts +6 -0
  7. package/apps/desktop/src/main/locales/default/common.ts +2 -2
  8. package/changelog/v1.json +10 -0
  9. package/docs/development/database-schema.dbml +4 -0
  10. package/e2e/CLAUDE.md +9 -8
  11. package/e2e/cucumber.config.js +1 -0
  12. package/e2e/src/features/page/README.md +118 -0
  13. package/e2e/src/features/page/crud.feature +62 -0
  14. package/e2e/src/features/page/editor-content.feature +93 -0
  15. package/e2e/src/features/page/editor-meta.feature +60 -0
  16. package/e2e/src/steps/agent/conversation.steps.ts +4 -4
  17. package/e2e/src/steps/home/sidebarAgent.steps.ts +91 -94
  18. package/e2e/src/steps/home/sidebarGroup.steps.ts +4 -4
  19. package/e2e/src/steps/hooks.ts +2 -0
  20. package/e2e/src/steps/page/editor-content.steps.ts +344 -0
  21. package/e2e/src/steps/page/editor-meta.steps.ts +410 -0
  22. package/e2e/src/steps/page/page-crud.steps.ts +363 -0
  23. package/e2e/src/support/world.ts +12 -0
  24. package/locales/ar/file.json +2 -0
  25. package/locales/bg-BG/file.json +2 -0
  26. package/locales/de-DE/file.json +2 -0
  27. package/locales/en-US/auth.json +1 -1
  28. package/locales/en-US/file.json +2 -0
  29. package/locales/en-US/metadata.json +2 -2
  30. package/locales/es-ES/file.json +2 -0
  31. package/locales/fa-IR/file.json +2 -0
  32. package/locales/fr-FR/file.json +2 -0
  33. package/locales/it-IT/file.json +2 -0
  34. package/locales/ja-JP/file.json +2 -0
  35. package/locales/ko-KR/file.json +2 -0
  36. package/locales/nl-NL/file.json +2 -0
  37. package/locales/pl-PL/file.json +2 -0
  38. package/locales/pt-BR/file.json +2 -0
  39. package/locales/ru-RU/file.json +2 -0
  40. package/locales/tr-TR/file.json +2 -0
  41. package/locales/vi-VN/file.json +2 -0
  42. package/locales/zh-CN/file.json +2 -0
  43. package/locales/zh-TW/file.json +2 -0
  44. package/package.json +1 -1
  45. package/packages/builtin-agents/src/agents/agent-builder/index.ts +1 -1
  46. package/packages/builtin-agents/src/agents/group-agent-builder/index.ts +1 -1
  47. package/packages/builtin-agents/src/agents/page-agent/index.ts +1 -1
  48. package/packages/const/src/settings/group.ts +0 -10
  49. package/packages/database/migrations/0068_update_group_data.sql +4 -0
  50. package/packages/database/migrations/meta/0068_snapshot.json +9588 -0
  51. package/packages/database/migrations/meta/_journal.json +7 -0
  52. package/packages/database/src/models/__tests__/chatGroup.test.ts +5 -7
  53. package/packages/database/src/models/__tests__/knowledgeBase.test.ts +185 -0
  54. package/packages/database/src/models/knowledgeBase.ts +67 -3
  55. package/packages/database/src/repositories/agentGroup/index.test.ts +23 -29
  56. package/packages/database/src/repositories/agentGroup/index.ts +4 -9
  57. package/packages/database/src/repositories/knowledge/index.ts +3 -3
  58. package/packages/database/src/schemas/chatGroup.ts +4 -3
  59. package/packages/database/src/types/chatGroup.ts +0 -7
  60. package/packages/types/src/agentGroup/index.ts +30 -9
  61. package/packages/utils/src/index.ts +1 -0
  62. package/packages/utils/src/platform.ts +35 -3
  63. package/src/app/[variants]/(desktop)/desktop-onboarding/_layout/index.tsx +30 -22
  64. package/src/app/[variants]/(desktop)/desktop-onboarding/_layout/style.ts +8 -5
  65. package/src/app/[variants]/(main)/_layout/DesktopLayoutContainer.tsx +2 -3
  66. package/src/app/[variants]/(main)/home/_layout/Body/Agent/ModalProvider.tsx +9 -32
  67. package/src/app/[variants]/(main)/home/_layout/hooks/useCreateMenuItems.tsx +3 -37
  68. package/src/app/[variants]/(main)/home/_layout/hooks/useSessionGroupMenuItems.tsx +7 -53
  69. package/src/app/[variants]/(main)/home/features/RecentPage/List.tsx +2 -1
  70. package/src/app/[variants]/(main)/resource/features/DndContextWrapper.tsx +1 -1
  71. package/src/app/[variants]/(main)/resource/library/_layout/Sidebar.tsx +2 -2
  72. package/src/app/[variants]/(main)/resource/library/features/LibraryMenu.tsx +2 -2
  73. package/src/app/[variants]/(mobile)/chat/settings/features/SettingButton.tsx +2 -12
  74. package/src/components/ChatGroupWizard/ChatGroupWizard.tsx +5 -27
  75. package/src/components/DragUpload/index.tsx +24 -27
  76. package/src/components/MemberSelectionModal/MemberSelectionModal.tsx +2 -11
  77. package/src/features/CommandMenu/useCommandMenu.ts +4 -14
  78. package/src/features/ElectronTitlebar/SimpleTitleBar.tsx +31 -0
  79. package/src/features/ElectronTitlebar/index.tsx +1 -0
  80. package/src/features/ResourceManager/components/Editor/index.tsx +2 -3
  81. package/src/features/ResourceManager/components/Explorer/Header/index.tsx +13 -17
  82. package/src/features/ResourceManager/components/Explorer/ItemDropdown/useFileItemDropdown.tsx +1 -1
  83. package/src/features/ResourceManager/components/Explorer/ListView/ListItem/TruncatedFileName.tsx +130 -0
  84. package/src/features/ResourceManager/components/Explorer/ListView/ListItem/index.tsx +36 -4
  85. package/src/features/ResourceManager/components/Explorer/ListView/Skeleton.tsx +4 -3
  86. package/src/features/ResourceManager/components/Explorer/ListView/index.tsx +58 -2
  87. package/src/features/ResourceManager/components/Explorer/MasonryView/index.tsx +58 -6
  88. package/src/features/ResourceManager/components/Explorer/MoveToFolderModal.tsx +2 -5
  89. package/src/features/ResourceManager/components/Explorer/ToolBar/BatchActionsDropdown.tsx +9 -5
  90. package/src/features/ResourceManager/components/Explorer/index.tsx +11 -56
  91. package/src/features/ResourceManager/components/Header/AddButton.tsx +5 -6
  92. package/src/features/ResourceManager/components/LibraryHierarchy/HierarchyNode.tsx +382 -0
  93. package/src/features/ResourceManager/components/LibraryHierarchy/index.tsx +396 -0
  94. package/src/features/ResourceManager/components/LibraryHierarchy/styles.ts +19 -0
  95. package/src/features/ResourceManager/components/LibraryHierarchy/treeState.ts +178 -0
  96. package/src/features/ResourceManager/components/LibraryHierarchy/types.ts +10 -0
  97. package/src/features/ResourceManager/index.tsx +3 -0
  98. package/src/layout/GlobalProvider/GroupWizardProvider.tsx +6 -29
  99. package/src/locales/default/auth.ts +1 -1
  100. package/src/locales/default/file.ts +2 -0
  101. package/src/locales/default/metadata.ts +2 -2
  102. package/src/server/modules/AgentRuntime/AgentRuntimeCoordinator.ts +30 -30
  103. package/src/server/modules/AgentRuntime/AgentStateManager.ts +23 -23
  104. package/src/server/modules/AgentRuntime/InMemoryAgentStateManager.ts +16 -16
  105. package/src/server/modules/AgentRuntime/InMemoryStreamEventManager.ts +13 -13
  106. package/src/server/modules/AgentRuntime/RuntimeExecutors.ts +2 -2
  107. package/src/server/modules/AgentRuntime/StreamEventManager.ts +18 -18
  108. package/src/server/modules/AgentRuntime/types.ts +21 -21
  109. package/src/server/routers/lambda/__tests__/agentGroup.test.ts +8 -8
  110. package/src/server/routers/lambda/agentGroup.ts +10 -12
  111. package/src/server/services/document/index.ts +1 -0
  112. package/src/store/agentGroup/slices/curd.test.ts +4 -4
  113. package/src/store/file/slices/fileManager/action.ts +12 -4
  114. package/src/store/home/slices/homeInput/action.ts +0 -3
  115. package/src/store/session/slices/session/action.ts +5 -9
  116. package/src/utils/platform.ts +2 -0
  117. package/src/app/[variants]/(mobile)/chat/settings/features/AgentTeamSettings/index.tsx +0 -95
  118. package/src/features/GroupChatSettings/AgentCard.tsx +0 -154
  119. package/src/features/GroupChatSettings/AgentTeamChatSettings.tsx +0 -179
  120. package/src/features/GroupChatSettings/AgentTeamMembersSettings.tsx +0 -244
  121. package/src/features/GroupChatSettings/AgentTeamMetaSettings.tsx +0 -94
  122. package/src/features/GroupChatSettings/AgentTeamSettings.tsx +0 -54
  123. package/src/features/GroupChatSettings/GroupCategory/index.tsx +0 -30
  124. package/src/features/GroupChatSettings/GroupCategory/useGroupCategory.tsx +0 -42
  125. package/src/features/GroupChatSettings/GroupChatSettingsProvider.tsx +0 -19
  126. package/src/features/GroupChatSettings/HostMemberCard.tsx +0 -113
  127. package/src/features/GroupChatSettings/StoreUpdater.tsx +0 -34
  128. package/src/features/GroupChatSettings/hooks/useGroupChatSettings.ts +0 -25
  129. package/src/features/GroupChatSettings/index.ts +0 -16
  130. package/src/features/GroupChatSettings/store/action.ts +0 -105
  131. package/src/features/GroupChatSettings/store/index.ts +0 -18
  132. package/src/features/GroupChatSettings/store/initialState.ts +0 -23
  133. package/src/features/GroupChatSettings/store/selectors.ts +0 -13
  134. package/src/features/ResourceManager/components/Tree/index.tsx +0 -883
  135. /package/src/features/ResourceManager/components/{Tree → LibraryHierarchy}/TreeSkeleton.tsx +0 -0
@@ -0,0 +1,382 @@
1
+ 'use client';
2
+
3
+ import { CaretDownFilled, LoadingOutlined } from '@ant-design/icons';
4
+ import { ActionIcon, Block, Flexbox, Icon, showContextMenu } from '@lobehub/ui';
5
+ import { App, Input } from 'antd';
6
+ import { cx } from 'antd-style';
7
+ import { FileText, FolderIcon, FolderOpenIcon } from 'lucide-react';
8
+ import * as motion from 'motion/react-m';
9
+ import React, { memo, useCallback, useMemo, useRef, useState } from 'react';
10
+ import { useNavigate } from 'react-router-dom';
11
+
12
+ import {
13
+ getTransparentDragImage,
14
+ useDragActive,
15
+ useDragState,
16
+ } from '@/app/[variants]/(main)/resource/features/DndContextWrapper';
17
+ import { useFolderPath } from '@/app/[variants]/(main)/resource/features/hooks/useFolderPath';
18
+ import { useResourceManagerStore } from '@/app/[variants]/(main)/resource/features/store';
19
+ import FileIcon from '@/components/FileIcon';
20
+ import { useFileStore } from '@/store/file';
21
+
22
+ import { useFileItemDropdown } from '../Explorer/ItemDropdown/useFileItemDropdown';
23
+ import { styles } from './styles';
24
+ import { clearTreeFolderCache } from './treeState';
25
+ import type { TreeItem } from './types';
26
+
27
+ interface HierarchyNodeProps {
28
+ expandedFolders: Set<string>;
29
+ folderChildrenCache: Map<string, TreeItem[]>;
30
+ item: TreeItem;
31
+ level?: number;
32
+ loadingFolders: Set<string>;
33
+ onLoadFolder: (_: string) => Promise<void>;
34
+ onToggleFolder: (_: string) => void;
35
+ selectedKey: string | null;
36
+ updateKey?: number;
37
+ }
38
+
39
+ // Row component for folder / file tree (virtualized by flattening visible nodes)
40
+ export const HierarchyNode = memo<HierarchyNodeProps>(
41
+ ({
42
+ item,
43
+ level = 0,
44
+ expandedFolders,
45
+ loadingFolders,
46
+ onToggleFolder,
47
+ onLoadFolder,
48
+ selectedKey,
49
+ folderChildrenCache,
50
+ }) => {
51
+ const navigate = useNavigate();
52
+ const { currentFolderSlug } = useFolderPath();
53
+ const { message } = App.useApp();
54
+
55
+ const [setMode, setCurrentViewItemId, libraryId] = useResourceManagerStore((s) => [
56
+ s.setMode,
57
+ s.setCurrentViewItemId,
58
+ s.libraryId,
59
+ ]);
60
+
61
+ const renameFolder = useFileStore((s) => s.renameFolder);
62
+
63
+ const [isRenaming, setIsRenaming] = useState(false);
64
+ const [renamingValue, setRenamingValue] = useState(item.name);
65
+ const inputRef = useRef<any>(null);
66
+
67
+ // Memoize computed values that don't change frequently
68
+ const { itemKey } = useMemo(
69
+ () => ({
70
+ itemKey: item.slug || item.id,
71
+ }),
72
+ [item.slug, item.id],
73
+ );
74
+
75
+ const handleRenameStart = useCallback(() => {
76
+ setIsRenaming(true);
77
+ setRenamingValue(item.name);
78
+ // Focus input after render
79
+ setTimeout(() => {
80
+ inputRef.current?.focus();
81
+ inputRef.current?.select();
82
+ }, 0);
83
+ }, [item.name]);
84
+
85
+ const handleRenameConfirm = useCallback(async () => {
86
+ if (!renamingValue.trim()) {
87
+ message.error('Folder name cannot be empty');
88
+ return;
89
+ }
90
+
91
+ if (renamingValue.trim() === item.name) {
92
+ setIsRenaming(false);
93
+ return;
94
+ }
95
+
96
+ try {
97
+ await renameFolder(item.id, renamingValue.trim());
98
+ if (libraryId) {
99
+ await clearTreeFolderCache(libraryId);
100
+ }
101
+ message.success('Renamed successfully');
102
+ setIsRenaming(false);
103
+ } catch (error) {
104
+ console.error('Rename error:', error);
105
+ message.error('Rename failed');
106
+ }
107
+ }, [item.id, item.name, libraryId, renamingValue, renameFolder, message]);
108
+
109
+ const handleRenameCancel = useCallback(() => {
110
+ setIsRenaming(false);
111
+ setRenamingValue(item.name);
112
+ }, [item.name]);
113
+
114
+ const { menuItems } = useFileItemDropdown({
115
+ fileType: item.fileType,
116
+ filename: item.name,
117
+ id: item.id,
118
+ libraryId,
119
+ onRenameStart: item.isFolder ? handleRenameStart : undefined,
120
+ sourceType: item.sourceType,
121
+ url: item.url,
122
+ });
123
+
124
+ const isDragActive = useDragActive();
125
+ const { setCurrentDrag } = useDragState();
126
+ const [isDragging, setIsDragging] = useState(false);
127
+ const [isOver, setIsOver] = useState(false);
128
+
129
+ // Memoize drag data to prevent recreation
130
+ const dragData = useMemo(
131
+ () => ({
132
+ fileType: item.fileType,
133
+ isFolder: item.isFolder,
134
+ name: item.name,
135
+ sourceType: item.sourceType,
136
+ }),
137
+ [item.fileType, item.isFolder, item.name, item.sourceType],
138
+ );
139
+
140
+ // Native HTML5 drag event handlers
141
+ const handleDragStart = useCallback(
142
+ (e: React.DragEvent<HTMLDivElement>) => {
143
+ setIsDragging(true);
144
+ setCurrentDrag({
145
+ data: dragData,
146
+ id: item.id,
147
+ type: item.isFolder ? 'folder' : 'file',
148
+ });
149
+
150
+ // Set drag image to be transparent (we use custom overlay)
151
+ const img = getTransparentDragImage();
152
+ if (img && e.dataTransfer) {
153
+ e.dataTransfer.setDragImage(img, 0, 0);
154
+ }
155
+ if (e.dataTransfer) {
156
+ e.dataTransfer.effectAllowed = 'move';
157
+ }
158
+ },
159
+ [dragData, item.id, item.isFolder, setCurrentDrag],
160
+ );
161
+
162
+ const handleDragEnd = useCallback(() => {
163
+ setIsDragging(false);
164
+ }, []);
165
+
166
+ const handleDragOver = useCallback(
167
+ (e: React.DragEvent<HTMLDivElement>) => {
168
+ if (!item.isFolder || !isDragActive) return;
169
+
170
+ e.preventDefault();
171
+ e.stopPropagation();
172
+ setIsOver(true);
173
+ },
174
+ [item.isFolder, isDragActive],
175
+ );
176
+
177
+ const handleDragLeave = useCallback(() => {
178
+ setIsOver(false);
179
+ }, []);
180
+
181
+ const handleDrop = useCallback(() => {
182
+ // Clear the highlight after drop
183
+ setIsOver(false);
184
+ }, []);
185
+
186
+ const handleItemClick = useCallback(() => {
187
+ // Open file modal using slug-based routing
188
+ const currentPath = currentFolderSlug
189
+ ? `/resource/library/${libraryId}/${currentFolderSlug}`
190
+ : `/resource/library/${libraryId}`;
191
+
192
+ setCurrentViewItemId(itemKey);
193
+ navigate(`${currentPath}?file=${itemKey}`);
194
+
195
+ if (itemKey.startsWith('doc')) {
196
+ setMode('page');
197
+ } else {
198
+ // Set mode to 'file' immediately to prevent flickering to list view
199
+ setMode('editor');
200
+ }
201
+ }, [itemKey, currentFolderSlug, libraryId, navigate, setMode, setCurrentViewItemId]);
202
+
203
+ const handleFolderClick = useCallback(
204
+ (folderId: string, folderSlug?: string | null) => {
205
+ const navKey = folderSlug || folderId;
206
+ navigate(`/resource/library/${libraryId}/${navKey}`);
207
+
208
+ setMode('explorer');
209
+ },
210
+ [libraryId, navigate],
211
+ );
212
+
213
+ if (item.isFolder) {
214
+ const isExpanded = expandedFolders.has(itemKey);
215
+ const isActive = selectedKey === itemKey;
216
+ const isLoading = loadingFolders.has(itemKey);
217
+
218
+ const handleToggle = async () => {
219
+ // Toggle folder expansion
220
+ onToggleFolder(itemKey);
221
+
222
+ // Only load if not already cached
223
+ if (!isExpanded && !folderChildrenCache.has(itemKey)) {
224
+ await onLoadFolder(itemKey);
225
+ }
226
+ };
227
+
228
+ return (
229
+ <Flexbox gap={2}>
230
+ <Block
231
+ align={'center'}
232
+ className={cx(
233
+ styles.treeItem,
234
+ isOver && styles.fileItemDragOver,
235
+ isDragging && styles.dragging,
236
+ )}
237
+ clickable
238
+ data-drop-target-id={item.id}
239
+ data-is-folder={String(item.isFolder)}
240
+ draggable
241
+ gap={8}
242
+ height={36}
243
+ horizontal
244
+ onClick={() => handleFolderClick(item.id, item.slug)}
245
+ onContextMenu={(e) => {
246
+ e.preventDefault();
247
+ showContextMenu(menuItems());
248
+ }}
249
+ onDragEnd={handleDragEnd}
250
+ onDragLeave={handleDragLeave}
251
+ onDragOver={handleDragOver}
252
+ onDragStart={handleDragStart}
253
+ onDrop={handleDrop}
254
+ paddingInline={4}
255
+ style={{
256
+ paddingInlineStart: level * 12 + 4,
257
+ }}
258
+ variant={isActive ? 'filled' : 'borderless'}
259
+ >
260
+ {isLoading ? (
261
+ <ActionIcon icon={LoadingOutlined as any} size={'small'} spin style={{ width: 20 }} />
262
+ ) : (
263
+ <motion.div
264
+ animate={{ rotate: isExpanded ? 0 : -90 }}
265
+ initial={false}
266
+ transition={{ duration: 0.2, ease: 'easeInOut' }}
267
+ >
268
+ <ActionIcon
269
+ icon={CaretDownFilled as any}
270
+ onClick={(e) => {
271
+ e.stopPropagation();
272
+ handleToggle();
273
+ }}
274
+ size={'small'}
275
+ style={{ width: 20 }}
276
+ />
277
+ </motion.div>
278
+ )}
279
+ <Flexbox
280
+ align={'center'}
281
+ flex={1}
282
+ gap={8}
283
+ horizontal
284
+ style={{ minHeight: 28, minWidth: 0, overflow: 'hidden' }}
285
+ >
286
+ <Icon icon={isExpanded ? FolderOpenIcon : FolderIcon} size={18} />
287
+ {isRenaming ? (
288
+ <Input
289
+ onBlur={handleRenameConfirm}
290
+ onChange={(e) => setRenamingValue(e.target.value)}
291
+ onClick={(e) => e.stopPropagation()}
292
+ onKeyDown={(e) => {
293
+ if (e.key === 'Enter') {
294
+ e.preventDefault();
295
+ handleRenameConfirm();
296
+ } else if (e.key === 'Escape') {
297
+ e.preventDefault();
298
+ handleRenameCancel();
299
+ }
300
+ }}
301
+ onPointerDown={(e) => e.stopPropagation()}
302
+ ref={inputRef}
303
+ size="small"
304
+ style={{ flex: 1 }}
305
+ value={renamingValue}
306
+ />
307
+ ) : (
308
+ <span
309
+ style={{
310
+ flex: 1,
311
+ overflow: 'hidden',
312
+ textOverflow: 'ellipsis',
313
+ whiteSpace: 'nowrap',
314
+ }}
315
+ >
316
+ {item.name}
317
+ </span>
318
+ )}
319
+ </Flexbox>
320
+ </Block>
321
+ </Flexbox>
322
+ );
323
+ }
324
+
325
+ // Render as file
326
+ const isActive = selectedKey === itemKey;
327
+ return (
328
+ <Flexbox gap={2}>
329
+ <Block
330
+ align={'center'}
331
+ className={cx(styles.treeItem, isDragging && styles.dragging)}
332
+ clickable
333
+ data-drop-target-id={item.id}
334
+ data-is-folder={false}
335
+ draggable
336
+ gap={8}
337
+ height={36}
338
+ horizontal
339
+ onClick={handleItemClick}
340
+ onContextMenu={(e) => {
341
+ e.preventDefault();
342
+ showContextMenu(menuItems());
343
+ }}
344
+ onDragEnd={handleDragEnd}
345
+ onDragStart={handleDragStart}
346
+ paddingInline={4}
347
+ style={{
348
+ paddingInlineStart: level * 12 + 4,
349
+ }}
350
+ variant={isActive ? 'filled' : 'borderless'}
351
+ >
352
+ <div style={{ width: 20 }} />
353
+ <Flexbox
354
+ align={'center'}
355
+ flex={1}
356
+ gap={8}
357
+ horizontal
358
+ style={{ minHeight: 28, minWidth: 0, overflow: 'hidden' }}
359
+ >
360
+ {item.sourceType === 'document' ? (
361
+ <Icon icon={FileText} size={18} />
362
+ ) : (
363
+ <FileIcon fileName={item.name} fileType={item.fileType} size={18} />
364
+ )}
365
+ <span
366
+ style={{
367
+ flex: 1,
368
+ overflow: 'hidden',
369
+ textOverflow: 'ellipsis',
370
+ whiteSpace: 'nowrap',
371
+ }}
372
+ >
373
+ {item.name}
374
+ </span>
375
+ </Flexbox>
376
+ </Block>
377
+ </Flexbox>
378
+ );
379
+ },
380
+ );
381
+
382
+ HierarchyNode.displayName = 'HierarchyNode';