@lobehub/lobehub 2.0.0-next.263 → 2.0.0-next.265
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/core/browser/Browser.ts +14 -0
- package/apps/desktop/src/main/core/browser/__tests__/Browser.test.ts +32 -0
- package/apps/desktop/src/main/utils/permissions.ts +86 -22
- package/changelog/v1.json +18 -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/features/Conversation/MainChatInput/index.tsx +2 -2
- 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/features/ChatInput/InputEditor/index.tsx +1 -0
- package/src/features/EditorCanvas/DiffAllToolbar.tsx +1 -1
- 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/home/slices/sidebarUI/action.test.ts +23 -22
- package/src/store/home/slices/sidebarUI/action.ts +37 -9
- 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
|
@@ -130,6 +130,21 @@ export const agentRouter = router({
|
|
|
130
130
|
return ctx.agentModel.deleteAgentKnowledgeBase(input.agentId, input.knowledgeBaseId);
|
|
131
131
|
}),
|
|
132
132
|
|
|
133
|
+
/**
|
|
134
|
+
* Duplicate an agent and its associated session.
|
|
135
|
+
* Returns the new agent ID and session ID.
|
|
136
|
+
*/
|
|
137
|
+
duplicateAgent: agentProcedure
|
|
138
|
+
.input(
|
|
139
|
+
z.object({
|
|
140
|
+
agentId: z.string(),
|
|
141
|
+
newTitle: z.string().optional(),
|
|
142
|
+
}),
|
|
143
|
+
)
|
|
144
|
+
.mutation(async ({ input, ctx }) => {
|
|
145
|
+
return ctx.agentModel.duplicate(input.agentId, input.newTitle);
|
|
146
|
+
}),
|
|
147
|
+
|
|
133
148
|
/**
|
|
134
149
|
* Get an agent by marketIdentifier
|
|
135
150
|
* @returns agent id if exists, null otherwise
|
|
@@ -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();
|
|
@@ -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) => {
|
|
@@ -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
|
-
};
|
|
@@ -1,238 +0,0 @@
|
|
|
1
|
-
import { SessionDefaultGroup } from '@lobechat/types';
|
|
2
|
-
import { Icon } from '@lobehub/ui';
|
|
3
|
-
import { App } from 'antd';
|
|
4
|
-
import { createStaticStyles } from 'antd-style';
|
|
5
|
-
import { type ItemType } from 'antd/es/menu/interface';
|
|
6
|
-
import isEqual from 'fast-deep-equal';
|
|
7
|
-
import {
|
|
8
|
-
Check,
|
|
9
|
-
FolderInputIcon,
|
|
10
|
-
LucideCopy,
|
|
11
|
-
LucidePlus,
|
|
12
|
-
Pen,
|
|
13
|
-
PictureInPicture2Icon,
|
|
14
|
-
Pin,
|
|
15
|
-
PinOff,
|
|
16
|
-
Trash,
|
|
17
|
-
} from 'lucide-react';
|
|
18
|
-
import { useCallback } from 'react';
|
|
19
|
-
import { useTranslation } from 'react-i18next';
|
|
20
|
-
|
|
21
|
-
import { useGlobalStore } from '@/store/global';
|
|
22
|
-
import { useHomeStore } from '@/store/home';
|
|
23
|
-
import { homeAgentListSelectors } from '@/store/home/selectors';
|
|
24
|
-
|
|
25
|
-
const styles = createStaticStyles(({ css }) => ({
|
|
26
|
-
modalRoot: css`
|
|
27
|
-
z-index: 2000;
|
|
28
|
-
`,
|
|
29
|
-
}));
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Hook for generating menu items for individual session/agent items
|
|
33
|
-
* Used in List/Item/Actions.tsx
|
|
34
|
-
*/
|
|
35
|
-
export const useSessionItemMenuItems = () => {
|
|
36
|
-
const { t } = useTranslation('chat');
|
|
37
|
-
const { modal, message } = App.useApp();
|
|
38
|
-
|
|
39
|
-
const openAgentInNewWindow = useGlobalStore((s) => s.openAgentInNewWindow);
|
|
40
|
-
const sessionCustomGroups = useHomeStore(homeAgentListSelectors.agentGroups, isEqual);
|
|
41
|
-
|
|
42
|
-
const [pinAgent, pinAgentGroup, duplicateAgent, updateAgentGroup, removeAgent, removeAgentGroup] =
|
|
43
|
-
useHomeStore((s) => [
|
|
44
|
-
s.pinAgent,
|
|
45
|
-
s.pinAgentGroup,
|
|
46
|
-
s.duplicateAgent,
|
|
47
|
-
s.updateAgentGroup,
|
|
48
|
-
s.removeAgent,
|
|
49
|
-
s.removeAgentGroup,
|
|
50
|
-
]);
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Pin/Unpin menu item
|
|
54
|
-
*/
|
|
55
|
-
const pinMenuItem = useCallback(
|
|
56
|
-
(id: string, isPinned: boolean, parentType: 'agent' | 'group'): ItemType => {
|
|
57
|
-
const iconElement = <Icon icon={isPinned ? PinOff : Pin} />;
|
|
58
|
-
return {
|
|
59
|
-
icon: iconElement,
|
|
60
|
-
key: 'pin',
|
|
61
|
-
label: t(isPinned ? 'pinOff' : 'pin'),
|
|
62
|
-
onClick: () => {
|
|
63
|
-
if (parentType === 'group') {
|
|
64
|
-
pinAgentGroup(id, !isPinned);
|
|
65
|
-
} else {
|
|
66
|
-
pinAgent(id, !isPinned);
|
|
67
|
-
}
|
|
68
|
-
},
|
|
69
|
-
};
|
|
70
|
-
},
|
|
71
|
-
[t, pinAgentGroup, pinAgent],
|
|
72
|
-
);
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Rename session menu item
|
|
76
|
-
*/
|
|
77
|
-
const renameMenuItem = useCallback(
|
|
78
|
-
(onToggleEdit: (visible?: boolean) => void): ItemType => {
|
|
79
|
-
const iconElement = <Icon icon={Pen} />;
|
|
80
|
-
return {
|
|
81
|
-
icon: iconElement,
|
|
82
|
-
key: 'rename',
|
|
83
|
-
label: t('rename', { ns: 'common' }),
|
|
84
|
-
onClick: (info: any) => {
|
|
85
|
-
info.domEvent?.stopPropagation();
|
|
86
|
-
onToggleEdit(true);
|
|
87
|
-
},
|
|
88
|
-
};
|
|
89
|
-
},
|
|
90
|
-
[t],
|
|
91
|
-
);
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Duplicate session menu item
|
|
95
|
-
*/
|
|
96
|
-
const duplicateMenuItem = useCallback(
|
|
97
|
-
(id: string): ItemType => {
|
|
98
|
-
const iconElement = <Icon icon={LucideCopy} />;
|
|
99
|
-
return {
|
|
100
|
-
icon: iconElement,
|
|
101
|
-
key: 'duplicate',
|
|
102
|
-
label: t('duplicate', { ns: 'common' }),
|
|
103
|
-
onClick: ({ domEvent }: any) => {
|
|
104
|
-
domEvent.stopPropagation();
|
|
105
|
-
duplicateAgent(id);
|
|
106
|
-
},
|
|
107
|
-
};
|
|
108
|
-
},
|
|
109
|
-
[t, duplicateAgent],
|
|
110
|
-
);
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Open in new window menu item
|
|
114
|
-
* Desktop: Opens in a new electron window
|
|
115
|
-
* Browser: Opens in a popup window
|
|
116
|
-
*/
|
|
117
|
-
const openInNewWindowMenuItem = useCallback(
|
|
118
|
-
(id: string): ItemType => {
|
|
119
|
-
const iconElement = <Icon icon={PictureInPicture2Icon} />;
|
|
120
|
-
return {
|
|
121
|
-
icon: iconElement,
|
|
122
|
-
key: 'openInNewWindow',
|
|
123
|
-
label: t('openInNewWindow'),
|
|
124
|
-
onClick: ({ domEvent }: any) => {
|
|
125
|
-
domEvent.stopPropagation();
|
|
126
|
-
openAgentInNewWindow(id);
|
|
127
|
-
},
|
|
128
|
-
};
|
|
129
|
-
},
|
|
130
|
-
[t, openAgentInNewWindow],
|
|
131
|
-
);
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Move to group submenu item
|
|
135
|
-
* Contains all custom groups, default list, and create new group option
|
|
136
|
-
*/
|
|
137
|
-
const moveToGroupMenuItem = useCallback(
|
|
138
|
-
(
|
|
139
|
-
id: string,
|
|
140
|
-
currentGroup: string | undefined,
|
|
141
|
-
onOpenCreateGroupModal: () => void,
|
|
142
|
-
): ItemType => {
|
|
143
|
-
const isDefault = currentGroup === SessionDefaultGroup.Default;
|
|
144
|
-
|
|
145
|
-
const children = [
|
|
146
|
-
...sessionCustomGroups.map(({ id: groupId, name }) => {
|
|
147
|
-
const checkIcon = currentGroup === groupId ? <Icon icon={Check} /> : <div />;
|
|
148
|
-
return {
|
|
149
|
-
icon: checkIcon,
|
|
150
|
-
key: groupId,
|
|
151
|
-
label: name,
|
|
152
|
-
onClick: () => {
|
|
153
|
-
updateAgentGroup(id, groupId);
|
|
154
|
-
},
|
|
155
|
-
};
|
|
156
|
-
}),
|
|
157
|
-
{
|
|
158
|
-
icon: isDefault ? <Icon icon={Check} /> : <div />,
|
|
159
|
-
key: 'defaultList',
|
|
160
|
-
label: t('defaultList'),
|
|
161
|
-
onClick: () => {
|
|
162
|
-
updateAgentGroup(id, SessionDefaultGroup.Default);
|
|
163
|
-
},
|
|
164
|
-
},
|
|
165
|
-
{
|
|
166
|
-
type: 'divider' as const,
|
|
167
|
-
},
|
|
168
|
-
{
|
|
169
|
-
icon: <Icon icon={LucidePlus} />,
|
|
170
|
-
key: 'createGroup',
|
|
171
|
-
label: <div>{t('sessionGroup.createGroup')}</div>,
|
|
172
|
-
onClick: ({ domEvent }: any) => {
|
|
173
|
-
domEvent.stopPropagation();
|
|
174
|
-
onOpenCreateGroupModal();
|
|
175
|
-
},
|
|
176
|
-
},
|
|
177
|
-
];
|
|
178
|
-
|
|
179
|
-
const folderIcon = <Icon icon={FolderInputIcon} />;
|
|
180
|
-
return {
|
|
181
|
-
children,
|
|
182
|
-
icon: folderIcon,
|
|
183
|
-
key: 'moveGroup',
|
|
184
|
-
label: t('sessionGroup.moveGroup'),
|
|
185
|
-
};
|
|
186
|
-
},
|
|
187
|
-
[t, sessionCustomGroups, updateAgentGroup],
|
|
188
|
-
);
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* Delete menu item with confirmation modal
|
|
192
|
-
* Handles both session and group types
|
|
193
|
-
*/
|
|
194
|
-
const deleteMenuItem = useCallback(
|
|
195
|
-
(id: string, parentType: 'agent' | 'group', sessionType?: string): ItemType => {
|
|
196
|
-
const trashIcon = <Icon icon={Trash} />;
|
|
197
|
-
return {
|
|
198
|
-
danger: true,
|
|
199
|
-
icon: trashIcon,
|
|
200
|
-
key: 'delete',
|
|
201
|
-
label: t('delete', { ns: 'common' }),
|
|
202
|
-
onClick: ({ domEvent }: any) => {
|
|
203
|
-
domEvent.stopPropagation();
|
|
204
|
-
modal.confirm({
|
|
205
|
-
centered: true,
|
|
206
|
-
classNames: {
|
|
207
|
-
root: styles.modalRoot,
|
|
208
|
-
},
|
|
209
|
-
okButtonProps: { danger: true },
|
|
210
|
-
onOk: async () => {
|
|
211
|
-
if (parentType === 'group') {
|
|
212
|
-
await removeAgentGroup(id);
|
|
213
|
-
message.success(t('confirmRemoveGroupSuccess'));
|
|
214
|
-
} else {
|
|
215
|
-
await removeAgent(id);
|
|
216
|
-
message.success(t('confirmRemoveSessionSuccess'));
|
|
217
|
-
}
|
|
218
|
-
},
|
|
219
|
-
title:
|
|
220
|
-
sessionType === 'group'
|
|
221
|
-
? t('confirmRemoveChatGroupItemAlert')
|
|
222
|
-
: t('confirmRemoveSessionItemAlert'),
|
|
223
|
-
});
|
|
224
|
-
},
|
|
225
|
-
};
|
|
226
|
-
},
|
|
227
|
-
[t, modal, styles.modalRoot, removeAgentGroup, message, removeAgent],
|
|
228
|
-
);
|
|
229
|
-
|
|
230
|
-
return {
|
|
231
|
-
deleteMenuItem,
|
|
232
|
-
duplicateMenuItem,
|
|
233
|
-
moveToGroupMenuItem,
|
|
234
|
-
openInNewWindowMenuItem,
|
|
235
|
-
pinMenuItem,
|
|
236
|
-
renameMenuItem,
|
|
237
|
-
};
|
|
238
|
-
};
|