@lobehub/lobehub 2.0.0-next.295 → 2.0.0-next.297

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 (132) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/changelog/v1.json +18 -0
  3. package/locales/en-US/plugin.json +4 -0
  4. package/locales/zh-CN/plugin.json +4 -0
  5. package/package.json +2 -2
  6. package/packages/agent-runtime/src/core/__tests__/runtime.test.ts +5 -5
  7. package/packages/agent-runtime/src/utils/stepContextComputer.test.ts +5 -5
  8. package/packages/builtin-tool-gtd/src/client/Inspector/index.ts +0 -4
  9. package/packages/builtin-tool-gtd/src/client/Intervention/AddTodo.tsx +1 -1
  10. package/packages/builtin-tool-gtd/src/client/Render/TodoList/index.tsx +39 -10
  11. package/packages/builtin-tool-gtd/src/client/Render/index.ts +0 -2
  12. package/packages/builtin-tool-gtd/src/client/components/SortableTodoList/TodoItemRow.tsx +26 -12
  13. package/packages/builtin-tool-gtd/src/client/components/SortableTodoList/store/actions.ts +5 -5
  14. package/packages/builtin-tool-gtd/src/client/components/SortableTodoList/store/store.test.ts +14 -8
  15. package/packages/builtin-tool-gtd/src/executor/index.test.ts +48 -227
  16. package/packages/builtin-tool-gtd/src/executor/index.ts +15 -158
  17. package/packages/builtin-tool-gtd/src/manifest.ts +12 -42
  18. package/packages/builtin-tool-gtd/src/systemRole.ts +14 -8
  19. package/packages/builtin-tool-gtd/src/types.ts +47 -41
  20. package/packages/builtin-tool-memory/package.json +8 -0
  21. package/packages/builtin-tool-memory/src/client/Inspector/AddContextMemory/index.tsx +60 -0
  22. package/packages/builtin-tool-memory/src/client/Inspector/AddExperienceMemory/index.tsx +60 -0
  23. package/packages/builtin-tool-memory/src/client/Inspector/AddIdentityMemory/index.tsx +60 -0
  24. package/packages/builtin-tool-memory/src/client/Inspector/AddPreferenceMemory/index.tsx +60 -0
  25. package/packages/builtin-tool-memory/src/client/Inspector/RemoveIdentityMemory/index.tsx +60 -0
  26. package/packages/builtin-tool-memory/src/client/Inspector/SearchUserMemory/index.tsx +67 -0
  27. package/packages/builtin-tool-memory/src/client/Inspector/UpdateIdentityMemory/index.tsx +60 -0
  28. package/packages/builtin-tool-memory/src/client/Inspector/index.ts +35 -0
  29. package/packages/builtin-tool-memory/src/client/Intervention/AddExperienceMemory/index.tsx +17 -0
  30. package/packages/builtin-tool-memory/src/client/Intervention/index.ts +13 -0
  31. package/packages/builtin-tool-memory/src/client/Render/AddExperienceMemory/index.tsx +17 -0
  32. package/packages/builtin-tool-memory/src/client/Render/SearchUserMemory/index.tsx +217 -0
  33. package/packages/builtin-tool-memory/src/client/Render/index.ts +15 -0
  34. package/packages/builtin-tool-memory/src/client/Streaming/AddExperienceMemory/index.tsx +17 -0
  35. package/packages/builtin-tool-memory/src/client/Streaming/index.ts +18 -0
  36. package/packages/builtin-tool-memory/src/client/components/ExperienceMemoryCard.tsx +231 -0
  37. package/packages/builtin-tool-memory/src/client/components/index.ts +1 -0
  38. package/packages/builtin-tool-memory/src/client/index.ts +27 -0
  39. package/packages/builtin-tool-memory/src/executor/index.ts +9 -1
  40. package/packages/builtin-tool-memory/src/types.ts +61 -0
  41. package/packages/context-engine/src/providers/GTDTodoInjector.ts +15 -7
  42. package/packages/conversation-flow/src/__tests__/fixtures/outputs/assistantGroup/tools-with-branches.json +4 -0
  43. package/packages/conversation-flow/src/transformation/FlatListBuilder.ts +1 -0
  44. package/packages/prompts/src/prompts/gtd/index.test.ts +32 -16
  45. package/packages/prompts/src/prompts/gtd/index.ts +9 -5
  46. package/packages/types/package.json +1 -1
  47. package/packages/types/src/discover/assistants.ts +4 -0
  48. package/packages/types/src/discover/groupAgents.ts +196 -0
  49. package/packages/types/src/discover/index.ts +5 -1
  50. package/packages/types/src/stepContext.ts +4 -1
  51. package/src/app/[variants]/(main)/community/(detail)/assistant/features/Details/Versions/index.tsx +2 -2
  52. package/src/app/[variants]/(main)/community/(detail)/group_agent/features/DetailProvider.tsx +19 -0
  53. package/src/app/[variants]/(main)/community/(detail)/group_agent/features/Details/Members/index.tsx +137 -0
  54. package/src/app/[variants]/(main)/community/(detail)/group_agent/features/Details/Nav.tsx +88 -0
  55. package/src/app/[variants]/(main)/community/(detail)/group_agent/features/Details/Overview/index.tsx +213 -0
  56. package/src/app/[variants]/(main)/community/(detail)/group_agent/features/Details/Related/index.tsx +85 -0
  57. package/src/app/[variants]/(main)/community/(detail)/group_agent/features/Details/SystemRole/TagList.tsx +20 -0
  58. package/src/app/[variants]/(main)/community/(detail)/group_agent/features/Details/SystemRole/index.tsx +71 -0
  59. package/src/app/[variants]/(main)/community/(detail)/group_agent/features/Details/Versions/index.tsx +119 -0
  60. package/src/app/[variants]/(main)/community/(detail)/group_agent/features/Details/index.tsx +51 -0
  61. package/src/app/[variants]/(main)/community/(detail)/group_agent/features/Header.tsx +253 -0
  62. package/src/app/[variants]/(main)/community/(detail)/group_agent/features/Sidebar/ActionButton/AddGroupAgent.tsx +222 -0
  63. package/src/app/[variants]/(main)/community/(detail)/group_agent/features/Sidebar/ActionButton/index.tsx +34 -0
  64. package/src/app/[variants]/(main)/community/(detail)/group_agent/features/Sidebar/Summary/index.tsx +42 -0
  65. package/src/app/[variants]/(main)/community/(detail)/group_agent/features/Sidebar/index.tsx +41 -0
  66. package/src/app/[variants]/(main)/community/(detail)/group_agent/features/StatusPage/index.tsx +104 -0
  67. package/src/app/[variants]/(main)/community/(detail)/group_agent/index.tsx +103 -0
  68. package/src/app/[variants]/(main)/community/(detail)/group_agent/loading.tsx +1 -0
  69. package/src/app/[variants]/(main)/community/(detail)/user/features/DetailProvider.tsx +7 -1
  70. package/src/app/[variants]/(main)/community/(detail)/user/features/UserContent.tsx +2 -0
  71. package/src/app/[variants]/(main)/community/(detail)/user/features/UserGroupCard.tsx +186 -0
  72. package/src/app/[variants]/(main)/community/(detail)/user/features/UserGroupList.tsx +59 -0
  73. package/src/app/[variants]/(main)/community/(detail)/user/index.tsx +3 -1
  74. package/src/app/[variants]/(main)/community/(list)/assistant/features/List/Item.tsx +26 -8
  75. package/src/app/[variants]/(main)/community/(list)/assistant/index.tsx +1 -0
  76. package/src/app/[variants]/(main)/community/features/Search.tsx +1 -1
  77. package/src/app/[variants]/(main)/group/profile/features/GroupProfile/index.tsx +2 -0
  78. package/src/app/[variants]/(main)/group/profile/features/Header/AgentPublishButton/PublishResultModal.tsx +2 -1
  79. package/src/app/[variants]/(main)/group/profile/features/Header/GroupPublishButton/GroupForkConfirmModal.tsx +60 -0
  80. package/src/app/[variants]/(main)/group/profile/features/Header/GroupPublishButton/GroupPublishResultModal.tsx +62 -0
  81. package/src/app/[variants]/(main)/group/profile/features/Header/GroupPublishButton/PublishButton.tsx +122 -0
  82. package/src/app/[variants]/(main)/group/profile/features/Header/GroupPublishButton/index.tsx +46 -0
  83. package/src/app/[variants]/(main)/group/profile/features/Header/GroupPublishButton/types.ts +12 -0
  84. package/src/app/[variants]/(main)/group/profile/features/Header/GroupPublishButton/useMarketGroupPublish.ts +211 -0
  85. package/src/app/[variants]/(main)/group/profile/features/Header/GroupPublishButton/utils.ts +22 -0
  86. package/src/app/[variants]/(main)/resource/features/DndContextWrapper.tsx +4 -2
  87. package/src/app/[variants]/(main)/resource/library/_layout/Header/LibraryHead.tsx +30 -35
  88. package/src/app/[variants]/(main)/resource/library/_layout/Header/index.tsx +9 -11
  89. package/src/app/[variants]/router/desktopRouter.config.tsx +7 -0
  90. package/src/features/Conversation/Messages/AssistantGroup/Tool/Actions/index.tsx +11 -17
  91. package/src/features/Conversation/Messages/AssistantGroup/Tool/{Render → Detail}/LoadingPlaceholder/index.tsx +13 -3
  92. package/src/features/Conversation/Messages/AssistantGroup/Tool/Detail/Render/CustomRender.tsx +43 -0
  93. package/src/features/Conversation/Messages/AssistantGroup/Tool/Detail/Render/FallbacktArgumentRender.tsx +59 -0
  94. package/src/features/Conversation/Messages/AssistantGroup/Tool/Detail/Render/index.tsx +46 -0
  95. package/src/features/Conversation/Messages/AssistantGroup/Tool/{Render → Detail}/index.tsx +13 -19
  96. package/src/features/Conversation/Messages/AssistantGroup/Tool/index.tsx +17 -17
  97. package/src/features/Conversation/Messages/Tool/Tool/index.tsx +10 -9
  98. package/src/features/Conversation/TodoProgress/index.tsx +56 -23
  99. package/src/features/PluginsUI/Render/MCPType/index.tsx +1 -1
  100. package/src/features/ResourceManager/components/Explorer/Header/index.tsx +57 -4
  101. package/src/features/ResourceManager/components/Explorer/ListView/ListItem/index.tsx +6 -4
  102. package/src/features/ResourceManager/components/Explorer/ListView/index.tsx +16 -5
  103. package/src/features/ResourceManager/components/LibraryHierarchy/styles.ts +5 -4
  104. package/src/hooks/useActiveTabKey.ts +1 -2
  105. package/src/locales/default/plugin.ts +1 -0
  106. package/src/locales/default/setting.ts +12 -0
  107. package/src/server/routers/lambda/market/agentGroup.ts +296 -0
  108. package/src/server/routers/lambda/market/index.ts +134 -4
  109. package/src/server/services/discover/index.ts +123 -7
  110. package/src/services/discover.ts +55 -0
  111. package/src/store/chat/slices/message/selectors/dbMessage.test.ts +11 -11
  112. package/src/store/discover/slices/groupAgent/action.ts +80 -0
  113. package/src/store/discover/store.ts +3 -0
  114. package/src/store/file/slices/resource/action.ts +4 -2
  115. package/src/tools/inspectors.ts +2 -0
  116. package/src/tools/interventions.ts +2 -0
  117. package/src/tools/renders.ts +3 -1
  118. package/src/tools/streamings.ts +2 -0
  119. package/packages/builtin-tool-gtd/src/client/Inspector/CompleteTodos/index.tsx +0 -52
  120. package/packages/builtin-tool-gtd/src/client/Inspector/RemoveTodos/index.tsx +0 -52
  121. package/src/features/Conversation/Messages/AssistantGroup/Tool/Render/CustomRender.tsx +0 -113
  122. package/src/features/Conversation/Messages/Tool/Tool/Render.tsx +0 -47
  123. /package/src/features/Conversation/Messages/AssistantGroup/Tool/{Render → Detail}/AbortResponse.tsx +0 -0
  124. /package/src/features/Conversation/Messages/AssistantGroup/Tool/{Render → Detail}/Arguments/index.tsx +0 -0
  125. /package/src/features/Conversation/Messages/AssistantGroup/Tool/{Render → Detail}/ErrorResponse.tsx +0 -0
  126. /package/src/features/Conversation/Messages/AssistantGroup/Tool/{Render → Detail}/Intervention/ApprovalActions.tsx +0 -0
  127. /package/src/features/Conversation/Messages/AssistantGroup/Tool/{Render → Detail}/Intervention/Fallback.tsx +0 -0
  128. /package/src/features/Conversation/Messages/AssistantGroup/Tool/{Render → Detail}/Intervention/KeyValueEditor.tsx +0 -0
  129. /package/src/features/Conversation/Messages/AssistantGroup/Tool/{Render → Detail}/Intervention/ModeSelector.tsx +0 -0
  130. /package/src/features/Conversation/Messages/AssistantGroup/Tool/{Render → Detail}/Intervention/index.tsx +0 -0
  131. /package/src/features/Conversation/Messages/AssistantGroup/Tool/{Render → Detail}/PluginSettings.tsx +0 -0
  132. /package/src/features/Conversation/Messages/AssistantGroup/Tool/{Render → Detail}/RejectedResponse.tsx +0 -0
@@ -3,7 +3,7 @@
3
3
  import { type StepContextTodos } from '@lobechat/types';
4
4
  import { Checkbox, Flexbox, Icon, Tag } from '@lobehub/ui';
5
5
  import { createStaticStyles, cssVar, cx } from 'antd-style';
6
- import { ChevronDown, ChevronUp, ListTodo } from 'lucide-react';
6
+ import { ChevronDown, ChevronUp, CircleArrowRight, ListTodo } from 'lucide-react';
7
7
  import { memo, useMemo, useState } from 'react';
8
8
  import { useTranslation } from 'react-i18next';
9
9
 
@@ -73,6 +73,11 @@ const styles = createStaticStyles(({ css, cssVar }) => ({
73
73
  opacity 0.2s ${cssVar.motionEaseInOut},
74
74
  padding 0.2s ${cssVar.motionEaseInOut};
75
75
  `,
76
+ processingRow: css`
77
+ display: flex;
78
+ gap: 6px;
79
+ align-items: center;
80
+ `,
76
81
  progress: css`
77
82
  flex: 1;
78
83
  height: 4px;
@@ -85,10 +90,16 @@ const styles = createStaticStyles(({ css, cssVar }) => ({
85
90
  background: ${cssVar.colorSuccess};
86
91
  transition: width 0.3s ${cssVar.motionEaseInOut};
87
92
  `,
88
- textChecked: css`
93
+ textCompleted: css`
89
94
  color: ${cssVar.colorTextQuaternary};
90
95
  text-decoration: line-through;
91
96
  `,
97
+ textProcessing: css`
98
+ color: ${cssVar.colorText};
99
+ `,
100
+ textTodo: css`
101
+ color: ${cssVar.colorTextSecondary};
102
+ `,
92
103
  }));
93
104
 
94
105
  interface TodoProgressProps {
@@ -112,11 +123,13 @@ const TodoProgress = memo<TodoProgressProps>(({ className }) => {
112
123
  // Calculate progress
113
124
  const items = todos?.items || [];
114
125
  const total = items.length;
115
- const completed = items.filter((item) => item.completed).length;
126
+ const completed = items.filter((item) => item.status === 'completed').length;
116
127
  const progressPercent = total > 0 ? (completed / total) * 100 : 0;
117
128
 
118
- // Find current pending task (first incomplete item)
119
- const currentPendingTask = items.find((item) => !item.completed);
129
+ // Find current pending task (first non-completed item, prioritize processing)
130
+ const currentPendingTask =
131
+ items.find((item) => item.status === 'processing') ||
132
+ items.find((item) => item.status === 'todo');
120
133
 
121
134
  // Don't render if no todos
122
135
  if (total === 0) return null;
@@ -156,24 +169,44 @@ const TodoProgress = memo<TodoProgressProps>(({ className }) => {
156
169
 
157
170
  {/* Expandable Todo List */}
158
171
  <div className={cx(styles.listContainer, expanded ? styles.expanded : styles.collapsed)}>
159
- {items.map((item, index) => (
160
- <Checkbox
161
- backgroundColor={cssVar.colorSuccess}
162
- checked={item.completed}
163
- classNames={{
164
- text: item.completed ? styles.textChecked : undefined,
165
- wrapper: styles.itemRow,
166
- }}
167
- key={index}
168
- shape="circle"
169
- style={{ borderWidth: 1.5, cursor: 'default', pointerEvents: 'none' }}
170
- textProps={{
171
- type: item.completed ? 'secondary' : undefined,
172
- }}
173
- >
174
- {item.text}
175
- </Checkbox>
176
- ))}
172
+ {items.map((item, index) => {
173
+ const isCompleted = item.status === 'completed';
174
+ const isProcessing = item.status === 'processing';
175
+
176
+ // Processing state uses CircleArrowRight icon
177
+ if (isProcessing) {
178
+ return (
179
+ <div className={cx(styles.itemRow, styles.processingRow)} key={index}>
180
+ <Icon
181
+ icon={CircleArrowRight}
182
+ size={17}
183
+ style={{ color: cssVar.colorTextSecondary }}
184
+ />
185
+ <span className={styles.textProcessing}>{item.text}</span>
186
+ </div>
187
+ );
188
+ }
189
+
190
+ // Todo and completed states use Checkbox
191
+ return (
192
+ <Checkbox
193
+ backgroundColor={cssVar.colorSuccess}
194
+ checked={isCompleted}
195
+ classNames={{
196
+ text: cx(styles.textTodo, isCompleted && styles.textCompleted),
197
+ wrapper: styles.itemRow,
198
+ }}
199
+ key={index}
200
+ shape="circle"
201
+ style={{ borderWidth: 1.5, cursor: 'default', pointerEvents: 'none' }}
202
+ textProps={{
203
+ type: isCompleted ? 'secondary' : undefined,
204
+ }}
205
+ >
206
+ {item.text}
207
+ </Checkbox>
208
+ );
209
+ })}
177
210
  </div>
178
211
  </div>
179
212
  </WideScreenContainer>
@@ -1,7 +1,7 @@
1
1
  import { Flexbox, Image, Markdown } from '@lobehub/ui';
2
2
  import { memo } from 'react';
3
3
 
4
- import Arguments from '@/features/Conversation/Messages/AssistantGroup/Tool/Render/Arguments';
4
+ import Arguments from '@/features/Conversation/Messages/AssistantGroup/Tool/Detail/Arguments';
5
5
  import { type ToolCallResult } from '@/libs/mcp';
6
6
 
7
7
  export interface MCPTypeProps {
@@ -1,8 +1,9 @@
1
1
  'use client';
2
2
 
3
3
  import { ActionIcon, Flexbox } from '@lobehub/ui';
4
+ import { App } from 'antd';
4
5
  import { cssVar } from 'antd-style';
5
- import { SearchIcon } from 'lucide-react';
6
+ import { BookMinusIcon, FileBoxIcon, SearchIcon, Trash2Icon } from 'lucide-react';
6
7
  import { memo } from 'react';
7
8
  import { useTranslation } from 'react-i18next';
8
9
 
@@ -18,7 +19,8 @@ import ViewSwitcher from '../ToolBar/ViewSwitcher';
18
19
  import Breadcrumb from './Breadcrumb';
19
20
 
20
21
  const Header = memo(() => {
21
- const { t } = useTranslation('file');
22
+ const { t } = useTranslation(['components', 'common', 'file', 'knowledgeBase']);
23
+ const { modal, message } = App.useApp();
22
24
 
23
25
  // Get state and actions from store
24
26
  const [libraryId, category, onActionClick, selectFileIds] = useResourceManagerStore((s) => [
@@ -29,8 +31,59 @@ const Header = memo(() => {
29
31
  ]);
30
32
  const toggleCommandMenu = useGlobalStore((s) => s.toggleCommandMenu);
31
33
 
34
+ const selectCount = selectFileIds.length;
35
+ const isMultiSelected = selectCount > 1;
36
+
32
37
  // If no libraryId, show category name or "Resource" for All
33
- const leftContent = !libraryId ? (
38
+ const leftContent = isMultiSelected ? (
39
+ <Flexbox align={'center'} gap={8} horizontal style={{ marginLeft: 0 }}>
40
+ {libraryId ? (
41
+ <ActionIcon
42
+ icon={BookMinusIcon}
43
+ onClick={() => {
44
+ modal.confirm({
45
+ okButtonProps: {
46
+ danger: true,
47
+ },
48
+ onOk: async () => {
49
+ await onActionClick('removeFromKnowledgeBase');
50
+ message.success(t('FileManager.actions.removeFromKnowledgeBaseSuccess'));
51
+ },
52
+ title: t('FileManager.actions.confirmRemoveFromKnowledgeBase', {
53
+ count: selectCount,
54
+ }),
55
+ });
56
+ }}
57
+ title={t('FileManager.actions.removeFromKnowledgeBase')}
58
+ />
59
+ ) : null}
60
+
61
+ <ActionIcon
62
+ icon={FileBoxIcon}
63
+ onClick={async () => {
64
+ await onActionClick('batchChunking');
65
+ }}
66
+ title={t('FileManager.actions.batchChunking')}
67
+ />
68
+
69
+ <ActionIcon
70
+ icon={Trash2Icon}
71
+ onClick={() => {
72
+ modal.confirm({
73
+ okButtonProps: {
74
+ danger: true,
75
+ },
76
+ onOk: async () => {
77
+ await onActionClick('delete');
78
+ message.success(t('FileManager.actions.deleteSuccess'));
79
+ },
80
+ title: t('FileManager.actions.confirmDeleteMultiFiles', { count: selectCount }),
81
+ });
82
+ }}
83
+ title={t('delete', { ns: 'common' })}
84
+ />
85
+ </Flexbox>
86
+ ) : !libraryId ? (
34
87
  <Flexbox style={{ marginLeft: 8 }}>
35
88
  {category === FilesTabs.All
36
89
  ? t('resource', { defaultValue: 'Resource' })
@@ -49,7 +102,7 @@ const Header = memo(() => {
49
102
  <>
50
103
  <ActionIcon icon={SearchIcon} onClick={() => toggleCommandMenu(true)} />
51
104
  <SortDropdown />
52
- <BatchActionsDropdown onActionClick={onActionClick} selectCount={selectFileIds.length} />
105
+ <BatchActionsDropdown onActionClick={onActionClick} selectCount={selectCount} />
53
106
  <ViewSwitcher />
54
107
  <Flexbox style={{ marginLeft: 8 }}>
55
108
  <AddButton />
@@ -48,11 +48,12 @@ const styles = createStaticStyles(({ css }) => {
48
48
  `,
49
49
 
50
50
  dragOver: css`
51
- color: ${cssVar.colorBgElevated} !important;
52
- background-color: ${cssVar.colorText} !important;
51
+ outline: 1px dashed ${cssVar.colorPrimaryBorder};
52
+ outline-offset: -2px;
53
53
 
54
- * {
55
- color: ${cssVar.colorBgElevated} !important;
54
+ &,
55
+ &:hover {
56
+ background: ${cssVar.colorPrimaryBg};
56
57
  }
57
58
  `,
58
59
 
@@ -510,6 +511,7 @@ const FileListItem = memo<FileListItemProps>(
510
511
  e.stopPropagation();
511
512
  }}
512
513
  onPointerDown={(e) => e.stopPropagation()}
514
+ paddingInline={8}
513
515
  >
514
516
  {!isFolder &&
515
517
  !isPage &&
@@ -34,7 +34,7 @@ const styles = createStaticStyles(({ css }) => ({
34
34
  `,
35
35
  dropZoneActive: css`
36
36
  background: ${cssVar.colorPrimaryBg};
37
- outline: 2px dashed ${cssVar.colorPrimary};
37
+ outline: 1px dashed ${cssVar.colorPrimaryBorder};
38
38
  outline-offset: -4px;
39
39
  `,
40
40
  header: css`
@@ -330,9 +330,15 @@ const ListView = memo(function ListView() {
330
330
 
331
331
  // Memoize footer component to show skeleton loaders when loading more
332
332
  const Footer = useCallback(() => {
333
- if (!isLoadingMore || !fileListHasMore) return null;
334
- return <ListViewSkeleton columnWidths={columnWidths} />;
335
- }, [isLoadingMore, fileListHasMore, columnWidths]);
333
+ if (isLoadingMore && fileListHasMore) return <ListViewSkeleton columnWidths={columnWidths} />;
334
+
335
+ // Leave some padding at the end when there are no more pages,
336
+ // so users can clearly feel they've reached the end of the list.
337
+ if (fileListHasMore === false && dataLength > 0)
338
+ return <div aria-hidden style={{ height: 96 }} />;
339
+
340
+ return null;
341
+ }, [columnWidths, dataLength, fileListHasMore, isLoadingMore]);
336
342
 
337
343
  if (showSkeleton) return <ListViewSkeleton columnWidths={columnWidths} />;
338
344
 
@@ -369,7 +375,12 @@ const ListView = memo(function ListView() {
369
375
  width: columnWidths.name,
370
376
  }}
371
377
  >
372
- {t('FileManager.title.title')}
378
+ {selectFileIds.length > 0
379
+ ? t('FileManager.total.selectedCount', {
380
+ count: selectFileIds.length,
381
+ ns: 'components',
382
+ })
383
+ : t('FileManager.title.title')}
373
384
  <ColumnResizeHandle
374
385
  column="name"
375
386
  currentWidth={columnWidths.name}
@@ -6,11 +6,12 @@ export const styles = createStaticStyles(({ css, cssVar }) => ({
6
6
  opacity: 0.5;
7
7
  `,
8
8
  fileItemDragOver: css`
9
- color: ${cssVar.colorBgElevated} !important;
10
- background-color: ${cssVar.colorText} !important;
9
+ outline: 1px dashed ${cssVar.colorPrimaryBorder};
10
+ outline-offset: -2px;
11
11
 
12
- * {
13
- color: ${cssVar.colorBgElevated} !important;
12
+ &,
13
+ &:hover {
14
+ background: ${cssVar.colorPrimaryBg};
14
15
  }
15
16
  `,
16
17
  treeItem: css`
@@ -1,5 +1,4 @@
1
- import { usePathname , useSearchParams } from '@/libs/router/navigation';
2
-
1
+ import { usePathname, useSearchParams } from '@/libs/router/navigation';
3
2
  import { ProfileTabs, SettingsTabs, SidebarTabKey } from '@/store/global/initialState';
4
3
 
5
4
  /**
@@ -174,6 +174,7 @@ export default {
174
174
  'builtins.lobe-user-memory.apiName.removeIdentityMemory': 'Delete identity memory',
175
175
  'builtins.lobe-user-memory.apiName.searchUserMemory': 'Search memory',
176
176
  'builtins.lobe-user-memory.apiName.updateIdentityMemory': 'Update identity memory',
177
+ 'builtins.lobe-user-memory.inspector.noResults': 'No results',
177
178
  'builtins.lobe-user-memory.title': 'Memory',
178
179
  'builtins.lobe-web-browsing.apiName.crawlMultiPages': 'Read multiple pages',
179
180
  'builtins.lobe-web-browsing.apiName.crawlSinglePage': 'Read page content',
@@ -187,9 +187,13 @@ export default {
187
187
  'llm.waitingForMoreLinkAriaLabel': 'Open the Provider request form',
188
188
  'marketPublish.forkConfirm.by': 'by {{author}}',
189
189
  'marketPublish.forkConfirm.confirm': 'Confirm Publish',
190
+ 'marketPublish.forkConfirm.confirmGroup': 'Confirm Publish',
190
191
  'marketPublish.forkConfirm.description':
191
192
  'You are about to publish a derivative version based on an existing agent from the community. Your new agent will be created as a separate entry in the marketplace.',
193
+ 'marketPublish.forkConfirm.descriptionGroup':
194
+ 'You are about to publish a derivative version based on an existing group from the community. Your new group will be created as a separate entry in the marketplace.',
192
195
  'marketPublish.forkConfirm.title': 'Publish Derivative Agent',
196
+ 'marketPublish.forkConfirm.titleGroup': 'Publish Derivative Group',
193
197
  'marketPublish.modal.changelog.extra':
194
198
  'Describe the key changes and improvements in this version',
195
199
  'marketPublish.modal.changelog.label': 'Changelog',
@@ -209,11 +213,14 @@ export default {
209
213
  'marketPublish.modal.identifier.required': 'Please enter the agent identifier',
210
214
  'marketPublish.modal.loading.fetchingRemote': 'Loading remote data...',
211
215
  'marketPublish.modal.loading.submit': 'Submitting Agent...',
216
+ 'marketPublish.modal.loading.submitGroup': 'Submitting Group...',
212
217
  'marketPublish.modal.loading.upload': 'Publishing new version...',
218
+ 'marketPublish.modal.loading.uploadGroup': 'Publishing new group version...',
213
219
  'marketPublish.modal.messages.createVersionFailed': 'Failed to create version: {{message}}',
214
220
  'marketPublish.modal.messages.fetchRemoteFailed': 'Failed to fetch remote agent data',
215
221
  'marketPublish.modal.messages.missingIdentifier':
216
222
  'This Agent doesn’t have a Community identifier yet.',
223
+ 'marketPublish.modal.messages.noGroup': 'No group selected',
217
224
  'marketPublish.modal.messages.notAuthenticated': 'Sign in to your Community account first.',
218
225
  'marketPublish.modal.messages.publishFailed': 'Publish failed: {{message}}',
219
226
  'marketPublish.modal.submitButton': 'Publish',
@@ -221,12 +228,16 @@ export default {
221
228
  'marketPublish.modal.title.upload': 'Publish New Version',
222
229
  'marketPublish.resultModal.message':
223
230
  'Your Agent has been submitted for review. Once approved, it will go live automatically.',
231
+ 'marketPublish.resultModal.messageGroup':
232
+ 'Your Group has been submitted for review. Once approved, it will go live automatically.',
224
233
  'marketPublish.resultModal.title': 'Submission Successful',
225
234
  'marketPublish.resultModal.view': 'View in Community',
226
235
  'marketPublish.submit.button': 'Share to Community',
227
236
  'marketPublish.submit.tooltip': 'Share this Agent to the Community',
237
+ 'marketPublish.submitGroup.tooltip': 'Share this Group to the Community',
228
238
  'marketPublish.upload.button': 'Publish New Version',
229
239
  'marketPublish.upload.tooltip': 'Publish a new version to Agent Community',
240
+ 'marketPublish.uploadGroup.tooltip': 'Publish a new version to Group Community',
230
241
  'memory.enabled.desc':
231
242
  'Allow LobeHub to extract preferences and info from conversations and use them later. You can view, edit, or clear memory anytime.',
232
243
  'memory.enabled.title': 'Enable Memory',
@@ -551,6 +562,7 @@ export default {
551
562
  'submitAgentModal.placeholder': 'Enter a unique identifier for the agent, e.g. web-development',
552
563
  'submitAgentModal.success': 'Agent submitted successfully',
553
564
  'submitAgentModal.tooltips': 'Share to Agent Community',
565
+ 'submitGroupModal.tooltips': 'Share to Group Community',
554
566
  'sync.device.deviceName.hint': 'Add a name for easy identification',
555
567
  'sync.device.deviceName.placeholder': 'Enter device name',
556
568
  'sync.device.deviceName.title': 'Device Name',
@@ -0,0 +1,296 @@
1
+ import { TRPCError } from '@trpc/server';
2
+ import debug from 'debug';
3
+ import { customAlphabet } from 'nanoid/non-secure';
4
+ import { z } from 'zod';
5
+
6
+ import { authedProcedure, router } from '@/libs/trpc/lambda';
7
+ import { marketSDK, marketUserInfo, serverDatabase } from '@/libs/trpc/lambda/middleware';
8
+ import { type TrustedClientUserInfo, generateTrustedClientToken } from '@/libs/trusted-client';
9
+
10
+ const MARKET_BASE_URL = process.env.NEXT_PUBLIC_MARKET_BASE_URL || 'https://market.lobehub.com';
11
+
12
+ interface MarketUserInfo {
13
+ accountId: number;
14
+ sub: string;
15
+ }
16
+
17
+ const log = debug('lambda-router:market:agent-group');
18
+
19
+ /**
20
+ * Generate a market identifier (8-character lowercase alphanumeric string)
21
+ * Format: [a-z0-9]{8}
22
+ */
23
+ const generateMarketIdentifier = () => {
24
+ const alphabet = '0123456789abcdefghijklmnopqrstuvwxyz';
25
+ const generate = customAlphabet(alphabet, 8);
26
+ return generate();
27
+ };
28
+
29
+ interface FetchMarketUserInfoOptions {
30
+ accessToken?: string;
31
+ userInfo?: TrustedClientUserInfo;
32
+ }
33
+
34
+ /**
35
+ * Fetch Market user info using either trustedClientToken or accessToken
36
+ * Returns the Market accountId which is different from LobeChat userId
37
+ */
38
+ const fetchMarketUserInfo = async (
39
+ options: FetchMarketUserInfoOptions,
40
+ ): Promise<MarketUserInfo | null> => {
41
+ const { userInfo, accessToken } = options;
42
+
43
+ try {
44
+ const userInfoUrl = `${MARKET_BASE_URL}/lobehub-oidc/userinfo`;
45
+ const headers: Record<string, string> = {
46
+ 'Content-Type': 'application/json',
47
+ };
48
+
49
+ if (userInfo) {
50
+ const trustedClientToken = generateTrustedClientToken(userInfo);
51
+ if (trustedClientToken) {
52
+ headers['x-lobe-trust-token'] = trustedClientToken;
53
+ log('Using trustedClientToken for user info fetch');
54
+ }
55
+ }
56
+
57
+ if (!headers['x-lobe-trust-token'] && accessToken) {
58
+ headers['Authorization'] = `Bearer ${accessToken}`;
59
+ log('Using accessToken for user info fetch');
60
+ }
61
+
62
+ if (!headers['x-lobe-trust-token'] && !headers['Authorization']) {
63
+ log('No authentication method available for fetching user info');
64
+ return null;
65
+ }
66
+
67
+ const response = await fetch(userInfoUrl, {
68
+ headers,
69
+ method: 'GET',
70
+ });
71
+
72
+ if (!response.ok) {
73
+ log('Failed to fetch Market user info: %s %s', response.status, response.statusText);
74
+ return null;
75
+ }
76
+
77
+ return (await response.json()) as MarketUserInfo;
78
+ } catch (error) {
79
+ log('Error fetching Market user info: %O', error);
80
+ return null;
81
+ }
82
+ };
83
+
84
+ // Authenticated procedure for agent group management
85
+ const agentGroupProcedure = authedProcedure
86
+ .use(serverDatabase)
87
+ .use(marketUserInfo)
88
+ .use(marketSDK)
89
+ .use(async ({ ctx, next }) => {
90
+ const { UserModel } = await import('@/database/models/user');
91
+ const userModel = new UserModel(ctx.serverDB, ctx.userId);
92
+
93
+ let marketOidcAccessToken: string | undefined;
94
+ try {
95
+ const userState = await userModel.getUserState(async () => ({}));
96
+ marketOidcAccessToken = userState.settings?.market?.accessToken;
97
+ log('marketOidcAccessToken from DB exists=%s', !!marketOidcAccessToken);
98
+ } catch (error) {
99
+ log('Failed to get marketOidcAccessToken from DB: %O', error);
100
+ }
101
+
102
+ return next({
103
+ ctx: {
104
+ marketOidcAccessToken,
105
+ },
106
+ });
107
+ });
108
+
109
+ // Schema definitions
110
+ const memberAgentSchema = z.object({
111
+ avatar: z.string().nullish(),
112
+ category: z.string().optional(),
113
+ config: z.record(z.any()),
114
+ description: z.string(),
115
+ displayOrder: z.number().optional(),
116
+ identifier: z.string(),
117
+ name: z.string(),
118
+ role: z.enum(['supervisor', 'participant']),
119
+ url: z.string(),
120
+ });
121
+
122
+ const publishOrCreateGroupSchema = z.object({
123
+ avatar: z.string().nullish(),
124
+ backgroundColor: z.string().nullish(),
125
+ category: z.string().optional(),
126
+ changelog: z.string().optional(),
127
+ config: z
128
+ .object({
129
+ allowDM: z.boolean().optional(),
130
+ openingMessage: z.string().optional(),
131
+ openingQuestions: z.array(z.string()).optional(),
132
+ revealDM: z.boolean().optional(),
133
+ systemPrompt: z.string().optional(),
134
+ })
135
+ .optional(),
136
+ description: z.string(),
137
+ identifier: z.string().optional(),
138
+ memberAgents: z.array(memberAgentSchema),
139
+ name: z.string(),
140
+ visibility: z.enum(['public', 'private', 'internal']).optional(),
141
+ });
142
+
143
+ export const agentGroupRouter = router({
144
+ /**
145
+ * Check if current user owns the specified group
146
+ */
147
+ checkOwnership: agentGroupProcedure
148
+ .input(z.object({ identifier: z.string() }))
149
+ .query(async ({ input, ctx }) => {
150
+ log('checkOwnership input: %O', input);
151
+
152
+ try {
153
+ const groupDetail = await ctx.marketSDK.agentGroups.getAgentGroupDetail(input.identifier);
154
+
155
+ if (!groupDetail) {
156
+ return {
157
+ exists: false,
158
+ isOwner: false,
159
+ originalGroup: null,
160
+ };
161
+ }
162
+
163
+ const userInfo = ctx.marketUserInfo as TrustedClientUserInfo | undefined;
164
+ const accessToken = (ctx as { marketOidcAccessToken?: string }).marketOidcAccessToken;
165
+ let currentAccountId: number | null = null;
166
+
167
+ const marketUserInfoResult = await fetchMarketUserInfo({ accessToken, userInfo });
168
+ currentAccountId = marketUserInfoResult?.accountId ?? null;
169
+
170
+ const ownerId = groupDetail.group.ownerId;
171
+ const isOwner = currentAccountId !== null && `${ownerId}` === `${currentAccountId}`;
172
+
173
+ log(
174
+ 'checkOwnership result: isOwner=%s, currentAccountId=%s, ownerId=%s',
175
+ isOwner,
176
+ currentAccountId,
177
+ ownerId,
178
+ );
179
+
180
+ return {
181
+ exists: true,
182
+ isOwner,
183
+ originalGroup: isOwner
184
+ ? null
185
+ : {
186
+ // TODO: Add author info from group detail
187
+ author: undefined,
188
+ avatar: groupDetail.group.avatar,
189
+ identifier: groupDetail.group.identifier,
190
+ name: groupDetail.group.name,
191
+ },
192
+ };
193
+ } catch (error) {
194
+ log('Error checking ownership: %O', error);
195
+ return {
196
+ exists: false,
197
+ isOwner: false,
198
+ originalGroup: null,
199
+ };
200
+ }
201
+ }),
202
+
203
+ /**
204
+ * Unified publish or create agent group flow
205
+ * 1. Check if identifier exists and if current user is owner
206
+ * 2. If not owner or no identifier, create new group
207
+ * 3. Create new version for the group if updating
208
+ */
209
+ publishOrCreate: agentGroupProcedure
210
+ .input(publishOrCreateGroupSchema)
211
+ .mutation(async ({ input, ctx }) => {
212
+ log('publishOrCreate input: %O', input);
213
+
214
+ const { identifier: inputIdentifier, name, memberAgents, ...groupData } = input;
215
+ let finalIdentifier = inputIdentifier;
216
+ let isNewGroup = false;
217
+
218
+ try {
219
+ // Step 1: Check ownership if identifier is provided
220
+ if (inputIdentifier) {
221
+ try {
222
+ const groupDetail =
223
+ await ctx.marketSDK.agentGroups.getAgentGroupDetail(inputIdentifier);
224
+ log('Group detail for ownership check: ownerId=%s', groupDetail?.group.ownerId);
225
+
226
+ const userInfo = ctx.marketUserInfo as TrustedClientUserInfo | undefined;
227
+ const accessToken = (ctx as { marketOidcAccessToken?: string }).marketOidcAccessToken;
228
+ let currentAccountId: number | null = null;
229
+
230
+ const marketUserInfoResult = await fetchMarketUserInfo({ accessToken, userInfo });
231
+ currentAccountId = marketUserInfoResult?.accountId ?? null;
232
+ log('Market user info: accountId=%s', currentAccountId);
233
+
234
+ const ownerId = groupDetail?.group.ownerId;
235
+
236
+ log('Ownership check: currentAccountId=%s, ownerId=%s', currentAccountId, ownerId);
237
+
238
+ if (!currentAccountId || `${ownerId}` !== `${currentAccountId}`) {
239
+ // Not the owner, need to create a new group
240
+ log('User is not owner, will create new group');
241
+ finalIdentifier = undefined;
242
+ isNewGroup = true;
243
+ }
244
+ } catch (detailError) {
245
+ // Group not found or error, create new
246
+ log('Group not found or error, will create new: %O', detailError);
247
+ finalIdentifier = undefined;
248
+ isNewGroup = true;
249
+ }
250
+ } else {
251
+ isNewGroup = true;
252
+ }
253
+
254
+ // Step 2: Create new group or update existing
255
+ if (!finalIdentifier || isNewGroup) {
256
+ // Generate a unique 8-character identifier
257
+ finalIdentifier = generateMarketIdentifier();
258
+ isNewGroup = true;
259
+
260
+ log('Creating new group with identifier: %s', finalIdentifier);
261
+
262
+ await ctx.marketSDK.agentGroups.createAgentGroup({
263
+ ...groupData,
264
+ identifier: finalIdentifier,
265
+ // @ts-ignore
266
+ memberAgents,
267
+ name,
268
+ });
269
+ } else {
270
+ // Update existing group - create new version
271
+ log('Creating new version for group: %s', finalIdentifier);
272
+
273
+ await ctx.marketSDK.agentGroups.createAgentGroupVersion({
274
+ ...groupData,
275
+ identifier: finalIdentifier,
276
+ // @ts-ignore
277
+ memberAgents,
278
+ name,
279
+ });
280
+ }
281
+
282
+ return {
283
+ identifier: finalIdentifier,
284
+ isNewGroup,
285
+ success: true,
286
+ };
287
+ } catch (error) {
288
+ log('Error in publishOrCreate: %O', error);
289
+ throw new TRPCError({
290
+ cause: error,
291
+ code: 'INTERNAL_SERVER_ERROR',
292
+ message: error instanceof Error ? error.message : 'Failed to publish group',
293
+ });
294
+ }
295
+ }),
296
+ });