@lobehub/lobehub 2.0.0-next.264 → 2.0.0-next.266

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 (157) hide show
  1. package/.github/workflows/manual-build-desktop.yml +16 -37
  2. package/CHANGELOG.md +52 -0
  3. package/apps/desktop/native-deps.config.mjs +19 -3
  4. package/apps/desktop/src/main/controllers/__tests__/SystemCtr.test.ts +13 -0
  5. package/apps/desktop/src/main/utils/permissions.ts +86 -22
  6. package/changelog/v1.json +18 -0
  7. package/locales/ar/chat.json +1 -0
  8. package/locales/ar/modelProvider.json +20 -0
  9. package/locales/ar/models.json +33 -10
  10. package/locales/ar/plugin.json +1 -0
  11. package/locales/ar/providers.json +1 -0
  12. package/locales/ar/setting.json +2 -0
  13. package/locales/bg-BG/chat.json +1 -0
  14. package/locales/bg-BG/modelProvider.json +20 -0
  15. package/locales/bg-BG/models.json +27 -7
  16. package/locales/bg-BG/plugin.json +1 -0
  17. package/locales/bg-BG/providers.json +1 -0
  18. package/locales/bg-BG/setting.json +2 -0
  19. package/locales/de-DE/chat.json +1 -0
  20. package/locales/de-DE/modelProvider.json +20 -0
  21. package/locales/de-DE/models.json +44 -10
  22. package/locales/de-DE/plugin.json +1 -0
  23. package/locales/de-DE/providers.json +1 -0
  24. package/locales/de-DE/setting.json +2 -0
  25. package/locales/en-US/chat.json +1 -0
  26. package/locales/en-US/modelProvider.json +20 -0
  27. package/locales/en-US/models.json +10 -10
  28. package/locales/en-US/providers.json +1 -0
  29. package/locales/en-US/setting.json +2 -1
  30. package/locales/es-ES/chat.json +1 -0
  31. package/locales/es-ES/modelProvider.json +20 -0
  32. package/locales/es-ES/models.json +53 -10
  33. package/locales/es-ES/plugin.json +1 -0
  34. package/locales/es-ES/providers.json +1 -0
  35. package/locales/es-ES/setting.json +2 -0
  36. package/locales/fa-IR/chat.json +1 -0
  37. package/locales/fa-IR/modelProvider.json +20 -0
  38. package/locales/fa-IR/models.json +33 -10
  39. package/locales/fa-IR/plugin.json +1 -0
  40. package/locales/fa-IR/providers.json +1 -0
  41. package/locales/fa-IR/setting.json +2 -0
  42. package/locales/fr-FR/chat.json +1 -0
  43. package/locales/fr-FR/modelProvider.json +20 -0
  44. package/locales/fr-FR/models.json +27 -7
  45. package/locales/fr-FR/plugin.json +1 -0
  46. package/locales/fr-FR/providers.json +1 -0
  47. package/locales/fr-FR/setting.json +2 -0
  48. package/locales/it-IT/chat.json +1 -0
  49. package/locales/it-IT/modelProvider.json +20 -0
  50. package/locales/it-IT/models.json +10 -10
  51. package/locales/it-IT/plugin.json +1 -0
  52. package/locales/it-IT/providers.json +1 -0
  53. package/locales/it-IT/setting.json +2 -0
  54. package/locales/ja-JP/chat.json +1 -0
  55. package/locales/ja-JP/modelProvider.json +20 -0
  56. package/locales/ja-JP/models.json +5 -10
  57. package/locales/ja-JP/plugin.json +1 -0
  58. package/locales/ja-JP/providers.json +1 -0
  59. package/locales/ja-JP/setting.json +2 -0
  60. package/locales/ko-KR/chat.json +1 -0
  61. package/locales/ko-KR/modelProvider.json +20 -0
  62. package/locales/ko-KR/models.json +36 -10
  63. package/locales/ko-KR/plugin.json +1 -0
  64. package/locales/ko-KR/providers.json +1 -0
  65. package/locales/ko-KR/setting.json +2 -0
  66. package/locales/nl-NL/chat.json +1 -0
  67. package/locales/nl-NL/modelProvider.json +20 -0
  68. package/locales/nl-NL/models.json +35 -4
  69. package/locales/nl-NL/plugin.json +1 -0
  70. package/locales/nl-NL/providers.json +1 -0
  71. package/locales/nl-NL/setting.json +2 -0
  72. package/locales/pl-PL/chat.json +1 -0
  73. package/locales/pl-PL/modelProvider.json +20 -0
  74. package/locales/pl-PL/models.json +37 -7
  75. package/locales/pl-PL/plugin.json +1 -0
  76. package/locales/pl-PL/providers.json +1 -0
  77. package/locales/pl-PL/setting.json +2 -0
  78. package/locales/pt-BR/chat.json +1 -0
  79. package/locales/pt-BR/modelProvider.json +20 -0
  80. package/locales/pt-BR/models.json +51 -9
  81. package/locales/pt-BR/plugin.json +1 -0
  82. package/locales/pt-BR/providers.json +1 -0
  83. package/locales/pt-BR/setting.json +2 -0
  84. package/locales/ru-RU/chat.json +1 -0
  85. package/locales/ru-RU/modelProvider.json +20 -0
  86. package/locales/ru-RU/models.json +48 -7
  87. package/locales/ru-RU/plugin.json +1 -0
  88. package/locales/ru-RU/providers.json +1 -0
  89. package/locales/ru-RU/setting.json +2 -0
  90. package/locales/tr-TR/chat.json +1 -0
  91. package/locales/tr-TR/modelProvider.json +20 -0
  92. package/locales/tr-TR/models.json +48 -7
  93. package/locales/tr-TR/plugin.json +1 -0
  94. package/locales/tr-TR/providers.json +1 -0
  95. package/locales/tr-TR/setting.json +2 -0
  96. package/locales/vi-VN/chat.json +1 -0
  97. package/locales/vi-VN/modelProvider.json +20 -0
  98. package/locales/vi-VN/models.json +5 -5
  99. package/locales/vi-VN/plugin.json +1 -0
  100. package/locales/vi-VN/providers.json +1 -0
  101. package/locales/vi-VN/setting.json +2 -0
  102. package/locales/zh-CN/modelProvider.json +20 -20
  103. package/locales/zh-CN/models.json +49 -8
  104. package/locales/zh-CN/providers.json +1 -0
  105. package/locales/zh-CN/setting.json +2 -1
  106. package/locales/zh-TW/chat.json +1 -0
  107. package/locales/zh-TW/modelProvider.json +20 -0
  108. package/locales/zh-TW/models.json +29 -10
  109. package/locales/zh-TW/plugin.json +1 -0
  110. package/locales/zh-TW/providers.json +1 -0
  111. package/locales/zh-TW/setting.json +2 -0
  112. package/package.json +2 -2
  113. package/packages/database/src/models/__tests__/agent.test.ts +165 -4
  114. package/packages/database/src/models/agent.ts +46 -0
  115. package/packages/database/src/repositories/agentGroup/index.test.ts +498 -0
  116. package/packages/database/src/repositories/agentGroup/index.ts +150 -0
  117. package/packages/database/src/repositories/home/__tests__/index.test.ts +113 -1
  118. package/packages/database/src/repositories/home/index.ts +48 -67
  119. package/pnpm-workspace.yaml +1 -0
  120. package/src/app/[variants]/(main)/agent/_layout/Sidebar/Body.tsx +1 -1
  121. package/src/app/[variants]/(main)/agent/_layout/Sidebar/Cron/CronTopicGroup.tsx +84 -0
  122. package/src/app/[variants]/(main)/agent/_layout/Sidebar/{Topic/CronTopicList → Cron}/CronTopicItem.tsx +1 -1
  123. package/src/app/[variants]/(main)/agent/_layout/Sidebar/{Topic/CronTopicList → Cron}/index.tsx +23 -33
  124. package/src/app/[variants]/(main)/agent/_layout/Sidebar/Topic/List/Item/Editing.tsx +12 -49
  125. package/src/app/[variants]/(main)/agent/_layout/Sidebar/Topic/List/index.tsx +3 -1
  126. package/src/app/[variants]/(main)/agent/_layout/Sidebar/Topic/TopicListContent/ThreadList/ThreadItem/Editing.tsx +12 -40
  127. package/src/app/[variants]/(main)/agent/_layout/Sidebar/Topic/hooks/useTopicNavigation.ts +5 -1
  128. package/src/app/[variants]/(main)/agent/features/Conversation/MainChatInput/index.tsx +2 -2
  129. package/src/app/[variants]/(main)/agent/profile/features/AgentCronJobs/CronJobCards.tsx +1 -1
  130. package/src/app/[variants]/(main)/agent/profile/features/AgentCronJobs/CronJobForm.tsx +1 -1
  131. package/src/app/[variants]/(main)/group/_layout/Sidebar/AddGroupMemberModal/AvailableAgentList.tsx +0 -1
  132. package/src/app/[variants]/(main)/group/_layout/Sidebar/AddGroupMemberModal/index.tsx +5 -1
  133. package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/AgentGroupItem/index.tsx +2 -6
  134. package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/AgentGroupItem/useDropdownMenu.tsx +100 -0
  135. package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/AgentItem/index.tsx +2 -4
  136. package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/AgentItem/useDropdownMenu.tsx +149 -0
  137. package/src/app/[variants]/(main)/home/_layout/hooks/index.ts +0 -1
  138. package/src/app/[variants]/(main)/home/features/InputArea/index.tsx +1 -1
  139. package/src/components/InlineRename/index.tsx +121 -0
  140. package/src/features/ChatInput/InputEditor/index.tsx +1 -0
  141. package/src/features/EditorCanvas/DiffAllToolbar.tsx +1 -1
  142. package/src/features/NavPanel/components/NavItem.tsx +1 -1
  143. package/src/locales/default/setting.ts +2 -0
  144. package/src/server/routers/lambda/agent.ts +15 -0
  145. package/src/server/routers/lambda/agentGroup.ts +16 -0
  146. package/src/services/agent.ts +11 -0
  147. package/src/services/chatGroup/index.ts +11 -0
  148. package/src/store/agent/slices/cron/action.ts +108 -0
  149. package/src/store/agent/slices/cron/index.ts +1 -0
  150. package/src/store/agent/store.ts +3 -0
  151. package/src/store/home/slices/sidebarUI/action.test.ts +23 -22
  152. package/src/store/home/slices/sidebarUI/action.ts +37 -9
  153. package/src/app/[variants]/(main)/agent/_layout/Sidebar/Topic/CronTopicList/CronTopicGroup.tsx +0 -74
  154. package/src/app/[variants]/(main)/group/features/ChangelogModal.tsx +0 -11
  155. package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/Item/useDropdownMenu.tsx +0 -62
  156. package/src/app/[variants]/(main)/home/_layout/hooks/useSessionItemMenuItems.tsx +0 -238
  157. package/src/hooks/useFetchCronTopicsWithJobInfo.ts +0 -56
@@ -126,6 +126,22 @@ export const agentGroupRouter = router({
126
126
  return ctx.agentGroupService.deleteGroup(input.id);
127
127
  }),
128
128
 
129
+ /**
130
+ * Duplicate a chat group with all its members.
131
+ * Creates a new group with the same config, a new supervisor, and copies of virtual members.
132
+ * Non-virtual members are referenced (not copied).
133
+ */
134
+ duplicateGroup: agentGroupProcedure
135
+ .input(
136
+ z.object({
137
+ groupId: z.string(),
138
+ newTitle: z.string().optional(),
139
+ }),
140
+ )
141
+ .mutation(async ({ input, ctx }) => {
142
+ return ctx.agentGroupRepo.duplicate(input.groupId, input.newTitle);
143
+ }),
144
+
129
145
  getGroup: agentGroupProcedure
130
146
  .input(z.object({ id: z.string() }))
131
147
  .query(async ({ input, ctx }) => {
@@ -185,6 +185,17 @@ class AgentService {
185
185
  updateAgentPinned = async (agentId: string, pinned: boolean) => {
186
186
  return lambdaClient.agent.updateAgentPinned.mutate({ id: agentId, pinned });
187
187
  };
188
+
189
+ /**
190
+ * Duplicate an agent.
191
+ * Returns the new agent ID.
192
+ */
193
+ duplicateAgent = async (
194
+ agentId: string,
195
+ newTitle?: string,
196
+ ): Promise<{ agentId: string } | null> => {
197
+ return lambdaClient.agent.duplicateAgent.mutate({ agentId, newTitle });
198
+ };
188
199
  }
189
200
 
190
201
  export const agentService = new AgentService();
@@ -107,6 +107,17 @@ class ChatGroupService {
107
107
  getGroupAgents = (groupId: string): Promise<ChatGroupAgentItem[]> => {
108
108
  return lambdaClient.group.getGroupAgents.query({ groupId });
109
109
  };
110
+
111
+ /**
112
+ * Duplicate a chat group with all its members.
113
+ * Returns the new group ID and supervisor agent ID.
114
+ */
115
+ duplicateGroup = (
116
+ groupId: string,
117
+ newTitle?: string,
118
+ ): Promise<{ groupId: string; supervisorAgentId: string } | null> => {
119
+ return lambdaClient.group.duplicateGroup.mutate({ groupId, newTitle });
120
+ };
110
121
  }
111
122
 
112
123
  export const chatGroupService = new ChatGroupService();
@@ -0,0 +1,108 @@
1
+ import { ENABLE_BUSINESS_FEATURES } from '@lobechat/business-const';
2
+ import type { SWRResponse } from 'swr';
3
+ import { type StateCreator } from 'zustand/vanilla';
4
+
5
+ import type { AgentCronJob } from '@/database/schemas/agentCronJob';
6
+ import { mutate, useClientDataSWR } from '@/libs/swr';
7
+ import { lambdaClient } from '@/libs/trpc/client/lambda';
8
+ import { agentCronJobService } from '@/services/agentCronJob';
9
+
10
+ import type { AgentStore } from '../../store';
11
+
12
+ const FETCH_CRON_TOPICS_WITH_JOB_INFO_KEY = 'cronTopicsWithJobInfo';
13
+
14
+ export interface CronTopicGroupWithJobInfo {
15
+ cronJob: AgentCronJob | null;
16
+ cronJobId: string;
17
+ topics: Array<{
18
+ createdAt: Date | string;
19
+ favorite?: boolean | null;
20
+ historySummary?: string | null;
21
+ id: string;
22
+ metadata?: any;
23
+ title?: string | null;
24
+ trigger?: string | null;
25
+ updatedAt: Date | string;
26
+ }>;
27
+ }
28
+
29
+ /**
30
+ * Cron Slice Actions
31
+ * Handles agent cron job operations
32
+ */
33
+ export interface CronSliceAction {
34
+ createAgentCronJob: () => Promise<string | null>;
35
+ internal_refreshCronTopics: () => Promise<void>;
36
+ useFetchCronTopicsWithJobInfo: (agentId?: string) => SWRResponse<CronTopicGroupWithJobInfo[]>;
37
+ }
38
+
39
+ export const createCronSlice: StateCreator<
40
+ AgentStore,
41
+ [['zustand/devtools', never]],
42
+ [],
43
+ CronSliceAction
44
+ > = (set, get) => ({
45
+ createAgentCronJob: async () => {
46
+ const { activeAgentId, internal_refreshCronTopics } = get();
47
+ if (!activeAgentId) return null;
48
+
49
+ try {
50
+ const result = await agentCronJobService.create({
51
+ agentId: activeAgentId,
52
+ content: '',
53
+ cronPattern: '*/30 * * * *',
54
+ enabled: false,
55
+ });
56
+
57
+ if (result.success) {
58
+ await internal_refreshCronTopics();
59
+ return result.data.id;
60
+ }
61
+ return null;
62
+ } catch (error) {
63
+ console.error('Failed to create cron job:', error);
64
+ return null;
65
+ }
66
+ },
67
+
68
+ internal_refreshCronTopics: async () => {
69
+ await mutate([FETCH_CRON_TOPICS_WITH_JOB_INFO_KEY, get().activeAgentId]);
70
+ },
71
+
72
+ useFetchCronTopicsWithJobInfo: (agentId) =>
73
+ useClientDataSWR<CronTopicGroupWithJobInfo[]>(
74
+ ENABLE_BUSINESS_FEATURES && agentId ? [FETCH_CRON_TOPICS_WITH_JOB_INFO_KEY, agentId] : null,
75
+ async ([, id]: [string, string]) => {
76
+ const [cronJobsResult, cronTopicsGroups] = await Promise.all([
77
+ lambdaClient.agentCronJob.findByAgent.query({ agentId: id }),
78
+ lambdaClient.topic.getCronTopicsGroupedByCronJob.query({ agentId: id }),
79
+ ]);
80
+
81
+ const cronJobs = cronJobsResult.success ? cronJobsResult.data : [];
82
+ const topicsByCronId = new Map(
83
+ cronTopicsGroups.map((group) => [group.cronJobId, group.topics]),
84
+ );
85
+ const cronJobIds = new Set(cronJobs.map((job) => job.id));
86
+
87
+ const groupsWithJobs = cronJobs.map((job) => ({
88
+ cronJob: job,
89
+ cronJobId: job.id,
90
+ topics: topicsByCronId.get(job.id) || [],
91
+ }));
92
+
93
+ const orphanGroups = cronTopicsGroups
94
+ .filter((group) => !cronJobIds.has(group.cronJobId))
95
+ .map((group) => ({
96
+ cronJob: null,
97
+ cronJobId: group.cronJobId,
98
+ topics: group.topics,
99
+ }));
100
+
101
+ return [...groupsWithJobs, ...orphanGroups];
102
+ },
103
+ {
104
+ fallbackData: [],
105
+ revalidateOnFocus: false,
106
+ },
107
+ ),
108
+ });
@@ -0,0 +1 @@
1
+ export { createCronSlice, type CronSliceAction } from './action';
@@ -6,6 +6,7 @@ import { createDevtools } from '../middleware/createDevtools';
6
6
  import { type AgentStoreState, initialState } from './initialState';
7
7
  import { type AgentSliceAction, createAgentSlice } from './slices/agent';
8
8
  import { type BuiltinAgentSliceAction, createBuiltinAgentSlice } from './slices/builtin';
9
+ import { type CronSliceAction, createCronSlice } from './slices/cron';
9
10
  import { type KnowledgeSliceAction, createKnowledgeSlice } from './slices/knowledge';
10
11
  import { type PluginSliceAction, createPluginSlice } from './slices/plugin';
11
12
 
@@ -15,6 +16,7 @@ export interface AgentStore
15
16
  extends
16
17
  AgentSliceAction,
17
18
  BuiltinAgentSliceAction,
19
+ CronSliceAction,
18
20
  KnowledgeSliceAction,
19
21
  PluginSliceAction,
20
22
  AgentStoreState {}
@@ -23,6 +25,7 @@ const createStore: StateCreator<AgentStore, [['zustand/devtools', never]]> = (..
23
25
  ...initialState,
24
26
  ...createAgentSlice(...parameters),
25
27
  ...createBuiltinAgentSlice(...parameters),
28
+ ...createCronSlice(...parameters),
26
29
  ...createKnowledgeSlice(...parameters),
27
30
  ...createPluginSlice(...parameters),
28
31
  });
@@ -6,6 +6,7 @@ import { agentService } from '@/services/agent';
6
6
  import { chatGroupService } from '@/services/chatGroup';
7
7
  import { homeService } from '@/services/home';
8
8
  import { sessionService } from '@/services/session';
9
+ import { getAgentStoreState } from '@/store/agent';
9
10
  import { useHomeStore } from '@/store/home';
10
11
  import { getSessionStoreState } from '@/store/session';
11
12
 
@@ -26,6 +27,12 @@ vi.mock('@/store/session', () => ({
26
27
  })),
27
28
  }));
28
29
 
30
+ vi.mock('@/store/agent', () => ({
31
+ getAgentStoreState: vi.fn(() => ({
32
+ setActiveAgentId: vi.fn(),
33
+ })),
34
+ }));
35
+
29
36
  afterEach(() => {
30
37
  vi.restoreAllMocks();
31
38
  });
@@ -136,17 +143,16 @@ describe('createSidebarUISlice', () => {
136
143
  });
137
144
 
138
145
  describe('duplicateAgent', () => {
139
- it('should duplicate an agent and switch to the new session', async () => {
146
+ it('should duplicate an agent and switch to the new agent', async () => {
140
147
  const mockAgentId = 'agent-123';
141
- const mockNewId = 'new-agent-456';
142
- const mockSwitchSession = vi.fn();
148
+ const mockNewAgentId = 'new-agent-456';
149
+ const mockSetActiveAgentId = vi.fn();
143
150
 
144
- vi.mocked(getSessionStoreState).mockReturnValue({
145
- activeId: 'other-agent',
146
- switchSession: mockSwitchSession,
151
+ vi.mocked(getAgentStoreState).mockReturnValue({
152
+ setActiveAgentId: mockSetActiveAgentId,
147
153
  } as any);
148
154
 
149
- vi.spyOn(sessionService, 'cloneSession').mockResolvedValueOnce(mockNewId);
155
+ vi.spyOn(agentService, 'duplicateAgent').mockResolvedValueOnce({ agentId: mockNewAgentId });
150
156
  const spyOnRefresh = vi.spyOn(useHomeStore.getState(), 'refreshAgentList');
151
157
 
152
158
  const { result } = renderHook(() => useHomeStore());
@@ -155,16 +161,16 @@ describe('createSidebarUISlice', () => {
155
161
  await result.current.duplicateAgent(mockAgentId, 'Copied Agent');
156
162
  });
157
163
 
158
- expect(sessionService.cloneSession).toHaveBeenCalledWith(mockAgentId, 'Copied Agent');
164
+ expect(agentService.duplicateAgent).toHaveBeenCalledWith(mockAgentId, 'Copied Agent');
159
165
  expect(spyOnRefresh).toHaveBeenCalled();
160
- expect(mockSwitchSession).toHaveBeenCalledWith(mockNewId);
166
+ expect(mockSetActiveAgentId).toHaveBeenCalledWith(mockNewAgentId);
161
167
  });
162
168
 
163
169
  it('should show error message when duplication fails', async () => {
164
170
  const mockAgentId = 'agent-123';
165
171
  const { message } = await import('@/components/AntdStaticMethods');
166
172
 
167
- vi.spyOn(sessionService, 'cloneSession').mockResolvedValueOnce(undefined);
173
+ vi.spyOn(agentService, 'duplicateAgent').mockResolvedValueOnce(null);
168
174
  vi.spyOn(useHomeStore.getState(), 'refreshAgentList');
169
175
 
170
176
  const { result } = renderHook(() => useHomeStore());
@@ -176,29 +182,24 @@ describe('createSidebarUISlice', () => {
176
182
  expect(message.error).toHaveBeenCalled();
177
183
  });
178
184
 
179
- it('should use default title when not provided', async () => {
185
+ it('should use provided title when duplicating', async () => {
180
186
  const mockAgentId = 'agent-123';
181
- const mockNewId = 'new-agent-456';
187
+ const mockNewAgentId = 'new-agent-456';
182
188
 
183
- vi.mocked(getSessionStoreState).mockReturnValue({
184
- activeId: 'other-agent',
185
- switchSession: vi.fn(),
189
+ vi.mocked(getAgentStoreState).mockReturnValue({
190
+ setActiveAgentId: vi.fn(),
186
191
  } as any);
187
192
 
188
- vi.spyOn(sessionService, 'cloneSession').mockResolvedValueOnce(mockNewId);
193
+ vi.spyOn(agentService, 'duplicateAgent').mockResolvedValueOnce({ agentId: mockNewAgentId });
189
194
  vi.spyOn(useHomeStore.getState(), 'refreshAgentList');
190
195
 
191
196
  const { result } = renderHook(() => useHomeStore());
192
197
 
193
198
  await act(async () => {
194
- await result.current.duplicateAgent(mockAgentId);
199
+ await result.current.duplicateAgent(mockAgentId, 'Custom Title');
195
200
  });
196
201
 
197
- // default title is i18n based
198
- expect(sessionService.cloneSession).toHaveBeenCalledWith(
199
- mockAgentId,
200
- expect.stringContaining('Copy'),
201
- );
202
+ expect(agentService.duplicateAgent).toHaveBeenCalledWith(mockAgentId, 'Custom Title');
202
203
  });
203
204
  });
204
205
 
@@ -7,8 +7,8 @@ import { agentService } from '@/services/agent';
7
7
  import { chatGroupService } from '@/services/chatGroup';
8
8
  import { homeService } from '@/services/home';
9
9
  import { sessionService } from '@/services/session';
10
+ import { getAgentStoreState } from '@/store/agent';
10
11
  import type { HomeStore } from '@/store/home/store';
11
- import { getSessionStoreState } from '@/store/session';
12
12
  import { type SessionGroupItem } from '@/types/session';
13
13
  import { setNamespace } from '@/utils/storeDebug';
14
14
 
@@ -17,9 +17,13 @@ const n = setNamespace('sidebarUI');
17
17
  export interface SidebarUIAction {
18
18
  // ========== Agent Operations ==========
19
19
  /**
20
- * Duplicate an agent
20
+ * Duplicate an agent using agentService
21
21
  */
22
22
  duplicateAgent: (agentId: string, newTitle?: string) => Promise<void>;
23
+ /**
24
+ * Duplicate a chat group (multi-agent group)
25
+ */
26
+ duplicateAgentGroup: (groupId: string, newTitle?: string) => Promise<void>;
23
27
  /**
24
28
  * Pin or unpin an agent
25
29
  */
@@ -94,11 +98,35 @@ export const createSidebarUISlice: StateCreator<
94
98
  key: messageLoadingKey,
95
99
  });
96
100
 
97
- // Use provided title or generate default
98
- const title = newTitle ?? t('duplicateSession.title', { ns: 'chat', title: 'Agent' }) ?? 'Copy';
99
- const newId = await sessionService.cloneSession(agentId, title);
101
+ const result = await agentService.duplicateAgent(agentId, newTitle);
102
+
103
+ if (!result) {
104
+ message.destroy(messageLoadingKey);
105
+ message.error(t('copyFail', { ns: 'common' }));
106
+ return;
107
+ }
108
+
109
+ await get().refreshAgentList();
110
+ message.destroy(messageLoadingKey);
111
+ message.success(t('duplicateSession.success', { ns: 'chat' }));
112
+
113
+ // Switch to the new agent
114
+ const agentStore = getAgentStoreState();
115
+ agentStore.setActiveAgentId(result.agentId);
116
+ },
117
+
118
+ duplicateAgentGroup: async (groupId, newTitle?: string) => {
119
+ const messageLoadingKey = 'duplicateAgentGroup.loading';
120
+
121
+ message.loading({
122
+ content: t('duplicateSession.loading', { ns: 'chat' }),
123
+ duration: 0,
124
+ key: messageLoadingKey,
125
+ });
126
+
127
+ const result = await chatGroupService.duplicateGroup(groupId, newTitle);
100
128
 
101
- if (!newId) {
129
+ if (!result) {
102
130
  message.destroy(messageLoadingKey);
103
131
  message.error(t('copyFail', { ns: 'common' }));
104
132
  return;
@@ -108,9 +136,9 @@ export const createSidebarUISlice: StateCreator<
108
136
  message.destroy(messageLoadingKey);
109
137
  message.success(t('duplicateSession.success', { ns: 'chat' }));
110
138
 
111
- // Switch to new session
112
- const sessionStore = getSessionStoreState();
113
- sessionStore.switchSession(newId);
139
+ // Switch to the new group (using supervisor agent id)
140
+ const agentStore = getAgentStoreState();
141
+ agentStore.setActiveAgentId(result.supervisorAgentId);
114
142
  },
115
143
 
116
144
  pinAgent: async (agentId, pinned) => {
@@ -1,74 +0,0 @@
1
- 'use client';
2
-
3
- import { Flexbox, Icon, Text } from '@lobehub/ui';
4
- import { cssVar } from 'antd-style';
5
- import { Clock } from 'lucide-react';
6
- import { type MouseEvent, memo, useCallback } from 'react';
7
- import { useParams } from 'react-router-dom';
8
-
9
- import { useRouter } from '@/app/[variants]/(main)/hooks/useRouter';
10
- import type { AgentCronJob } from '@/database/schemas/agentCronJob';
11
-
12
- import CronTopicItem from './CronTopicItem';
13
-
14
- interface CronTopicGroupProps {
15
- cronJob: AgentCronJob | null;
16
- cronJobId: string;
17
- topics: Array<{
18
- createdAt: Date | string;
19
- favorite?: boolean | null;
20
- historySummary?: string | null;
21
- id: string;
22
- metadata?: any;
23
- title?: string | null;
24
- trigger?: string | null;
25
- updatedAt: Date | string;
26
- }>;
27
- }
28
-
29
- const CronTopicGroup = memo<CronTopicGroupProps>(({ cronJob, cronJobId, topics }) => {
30
- const { aid } = useParams<{ aid?: string }>();
31
- const router = useRouter();
32
- const handleOpenCronJob = useCallback(
33
- (event: MouseEvent) => {
34
- event.stopPropagation();
35
- if (!aid) return;
36
- router.push(`/agent/${aid}/cron/${cronJobId}`);
37
- },
38
- [aid, cronJobId, router],
39
- );
40
-
41
- const cronJobName = cronJob?.name || `Cron Job ${cronJobId.slice(-8)}`;
42
- const isEnabled = cronJob?.enabled ?? false;
43
-
44
- return (
45
- <Flexbox gap={1}>
46
- <Flexbox
47
- align="center"
48
- gap={6}
49
- height={24}
50
- horizontal
51
- onClick={handleOpenCronJob}
52
- paddingInline={8}
53
- style={{ cursor: 'pointer', opacity: isEnabled ? 1 : 0.6, overflow: 'hidden' }}
54
- >
55
- <Icon icon={Clock} style={{ color: cssVar.colorTextDescription, opacity: 0.7 }} />
56
- <Text ellipsis fontSize={12} style={{ flex: 1 }} type={'secondary'} weight={500}>
57
- {cronJobName}
58
- </Text>
59
- {topics.length > 0 && (
60
- <Text style={{ color: cssVar.colorTextDescription, fontSize: 11 }}>{topics.length}</Text>
61
- )}
62
- </Flexbox>
63
- {topics.length > 0 && (
64
- <Flexbox gap={1} paddingBlock={1}>
65
- {topics.map((topic) => (
66
- <CronTopicItem key={topic.id} topic={topic} />
67
- ))}
68
- </Flexbox>
69
- )}
70
- </Flexbox>
71
- );
72
- });
73
-
74
- export default CronTopicGroup;
@@ -1,11 +0,0 @@
1
- import ChangelogModal from '@/features/ChangelogModal';
2
- import { ChangelogService } from '@/server/services/changelog';
3
-
4
- const Changelog = async () => {
5
- const service = new ChangelogService();
6
- const id = await service.getLatestChangelogId();
7
-
8
- return <ChangelogModal currentId={id} />;
9
- };
10
-
11
- export default Changelog;
@@ -1,62 +0,0 @@
1
- import { type MenuProps } from '@lobehub/ui';
2
- import { useCallback } from 'react';
3
-
4
- import { useSessionItemMenuItems } from '../../../../hooks';
5
-
6
- interface ActionProps {
7
- group: string | undefined;
8
- id: string;
9
- openCreateGroupModal: () => void;
10
- parentType: 'agent' | 'group';
11
- pinned: boolean;
12
- sessionType?: string;
13
- toggleEditing: (visible?: boolean) => void;
14
- }
15
-
16
- export const useDropdownMenu = ({
17
- group,
18
- id,
19
- openCreateGroupModal,
20
- parentType,
21
- pinned,
22
- sessionType,
23
- toggleEditing,
24
- }: ActionProps): (() => MenuProps['items']) => {
25
- const {
26
- pinMenuItem,
27
- renameMenuItem,
28
- duplicateMenuItem,
29
- openInNewWindowMenuItem,
30
- moveToGroupMenuItem,
31
- deleteMenuItem,
32
- } = useSessionItemMenuItems();
33
-
34
- return useCallback(
35
- () =>
36
- [
37
- pinMenuItem(id, pinned, parentType),
38
- renameMenuItem(toggleEditing),
39
- duplicateMenuItem(id),
40
- openInNewWindowMenuItem(id),
41
- { type: 'divider' },
42
- moveToGroupMenuItem(id, group, openCreateGroupModal),
43
- { type: 'divider' },
44
- deleteMenuItem(id, parentType, sessionType),
45
- ].filter(Boolean) as MenuProps['items'],
46
- [
47
- id,
48
- pinned,
49
- parentType,
50
- group,
51
- sessionType,
52
- pinMenuItem,
53
- renameMenuItem,
54
- duplicateMenuItem,
55
- openInNewWindowMenuItem,
56
- moveToGroupMenuItem,
57
- deleteMenuItem,
58
- openCreateGroupModal,
59
- toggleEditing,
60
- ],
61
- );
62
- };