@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,270 @@
1
+ import CharacterForm from '@/components/character-form';
2
+ import { useAgentUpdate } from '@/hooks/use-agent-update';
3
+ import { useAgentManagement } from '@/hooks/use-agent-management';
4
+ import ConfirmationDialog from '@/components/confirmation-dialog';
5
+ import { useConfirmation } from '@/hooks/use-confirmation';
6
+ import { useToast } from '@/hooks/use-toast';
7
+ import { createElizaClient } from '@/lib/api-client-config';
8
+ import type { Agent, UUID } from '@elizaos/core';
9
+ import { AgentStatus } from '@elizaos/core';
10
+ import { useQueryClient } from '@tanstack/react-query';
11
+ import { useState, useRef } from 'react';
12
+ import { useNavigate } from 'react-router-dom';
13
+ import AvatarPanel from './avatar-panel';
14
+ import PluginsPanel from './plugins-panel';
15
+ import { SecretPanel, type SecretPanelRef } from './secret-panel';
16
+ import { useDeleteAgent } from '@/hooks/use-delete-agent';
17
+
18
+ export default function AgentSettings({
19
+ agent,
20
+ agentId,
21
+ onSaveComplete,
22
+ }: {
23
+ agent: Agent;
24
+ agentId: UUID;
25
+ onSaveComplete?: () => void;
26
+ }) {
27
+ const { toast } = useToast();
28
+ const navigate = useNavigate();
29
+ const queryClient = useQueryClient();
30
+ const { confirm, isOpen, onOpenChange, onConfirm, options } = useConfirmation();
31
+ const isActive = agent?.status === AgentStatus.ACTIVE;
32
+ const secretPanelRef = useRef<SecretPanelRef>(null);
33
+ const [currentSecrets, setCurrentSecrets] = useState<Record<string, string | null>>({});
34
+
35
+ const { handleDelete: handleDeleteAgent, isDeleting: isDeletingAgent } = useDeleteAgent(agent);
36
+
37
+ // Use our enhanced agent update hook for more intelligent handling of JSONb fields
38
+ const agentState = useAgentUpdate(agent);
39
+
40
+ // Use agent management hook for stop functionality
41
+ const { stopAgent, isAgentStopping } = useAgentManagement();
42
+
43
+ const handleStopAgent = async () => {
44
+ try {
45
+ await stopAgent(agent);
46
+ toast({
47
+ title: 'Success',
48
+ description: 'Agent stopped successfully',
49
+ });
50
+ } catch (error) {
51
+ toast({
52
+ title: 'Error',
53
+ description: error instanceof Error ? error.message : 'Failed to stop agent',
54
+ variant: 'destructive',
55
+ });
56
+ }
57
+ };
58
+
59
+ const handleSubmit = async () => {
60
+ if (!agentId) {
61
+ toast({
62
+ title: 'Error',
63
+ description: 'Agent ID is missing',
64
+ variant: 'destructive',
65
+ });
66
+ return;
67
+ }
68
+
69
+ // Define the actual save logic
70
+ const performSave = async () => {
71
+ try {
72
+ const elizaClient = createElizaClient();
73
+ // Get secrets from state (or ref as fallback)
74
+ const secrets =
75
+ Object.keys(currentSecrets).length > 0
76
+ ? currentSecrets
77
+ : secretPanelRef.current?.getSecrets() || {};
78
+
79
+ // Get only the fields that have changed
80
+ const changedFields = agentState.getChangedFields();
81
+
82
+ // Manually add secrets to changedFields if they exist
83
+ if (secrets && Object.keys(secrets).length > 0) {
84
+ // Ensure settings object exists in changedFields
85
+ if (!changedFields.settings) {
86
+ changedFields.settings = {};
87
+ }
88
+
89
+ const activeSecrets = Object.entries(secrets)
90
+ .filter(([_, value]) => value !== null)
91
+ .reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {});
92
+
93
+ // Add only active secrets to the settings (exclude deleted ones)
94
+ changedFields.settings.secrets = activeSecrets;
95
+ }
96
+
97
+ // No need to send update if nothing changed
98
+ if (Object.keys(changedFields).length === 0) {
99
+ const secrets = agentState.agent?.settings?.secrets;
100
+ // Force include secrets if they exist even if no other changes detected
101
+ if (secrets && Object.keys(secrets).length > 0) {
102
+ const forceUpdate = {
103
+ id: agentId,
104
+ settings: { secrets },
105
+ };
106
+
107
+ await elizaClient.agents.updateAgent(agentId, forceUpdate as Partial<Agent>);
108
+
109
+ queryClient.invalidateQueries({ queryKey: ['agent', agentId] });
110
+ queryClient.invalidateQueries({ queryKey: ['agents'] });
111
+
112
+ toast({
113
+ title: 'Success',
114
+ description: 'Agent secrets updated successfully',
115
+ });
116
+
117
+ if (onSaveComplete) {
118
+ onSaveComplete();
119
+ } else {
120
+ navigate('/');
121
+ }
122
+ return;
123
+ }
124
+
125
+ toast({
126
+ title: 'No Changes',
127
+ description: 'No changes were made to the agent',
128
+ });
129
+
130
+ if (onSaveComplete) {
131
+ onSaveComplete();
132
+ } else {
133
+ navigate('/');
134
+ }
135
+ return;
136
+ }
137
+
138
+ // Always include the ID
139
+ const partialUpdate = {
140
+ id: agentId,
141
+ ...changedFields,
142
+ };
143
+
144
+ // Send the partial update
145
+ await elizaClient.agents.updateAgent(agentId, partialUpdate as Agent);
146
+
147
+ // Invalidate both the agent query and the agents list
148
+ queryClient.invalidateQueries({ queryKey: ['agent', agentId] });
149
+ queryClient.invalidateQueries({ queryKey: ['agents'] });
150
+
151
+ toast({
152
+ title: 'Success',
153
+ description: 'Agent updated successfully',
154
+ });
155
+
156
+ // Call the onSaveComplete callback if provided, otherwise navigate
157
+ if (onSaveComplete) {
158
+ onSaveComplete();
159
+ } else {
160
+ navigate('/');
161
+ }
162
+ } catch (error) {
163
+ toast({
164
+ title: 'Error',
165
+ description: error instanceof Error ? error.message : 'Failed to update agent',
166
+ variant: 'destructive',
167
+ });
168
+ throw error;
169
+ }
170
+ };
171
+
172
+ // Validate required secrets if we have a secret panel ref
173
+ if (secretPanelRef?.current) {
174
+ const secretValidation = secretPanelRef.current.validateSecrets();
175
+ if (!secretValidation.isValid) {
176
+ // Show confirmation dialog for missing secrets
177
+ confirm(
178
+ {
179
+ title: 'Missing Required Secrets',
180
+ description: `The following required secrets are missing: ${secretValidation.missingSecrets.join(', ')}. Do you want to save anyway?`,
181
+ confirmText: 'Save Anyway',
182
+ cancelText: 'Cancel',
183
+ variant: 'destructive',
184
+ },
185
+ performSave
186
+ );
187
+ return; // Exit early - performSave will be called if user confirms
188
+ }
189
+ }
190
+
191
+ // If validation passes or no secret panel, proceed with save
192
+ await performSave();
193
+ };
194
+
195
+ const handleDelete = () => {
196
+ if (isDeletingAgent) return; // Prevent multiple clicks
197
+
198
+ confirm(
199
+ {
200
+ title: 'Delete Agent',
201
+ description: `Are you sure you want to delete the agent "${agent.name}"? This action cannot be undone.`,
202
+ confirmText: 'Delete',
203
+ variant: 'destructive',
204
+ },
205
+ handleDeleteAgent
206
+ );
207
+ };
208
+
209
+ return (
210
+ <>
211
+ <CharacterForm
212
+ characterValue={agentState.agent}
213
+ setCharacterValue={agentState}
214
+ title="Agent Settings"
215
+ description="Configure your AI agent's behaviour and capabilities."
216
+ onSubmit={handleSubmit}
217
+ onReset={agentState.reset}
218
+ onDelete={handleDelete}
219
+ onStopAgent={isActive ? handleStopAgent : undefined}
220
+ isAgent={true}
221
+ isDeleting={isDeletingAgent}
222
+ isStopping={isAgentStopping(agentId)}
223
+ customComponents={[
224
+ {
225
+ name: 'Plugins',
226
+ component: (
227
+ <PluginsPanel
228
+ characterValue={agentState.agent}
229
+ setCharacterValue={agentState}
230
+ initialPlugins={agent.plugins}
231
+ />
232
+ ),
233
+ },
234
+ {
235
+ name: 'Secret',
236
+ component: (
237
+ <SecretPanel
238
+ characterValue={agentState.agent}
239
+ ref={secretPanelRef}
240
+ onChange={(secrets) => {
241
+ setCurrentSecrets(secrets);
242
+ // Also update the agent state so changes persist across tab switches
243
+ agentState.updateSettings({ secrets });
244
+ }}
245
+ />
246
+ ),
247
+ },
248
+ {
249
+ name: 'Avatar',
250
+ component: (
251
+ <AvatarPanel characterValue={agentState.agent} setCharacterValue={agentState} />
252
+ ),
253
+ },
254
+ ]}
255
+ />
256
+
257
+ {/* Confirmation Dialog */}
258
+ <ConfirmationDialog
259
+ open={isOpen}
260
+ onOpenChange={onOpenChange}
261
+ title={options?.title || ''}
262
+ description={options?.description || ''}
263
+ confirmText={options?.confirmText}
264
+ cancelText={options?.cancelText}
265
+ variant={options?.variant}
266
+ onConfirm={onConfirm}
267
+ />
268
+ </>
269
+ );
270
+ }
@@ -0,0 +1,178 @@
1
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
2
+ import { useAgentPanels, useAgent, type AgentPanel } from '@/hooks/use-query-hooks';
3
+ import type { UUID, Agent } from '@elizaos/core';
4
+ import { Columns3, Database, Eye, Code, InfoIcon, Loader2 } from 'lucide-react';
5
+ import { JSX, useMemo } from 'react';
6
+ import { AgentActionViewer } from './agent-action-viewer';
7
+ import { AgentLogViewer } from './agent-log-viewer';
8
+ import { AgentMemoryViewer } from './agent-memory-viewer';
9
+ import { Skeleton } from './ui/skeleton';
10
+ import AgentSettings from '@/components/agent-settings';
11
+ import { useAgentTabState } from '@/hooks/use-agent-tab-state';
12
+
13
+ type AgentSidebarProps = {
14
+ agentId: UUID | undefined;
15
+ agentName: string;
16
+ channelId?: UUID;
17
+ };
18
+
19
+ type FixedTabValue = 'details' | 'actions' | 'logs' | 'memories';
20
+ type TabValue = FixedTabValue | string;
21
+
22
+ export function AgentSidebar({ agentId, agentName, channelId }: AgentSidebarProps) {
23
+ const { currentTab: detailsTab, setTab: setDetailsTab } = useAgentTabState(agentId);
24
+ const { data: panelsResponse, isLoading: isLoadingPanels } = useAgentPanels(agentId!, {
25
+ enabled: !!agentId,
26
+ });
27
+
28
+ const {
29
+ data: agentDataResponse,
30
+ isLoading: isLoadingAgent,
31
+ error: agentError,
32
+ } = useAgent(agentId, { enabled: !!agentId && detailsTab === 'details' });
33
+
34
+ const agent = agentDataResponse?.data as Agent | undefined;
35
+
36
+ const agentPanels = useMemo(() => {
37
+ return panelsResponse?.data || [];
38
+ }, [panelsResponse]);
39
+
40
+ const allTabs: { value: TabValue; label: string; icon: JSX.Element }[] = useMemo(() => {
41
+ const fixedTabs: { value: FixedTabValue; label: string; icon: JSX.Element }[] = [
42
+ { value: 'details', label: 'Details', icon: <InfoIcon className="h-4 w-4" /> },
43
+ { value: 'actions', label: 'Model Calls', icon: <Eye className="h-4 w-4" /> },
44
+ { value: 'memories', label: 'Memories', icon: <Database className="h-4 w-4" /> },
45
+ { value: 'logs', label: 'Logs', icon: <Code className="h-4 w-4" /> },
46
+ ];
47
+
48
+ const dynamicTabs = agentPanels.map((panel: AgentPanel) => ({
49
+ value: panel.name,
50
+ label: panel.name,
51
+ icon: <Columns3 className="h-4 w-4" />,
52
+ }));
53
+
54
+ return [...fixedTabs, ...dynamicTabs];
55
+ }, [agentPanels]);
56
+
57
+ return (
58
+ <Tabs
59
+ defaultValue="details"
60
+ value={detailsTab}
61
+ onValueChange={setDetailsTab}
62
+ className="flex flex-col h-screen w-full max-w-full"
63
+ >
64
+ <TabsList className="flex w-full max-w-full overflow-x-auto flex-shrink-0">
65
+ {allTabs.map((tab) => (
66
+ <TabsTrigger
67
+ key={tab.value}
68
+ value={tab.value}
69
+ className="flex items-center gap-1.5 flex-shrink-0"
70
+ >
71
+ {tab.icon}
72
+ <span className="truncate">{tab.label}</span>
73
+ </TabsTrigger>
74
+ ))}
75
+ {isLoadingPanels && (
76
+ <>
77
+ {[...Array(2)].map((_, i) => (
78
+ <Skeleton key={`skel-tab-${i}`} className="h-9 w-full rounded-md flex-shrink-0" />
79
+ ))}
80
+ </>
81
+ )}
82
+ </TabsList>
83
+
84
+ <TabsContent
85
+ value="details"
86
+ className="overflow-y-auto overflow-x-hidden flex-1 p-4 w-full max-w-full min-h-0"
87
+ >
88
+ {detailsTab === 'details' && agentId && (
89
+ <>
90
+ {isLoadingAgent && (
91
+ <div className="flex items-center justify-center h-full">
92
+ <Loader2 className="h-6 w-6 animate-spin text-primary" />
93
+ </div>
94
+ )}
95
+ {agentError && (
96
+ <div className="text-red-500 break-words">
97
+ Error loading agent details: {agentError.message}
98
+ </div>
99
+ )}
100
+ {!isLoadingAgent && !agentError && agent && (
101
+ <div className="w-full max-w-full">
102
+ <AgentSettings
103
+ agent={agent}
104
+ agentId={agentId}
105
+ onSaveComplete={() => {
106
+ // Stay on the same page after save in sidebar context
107
+ // Agent settings saved from sidebar
108
+ }}
109
+ />
110
+ </div>
111
+ )}
112
+ {!isLoadingAgent && !agentError && !agent && !isLoadingPanels && (
113
+ <div className="text-muted-foreground">Agent details not found.</div>
114
+ )}
115
+ </>
116
+ )}
117
+ {detailsTab === 'details' && !agentId && (
118
+ <div className="p-4 text-muted-foreground">Select an agent to see their details.</div>
119
+ )}
120
+ </TabsContent>
121
+
122
+ <TabsContent
123
+ value="actions"
124
+ className="overflow-y-auto overflow-x-hidden flex-1 w-full max-w-full min-h-0"
125
+ >
126
+ {detailsTab === 'actions' && agentId && (
127
+ <div className="w-full max-w-full">
128
+ <AgentActionViewer agentId={agentId} />
129
+ </div>
130
+ )}
131
+ {detailsTab === 'actions' && !agentId && (
132
+ <div className="p-4 text-muted-foreground">Select an agent to see their actions.</div>
133
+ )}
134
+ </TabsContent>
135
+ <TabsContent
136
+ value="logs"
137
+ className="overflow-y-auto overflow-x-hidden flex-1 w-full max-w-full min-h-0"
138
+ >
139
+ {detailsTab === 'logs' && agentId && (
140
+ <div className="w-full max-w-full">
141
+ <AgentLogViewer agentName={agentName} level="all" />
142
+ </div>
143
+ )}
144
+ {detailsTab === 'logs' && !agentId && (
145
+ <div className="p-4 text-muted-foreground">Select an agent to see their logs.</div>
146
+ )}
147
+ </TabsContent>
148
+ <TabsContent
149
+ value="memories"
150
+ className="overflow-y-auto overflow-x-hidden flex-1 w-full max-w-full min-h-0"
151
+ >
152
+ {detailsTab === 'memories' && agentId && (
153
+ <div className="w-full max-w-full">
154
+ <AgentMemoryViewer agentId={agentId} agentName={agentName} channelId={channelId} />
155
+ </div>
156
+ )}
157
+ {detailsTab === 'memories' && !agentId && (
158
+ <div className="p-4 text-muted-foreground">Select an agent to see their memories.</div>
159
+ )}
160
+ </TabsContent>
161
+ {agentPanels.map((panel: AgentPanel) => (
162
+ <TabsContent
163
+ key={panel.name}
164
+ value={panel.name}
165
+ className="overflow-y-auto overflow-x-hidden flex-1 w-full max-w-full min-h-0"
166
+ >
167
+ {detailsTab === panel.name && agentId && (
168
+ <iframe
169
+ src={panel.url}
170
+ title={panel.name}
171
+ className="w-full h-full border-0 flex-1 max-w-full"
172
+ />
173
+ )}
174
+ </TabsContent>
175
+ ))}
176
+ </Tabs>
177
+ );
178
+ }
@@ -0,0 +1,113 @@
1
+ import { useState, useEffect } from 'react';
2
+ import { Button } from '@/components/ui/button';
3
+ import {
4
+ Dialog,
5
+ DialogContent,
6
+ DialogDescription,
7
+ DialogFooter,
8
+ DialogHeader,
9
+ DialogTitle,
10
+ } from '@/components/ui/dialog';
11
+ import { Input } from '@/components/ui/input';
12
+ import { Label } from '@/components/ui/label';
13
+ import { Eye, EyeOff } from 'lucide-react';
14
+ import { useToast } from '@/hooks/use-toast';
15
+
16
+ const getLocalStorageApiKey = () =>
17
+ typeof window === 'undefined' ? 'eliza-api-key' : `eliza-api-key-${window.location.origin}`;
18
+ interface ApiKeyDialogProps {
19
+ open: boolean;
20
+ onOpenChange: (open: boolean) => void;
21
+ onApiKeySaved: () => void;
22
+ }
23
+
24
+ export function ApiKeyDialog({ open, onOpenChange, onApiKeySaved }: ApiKeyDialogProps) {
25
+ const [apiKey, setApiKey] = useState('');
26
+ const [showKey, setShowKey] = useState(false);
27
+ const { toast } = useToast();
28
+
29
+ const storageKey = getLocalStorageApiKey();
30
+
31
+ useEffect(() => {
32
+ if (open) {
33
+ try {
34
+ const storedKey = localStorage.getItem(storageKey);
35
+ if (storedKey) setApiKey(storedKey);
36
+ } catch (err) {
37
+ console.error('Unable to access localStorage', err);
38
+ }
39
+ }
40
+ }, [open, storageKey]);
41
+
42
+ const handleSave = () => {
43
+ if (!apiKey.trim()) {
44
+ toast({
45
+ title: 'API Key Required',
46
+ description: 'Please enter a valid API key.',
47
+ variant: 'destructive',
48
+ });
49
+ return;
50
+ }
51
+ try {
52
+ localStorage.setItem(storageKey, apiKey);
53
+ } catch (err) {
54
+ toast({
55
+ title: 'Storage Error',
56
+ description: 'Unable to save the API key (browser storage blocked).',
57
+ variant: 'destructive',
58
+ });
59
+ return;
60
+ }
61
+ onApiKeySaved();
62
+ toast({
63
+ title: 'API Key Saved',
64
+ description: 'The API key has been stored locally.',
65
+ });
66
+ onOpenChange(false);
67
+ };
68
+
69
+ return (
70
+ <Dialog open={open} onOpenChange={onOpenChange}>
71
+ <DialogContent className="sm:max-w-[425px]">
72
+ <DialogHeader>
73
+ <DialogTitle>API Key Required</DialogTitle>
74
+ <DialogDescription>
75
+ Please enter the API key required to connect to the Eliza server. This key will be
76
+ stored locally in your browser.
77
+ </DialogDescription>
78
+ </DialogHeader>
79
+ <div className="grid gap-4 py-4">
80
+ <div className="grid grid-cols-4 items-center gap-4">
81
+ <Label htmlFor="api-key" className="text-right">
82
+ API Key
83
+ </Label>
84
+ <div className="col-span-3 relative">
85
+ <Input
86
+ id="api-key"
87
+ type={showKey ? 'text' : 'password'}
88
+ value={apiKey}
89
+ onChange={(e) => setApiKey(e.target.value)}
90
+ className="pr-10"
91
+ />
92
+ <Button
93
+ type="button"
94
+ variant="ghost"
95
+ size="icon"
96
+ className="absolute right-1 top-1/2 h-7 w-7 -translate-y-1/2 text-muted-foreground"
97
+ onClick={() => setShowKey(!showKey)}
98
+ aria-label={showKey ? 'Hide API key' : 'Show API key'}
99
+ >
100
+ {showKey ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
101
+ </Button>
102
+ </div>
103
+ </div>
104
+ </div>
105
+ <DialogFooter>
106
+ <Button type="button" onClick={handleSave}>
107
+ Save Key
108
+ </Button>
109
+ </DialogFooter>
110
+ </DialogContent>
111
+ </Dialog>
112
+ );
113
+ }