@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,685 @@
1
+ import ConfirmationDialog from '@/components/confirmation-dialog';
2
+ import ConnectionStatus from '@/components/connection-status';
3
+ import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
4
+ import { Button } from '@/components/ui/button';
5
+ import {
6
+ Sidebar,
7
+ SidebarContent,
8
+ SidebarFooter,
9
+ SidebarGroup,
10
+ SidebarGroupContent,
11
+ SidebarHeader,
12
+ SidebarMenu,
13
+ SidebarMenuButton,
14
+ SidebarMenuItem,
15
+ SidebarMenuSkeleton,
16
+ } from '@/components/ui/sidebar';
17
+ import { useConfirmation } from '@/hooks/use-confirmation';
18
+
19
+ import {
20
+ useAgentsWithDetails,
21
+ useChannelParticipants, // New hook
22
+ useChannels,
23
+ useServers, // New hook
24
+ } from '@/hooks/use-query-hooks';
25
+ import { useServerVersionString } from '@/hooks/use-server-version';
26
+ import { cn, formatAgentName, generateGroupName, getAgentAvatar, getEntityId } from '@/lib/utils';
27
+ import type {
28
+ MessageChannel as ClientMessageChannel,
29
+ MessageServer as ClientMessageServer,
30
+ } from '@/types';
31
+ import {
32
+ AgentStatus as CoreAgentStatus,
33
+ ChannelType as CoreChannelType,
34
+ type Agent,
35
+ type UUID,
36
+ } from '@elizaos/core';
37
+
38
+ import { useDeleteChannel } from '@/hooks/use-query-hooks';
39
+ import clientLogger from '@/lib/logger'; // Added import
40
+ import { useQueryClient } from '@tanstack/react-query'; // Import useQueryClient
41
+ import { Book, Cog, Plus, TerminalIcon, Trash2, Users } from 'lucide-react'; // Added Hash for channels
42
+ import { useMemo, useState } from 'react';
43
+ import { NavLink, useLocation, useNavigate } from 'react-router-dom'; // Added useNavigate
44
+ import {
45
+ DropdownMenu,
46
+ DropdownMenuContent,
47
+ DropdownMenuItem,
48
+ DropdownMenuTrigger,
49
+ } from './ui/dropdown-menu';
50
+ import { Separator } from './ui/separator';
51
+
52
+ /* ---------- helpers ---------- */
53
+ const partition = <T,>(src: T[], pred: (v: T) => boolean): [T[], T[]] => {
54
+ const pass: T[] = [];
55
+ const fail: T[] = [];
56
+ src.forEach((v) => (pred(v) ? pass : fail).push(v));
57
+ return [pass, fail];
58
+ };
59
+
60
+ /* ---------- tiny components ---------- */
61
+ const SectionHeader = ({
62
+ children,
63
+ className = '',
64
+ }: {
65
+ children: React.ReactNode;
66
+ className?: string;
67
+ }) => (
68
+ <div
69
+ className={cn(
70
+ 'px-4 pt-1 pb-0 text-sm font-medium text-muted-foreground sidebar-section-header',
71
+ className
72
+ )}
73
+ >
74
+ {children}
75
+ </div>
76
+ );
77
+
78
+ const SidebarSection = ({
79
+ title,
80
+ children,
81
+ className = '',
82
+ }: {
83
+ title: string;
84
+ children: React.ReactNode;
85
+ className?: string;
86
+ }) => (
87
+ <>
88
+ <SectionHeader className={className}>{title}</SectionHeader>
89
+ <SidebarGroup>
90
+ <SidebarGroupContent className="mt-0">
91
+ <SidebarMenu>{children}</SidebarMenu>
92
+ </SidebarGroupContent>
93
+ </SidebarGroup>
94
+ </>
95
+ );
96
+
97
+ const AgentRow = ({
98
+ agent,
99
+ isOnline,
100
+ active,
101
+ }: {
102
+ agent: Agent;
103
+ isOnline: boolean;
104
+ active: boolean;
105
+ }) => (
106
+ <SidebarMenuItem>
107
+ <NavLink to={`/chat/${agent.id}`}>
108
+ <SidebarMenuButton
109
+ isActive={active}
110
+ className="px-2 py-2 my-1 h-full rounded justify-between cursor-pointer"
111
+ >
112
+ <span className="text-base truncate max-w-36">{agent.name}</span>
113
+ <div className="flex items-center">
114
+ <div className="relative">
115
+ <Avatar className="h-6 w-6 rounded-full">
116
+ <AvatarImage src={getAgentAvatar(agent)} alt={agent.name || 'avatar'} />
117
+ <AvatarFallback className="rounded-full">
118
+ {formatAgentName(agent.name || '')}
119
+ </AvatarFallback>
120
+ </Avatar>
121
+ <span
122
+ className={cn(
123
+ 'absolute bottom-0 right-0 w-[8px] h-[8px] rounded border border-white',
124
+ isOnline ? 'bg-green-500' : 'bg-muted-foreground'
125
+ )}
126
+ />
127
+ </div>
128
+ </div>
129
+ </SidebarMenuButton>
130
+ </NavLink>
131
+ </SidebarMenuItem>
132
+ );
133
+
134
+ const GroupRow = ({
135
+ channel,
136
+ serverId,
137
+ active,
138
+ }: {
139
+ channel: ClientMessageChannel;
140
+ serverId: UUID;
141
+ active: boolean;
142
+ }) => {
143
+ const currentClientId = getEntityId();
144
+
145
+ const { data: agentsData } = useAgentsWithDetails();
146
+ const allAgents = agentsData?.agents || [];
147
+
148
+ const { data: participantsData } = useChannelParticipants(channel.id as UUID);
149
+ const participants = participantsData?.data;
150
+ const participantsIds: UUID[] = participants && Array.isArray(participants) ? participants : [];
151
+ const groupAgents = allAgents.filter((agent) => agent.id && participantsIds.includes(agent.id));
152
+
153
+ const displayedAgents = groupAgents.slice(0, 3);
154
+ const extraCount = groupAgents.length > 3 ? groupAgents.length - 3 : 0;
155
+
156
+ return (
157
+ <SidebarMenuItem>
158
+ <NavLink to={`/group/${channel.id}?serverId=${serverId}`} className="flex-1">
159
+ <SidebarMenuButton
160
+ isActive={active}
161
+ className="px-2 py-2 my-1 h-full rounded justify-between cursor-pointer"
162
+ >
163
+ {/* Name */}
164
+ <span className="text-base truncate max-w-36">
165
+ {channel.name ||
166
+ generateGroupName(channel, (channel as any).participants || [], currentClientId)}
167
+ </span>
168
+ <div className="flex items-center gap-2">
169
+ {/* Avatars */}
170
+ <div className="flex -space-x-2">
171
+ {displayedAgents.map((agent) => (
172
+ <Avatar key={agent.id} className="h-6 w-6 rounded-full border border-background">
173
+ <AvatarImage
174
+ src={typeof agent.settings?.avatar === 'string' ? agent.settings.avatar : ''}
175
+ alt={agent.name || ''}
176
+ />
177
+ <AvatarFallback className="rounded-full text-xs">
178
+ {formatAgentName(agent.name || '')}
179
+ </AvatarFallback>
180
+ </Avatar>
181
+ ))}
182
+ {extraCount > 0 && (
183
+ <div className="w-6 h-6 rounded-full bg-muted text-[10px] flex items-center justify-center border border-background">
184
+ +{extraCount}
185
+ </div>
186
+ )}
187
+ </div>
188
+ </div>
189
+ </SidebarMenuButton>
190
+ </NavLink>
191
+ </SidebarMenuItem>
192
+ );
193
+ };
194
+
195
+ const AgentListSection = ({
196
+ agents,
197
+ activePath,
198
+ }: {
199
+ agents: Partial<Agent>[];
200
+ activePath: string;
201
+ }) => (
202
+ <>
203
+ <div className="flex items-center px-4 pt-1 pb-0 text-muted-foreground">
204
+ <SectionHeader className="px-0 py-0 text-xs flex gap-1 mr-2">
205
+ <div>Agents</div>
206
+ </SectionHeader>
207
+ <Separator />
208
+ </div>
209
+ <SidebarGroup>
210
+ <SidebarGroupContent className="px-1 mt-0">
211
+ <SidebarMenu>
212
+ {agents.map((a) => (
213
+ <AgentRow
214
+ key={a?.id}
215
+ agent={a as Agent}
216
+ isOnline={a.status === CoreAgentStatus.ACTIVE}
217
+ active={activePath.includes(`/chat/${String(a?.id)}`)}
218
+ />
219
+ ))}
220
+ </SidebarMenu>
221
+ </SidebarGroupContent>
222
+ </SidebarGroup>
223
+ </>
224
+ );
225
+
226
+ const GroupListSection = ({
227
+ servers,
228
+ isLoadingServers,
229
+ activePath,
230
+ }: {
231
+ servers: ClientMessageServer[] | undefined;
232
+ isLoadingServers: boolean;
233
+ activePath: string;
234
+ }) => {
235
+ return (
236
+ <>
237
+ <div className="flex items-center px-4 pt-1 pb-0 text-muted-foreground">
238
+ <SectionHeader className="px-0 py-0 text-xs flex gap-1 mr-2">
239
+ <div>Groups</div>
240
+ </SectionHeader>
241
+ <Separator />
242
+ </div>
243
+ <SidebarGroup>
244
+ <SidebarGroupContent className="px-1 mt-0">
245
+ <SidebarMenu>
246
+ {isLoadingServers &&
247
+ Array.from({ length: 3 }).map((_, i) => (
248
+ <SidebarMenuItem key={`skel-group-${i}`}>
249
+ <SidebarMenuSkeleton />
250
+ </SidebarMenuItem>
251
+ ))}
252
+ {servers?.map((server) => (
253
+ <GroupChannelsForServer
254
+ key={server.id}
255
+ serverId={server.id}
256
+ activePath={activePath}
257
+ />
258
+ ))}
259
+ {(!servers || servers.length === 0) && !isLoadingServers && (
260
+ <SidebarMenuItem>
261
+ <div className="p-4 text-xs text-muted-foreground">No groups found.</div>
262
+ </SidebarMenuItem>
263
+ )}
264
+ </SidebarMenu>
265
+ </SidebarGroupContent>
266
+ </SidebarGroup>
267
+ </>
268
+ );
269
+ };
270
+
271
+ // Updated RoomListSection to GroupChannelListSection
272
+ const GroupChannelListSection = ({
273
+ servers,
274
+ isLoadingServers,
275
+ className = '',
276
+ onManageServers,
277
+ }: {
278
+ servers: ClientMessageServer[] | undefined;
279
+ isLoadingServers: boolean;
280
+ className?: string;
281
+ onManageServers: () => void;
282
+ }) => {
283
+ const navigate = useNavigate();
284
+
285
+ return (
286
+ <SidebarSection title="Groups" className={className}>
287
+ {isLoadingServers &&
288
+ Array.from({ length: 3 }).map((_, i) => (
289
+ <SidebarMenuItem key={`skel-server-${i}`}>
290
+ <SidebarMenuSkeleton />
291
+ </SidebarMenuItem>
292
+ ))}
293
+ {servers?.map((server) => (
294
+ <SidebarGroup key={server.id} className="mt-1">
295
+ {/* Optionally display server name if relevant, or just list all groups flatly */}
296
+ {/* <div className="px-3 py-1 text-xs text-muted-foreground">{server.name}</div> */}
297
+ <ChannelsForServer serverId={server.id} navigate={navigate} />
298
+ </SidebarGroup>
299
+ ))}
300
+ {(!servers || servers.length === 0) && !isLoadingServers && (
301
+ <SidebarMenuItem>
302
+ <div className="p-4 text-xs text-muted-foreground">No groups found.</div>
303
+ </SidebarMenuItem>
304
+ )}
305
+ <div className="flex justify-endtop-0">
306
+ <Button
307
+ variant="ghost"
308
+ size="sm"
309
+ onClick={() => navigate('/group/new')}
310
+ className="text-xs"
311
+ >
312
+ <Plus className="h-3 w-3 mr-1" /> New Group
313
+ </Button>
314
+ </div>
315
+ </SidebarSection>
316
+ );
317
+ };
318
+
319
+ const ChannelsForServer = ({
320
+ serverId,
321
+ navigate,
322
+ }: {
323
+ serverId: UUID;
324
+ navigate: ReturnType<typeof useNavigate>;
325
+ }) => {
326
+ const { data: channelsData, isLoading: isLoadingChannels } = useChannels(serverId);
327
+ const currentClientId = getEntityId(); // Get current client/user ID
328
+ const deleteChannelMutation = useDeleteChannel();
329
+ const [deletingChannelId, setDeletingChannelId] = useState<UUID | null>(null);
330
+ const { confirm, isOpen, onOpenChange, onConfirm, options } = useConfirmation();
331
+
332
+ const groupChannels = useMemo(
333
+ () => channelsData?.data?.channels?.filter((ch) => ch.type === CoreChannelType.GROUP) || [],
334
+ [channelsData]
335
+ );
336
+
337
+ const handleDeleteChannel = (e: React.MouseEvent, channelId: UUID) => {
338
+ e.preventDefault();
339
+ e.stopPropagation();
340
+
341
+ confirm(
342
+ {
343
+ title: 'Delete Group',
344
+ description: 'Are you sure you want to delete this group? This action cannot be undone.',
345
+ confirmText: 'Delete',
346
+ variant: 'destructive',
347
+ },
348
+ async () => {
349
+ setDeletingChannelId(channelId);
350
+ try {
351
+ await deleteChannelMutation.mutateAsync({ channelId, serverId });
352
+ } catch (error) {
353
+ console.error('Failed to delete channel:', error);
354
+ } finally {
355
+ setDeletingChannelId(null);
356
+ }
357
+ }
358
+ );
359
+ };
360
+
361
+ if (isLoadingChannels) {
362
+ return (
363
+ <SidebarMenuItem>
364
+ <SidebarMenuSkeleton />
365
+ </SidebarMenuItem>
366
+ );
367
+ }
368
+ if (!groupChannels.length) {
369
+ return null; // Don't render section if no group channels for this server
370
+ }
371
+
372
+ return (
373
+ <>
374
+ <SidebarGroupContent className="px-1 mt-0">
375
+ <SidebarMenu>
376
+ {groupChannels.map((channel) => (
377
+ <SidebarMenuItem key={channel.id} className="h-12 group">
378
+ <div className="flex items-center gap-1 w-full">
379
+ <NavLink to={`/group/${channel.id}?serverId=${serverId}`} className="flex-1">
380
+ <SidebarMenuButton className="px-4 py-2 my-1 h-full rounded cursor-pointer">
381
+ <div className="flex items-center gap-3">
382
+ <Users className="h-5 w-5 text-muted-foreground" /> {/* Group icon */}
383
+ <span className="text-sm truncate max-w-32">
384
+ {/* Use generateGroupName - assumes channel.participants exists or will be added */}
385
+ {generateGroupName(
386
+ channel,
387
+ (channel as any).participants || [],
388
+ currentClientId
389
+ )}
390
+ </span>
391
+ </div>
392
+ </SidebarMenuButton>
393
+ </NavLink>
394
+ <Button
395
+ variant="ghost"
396
+ size="icon"
397
+ className="h-8 w-8 opacity-0 group-hover:opacity-100 transition-opacity"
398
+ onClick={(e) => handleDeleteChannel(e, channel.id)}
399
+ disabled={deletingChannelId === channel.id}
400
+ >
401
+ <Trash2 className="h-4 w-4 text-destructive" />
402
+ </Button>
403
+ </div>
404
+ </SidebarMenuItem>
405
+ ))}
406
+ </SidebarMenu>
407
+ </SidebarGroupContent>
408
+
409
+ {/* Confirmation Dialog */}
410
+ <ConfirmationDialog
411
+ open={isOpen}
412
+ onOpenChange={onOpenChange}
413
+ title={options?.title || ''}
414
+ description={options?.description || ''}
415
+ confirmText={options?.confirmText}
416
+ cancelText={options?.cancelText}
417
+ variant={options?.variant}
418
+ onConfirm={onConfirm}
419
+ />
420
+ </>
421
+ );
422
+ };
423
+
424
+ const GroupChannelsForServer = ({
425
+ serverId,
426
+ activePath,
427
+ }: {
428
+ serverId: UUID;
429
+ activePath: string;
430
+ }) => {
431
+ const { data: channelsData, isLoading: isLoadingChannels } = useChannels(serverId);
432
+
433
+ const groupChannels = useMemo(
434
+ () => channelsData?.data?.channels?.filter((ch) => ch.type === CoreChannelType.GROUP) || [],
435
+ [channelsData]
436
+ );
437
+
438
+ if (isLoadingChannels) {
439
+ return (
440
+ <SidebarMenuItem>
441
+ <SidebarMenuSkeleton />
442
+ </SidebarMenuItem>
443
+ );
444
+ }
445
+
446
+ if (!groupChannels.length) {
447
+ return null; // Don't render if no group channels for this server
448
+ }
449
+
450
+ return (
451
+ <>
452
+ {groupChannels.map((channel) => (
453
+ <GroupRow
454
+ key={channel.id}
455
+ channel={channel}
456
+ serverId={serverId}
457
+ active={activePath.includes(`/group/${channel.id}`)}
458
+ />
459
+ ))}
460
+ </>
461
+ );
462
+ };
463
+
464
+ // Updated CreateButton: Removed DropdownMenu, simplified to a single action (Create Agent)
465
+ // For "Create Group", users will use the button in the "Groups" section header.
466
+ const CreateAgentButton = ({ onClick }: { onClick: () => void }) => {
467
+ return (
468
+ <Button variant="outline" size="sm" onClick={onClick} className="w-full">
469
+ <Plus className="h-4 w-4 mr-2" />
470
+ Create Agent
471
+ </Button>
472
+ );
473
+ };
474
+
475
+ interface AppSidebarProps {
476
+ refreshHomePage: () => void;
477
+ }
478
+
479
+ /**
480
+ * Renders the main application sidebar, displaying navigation, agent lists, group rooms, and utility links.
481
+ *
482
+ * The sidebar includes sections for online and offline agents, group rooms, a create button for agents and groups, and footer links to documentation, logs, and settings. It handles loading and error states for agent and room data, and conditionally displays a group creation panel.
483
+ */
484
+ export function AppSidebar({
485
+ refreshHomePage,
486
+ isMobile = false,
487
+ }: AppSidebarProps & { isMobile?: boolean }) {
488
+ const location = useLocation();
489
+ const navigate = useNavigate();
490
+ const queryClient = useQueryClient(); // Get query client instance
491
+ const version = useServerVersionString(); // Get server version
492
+
493
+ const {
494
+ data: agentsData,
495
+ error: agentsError,
496
+ isLoading: isLoadingAgents,
497
+ } = useAgentsWithDetails();
498
+ const { data: serversData, isLoading: isLoadingServers } = useServers();
499
+
500
+ const agents = useMemo(() => agentsData?.agents || [], [agentsData]);
501
+ const servers = useMemo(() => serversData?.data?.servers || [], [serversData]);
502
+
503
+ const [onlineAgents, offlineAgents] = useMemo(
504
+ () => partition(agents, (a) => a.status === CoreAgentStatus.ACTIVE),
505
+ [agents]
506
+ );
507
+
508
+ const agentLoadError = agentsError
509
+ ? 'Error loading agents: NetworkError: Unable to connect to the server. Please check if the server is running.'
510
+ : undefined;
511
+
512
+ const handleLogoClick = (e: React.MouseEvent<HTMLAnchorElement>) => {
513
+ e.preventDefault();
514
+ clientLogger.info('[AppSidebar] handleLogoClick triggered', { currentPath: location.pathname });
515
+
516
+ // Invalidate queries that should be fresh on home page
517
+ queryClient.invalidateQueries({ queryKey: ['agents'] });
518
+ queryClient.invalidateQueries({ queryKey: ['agentsWithDetails'] }); // if this is a separate key
519
+ queryClient.invalidateQueries({ queryKey: ['servers'] });
520
+ queryClient.invalidateQueries({ queryKey: ['channels'] }); // This is broad, consider more specific invalidations if performance is an issue
521
+ // Example: if you know active server IDs, invalidate ['channels', serverId]
522
+
523
+ if (location.pathname === '/') {
524
+ clientLogger.info('[AppSidebar] Already on home page. Calling refreshHomePage().');
525
+ // refreshHomePage should ideally trigger a re-render/refetch in Home.tsx
526
+ // This can be done by changing a key prop on Home.tsx or further query invalidations if needed.
527
+ refreshHomePage();
528
+ } else {
529
+ clientLogger.info('[AppSidebar] Not on home page. Navigating to "/".');
530
+ navigate('/');
531
+ }
532
+ };
533
+
534
+ function renderCreateNewButton() {
535
+ const navigate = useNavigate();
536
+
537
+ const handleCreateAgent = () => {
538
+ navigate('/create');
539
+ };
540
+
541
+ const handleCreateGroup = () => {
542
+ navigate('/group/new');
543
+ };
544
+
545
+ return (
546
+ <DropdownMenu>
547
+ <DropdownMenuTrigger asChild>
548
+ <Button
549
+ variant="ghost"
550
+ className="w-full bg-sidebar-accent hover:bg-sidebar-accent/80 h-10 rounded justify-start"
551
+ >
552
+ <Plus className="w-4 h-4 bg" />
553
+ Create New
554
+ </Button>
555
+ </DropdownMenuTrigger>
556
+ <DropdownMenuContent
557
+ align="start"
558
+ className="w-full min-w-[var(--radix-dropdown-menu-trigger-width)]"
559
+ >
560
+ <DropdownMenuItem onClick={handleCreateAgent} className="w-full">
561
+ Create New Agent
562
+ </DropdownMenuItem>
563
+ <DropdownMenuItem onClick={handleCreateGroup} className="w-full">
564
+ Create New Group
565
+ </DropdownMenuItem>
566
+ </DropdownMenuContent>
567
+ </DropdownMenu>
568
+ );
569
+ }
570
+
571
+ return (
572
+ <>
573
+ <Sidebar
574
+ className={cn(
575
+ 'bg-background border-r overflow-hidden',
576
+ isMobile ? 'p-3 pt-12 w-full h-full' : 'p-4 w-72 fixed left-0 top-0 z-40 h-screen',
577
+ !isMobile && 'hidden md:flex md:flex-col'
578
+ )}
579
+ collapsible="none"
580
+ data-testid="app-sidebar"
581
+ >
582
+ {/* ---------- header ---------- */}
583
+ <SidebarHeader>
584
+ <SidebarMenu>
585
+ <SidebarMenuItem>
586
+ <SidebarMenuButton size="lg" asChild>
587
+ <a
588
+ href="/"
589
+ onClick={handleLogoClick}
590
+ className="px-4 py-2 h-full sidebar-logo no-underline cursor-pointer"
591
+ >
592
+ <div className="flex flex-col pt-2 gap-1 items-start justify-center">
593
+ <img
594
+ alt="elizaos-logo"
595
+ src="/elizaos-logo-light.png"
596
+ className="w-32 max-w-full"
597
+ />
598
+ <span className="text-xs font-mono text-muted-foreground">v{version}</span>
599
+ </div>
600
+ </a>
601
+ </SidebarMenuButton>
602
+ </SidebarMenuItem>
603
+ </SidebarMenu>
604
+ </SidebarHeader>
605
+
606
+ {/* ---------- content ---------- */}
607
+ <SidebarContent className="flex-1 overflow-y-auto">
608
+ {/* create agent button - moved from old CreateButton dropdown */}
609
+ {/* This section is for the "Agents" list.
610
+ The "Create Agent" button should ideally be next to the "Agents" title.
611
+ Let's adjust the structure slightly if needed or place it prominently.
612
+ */}
613
+ {agentLoadError && <div className="px-4 py-2 text-xs text-red-500">{agentLoadError}</div>}
614
+
615
+ <SidebarMenu className="my-2">
616
+ <SidebarMenuItem className="list-none">{renderCreateNewButton()}</SidebarMenuItem>
617
+ </SidebarMenu>
618
+
619
+ <div className="pt-2">
620
+ {isLoadingAgents && !agentLoadError && (
621
+ <SidebarSection title="Agents">
622
+ <SidebarMenuSkeleton />
623
+ </SidebarSection>
624
+ )}
625
+
626
+ {!isLoadingAgents && !agentLoadError && (
627
+ <>
628
+ <AgentListSection
629
+ agents={[...onlineAgents, ...offlineAgents]}
630
+ activePath={location.pathname}
631
+ />
632
+ <GroupListSection
633
+ servers={servers}
634
+ isLoadingServers={isLoadingServers}
635
+ activePath={location.pathname}
636
+ />
637
+ </>
638
+ )}
639
+ </div>
640
+ </SidebarContent>
641
+
642
+ {/* ---------- footer ---------- */}
643
+ <SidebarFooter className="px-2 py-4">
644
+ <SidebarMenu>
645
+ <FooterLink to="https://eliza.how/" Icon={Book} label="Documentation" />
646
+ <FooterLink to="/logs" Icon={TerminalIcon} label="Logs" />
647
+ <FooterLink to="/settings" Icon={Cog} label="Settings" />
648
+ <ConnectionStatus />
649
+ </SidebarMenu>
650
+ </SidebarFooter>
651
+ </Sidebar>
652
+
653
+ {/* Server management hidden - using single default server */}
654
+ </>
655
+ );
656
+ }
657
+
658
+ /* ---------- footer link ---------- */
659
+ const FooterLink = ({ to, Icon, label }: { to: string; Icon: typeof Book; label: string }) => {
660
+ const isExternal = to.startsWith('http://') || to.startsWith('https://');
661
+
662
+ if (isExternal) {
663
+ return (
664
+ <SidebarMenuItem>
665
+ <a href={to} target="_blank" rel="noopener noreferrer">
666
+ <SidebarMenuButton className="rounded cursor-pointer">
667
+ <Icon className="h-4 w-4 mr-3" />
668
+ {label}
669
+ </SidebarMenuButton>
670
+ </a>
671
+ </SidebarMenuItem>
672
+ );
673
+ }
674
+
675
+ return (
676
+ <SidebarMenuItem>
677
+ <NavLink to={to}>
678
+ <SidebarMenuButton className="rounded cursor-pointer">
679
+ <Icon className="h-4 w-4 mr-3" />
680
+ {label}
681
+ </SidebarMenuButton>
682
+ </NavLink>
683
+ </SidebarMenuItem>
684
+ );
685
+ };