@elizaos/client 1.5.5-alpha.10

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 (209) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +350 -0
  3. package/dist/assets/empty-module-CLMscLYw.js +1 -0
  4. package/dist/assets/main-BBZ_3lkn.css +5999 -0
  5. package/dist/assets/main-C5zNUkXH.js +7 -0
  6. package/dist/assets/main-Dz64ENQg.js +614 -0
  7. package/dist/assets/react-vendor-DM5m98rr.js +545 -0
  8. package/dist/assets/ui-vendor-BQCqNqg0.js +1 -0
  9. package/dist/elizaos-avatar.png +0 -0
  10. package/dist/elizaos-icon.png +0 -0
  11. package/dist/elizaos-logo-light.png +0 -0
  12. package/dist/elizaos.webp +0 -0
  13. package/dist/favicon.ico +0 -0
  14. package/dist/images/agents/agent1.png +0 -0
  15. package/dist/images/agents/agent2.png +0 -0
  16. package/dist/images/agents/agent3.png +0 -0
  17. package/dist/images/agents/agent4.png +0 -0
  18. package/dist/images/agents/agent5.png +0 -0
  19. package/dist/index.html +14 -0
  20. package/index.html +24 -0
  21. package/package.json +159 -0
  22. package/postcss.config.js +3 -0
  23. package/public/elizaos-avatar.png +0 -0
  24. package/public/elizaos-icon.png +0 -0
  25. package/public/elizaos-logo-light.png +0 -0
  26. package/public/elizaos.webp +0 -0
  27. package/public/favicon.ico +0 -0
  28. package/public/images/agents/agent1.png +0 -0
  29. package/public/images/agents/agent2.png +0 -0
  30. package/public/images/agents/agent3.png +0 -0
  31. package/public/images/agents/agent4.png +0 -0
  32. package/public/images/agents/agent5.png +0 -0
  33. package/src/App.tsx +222 -0
  34. package/src/components/AgentDetailsPanel.tsx +147 -0
  35. package/src/components/ChatInputArea.tsx +196 -0
  36. package/src/components/ChatMessageListComponent.tsx +139 -0
  37. package/src/components/actionTool.tsx +186 -0
  38. package/src/components/add-agent-card.tsx +77 -0
  39. package/src/components/agent-action-viewer.tsx +816 -0
  40. package/src/components/agent-avatar-stack.tsx +121 -0
  41. package/src/components/agent-card.cy.tsx +259 -0
  42. package/src/components/agent-card.tsx +177 -0
  43. package/src/components/agent-creator.tsx +142 -0
  44. package/src/components/agent-log-viewer.tsx +645 -0
  45. package/src/components/agent-memory-edit-overlay.tsx +461 -0
  46. package/src/components/agent-memory-viewer.tsx +504 -0
  47. package/src/components/agent-settings.tsx +270 -0
  48. package/src/components/agent-sidebar.tsx +178 -0
  49. package/src/components/api-key-dialog.tsx +113 -0
  50. package/src/components/app-sidebar.tsx +685 -0
  51. package/src/components/array-input.tsx +116 -0
  52. package/src/components/audio-recorder.tsx +292 -0
  53. package/src/components/avatar-panel.tsx +141 -0
  54. package/src/components/character-form.tsx +1138 -0
  55. package/src/components/chat.tsx +1813 -0
  56. package/src/components/combobox.tsx +187 -0
  57. package/src/components/confirmation-dialog.tsx +59 -0
  58. package/src/components/connection-error-banner.tsx +101 -0
  59. package/src/components/connection-status.cy.tsx +73 -0
  60. package/src/components/connection-status.tsx +155 -0
  61. package/src/components/copy-button.tsx +35 -0
  62. package/src/components/delete-button.tsx +24 -0
  63. package/src/components/env-settings.tsx +261 -0
  64. package/src/components/group-card.tsx +160 -0
  65. package/src/components/group-panel.tsx +543 -0
  66. package/src/components/input-copy.tsx +21 -0
  67. package/src/components/logs-page.tsx +41 -0
  68. package/src/components/media-content.tsx +385 -0
  69. package/src/components/memory-graph.tsx +170 -0
  70. package/src/components/missing-secrets-dialog.tsx +72 -0
  71. package/src/components/onboarding-tour.tsx +247 -0
  72. package/src/components/page-title.tsx +8 -0
  73. package/src/components/plugins-panel.tsx +383 -0
  74. package/src/components/profile-card.tsx +66 -0
  75. package/src/components/profile-overlay.tsx +283 -0
  76. package/src/components/retry-button.tsx +28 -0
  77. package/src/components/secret-panel.tsx +1505 -0
  78. package/src/components/server-management.tsx +264 -0
  79. package/src/components/split-button.tsx +148 -0
  80. package/src/components/stop-agent-button.tsx +99 -0
  81. package/src/components/ui/alert-dialog.cy.tsx +333 -0
  82. package/src/components/ui/alert-dialog.tsx +115 -0
  83. package/src/components/ui/alert.tsx +49 -0
  84. package/src/components/ui/avatar.cy.tsx +180 -0
  85. package/src/components/ui/avatar.tsx +57 -0
  86. package/src/components/ui/badge.cy.tsx +146 -0
  87. package/src/components/ui/badge.tsx +43 -0
  88. package/src/components/ui/button.cy.tsx +177 -0
  89. package/src/components/ui/button.tsx +56 -0
  90. package/src/components/ui/card.cy.tsx +160 -0
  91. package/src/components/ui/card.tsx +73 -0
  92. package/src/components/ui/chat/animated-markdown.tsx +59 -0
  93. package/src/components/ui/chat/chat-bubble.tsx +178 -0
  94. package/src/components/ui/chat/chat-container.tsx +51 -0
  95. package/src/components/ui/chat/chat-input.cy.tsx +169 -0
  96. package/src/components/ui/chat/chat-input.tsx +47 -0
  97. package/src/components/ui/chat/chat-message-list.tsx +61 -0
  98. package/src/components/ui/chat/chat-tts-button.tsx +199 -0
  99. package/src/components/ui/chat/code-block.tsx +79 -0
  100. package/src/components/ui/chat/expandable-chat.tsx +131 -0
  101. package/src/components/ui/chat/hooks/useAutoScroll.ts +86 -0
  102. package/src/components/ui/chat/markdown.tsx +209 -0
  103. package/src/components/ui/chat/message-loading.tsx +48 -0
  104. package/src/components/ui/checkbox.cy.tsx +170 -0
  105. package/src/components/ui/checkbox.tsx +30 -0
  106. package/src/components/ui/collapsible.cy.tsx +283 -0
  107. package/src/components/ui/collapsible.tsx +9 -0
  108. package/src/components/ui/command.cy.tsx +313 -0
  109. package/src/components/ui/command.tsx +143 -0
  110. package/src/components/ui/dialog.cy.tsx +279 -0
  111. package/src/components/ui/dialog.tsx +104 -0
  112. package/src/components/ui/dropdown-menu.cy.tsx +273 -0
  113. package/src/components/ui/dropdown-menu.tsx +281 -0
  114. package/src/components/ui/input.cy.tsx +82 -0
  115. package/src/components/ui/input.tsx +27 -0
  116. package/src/components/ui/label.cy.tsx +157 -0
  117. package/src/components/ui/label.tsx +19 -0
  118. package/src/components/ui/resizable.tsx +42 -0
  119. package/src/components/ui/scroll-area.cy.tsx +242 -0
  120. package/src/components/ui/scroll-area.tsx +46 -0
  121. package/src/components/ui/select.cy.tsx +277 -0
  122. package/src/components/ui/select.tsx +155 -0
  123. package/src/components/ui/separator.cy.tsx +145 -0
  124. package/src/components/ui/separator.tsx +29 -0
  125. package/src/components/ui/sheet.cy.tsx +324 -0
  126. package/src/components/ui/sheet.tsx +119 -0
  127. package/src/components/ui/sidebar.tsx +734 -0
  128. package/src/components/ui/skeleton.cy.tsx +149 -0
  129. package/src/components/ui/skeleton.tsx +17 -0
  130. package/src/components/ui/split-button.cy.tsx +274 -0
  131. package/src/components/ui/split-button.tsx +112 -0
  132. package/src/components/ui/switch.tsx +28 -0
  133. package/src/components/ui/tabs.cy.tsx +271 -0
  134. package/src/components/ui/tabs.tsx +53 -0
  135. package/src/components/ui/textarea.cy.tsx +136 -0
  136. package/src/components/ui/textarea.tsx +26 -0
  137. package/src/components/ui/toast.cy.tsx +209 -0
  138. package/src/components/ui/toast.tsx +126 -0
  139. package/src/components/ui/toaster.tsx +29 -0
  140. package/src/components/ui/tooltip.cy.tsx +244 -0
  141. package/src/components/ui/tooltip.tsx +30 -0
  142. package/src/config/agent-templates.ts +349 -0
  143. package/src/config/voice-models.ts +181 -0
  144. package/src/constants.ts +23 -0
  145. package/src/context/AuthContext.tsx +44 -0
  146. package/src/context/ConnectionContext.tsx +194 -0
  147. package/src/entry.tsx +9 -0
  148. package/src/hooks/__tests__/use-agent-tab-state.test.ts +137 -0
  149. package/src/hooks/__tests__/use-agent-update.test.tsx +250 -0
  150. package/src/hooks/__tests__/use-character-convert.test.ts +102 -0
  151. package/src/hooks/__tests__/use-panel-width-state.test.ts +243 -0
  152. package/src/hooks/__tests__/use-sidebar-state.test.ts +117 -0
  153. package/src/hooks/use-agent-management.ts +130 -0
  154. package/src/hooks/use-agent-tab-state.ts +74 -0
  155. package/src/hooks/use-agent-update.ts +469 -0
  156. package/src/hooks/use-character-convert.ts +138 -0
  157. package/src/hooks/use-confirmation.ts +55 -0
  158. package/src/hooks/use-delete-agent.ts +123 -0
  159. package/src/hooks/use-dm-channels.ts +198 -0
  160. package/src/hooks/use-elevenlabs-voices.ts +83 -0
  161. package/src/hooks/use-file-upload.ts +224 -0
  162. package/src/hooks/use-mobile.tsx +19 -0
  163. package/src/hooks/use-onboarding.tsx +49 -0
  164. package/src/hooks/use-panel-width-state.ts +147 -0
  165. package/src/hooks/use-partial-update.ts +288 -0
  166. package/src/hooks/use-plugin-details.ts +462 -0
  167. package/src/hooks/use-plugins.ts +119 -0
  168. package/src/hooks/use-query-hooks.ts +1263 -0
  169. package/src/hooks/use-server-agents.ts +62 -0
  170. package/src/hooks/use-server-version.tsx +47 -0
  171. package/src/hooks/use-sidebar-state.ts +50 -0
  172. package/src/hooks/use-socket-chat.ts +264 -0
  173. package/src/hooks/use-toast.ts +260 -0
  174. package/src/hooks/use-version.tsx +64 -0
  175. package/src/index.css +146 -0
  176. package/src/lib/api-client-config.ts +53 -0
  177. package/src/lib/api-type-mappers.ts +196 -0
  178. package/src/lib/export-utils.ts +123 -0
  179. package/src/lib/logger.ts +19 -0
  180. package/src/lib/media-utils.ts +170 -0
  181. package/src/lib/pca.test.ts +17 -0
  182. package/src/lib/pca.ts +52 -0
  183. package/src/lib/socketio-manager.ts +664 -0
  184. package/src/lib/utils.ts +168 -0
  185. package/src/main.tsx +16 -0
  186. package/src/mocks/empty-module.ts +12 -0
  187. package/src/mocks/node-module.ts +57 -0
  188. package/src/polyfills.ts +37 -0
  189. package/src/routes/agent-detail.tsx +30 -0
  190. package/src/routes/agent-list.tsx +27 -0
  191. package/src/routes/agent-settings.tsx +48 -0
  192. package/src/routes/character-detail.tsx +52 -0
  193. package/src/routes/character-form.tsx +79 -0
  194. package/src/routes/character-list.tsx +38 -0
  195. package/src/routes/chat.tsx +128 -0
  196. package/src/routes/createAgent.tsx +13 -0
  197. package/src/routes/group-new.tsx +50 -0
  198. package/src/routes/group.tsx +29 -0
  199. package/src/routes/home.tsx +218 -0
  200. package/src/routes/not-found.tsx +71 -0
  201. package/src/test/setup.ts +154 -0
  202. package/src/types/crypto-browserify.d.ts +4 -0
  203. package/src/types/index.ts +13 -0
  204. package/src/types/rooms.ts +8 -0
  205. package/src/types.ts +84 -0
  206. package/src/vite-env.d.ts +40 -0
  207. package/tailwind.config.ts +90 -0
  208. package/tsconfig.json +10 -0
  209. package/vite.config.ts +102 -0
@@ -0,0 +1,264 @@
1
+ import { Button } from '@/components/ui/button';
2
+ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
3
+ import {
4
+ Dialog,
5
+ DialogContent,
6
+ DialogDescription,
7
+ DialogFooter,
8
+ DialogHeader,
9
+ DialogTitle,
10
+ } from '@/components/ui/dialog';
11
+ import { Label } from '@/components/ui/label';
12
+ import { ScrollArea } from '@/components/ui/scroll-area';
13
+ import {
14
+ Select,
15
+ SelectContent,
16
+ SelectItem,
17
+ SelectTrigger,
18
+ SelectValue,
19
+ } from '@/components/ui/select';
20
+ import { useAgents, useServers } from '@/hooks/use-query-hooks';
21
+ import { useToast } from '@/hooks/use-toast';
22
+ import { createElizaClient } from '@/lib/api-client-config';
23
+ import type { UUID } from '@elizaos/core';
24
+ import { Loader2, Plus, X } from 'lucide-react';
25
+ import { useEffect, useState } from 'react';
26
+
27
+ interface ServerManagementProps {
28
+ open: boolean;
29
+ onOpenChange: (open: boolean) => void;
30
+ }
31
+
32
+ export function ServerManagement({ open, onOpenChange }: ServerManagementProps) {
33
+ const { toast } = useToast();
34
+ const { data: serversData } = useServers();
35
+ const { data: agentsData } = useAgents();
36
+
37
+ const [selectedServerId, setSelectedServerId] = useState<UUID | null>(null);
38
+ const [selectedAgentId, setSelectedAgentId] = useState<UUID | null>(null);
39
+ const [serverAgents, setServerAgents] = useState<Map<UUID, UUID[]>>(new Map());
40
+ const [isLoading, setIsLoading] = useState(false);
41
+
42
+ // Load agents for each server
43
+ useEffect(() => {
44
+ const loadServerAgents = async () => {
45
+ if (!serversData?.data?.servers) return;
46
+
47
+ const newServerAgents = new Map<UUID, UUID[]>();
48
+
49
+ for (const server of serversData.data.servers) {
50
+ try {
51
+ const elizaClient = createElizaClient();
52
+ const response = await elizaClient.agents.getAgentsForServer(server.id);
53
+ if (response.success) {
54
+ newServerAgents.set(server.id, response.data.agents);
55
+ }
56
+ } catch (error) {
57
+ console.error(`Failed to load agents for server ${server.id}:`, error);
58
+ }
59
+ }
60
+
61
+ setServerAgents(newServerAgents);
62
+ };
63
+
64
+ loadServerAgents();
65
+ }, [serversData]);
66
+
67
+ const handleAddAgentToServer = async () => {
68
+ if (!selectedServerId || !selectedAgentId) {
69
+ toast({
70
+ title: 'Error',
71
+ description: 'Please select both a server and an agent',
72
+ variant: 'destructive',
73
+ });
74
+ return;
75
+ }
76
+
77
+ setIsLoading(true);
78
+ try {
79
+ const elizaClient = createElizaClient();
80
+ await elizaClient.agents.addAgentToServer(selectedServerId, selectedAgentId);
81
+
82
+ // Update local state
83
+ setServerAgents((prev) => {
84
+ const newMap = new Map(prev);
85
+ const agents = newMap.get(selectedServerId) || [];
86
+ if (!agents.includes(selectedAgentId)) {
87
+ newMap.set(selectedServerId, [...agents, selectedAgentId]);
88
+ }
89
+ return newMap;
90
+ });
91
+
92
+ toast({
93
+ title: 'Success',
94
+ description: 'Agent added to server successfully',
95
+ });
96
+
97
+ // Reset selection
98
+ setSelectedAgentId(null);
99
+ } catch (error) {
100
+ toast({
101
+ title: 'Error',
102
+ description: error instanceof Error ? error.message : 'Failed to add agent to server',
103
+ variant: 'destructive',
104
+ });
105
+ } finally {
106
+ setIsLoading(false);
107
+ }
108
+ };
109
+
110
+ const handleRemoveAgentFromServer = async (serverId: UUID, agentId: UUID) => {
111
+ setIsLoading(true);
112
+ try {
113
+ const elizaClient = createElizaClient();
114
+ await elizaClient.agents.removeAgentFromServer(serverId, agentId);
115
+
116
+ // Update local state
117
+ setServerAgents((prev) => {
118
+ const newMap = new Map(prev);
119
+ const agents = newMap.get(serverId) || [];
120
+ newMap.set(
121
+ serverId,
122
+ agents.filter((id) => id !== agentId)
123
+ );
124
+ return newMap;
125
+ });
126
+
127
+ toast({
128
+ title: 'Success',
129
+ description: 'Agent removed from server successfully',
130
+ });
131
+ } catch (error) {
132
+ toast({
133
+ title: 'Error',
134
+ description: error instanceof Error ? error.message : 'Failed to remove agent from server',
135
+ variant: 'destructive',
136
+ });
137
+ } finally {
138
+ setIsLoading(false);
139
+ }
140
+ };
141
+
142
+ const getAgentName = (agentId: UUID) => {
143
+ const agent = agentsData?.data?.agents?.find((a) => a.id === agentId);
144
+ return agent?.name || agentId;
145
+ };
146
+
147
+ const getAvailableAgents = () => {
148
+ if (!selectedServerId || !agentsData?.data?.agents) return [];
149
+
150
+ const currentAgents = serverAgents.get(selectedServerId) || [];
151
+ return agentsData.data.agents.filter(
152
+ (agent) => agent.id && !currentAgents.includes(agent.id as UUID)
153
+ );
154
+ };
155
+
156
+ return (
157
+ <Dialog open={open} onOpenChange={onOpenChange}>
158
+ <DialogContent className="max-w-4xl max-h-[80vh] overflow-hidden">
159
+ <DialogHeader>
160
+ <DialogTitle>Server Management</DialogTitle>
161
+ <DialogDescription>
162
+ Manage server-agent associations. Add or remove agents from servers to control which
163
+ agents can process messages.
164
+ </DialogDescription>
165
+ </DialogHeader>
166
+
167
+ <div className="space-y-4">
168
+ {/* Server Selection */}
169
+ <div className="space-y-2">
170
+ <Label>Select Server</Label>
171
+ <Select
172
+ value={selectedServerId || undefined}
173
+ onValueChange={(value) => setSelectedServerId(value as UUID)}
174
+ >
175
+ <SelectTrigger>
176
+ <SelectValue placeholder="Choose a server" />
177
+ </SelectTrigger>
178
+ <SelectContent>
179
+ {serversData?.data?.servers.map((server) => (
180
+ <SelectItem key={server.id} value={server.id}>
181
+ {server.name}
182
+ </SelectItem>
183
+ ))}
184
+ </SelectContent>
185
+ </Select>
186
+ </div>
187
+
188
+ {/* Server Agents */}
189
+ {selectedServerId && (
190
+ <Card>
191
+ <CardHeader>
192
+ <CardTitle className="text-lg">Agents in Server</CardTitle>
193
+ <CardDescription>Agents currently associated with this server</CardDescription>
194
+ </CardHeader>
195
+ <CardContent>
196
+ <ScrollArea className="h-[200px]">
197
+ <div className="space-y-2">
198
+ {(serverAgents.get(selectedServerId) || []).length === 0 ? (
199
+ <p className="text-sm text-muted-foreground">No agents in this server</p>
200
+ ) : (
201
+ (serverAgents.get(selectedServerId) || []).map((agentId) => (
202
+ <div
203
+ key={agentId}
204
+ className="flex items-center justify-between p-2 rounded-lg border"
205
+ >
206
+ <span className="text-sm font-medium">{getAgentName(agentId)}</span>
207
+ <Button
208
+ variant="ghost"
209
+ size="sm"
210
+ onClick={() => handleRemoveAgentFromServer(selectedServerId, agentId)}
211
+ disabled={isLoading}
212
+ >
213
+ <X className="h-4 w-4" />
214
+ </Button>
215
+ </div>
216
+ ))
217
+ )}
218
+ </div>
219
+ </ScrollArea>
220
+ </CardContent>
221
+ </Card>
222
+ )}
223
+
224
+ {/* Add Agent Section */}
225
+ {selectedServerId && (
226
+ <div className="space-y-2">
227
+ <Label>Add Agent to Server</Label>
228
+ <div className="flex gap-2">
229
+ <Select
230
+ value={selectedAgentId || undefined}
231
+ onValueChange={(value) => setSelectedAgentId(value as UUID)}
232
+ >
233
+ <SelectTrigger className="flex-1">
234
+ <SelectValue placeholder="Choose an agent" />
235
+ </SelectTrigger>
236
+ <SelectContent>
237
+ {getAvailableAgents().map((agent) => (
238
+ <SelectItem key={agent.id} value={agent.id!}>
239
+ {agent.name}
240
+ </SelectItem>
241
+ ))}
242
+ </SelectContent>
243
+ </Select>
244
+ <Button onClick={handleAddAgentToServer} disabled={!selectedAgentId || isLoading}>
245
+ {isLoading ? (
246
+ <Loader2 className="h-4 w-4 animate-spin" />
247
+ ) : (
248
+ <Plus className="h-4 w-4" />
249
+ )}
250
+ </Button>
251
+ </div>
252
+ </div>
253
+ )}
254
+ </div>
255
+
256
+ <DialogFooter>
257
+ <Button variant="outline" onClick={() => onOpenChange(false)}>
258
+ Close
259
+ </Button>
260
+ </DialogFooter>
261
+ </DialogContent>
262
+ </Dialog>
263
+ );
264
+ }
@@ -0,0 +1,148 @@
1
+ import { ChevronDownIcon } from 'lucide-react';
2
+ import { useState } from 'react';
3
+
4
+ import { Button } from '@/components/ui/button';
5
+ import {
6
+ DropdownMenu,
7
+ DropdownMenuContent,
8
+ DropdownMenuRadioGroup,
9
+ DropdownMenuRadioItem,
10
+ DropdownMenuTrigger,
11
+ } from '@/components/ui/dropdown-menu';
12
+ import { cn } from '@/lib/utils';
13
+
14
+ export interface SplitButtonOption {
15
+ label: string;
16
+ description?: string;
17
+ value?: string;
18
+ onClick?: () => void;
19
+ }
20
+
21
+ export interface SplitButtonProps {
22
+ options: SplitButtonOption[];
23
+ defaultValue?: string;
24
+ value?: string;
25
+ onValueChange?: (value: string) => void;
26
+ onClick?: (option: SplitButtonOption, value: string) => void;
27
+ variant?: 'default' | 'destructive';
28
+ className?: string;
29
+ buttonClassName?: string;
30
+ dropdownClassName?: string;
31
+ disabled?: boolean;
32
+ 'aria-label'?: string;
33
+ }
34
+
35
+ export default function SplitButton({
36
+ options,
37
+ defaultValue = '0',
38
+ value,
39
+ onValueChange,
40
+ onClick,
41
+ variant = 'default',
42
+ className = '',
43
+ buttonClassName = '',
44
+ dropdownClassName = '',
45
+ disabled = false,
46
+ 'aria-label': ariaLabel = 'Options',
47
+ }: SplitButtonProps) {
48
+ const [internalValue, setInternalValue] = useState(defaultValue);
49
+
50
+ // Use controlled value if provided, otherwise use internal state
51
+ const selectedValue = value !== undefined ? value : internalValue;
52
+
53
+ const handleValueChange = (newValue: string) => {
54
+ if (value === undefined) {
55
+ setInternalValue(newValue);
56
+ }
57
+ onValueChange?.(newValue);
58
+
59
+ // Find the selected option and call its onClick if it exists
60
+ const selectedOption = options.find(
61
+ (opt) => (opt.value || String(options.indexOf(opt))) === newValue
62
+ );
63
+ if (selectedOption?.onClick) {
64
+ selectedOption.onClick();
65
+ }
66
+ };
67
+
68
+ const handleMainButtonClick = () => {
69
+ const selectedOption = options[Number(selectedValue)] || options[0];
70
+ if (selectedOption) {
71
+ // Call the option's specific onClick handler
72
+ selectedOption.onClick?.();
73
+ // Call the general onClick handler
74
+ onClick?.(selectedOption, selectedValue);
75
+ }
76
+ };
77
+
78
+ const selectedOption = options[Number(selectedValue)] || options[0];
79
+
80
+ if (!options.length) {
81
+ return null;
82
+ }
83
+
84
+ // Determine divider classes based on variant
85
+ const dividerClasses =
86
+ variant === 'destructive' ? 'divide-white/20' : 'divide-primary-foreground/30';
87
+
88
+ return (
89
+ <div
90
+ className={cn(
91
+ 'inline-flex divide-x rounded-md shadow-xs rtl:space-x-reverse',
92
+ dividerClasses,
93
+ className
94
+ )}
95
+ >
96
+ <Button
97
+ className={cn(
98
+ 'rounded-none shadow-none first:rounded-s-md last:rounded-e-md focus-visible:z-10',
99
+ buttonClassName
100
+ )}
101
+ variant={variant}
102
+ disabled={disabled}
103
+ onClick={handleMainButtonClick}
104
+ >
105
+ {selectedOption.label}
106
+ </Button>
107
+ <DropdownMenu>
108
+ <DropdownMenuTrigger asChild>
109
+ <Button
110
+ className={cn(
111
+ 'rounded-none shadow-none first:rounded-s-md last:rounded-e-md focus-visible:z-10',
112
+ buttonClassName
113
+ )}
114
+ variant={variant}
115
+ size="icon"
116
+ aria-label={ariaLabel}
117
+ disabled={disabled}
118
+ >
119
+ <ChevronDownIcon size={16} aria-hidden="true" />
120
+ </Button>
121
+ </DropdownMenuTrigger>
122
+ <DropdownMenuContent
123
+ className={cn('max-w-64 md:max-w-xs', dropdownClassName)}
124
+ side="bottom"
125
+ sideOffset={4}
126
+ align="end"
127
+ >
128
+ <DropdownMenuRadioGroup value={selectedValue} onValueChange={handleValueChange}>
129
+ {options.map((option, index) => (
130
+ <DropdownMenuRadioItem
131
+ key={option.value || option.label}
132
+ value={option.value || String(index)}
133
+ className="items-start [&>span]:pt-1.5"
134
+ >
135
+ <div className="flex flex-col gap-1">
136
+ <span className="text-sm font-medium">{option.label}</span>
137
+ {option.description && (
138
+ <span className="text-muted-foreground text-xs">{option.description}</span>
139
+ )}
140
+ </div>
141
+ </DropdownMenuRadioItem>
142
+ ))}
143
+ </DropdownMenuRadioGroup>
144
+ </DropdownMenuContent>
145
+ </DropdownMenu>
146
+ </div>
147
+ );
148
+ }
@@ -0,0 +1,99 @@
1
+ import { Button } from '@/components/ui/button';
2
+ import { useAgentManagement } from '@/hooks/use-agent-management';
3
+ import ConfirmationDialog from '@/components/confirmation-dialog';
4
+ import { useConfirmation } from '@/hooks/use-confirmation';
5
+ import clientLogger from '@/lib/logger';
6
+ import type { Agent } from '@elizaos/core';
7
+ import { Loader2, Square } from 'lucide-react';
8
+ import { useNavigate } from 'react-router-dom';
9
+
10
+ interface StopAgentButtonProps {
11
+ agent: Agent;
12
+ variant?: 'default' | 'destructive' | 'outline';
13
+ size?: 'default' | 'sm' | 'lg' | 'icon';
14
+ className?: string;
15
+ showIcon?: boolean;
16
+ redirectToHome?: boolean;
17
+ onStopComplete?: () => void;
18
+ }
19
+
20
+ export default function StopAgentButton({
21
+ agent,
22
+ variant = 'destructive',
23
+ size = 'default',
24
+ className = '',
25
+ showIcon = true,
26
+ redirectToHome = false,
27
+ onStopComplete,
28
+ }: StopAgentButtonProps) {
29
+ const { stopAgent, isAgentStopping } = useAgentManagement();
30
+ const navigate = useNavigate();
31
+ const isStoppingAgent = isAgentStopping(agent.id);
32
+ const { confirm, isOpen, onOpenChange, onConfirm, options } = useConfirmation();
33
+
34
+ const handleStopAgent = () => {
35
+ confirm(
36
+ {
37
+ title: 'Stop Agent',
38
+ description: `Are you sure you want to stop "${agent.name}"?`,
39
+ confirmText: 'Stop',
40
+ variant: 'destructive',
41
+ },
42
+ async () => {
43
+ try {
44
+ await stopAgent(agent);
45
+
46
+ // Call the onStopComplete callback if provided
47
+ if (onStopComplete) {
48
+ onStopComplete();
49
+ }
50
+
51
+ // Navigate to homepage if redirectToHome is true
52
+ if (redirectToHome) {
53
+ navigate('/');
54
+ }
55
+ } catch (error) {
56
+ // If error occurs, don't navigate or call callback
57
+ clientLogger.error('Error stopping agent:', error);
58
+ }
59
+ }
60
+ );
61
+ };
62
+
63
+ return (
64
+ <>
65
+ <Button
66
+ type="button"
67
+ variant={variant}
68
+ size={size}
69
+ className={className}
70
+ onClick={handleStopAgent}
71
+ disabled={isStoppingAgent}
72
+ >
73
+ {isStoppingAgent ? (
74
+ <>
75
+ {showIcon && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
76
+ Stopping...
77
+ </>
78
+ ) : (
79
+ <>
80
+ {showIcon && <Square className="mr-2 h-4 w-4" />}
81
+ Stop
82
+ </>
83
+ )}
84
+ </Button>
85
+
86
+ {/* Confirmation Dialog */}
87
+ <ConfirmationDialog
88
+ open={isOpen}
89
+ onOpenChange={onOpenChange}
90
+ title={options?.title || ''}
91
+ description={options?.description || ''}
92
+ confirmText={options?.confirmText}
93
+ cancelText={options?.cancelText}
94
+ variant={options?.variant}
95
+ onConfirm={onConfirm}
96
+ />
97
+ </>
98
+ );
99
+ }