@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.
- package/.github/workflows/manual-build-desktop.yml +16 -37
- package/CHANGELOG.md +52 -0
- package/apps/desktop/native-deps.config.mjs +19 -3
- package/apps/desktop/src/main/controllers/__tests__/SystemCtr.test.ts +13 -0
- package/apps/desktop/src/main/utils/permissions.ts +86 -22
- package/changelog/v1.json +18 -0
- package/locales/ar/chat.json +1 -0
- package/locales/ar/modelProvider.json +20 -0
- package/locales/ar/models.json +33 -10
- package/locales/ar/plugin.json +1 -0
- package/locales/ar/providers.json +1 -0
- package/locales/ar/setting.json +2 -0
- package/locales/bg-BG/chat.json +1 -0
- package/locales/bg-BG/modelProvider.json +20 -0
- package/locales/bg-BG/models.json +27 -7
- package/locales/bg-BG/plugin.json +1 -0
- package/locales/bg-BG/providers.json +1 -0
- package/locales/bg-BG/setting.json +2 -0
- package/locales/de-DE/chat.json +1 -0
- package/locales/de-DE/modelProvider.json +20 -0
- package/locales/de-DE/models.json +44 -10
- package/locales/de-DE/plugin.json +1 -0
- package/locales/de-DE/providers.json +1 -0
- package/locales/de-DE/setting.json +2 -0
- package/locales/en-US/chat.json +1 -0
- package/locales/en-US/modelProvider.json +20 -0
- package/locales/en-US/models.json +10 -10
- package/locales/en-US/providers.json +1 -0
- package/locales/en-US/setting.json +2 -1
- package/locales/es-ES/chat.json +1 -0
- package/locales/es-ES/modelProvider.json +20 -0
- package/locales/es-ES/models.json +53 -10
- package/locales/es-ES/plugin.json +1 -0
- package/locales/es-ES/providers.json +1 -0
- package/locales/es-ES/setting.json +2 -0
- package/locales/fa-IR/chat.json +1 -0
- package/locales/fa-IR/modelProvider.json +20 -0
- package/locales/fa-IR/models.json +33 -10
- package/locales/fa-IR/plugin.json +1 -0
- package/locales/fa-IR/providers.json +1 -0
- package/locales/fa-IR/setting.json +2 -0
- package/locales/fr-FR/chat.json +1 -0
- package/locales/fr-FR/modelProvider.json +20 -0
- package/locales/fr-FR/models.json +27 -7
- package/locales/fr-FR/plugin.json +1 -0
- package/locales/fr-FR/providers.json +1 -0
- package/locales/fr-FR/setting.json +2 -0
- package/locales/it-IT/chat.json +1 -0
- package/locales/it-IT/modelProvider.json +20 -0
- package/locales/it-IT/models.json +10 -10
- package/locales/it-IT/plugin.json +1 -0
- package/locales/it-IT/providers.json +1 -0
- package/locales/it-IT/setting.json +2 -0
- package/locales/ja-JP/chat.json +1 -0
- package/locales/ja-JP/modelProvider.json +20 -0
- package/locales/ja-JP/models.json +5 -10
- package/locales/ja-JP/plugin.json +1 -0
- package/locales/ja-JP/providers.json +1 -0
- package/locales/ja-JP/setting.json +2 -0
- package/locales/ko-KR/chat.json +1 -0
- package/locales/ko-KR/modelProvider.json +20 -0
- package/locales/ko-KR/models.json +36 -10
- package/locales/ko-KR/plugin.json +1 -0
- package/locales/ko-KR/providers.json +1 -0
- package/locales/ko-KR/setting.json +2 -0
- package/locales/nl-NL/chat.json +1 -0
- package/locales/nl-NL/modelProvider.json +20 -0
- package/locales/nl-NL/models.json +35 -4
- package/locales/nl-NL/plugin.json +1 -0
- package/locales/nl-NL/providers.json +1 -0
- package/locales/nl-NL/setting.json +2 -0
- package/locales/pl-PL/chat.json +1 -0
- package/locales/pl-PL/modelProvider.json +20 -0
- package/locales/pl-PL/models.json +37 -7
- package/locales/pl-PL/plugin.json +1 -0
- package/locales/pl-PL/providers.json +1 -0
- package/locales/pl-PL/setting.json +2 -0
- package/locales/pt-BR/chat.json +1 -0
- package/locales/pt-BR/modelProvider.json +20 -0
- package/locales/pt-BR/models.json +51 -9
- package/locales/pt-BR/plugin.json +1 -0
- package/locales/pt-BR/providers.json +1 -0
- package/locales/pt-BR/setting.json +2 -0
- package/locales/ru-RU/chat.json +1 -0
- package/locales/ru-RU/modelProvider.json +20 -0
- package/locales/ru-RU/models.json +48 -7
- package/locales/ru-RU/plugin.json +1 -0
- package/locales/ru-RU/providers.json +1 -0
- package/locales/ru-RU/setting.json +2 -0
- package/locales/tr-TR/chat.json +1 -0
- package/locales/tr-TR/modelProvider.json +20 -0
- package/locales/tr-TR/models.json +48 -7
- package/locales/tr-TR/plugin.json +1 -0
- package/locales/tr-TR/providers.json +1 -0
- package/locales/tr-TR/setting.json +2 -0
- package/locales/vi-VN/chat.json +1 -0
- package/locales/vi-VN/modelProvider.json +20 -0
- package/locales/vi-VN/models.json +5 -5
- package/locales/vi-VN/plugin.json +1 -0
- package/locales/vi-VN/providers.json +1 -0
- package/locales/vi-VN/setting.json +2 -0
- package/locales/zh-CN/modelProvider.json +20 -20
- package/locales/zh-CN/models.json +49 -8
- package/locales/zh-CN/providers.json +1 -0
- package/locales/zh-CN/setting.json +2 -1
- package/locales/zh-TW/chat.json +1 -0
- package/locales/zh-TW/modelProvider.json +20 -0
- package/locales/zh-TW/models.json +29 -10
- package/locales/zh-TW/plugin.json +1 -0
- package/locales/zh-TW/providers.json +1 -0
- package/locales/zh-TW/setting.json +2 -0
- package/package.json +2 -2
- package/packages/database/src/models/__tests__/agent.test.ts +165 -4
- package/packages/database/src/models/agent.ts +46 -0
- package/packages/database/src/repositories/agentGroup/index.test.ts +498 -0
- package/packages/database/src/repositories/agentGroup/index.ts +150 -0
- package/packages/database/src/repositories/home/__tests__/index.test.ts +113 -1
- package/packages/database/src/repositories/home/index.ts +48 -67
- package/pnpm-workspace.yaml +1 -0
- package/src/app/[variants]/(main)/agent/_layout/Sidebar/Body.tsx +1 -1
- package/src/app/[variants]/(main)/agent/_layout/Sidebar/Cron/CronTopicGroup.tsx +84 -0
- package/src/app/[variants]/(main)/agent/_layout/Sidebar/{Topic/CronTopicList → Cron}/CronTopicItem.tsx +1 -1
- package/src/app/[variants]/(main)/agent/_layout/Sidebar/{Topic/CronTopicList → Cron}/index.tsx +23 -33
- package/src/app/[variants]/(main)/agent/_layout/Sidebar/Topic/List/Item/Editing.tsx +12 -49
- package/src/app/[variants]/(main)/agent/_layout/Sidebar/Topic/List/index.tsx +3 -1
- package/src/app/[variants]/(main)/agent/_layout/Sidebar/Topic/TopicListContent/ThreadList/ThreadItem/Editing.tsx +12 -40
- package/src/app/[variants]/(main)/agent/_layout/Sidebar/Topic/hooks/useTopicNavigation.ts +5 -1
- package/src/app/[variants]/(main)/agent/features/Conversation/MainChatInput/index.tsx +2 -2
- package/src/app/[variants]/(main)/agent/profile/features/AgentCronJobs/CronJobCards.tsx +1 -1
- package/src/app/[variants]/(main)/agent/profile/features/AgentCronJobs/CronJobForm.tsx +1 -1
- package/src/app/[variants]/(main)/group/_layout/Sidebar/AddGroupMemberModal/AvailableAgentList.tsx +0 -1
- package/src/app/[variants]/(main)/group/_layout/Sidebar/AddGroupMemberModal/index.tsx +5 -1
- package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/AgentGroupItem/index.tsx +2 -6
- package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/AgentGroupItem/useDropdownMenu.tsx +100 -0
- package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/AgentItem/index.tsx +2 -4
- package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/AgentItem/useDropdownMenu.tsx +149 -0
- package/src/app/[variants]/(main)/home/_layout/hooks/index.ts +0 -1
- package/src/app/[variants]/(main)/home/features/InputArea/index.tsx +1 -1
- package/src/components/InlineRename/index.tsx +121 -0
- package/src/features/ChatInput/InputEditor/index.tsx +1 -0
- package/src/features/EditorCanvas/DiffAllToolbar.tsx +1 -1
- package/src/features/NavPanel/components/NavItem.tsx +1 -1
- package/src/locales/default/setting.ts +2 -0
- package/src/server/routers/lambda/agent.ts +15 -0
- package/src/server/routers/lambda/agentGroup.ts +16 -0
- package/src/services/agent.ts +11 -0
- package/src/services/chatGroup/index.ts +11 -0
- package/src/store/agent/slices/cron/action.ts +108 -0
- package/src/store/agent/slices/cron/index.ts +1 -0
- package/src/store/agent/store.ts +3 -0
- package/src/store/home/slices/sidebarUI/action.test.ts +23 -22
- package/src/store/home/slices/sidebarUI/action.ts +37 -9
- package/src/app/[variants]/(main)/agent/_layout/Sidebar/Topic/CronTopicList/CronTopicGroup.tsx +0 -74
- package/src/app/[variants]/(main)/group/features/ChangelogModal.tsx +0 -11
- package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/Item/useDropdownMenu.tsx +0 -62
- package/src/app/[variants]/(main)/home/_layout/hooks/useSessionItemMenuItems.tsx +0 -238
- 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 }) => {
|
package/src/services/agent.ts
CHANGED
|
@@ -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';
|
package/src/store/agent/store.ts
CHANGED
|
@@ -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
|
|
146
|
+
it('should duplicate an agent and switch to the new agent', async () => {
|
|
140
147
|
const mockAgentId = 'agent-123';
|
|
141
|
-
const
|
|
142
|
-
const
|
|
148
|
+
const mockNewAgentId = 'new-agent-456';
|
|
149
|
+
const mockSetActiveAgentId = vi.fn();
|
|
143
150
|
|
|
144
|
-
vi.mocked(
|
|
145
|
-
|
|
146
|
-
switchSession: mockSwitchSession,
|
|
151
|
+
vi.mocked(getAgentStoreState).mockReturnValue({
|
|
152
|
+
setActiveAgentId: mockSetActiveAgentId,
|
|
147
153
|
} as any);
|
|
148
154
|
|
|
149
|
-
vi.spyOn(
|
|
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(
|
|
164
|
+
expect(agentService.duplicateAgent).toHaveBeenCalledWith(mockAgentId, 'Copied Agent');
|
|
159
165
|
expect(spyOnRefresh).toHaveBeenCalled();
|
|
160
|
-
expect(
|
|
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(
|
|
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
|
|
185
|
+
it('should use provided title when duplicating', async () => {
|
|
180
186
|
const mockAgentId = 'agent-123';
|
|
181
|
-
const
|
|
187
|
+
const mockNewAgentId = 'new-agent-456';
|
|
182
188
|
|
|
183
|
-
vi.mocked(
|
|
184
|
-
|
|
185
|
-
switchSession: vi.fn(),
|
|
189
|
+
vi.mocked(getAgentStoreState).mockReturnValue({
|
|
190
|
+
setActiveAgentId: vi.fn(),
|
|
186
191
|
} as any);
|
|
187
192
|
|
|
188
|
-
vi.spyOn(
|
|
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
|
-
|
|
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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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 (!
|
|
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
|
|
112
|
-
const
|
|
113
|
-
|
|
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) => {
|
package/src/app/[variants]/(main)/agent/_layout/Sidebar/Topic/CronTopicList/CronTopicGroup.tsx
DELETED
|
@@ -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
|
-
};
|