@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,261 @@
1
+ import { useEffect, useRef, useState } from 'react';
2
+ import { Card, CardContent } from '@/components/ui/card';
3
+ import { Input } from './ui/input';
4
+ import { Check, Eye, EyeOff, MoreVertical, Settings, X } from 'lucide-react';
5
+ import { Button } from './ui/button';
6
+ import { createElizaClient } from '@/lib/api-client-config';
7
+ import { ApiKeyDialog } from './api-key-dialog';
8
+ import { useToast } from '@/hooks/use-toast';
9
+
10
+ export default function EnvSettings() {
11
+ const [name, setName] = useState('');
12
+ const [value, setValue] = useState('');
13
+ const [showPassword, setShowPassword] = useState(false);
14
+ const [openIndex, setOpenIndex] = useState<number | null>(null);
15
+ const [editingIndex, setEditingIndex] = useState<number | null>(null);
16
+ const [editedValue, setEditedValue] = useState('');
17
+ const [localEnvs, setLocalEnvs] = useState<Record<string, string>>({});
18
+ const dropdownRef = useRef<HTMLDivElement>(null);
19
+ const [isUpdating, setIsUpdating] = useState(false);
20
+ const [isApiKeyDialogOpen, setIsApiKeyDialogOpen] = useState(false);
21
+ const { toast } = useToast();
22
+
23
+ useEffect(() => {
24
+ const handleClickOutside = (event: MouseEvent) => {
25
+ if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
26
+ setOpenIndex(null);
27
+ }
28
+ };
29
+
30
+ document.addEventListener('mousedown', handleClickOutside);
31
+ return () => {
32
+ document.removeEventListener('mousedown', handleClickOutside);
33
+ };
34
+ }, []);
35
+
36
+ useEffect(() => {
37
+ fetchLocalEnvs();
38
+ }, []);
39
+
40
+ const fetchLocalEnvs = async () => {
41
+ const elizaClient = createElizaClient();
42
+ const data = await elizaClient.system.getEnvironment();
43
+ setLocalEnvs(data);
44
+ };
45
+
46
+ const handleReset = async () => {
47
+ await fetchLocalEnvs();
48
+
49
+ setEditingIndex(null);
50
+ setOpenIndex(null);
51
+ setName('');
52
+ setValue('');
53
+ };
54
+
55
+ const handleEdit = (key: string) => {
56
+ setEditingIndex(openIndex);
57
+ setEditedValue(localEnvs[key]);
58
+ setOpenIndex(null);
59
+ };
60
+
61
+ const handleRemove = (key: string) => {
62
+ const updatedData = { ...localEnvs };
63
+ delete updatedData[key];
64
+ setLocalEnvs(updatedData);
65
+ setOpenIndex(null);
66
+ };
67
+
68
+ const saveEdit = (key: string) => {
69
+ setLocalEnvs({
70
+ ...localEnvs,
71
+ [key]: editedValue,
72
+ });
73
+ setEditingIndex(null);
74
+ };
75
+
76
+ const addEnv = () => {
77
+ if (!name || !value) return;
78
+
79
+ setLocalEnvs({
80
+ ...localEnvs,
81
+ [name]: value,
82
+ });
83
+
84
+ setName('');
85
+ setValue('');
86
+ setEditingIndex(null);
87
+ };
88
+
89
+ // Dummy function for onApiKeySaved
90
+ const handleApiKeySaved = () => {
91
+ console.log('API Key was saved');
92
+ // Potentially refetch envs or perform other actions here
93
+ };
94
+
95
+ return (
96
+ <div className="container max-w-4xl mx-auto p-6">
97
+ <ApiKeyDialog
98
+ open={isApiKeyDialogOpen}
99
+ onOpenChange={setIsApiKeyDialogOpen}
100
+ onApiKeySaved={handleApiKeySaved}
101
+ />
102
+
103
+ <div className="flex items-center justify-between mb-6">
104
+ <div>
105
+ <h1 className="text-3xl font-bold">Env settings</h1>
106
+ <p className="text-muted-foreground mt-1">Env settings</p>
107
+ </div>
108
+ <Button onClick={() => setIsApiKeyDialogOpen(true)} aria-label="Manage API Key">
109
+ <Settings className="h-5 w-5" />
110
+ Manage API Key
111
+ </Button>
112
+ </div>
113
+
114
+ <Card>
115
+ <CardContent className="p-6">
116
+ <div className="space-y-6">
117
+ <div className="rounded-lg w-full flex flex-col gap-3">
118
+ <h2 className="text-xl font-bold mb-4 pb-5 ml-1">Environment Settings</h2>
119
+ <div className="grid grid-cols-[1fr_2fr_auto] gap-4 items-end w-full pb-4">
120
+ <div className="flex flex-col gap-1">
121
+ <label htmlFor="secret-name" className="ml-2 text-xs font-medium text-gray-400">
122
+ NAME
123
+ </label>
124
+ <Input
125
+ id="secret-name"
126
+ placeholder="VARIABLE_NAME"
127
+ value={name}
128
+ onChange={(e) => setName(e.target.value)}
129
+ />
130
+ </div>
131
+ <div className="flex flex-col gap-1 relative">
132
+ <label htmlFor="secret-value" className="ml-2 text-xs font-medium text-gray-400">
133
+ VALUE
134
+ </label>
135
+ <div className="relative">
136
+ <Input
137
+ id="secret-value"
138
+ type={showPassword ? 'text' : 'password'}
139
+ placeholder="i9ju23nfsdf56"
140
+ value={value}
141
+ onChange={(e) => setValue(e.target.value)}
142
+ className="pr-10"
143
+ />
144
+ <div
145
+ className="absolute inset-y-0 right-3 flex items-center cursor-pointer text-gray-500"
146
+ onClick={() => setShowPassword(!showPassword)}
147
+ >
148
+ {showPassword ? <EyeOff /> : <Eye />}
149
+ </div>
150
+ </div>
151
+ </div>
152
+ <Button className="shrink-0" onClick={addEnv}>
153
+ Add
154
+ </Button>
155
+ </div>
156
+
157
+ {Object.keys(localEnvs).length > 0 && (
158
+ <div className="grid grid-cols-[1fr_2fr_auto] gap-4 mt-6 font-medium text-gray-400 border-b pb-2 ml-1">
159
+ <div>Name</div>
160
+ <div>Value</div>
161
+ <div>Action</div>
162
+ </div>
163
+ )}
164
+
165
+ <div className="mt-2">
166
+ {Object.entries(localEnvs).map(([key, value], index) => (
167
+ <div
168
+ key={index}
169
+ className="grid grid-cols-[1fr_2fr_auto] gap-4 items-center border-b py-2 ml-1 relative"
170
+ >
171
+ <div className="truncate max-w-48">{key}</div>
172
+ <div>
173
+ {editingIndex === index ? (
174
+ <div className="flex items-center gap-2">
175
+ <Input
176
+ value={editedValue}
177
+ onChange={(e) => setEditedValue(e.target.value)}
178
+ className="w-full"
179
+ />
180
+ <Button variant="ghost" onClick={() => saveEdit(key)}>
181
+ <Check className="w-5 h-5 text-green-500" />
182
+ </Button>
183
+ <Button variant="ghost" onClick={() => setEditingIndex(null)}>
184
+ <X className="w-5 h-5 text-red-500" />
185
+ </Button>
186
+ </div>
187
+ ) : (
188
+ <div className="truncate text-gray-500">Encrypted</div>
189
+ )}
190
+ </div>
191
+ <div className="relative">
192
+ <Button
193
+ variant="ghost"
194
+ className="p-2 text-gray-500"
195
+ onClick={() => setOpenIndex(openIndex === index ? null : index)}
196
+ >
197
+ <MoreVertical className="h-5 w-5" />
198
+ </Button>
199
+ {openIndex === index && (
200
+ <div
201
+ ref={dropdownRef}
202
+ className="absolute right-0 mt-2 w-56 rounded-md shadow-lg bg-card ring-1 ring-black ring-opacity-5 z-10"
203
+ >
204
+ <div className="py-1">
205
+ <button
206
+ onClick={() => handleEdit(key)}
207
+ className="w-full text-left px-4 py-2 text-sm text-muted-foreground hover:bg-accent hover:text-accent-foreground"
208
+ >
209
+ Edit
210
+ </button>
211
+ <button
212
+ onClick={() => handleRemove(key)}
213
+ className="w-full text-left px-4 py-2 text-sm text-red-500 hover:bg-accent"
214
+ >
215
+ Remove
216
+ </button>
217
+ </div>
218
+ </div>
219
+ )}
220
+ </div>
221
+ </div>
222
+ ))}
223
+ </div>
224
+ </div>
225
+ </div>
226
+ </CardContent>
227
+ </Card>
228
+
229
+ <div className="flex gap-2 mt-4 justify-end">
230
+ <Button variant="outline" onClick={handleReset}>
231
+ Reset
232
+ </Button>
233
+ <Button
234
+ type="submit"
235
+ disabled={isUpdating}
236
+ onClick={async () => {
237
+ setIsUpdating(true);
238
+ try {
239
+ const elizaClient = createElizaClient();
240
+ await elizaClient.system.updateLocalEnvironment(localEnvs);
241
+ toast({
242
+ title: 'Success',
243
+ description: 'Environment variables updated successfully!',
244
+ });
245
+ } catch (error) {
246
+ toast({
247
+ title: 'Error',
248
+ description: 'Failed to update environment variables.',
249
+ variant: 'destructive',
250
+ });
251
+ } finally {
252
+ setIsUpdating(false);
253
+ }
254
+ }}
255
+ >
256
+ Save Changes
257
+ </Button>
258
+ </div>
259
+ </div>
260
+ );
261
+ }
@@ -0,0 +1,160 @@
1
+ import React, { useState } from 'react';
2
+ import { useNavigate } from 'react-router-dom';
3
+ import { Card, CardContent } from '@/components/ui/card';
4
+ import { Button } from '@/components/ui/button';
5
+ import { Avatar } from '@/components/ui/avatar';
6
+ import type { UUID, Agent } from '@elizaos/core';
7
+ import type { MessageChannel as ClientMessageChannel } from '@/types';
8
+ import { Settings } from 'lucide-react';
9
+ import { formatAgentName, generateGroupName, getEntityId } from '@/lib/utils';
10
+ import GroupPanel from './group-panel';
11
+ import { useAgentsWithDetails, useChannelParticipants } from '@/hooks/use-query-hooks';
12
+
13
+ // The group prop will be a central channel, enriched with server_id for navigation context
14
+ // Assume group.participants might be available or added later.
15
+ interface GroupCardProps {
16
+ group: ClientMessageChannel & { server_id: UUID; participants?: Partial<Agent>[] };
17
+ // onEdit?: (group: ClientMessageChannel) => void;
18
+ }
19
+
20
+ const GroupCard: React.FC<GroupCardProps> = ({ group /*, onEdit */ }) => {
21
+ const navigate = useNavigate();
22
+ const currentClientId = getEntityId(); // Get current client/user ID
23
+ const [showGroupPanel, setShowGroupPanel] = useState(false);
24
+
25
+ if (!group || !group.id) {
26
+ return (
27
+ <Card className="p-4 min-h-[180px] flex items-center justify-center text-muted-foreground">
28
+ Group data not available.
29
+ </Card>
30
+ );
31
+ }
32
+
33
+ const groupName = generateGroupName(group, group.participants || [], currentClientId);
34
+
35
+ const handleChatClick = () => {
36
+ navigate(`/group/${group.id}?serverId=${group.server_id}`);
37
+ };
38
+
39
+ const { data: agentsData } = useAgentsWithDetails();
40
+ const allAgents = agentsData?.agents || [];
41
+
42
+ const { data: participantsData } = useChannelParticipants(group.id);
43
+ const participants = participantsData?.data;
44
+ const participantsIds: UUID[] = participants && Array.isArray(participants) ? participants : [];
45
+
46
+ const groupAgents = participantsIds
47
+ ? allAgents.filter((agent) => agent.id && participantsIds.includes(agent.id))
48
+ : [];
49
+
50
+ const handleSettings = () => {
51
+ setShowGroupPanel(true);
52
+ };
53
+
54
+ const agentNames =
55
+ groupAgents
56
+ .map((agent) => agent.name)
57
+ .filter(Boolean)
58
+ .join(', ') || 'No members';
59
+
60
+ return (
61
+ <>
62
+ <Card
63
+ className="w-full transition-all bg-card border border-border/50 rounded-sm"
64
+ data-testid="agent-card"
65
+ >
66
+ <CardContent className="p-0 relative h-full">
67
+ <div className="flex flex-col justify-between h-full">
68
+ <div className="flex items-center gap-4 p-2 h-[90%]">
69
+ {/* Avatar */}
70
+ <Avatar className="bg-[#282829] h-16 w-16 flex-shrink-0 rounded-[3px] relative overflow-hidden">
71
+ {groupAgents && groupAgents.length > 0 ? (
72
+ <div className="grid grid-cols-2 grid-rows-2 gap-1 w-full h-full p-1">
73
+ {groupAgents.slice(0, 3).map((agent) => {
74
+ const avatarUrl =
75
+ typeof agent.settings?.avatar === 'string' ? agent.settings.avatar : null;
76
+ return avatarUrl ? (
77
+ <img
78
+ key={agent.id}
79
+ src={avatarUrl}
80
+ alt={agent.name}
81
+ className="object-cover w-full h-full rounded-[3px]"
82
+ />
83
+ ) : (
84
+ <div
85
+ key={agent.id}
86
+ className="flex items-center justify-center bg-[#3F3F3F] text-xs font-medium w-full h-full rounded-[3px]"
87
+ >
88
+ {formatAgentName(agent.name)}
89
+ </div>
90
+ );
91
+ })}
92
+ {groupAgents.length > 3 ? (
93
+ <div className="flex items-center justify-center bg-[#3F3F3F] text-xs font-medium w-full h-full rounded-[3px]">
94
+ +{groupAgents.length - 3}
95
+ </div>
96
+ ) : (
97
+ Array.from({ length: 4 - groupAgents.slice(0, 3).length }).map((_, idx) => (
98
+ <div
99
+ key={`empty-${idx}`}
100
+ className="flex items-center justify-center bg-[#2D2D2D] text-xs font-medium w-full h-full rounded-[3px]"
101
+ ></div>
102
+ ))
103
+ )}
104
+ </div>
105
+ ) : (
106
+ <div className="flex items-center justify-center bg-muted text-lg font-medium w-full h-full">
107
+ {formatAgentName(groupName)}
108
+ </div>
109
+ )}
110
+ </Avatar>
111
+
112
+ {/* Content - Name and Description */}
113
+ <div className="flex-1 min-w-0">
114
+ <h3 className="font-semibold text-xl mb-1 truncate" title={groupName}>
115
+ {groupName}
116
+ </h3>
117
+ <p className="text-sm text-muted-foreground line-clamp-2 leading-relaxed">
118
+ {agentNames}
119
+ </p>
120
+ </div>
121
+ </div>
122
+ <div className="border-t border-muted" />
123
+ <div className="flex items-center justify-between py-1 px-2">
124
+ {/* Settings button */}
125
+ <Button
126
+ variant="ghost"
127
+ size="sm"
128
+ onClick={(e) => {
129
+ e.stopPropagation();
130
+ handleSettings();
131
+ }}
132
+ className="h-8 w-8 p-0 hover:bg-muted/50 cursor-pointer"
133
+ >
134
+ <Settings className="h-4 w-4 text-muted-foreground" />
135
+ </Button>
136
+
137
+ {/* New Chat button - ghost variant */}
138
+ <Button
139
+ variant="outline"
140
+ size="sm"
141
+ onClick={(e) => {
142
+ e.stopPropagation();
143
+ handleChatClick();
144
+ }}
145
+ className="h-8 px-2 rounded-sm bg-muted hover:bg-muted/50 cursor-pointer"
146
+ >
147
+ Chat
148
+ </Button>
149
+ </div>
150
+ </div>
151
+ </CardContent>
152
+ </Card>
153
+ {showGroupPanel && (
154
+ <GroupPanel onClose={() => setShowGroupPanel(false)} channelId={group.id} />
155
+ )}
156
+ </>
157
+ );
158
+ };
159
+
160
+ export default GroupCard;