@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.
- package/LICENSE +21 -0
- package/README.md +350 -0
- package/dist/assets/empty-module-CLMscLYw.js +1 -0
- package/dist/assets/main-BBZ_3lkn.css +5999 -0
- package/dist/assets/main-C5zNUkXH.js +7 -0
- package/dist/assets/main-Dz64ENQg.js +614 -0
- package/dist/assets/react-vendor-DM5m98rr.js +545 -0
- package/dist/assets/ui-vendor-BQCqNqg0.js +1 -0
- package/dist/elizaos-avatar.png +0 -0
- package/dist/elizaos-icon.png +0 -0
- package/dist/elizaos-logo-light.png +0 -0
- package/dist/elizaos.webp +0 -0
- package/dist/favicon.ico +0 -0
- package/dist/images/agents/agent1.png +0 -0
- package/dist/images/agents/agent2.png +0 -0
- package/dist/images/agents/agent3.png +0 -0
- package/dist/images/agents/agent4.png +0 -0
- package/dist/images/agents/agent5.png +0 -0
- package/dist/index.html +14 -0
- package/index.html +24 -0
- package/package.json +159 -0
- package/postcss.config.js +3 -0
- package/public/elizaos-avatar.png +0 -0
- package/public/elizaos-icon.png +0 -0
- package/public/elizaos-logo-light.png +0 -0
- package/public/elizaos.webp +0 -0
- package/public/favicon.ico +0 -0
- package/public/images/agents/agent1.png +0 -0
- package/public/images/agents/agent2.png +0 -0
- package/public/images/agents/agent3.png +0 -0
- package/public/images/agents/agent4.png +0 -0
- package/public/images/agents/agent5.png +0 -0
- package/src/App.tsx +222 -0
- package/src/components/AgentDetailsPanel.tsx +147 -0
- package/src/components/ChatInputArea.tsx +196 -0
- package/src/components/ChatMessageListComponent.tsx +139 -0
- package/src/components/actionTool.tsx +186 -0
- package/src/components/add-agent-card.tsx +77 -0
- package/src/components/agent-action-viewer.tsx +816 -0
- package/src/components/agent-avatar-stack.tsx +121 -0
- package/src/components/agent-card.cy.tsx +259 -0
- package/src/components/agent-card.tsx +177 -0
- package/src/components/agent-creator.tsx +142 -0
- package/src/components/agent-log-viewer.tsx +645 -0
- package/src/components/agent-memory-edit-overlay.tsx +461 -0
- package/src/components/agent-memory-viewer.tsx +504 -0
- package/src/components/agent-settings.tsx +270 -0
- package/src/components/agent-sidebar.tsx +178 -0
- package/src/components/api-key-dialog.tsx +113 -0
- package/src/components/app-sidebar.tsx +685 -0
- package/src/components/array-input.tsx +116 -0
- package/src/components/audio-recorder.tsx +292 -0
- package/src/components/avatar-panel.tsx +141 -0
- package/src/components/character-form.tsx +1138 -0
- package/src/components/chat.tsx +1813 -0
- package/src/components/combobox.tsx +187 -0
- package/src/components/confirmation-dialog.tsx +59 -0
- package/src/components/connection-error-banner.tsx +101 -0
- package/src/components/connection-status.cy.tsx +73 -0
- package/src/components/connection-status.tsx +155 -0
- package/src/components/copy-button.tsx +35 -0
- package/src/components/delete-button.tsx +24 -0
- package/src/components/env-settings.tsx +261 -0
- package/src/components/group-card.tsx +160 -0
- package/src/components/group-panel.tsx +543 -0
- package/src/components/input-copy.tsx +21 -0
- package/src/components/logs-page.tsx +41 -0
- package/src/components/media-content.tsx +385 -0
- package/src/components/memory-graph.tsx +170 -0
- package/src/components/missing-secrets-dialog.tsx +72 -0
- package/src/components/onboarding-tour.tsx +247 -0
- package/src/components/page-title.tsx +8 -0
- package/src/components/plugins-panel.tsx +383 -0
- package/src/components/profile-card.tsx +66 -0
- package/src/components/profile-overlay.tsx +283 -0
- package/src/components/retry-button.tsx +28 -0
- package/src/components/secret-panel.tsx +1505 -0
- package/src/components/server-management.tsx +264 -0
- package/src/components/split-button.tsx +148 -0
- package/src/components/stop-agent-button.tsx +99 -0
- package/src/components/ui/alert-dialog.cy.tsx +333 -0
- package/src/components/ui/alert-dialog.tsx +115 -0
- package/src/components/ui/alert.tsx +49 -0
- package/src/components/ui/avatar.cy.tsx +180 -0
- package/src/components/ui/avatar.tsx +57 -0
- package/src/components/ui/badge.cy.tsx +146 -0
- package/src/components/ui/badge.tsx +43 -0
- package/src/components/ui/button.cy.tsx +177 -0
- package/src/components/ui/button.tsx +56 -0
- package/src/components/ui/card.cy.tsx +160 -0
- package/src/components/ui/card.tsx +73 -0
- package/src/components/ui/chat/animated-markdown.tsx +59 -0
- package/src/components/ui/chat/chat-bubble.tsx +178 -0
- package/src/components/ui/chat/chat-container.tsx +51 -0
- package/src/components/ui/chat/chat-input.cy.tsx +169 -0
- package/src/components/ui/chat/chat-input.tsx +47 -0
- package/src/components/ui/chat/chat-message-list.tsx +61 -0
- package/src/components/ui/chat/chat-tts-button.tsx +199 -0
- package/src/components/ui/chat/code-block.tsx +79 -0
- package/src/components/ui/chat/expandable-chat.tsx +131 -0
- package/src/components/ui/chat/hooks/useAutoScroll.ts +86 -0
- package/src/components/ui/chat/markdown.tsx +209 -0
- package/src/components/ui/chat/message-loading.tsx +48 -0
- package/src/components/ui/checkbox.cy.tsx +170 -0
- package/src/components/ui/checkbox.tsx +30 -0
- package/src/components/ui/collapsible.cy.tsx +283 -0
- package/src/components/ui/collapsible.tsx +9 -0
- package/src/components/ui/command.cy.tsx +313 -0
- package/src/components/ui/command.tsx +143 -0
- package/src/components/ui/dialog.cy.tsx +279 -0
- package/src/components/ui/dialog.tsx +104 -0
- package/src/components/ui/dropdown-menu.cy.tsx +273 -0
- package/src/components/ui/dropdown-menu.tsx +281 -0
- package/src/components/ui/input.cy.tsx +82 -0
- package/src/components/ui/input.tsx +27 -0
- package/src/components/ui/label.cy.tsx +157 -0
- package/src/components/ui/label.tsx +19 -0
- package/src/components/ui/resizable.tsx +42 -0
- package/src/components/ui/scroll-area.cy.tsx +242 -0
- package/src/components/ui/scroll-area.tsx +46 -0
- package/src/components/ui/select.cy.tsx +277 -0
- package/src/components/ui/select.tsx +155 -0
- package/src/components/ui/separator.cy.tsx +145 -0
- package/src/components/ui/separator.tsx +29 -0
- package/src/components/ui/sheet.cy.tsx +324 -0
- package/src/components/ui/sheet.tsx +119 -0
- package/src/components/ui/sidebar.tsx +734 -0
- package/src/components/ui/skeleton.cy.tsx +149 -0
- package/src/components/ui/skeleton.tsx +17 -0
- package/src/components/ui/split-button.cy.tsx +274 -0
- package/src/components/ui/split-button.tsx +112 -0
- package/src/components/ui/switch.tsx +28 -0
- package/src/components/ui/tabs.cy.tsx +271 -0
- package/src/components/ui/tabs.tsx +53 -0
- package/src/components/ui/textarea.cy.tsx +136 -0
- package/src/components/ui/textarea.tsx +26 -0
- package/src/components/ui/toast.cy.tsx +209 -0
- package/src/components/ui/toast.tsx +126 -0
- package/src/components/ui/toaster.tsx +29 -0
- package/src/components/ui/tooltip.cy.tsx +244 -0
- package/src/components/ui/tooltip.tsx +30 -0
- package/src/config/agent-templates.ts +349 -0
- package/src/config/voice-models.ts +181 -0
- package/src/constants.ts +23 -0
- package/src/context/AuthContext.tsx +44 -0
- package/src/context/ConnectionContext.tsx +194 -0
- package/src/entry.tsx +9 -0
- package/src/hooks/__tests__/use-agent-tab-state.test.ts +137 -0
- package/src/hooks/__tests__/use-agent-update.test.tsx +250 -0
- package/src/hooks/__tests__/use-character-convert.test.ts +102 -0
- package/src/hooks/__tests__/use-panel-width-state.test.ts +243 -0
- package/src/hooks/__tests__/use-sidebar-state.test.ts +117 -0
- package/src/hooks/use-agent-management.ts +130 -0
- package/src/hooks/use-agent-tab-state.ts +74 -0
- package/src/hooks/use-agent-update.ts +469 -0
- package/src/hooks/use-character-convert.ts +138 -0
- package/src/hooks/use-confirmation.ts +55 -0
- package/src/hooks/use-delete-agent.ts +123 -0
- package/src/hooks/use-dm-channels.ts +198 -0
- package/src/hooks/use-elevenlabs-voices.ts +83 -0
- package/src/hooks/use-file-upload.ts +224 -0
- package/src/hooks/use-mobile.tsx +19 -0
- package/src/hooks/use-onboarding.tsx +49 -0
- package/src/hooks/use-panel-width-state.ts +147 -0
- package/src/hooks/use-partial-update.ts +288 -0
- package/src/hooks/use-plugin-details.ts +462 -0
- package/src/hooks/use-plugins.ts +119 -0
- package/src/hooks/use-query-hooks.ts +1263 -0
- package/src/hooks/use-server-agents.ts +62 -0
- package/src/hooks/use-server-version.tsx +47 -0
- package/src/hooks/use-sidebar-state.ts +50 -0
- package/src/hooks/use-socket-chat.ts +264 -0
- package/src/hooks/use-toast.ts +260 -0
- package/src/hooks/use-version.tsx +64 -0
- package/src/index.css +146 -0
- package/src/lib/api-client-config.ts +53 -0
- package/src/lib/api-type-mappers.ts +196 -0
- package/src/lib/export-utils.ts +123 -0
- package/src/lib/logger.ts +19 -0
- package/src/lib/media-utils.ts +170 -0
- package/src/lib/pca.test.ts +17 -0
- package/src/lib/pca.ts +52 -0
- package/src/lib/socketio-manager.ts +664 -0
- package/src/lib/utils.ts +168 -0
- package/src/main.tsx +16 -0
- package/src/mocks/empty-module.ts +12 -0
- package/src/mocks/node-module.ts +57 -0
- package/src/polyfills.ts +37 -0
- package/src/routes/agent-detail.tsx +30 -0
- package/src/routes/agent-list.tsx +27 -0
- package/src/routes/agent-settings.tsx +48 -0
- package/src/routes/character-detail.tsx +52 -0
- package/src/routes/character-form.tsx +79 -0
- package/src/routes/character-list.tsx +38 -0
- package/src/routes/chat.tsx +128 -0
- package/src/routes/createAgent.tsx +13 -0
- package/src/routes/group-new.tsx +50 -0
- package/src/routes/group.tsx +29 -0
- package/src/routes/home.tsx +218 -0
- package/src/routes/not-found.tsx +71 -0
- package/src/test/setup.ts +154 -0
- package/src/types/crypto-browserify.d.ts +4 -0
- package/src/types/index.ts +13 -0
- package/src/types/rooms.ts +8 -0
- package/src/types.ts +84 -0
- package/src/vite-env.d.ts +40 -0
- package/tailwind.config.ts +90 -0
- package/tsconfig.json +10 -0
- 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
|
+
};
|