@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,645 @@
1
+ import { Database, LoaderIcon, Search, Clock, Trash2, Filter, RefreshCw } from 'lucide-react';
2
+ import { useState, useEffect, useCallback } from 'react';
3
+ import { useQuery, useQueryClient } from '@tanstack/react-query';
4
+ import { Button } from '@/components/ui/button';
5
+ import { Input } from '@/components/ui/input';
6
+ import {
7
+ Select,
8
+ SelectContent,
9
+ SelectItem,
10
+ SelectTrigger,
11
+ SelectValue,
12
+ } from '@/components/ui/select';
13
+ import { Badge } from '@/components/ui/badge';
14
+ import { useAgents } from '../hooks/use-query-hooks';
15
+ import { createElizaClient } from '../lib/api-client-config';
16
+ import SocketIOManager, { type LogStreamData } from '../lib/socketio-manager';
17
+ import { useConfirmation } from '@/hooks/use-confirmation';
18
+ import ConfirmationDialog from './confirmation-dialog';
19
+
20
+ // Types
21
+ interface LogEntry {
22
+ level: number;
23
+ time: number;
24
+ msg: string;
25
+ agentId?: string;
26
+ agentName?: string;
27
+ roomId?: string;
28
+ [key: string]: string | number | boolean | null | undefined;
29
+ }
30
+
31
+ interface LogResponse {
32
+ logs: LogEntry[];
33
+ count: number;
34
+ total: number;
35
+ level: string;
36
+ levels: string[];
37
+ }
38
+
39
+ interface AgentLogViewerProps {
40
+ agentName?: string;
41
+ level?: string;
42
+ }
43
+
44
+ // Log level mappings
45
+ const LOG_LEVEL_NUMBERS = {
46
+ 10: 'TRACE',
47
+ 20: 'DEBUG',
48
+ 27: 'SUCCESS',
49
+ 28: 'PROGRESS',
50
+ 29: 'LOG',
51
+ 30: 'INFO',
52
+ 40: 'WARN',
53
+ 50: 'ERROR',
54
+ 60: 'FATAL',
55
+ } as const;
56
+
57
+ // Helper functions
58
+ function getLevelName(level: number): string {
59
+ return LOG_LEVEL_NUMBERS[level as keyof typeof LOG_LEVEL_NUMBERS] || 'UNKNOWN';
60
+ }
61
+
62
+ function getLevelColor(level: number): string {
63
+ if (level >= 50) return 'bg-red-600/80'; // ERROR/FATAL - more muted red
64
+ if (level >= 40) return 'bg-amber-600/80'; // WARN - more muted amber
65
+ if (level >= 27) return 'bg-emerald-600/80'; // SUCCESS - more muted green
66
+ return 'bg-slate-500'; // INFO/DEBUG/TRACE - neutral gray instead of blue
67
+ }
68
+
69
+ function formatTimestamp(timestamp: number): string {
70
+ const date = new Date(timestamp);
71
+ return date.toLocaleDateString('en-US', {
72
+ month: 'short',
73
+ day: '2-digit',
74
+ hour: '2-digit',
75
+ minute: '2-digit',
76
+ second: '2-digit',
77
+ hour12: false,
78
+ });
79
+ }
80
+
81
+ function generateLogChart(logs: LogEntry[]) {
82
+ // Group logs by hour for the last 24 hours
83
+ const now = Date.now();
84
+ const hours = 24;
85
+ const hourlyData: Record<string, { info: number; errors: number }> = {};
86
+
87
+ // Initialize all hours
88
+ for (let i = hours - 1; i >= 0; i--) {
89
+ const hourStart = new Date(now - i * 60 * 60 * 1000);
90
+ hourStart.setMinutes(0, 0, 0);
91
+ const key = hourStart.toISOString();
92
+ hourlyData[key] = { info: 0, errors: 0 };
93
+ }
94
+
95
+ // Count logs by hour - filter logs to last 24 hours only
96
+ const twentyFourHoursAgo = now - 24 * 60 * 60 * 1000;
97
+ logs.forEach((log) => {
98
+ // Only count logs from the last 24 hours
99
+ if (log.time >= twentyFourHoursAgo) {
100
+ const logDate = new Date(log.time);
101
+ logDate.setMinutes(0, 0, 0);
102
+ const key = logDate.toISOString();
103
+
104
+ if (hourlyData[key]) {
105
+ if (log.level >= 50) {
106
+ hourlyData[key].errors++;
107
+ } else {
108
+ hourlyData[key].info++;
109
+ }
110
+ }
111
+ }
112
+ });
113
+
114
+ return Object.entries(hourlyData).map(([timestamp, counts]) => ({
115
+ timestamp: new Date(timestamp),
116
+ ...counts,
117
+ total: counts.info + counts.errors,
118
+ }));
119
+ }
120
+
121
+ function LogChart({ data }: { data: ReturnType<typeof generateLogChart> }) {
122
+ const maxValue = Math.max(...data.map((d) => d.total), 1);
123
+
124
+ return (
125
+ <div className="mb-6">
126
+ <div className="flex items-center justify-between mb-4">
127
+ <div className="flex items-center gap-4">
128
+ <div className="flex items-center gap-2">
129
+ <div className="w-3 h-3 rounded-full bg-slate-500"></div>
130
+ <span className="text-sm text-muted-foreground">
131
+ Info{' '}
132
+ <span className="font-mono">
133
+ {data.reduce((sum, d) => sum + d.info, 0).toLocaleString()}
134
+ </span>
135
+ </span>
136
+ </div>
137
+ <div className="flex items-center gap-2">
138
+ <div className="w-3 h-3 rounded-full bg-red-600/80"></div>
139
+ <span className="text-sm text-muted-foreground">
140
+ Errors{' '}
141
+ <span className="font-mono">
142
+ {data.reduce((sum, d) => sum + d.errors, 0).toLocaleString()}
143
+ </span>
144
+ </span>
145
+ </div>
146
+ </div>
147
+ <div className="text-sm text-muted-foreground">
148
+ {data[data.length - 1]?.timestamp.toLocaleDateString('en-US', {
149
+ month: 'short',
150
+ day: 'numeric',
151
+ hour: '2-digit',
152
+ minute: '2-digit',
153
+ })}{' '}
154
+ - (UTC-7)
155
+ </div>
156
+ </div>
157
+
158
+ <div className="h-32 flex items-end gap-1 bg-muted/30 rounded p-4">
159
+ {data.map((point, index) => {
160
+ return (
161
+ <div key={index} className="flex-1 flex flex-col justify-end">
162
+ <div
163
+ key={`${point.timestamp.getTime()}-info`}
164
+ style={{ height: `${(point.info / maxValue) * 100}%` }}
165
+ className="bg-slate-500 rounded-sm min-h-[2px] transition-all duration-200"
166
+ title={`${point.timestamp.toLocaleTimeString()}: ${point.info} info logs`}
167
+ />
168
+ <div
169
+ key={`${point.timestamp.getTime()}-errors`}
170
+ style={{ height: `${(point.errors / maxValue) * 100}%` }}
171
+ className="bg-red-600/80 rounded-sm min-h-[2px] transition-all duration-200"
172
+ title={`${point.timestamp.toLocaleTimeString()}: ${point.errors} error logs`}
173
+ />
174
+ </div>
175
+ );
176
+ })}
177
+ </div>
178
+
179
+ <div className="flex justify-between text-xs text-muted-foreground mt-2">
180
+ <span>
181
+ {data[0]?.timestamp.toLocaleDateString('en-US', {
182
+ month: 'short',
183
+ day: 'numeric',
184
+ hour: '2-digit',
185
+ minute: '2-digit',
186
+ })}
187
+ </span>
188
+ <span>
189
+ {data[data.length - 1]?.timestamp.toLocaleDateString('en-US', {
190
+ month: 'short',
191
+ day: 'numeric',
192
+ hour: '2-digit',
193
+ })}
194
+ </span>
195
+ </div>
196
+ </div>
197
+ );
198
+ }
199
+
200
+ function LoadingIndicator() {
201
+ return (
202
+ <div className="flex items-center justify-center py-8">
203
+ <div className="flex flex-col items-center gap-4">
204
+ <LoaderIcon className="h-8 w-8 animate-spin text-muted-foreground" />
205
+ <div className="text-center">
206
+ <h3 className="font-medium">Loading Logs</h3>
207
+ <p className="text-sm text-muted-foreground">Fetching system logs...</p>
208
+ </div>
209
+ </div>
210
+ </div>
211
+ );
212
+ }
213
+
214
+ function EmptyState({
215
+ selectedLevel,
216
+ searchQuery,
217
+ }: {
218
+ selectedLevel: string;
219
+ searchQuery: string;
220
+ }) {
221
+ return (
222
+ <div className="flex flex-col items-center justify-center py-16 text-center">
223
+ <Database className="h-16 w-16 text-muted-foreground/30 mb-4" />
224
+ <h3 className="text-lg font-medium mb-2">No Logs Found</h3>
225
+ <p className="text-muted-foreground max-w-md">
226
+ {searchQuery
227
+ ? `No logs match "${searchQuery}". Try adjusting your search or filter.`
228
+ : selectedLevel === 'all'
229
+ ? 'No logs available. Logs will appear here as the system generates them.'
230
+ : `No ${selectedLevel.toUpperCase()} logs found.`}
231
+ </p>
232
+ </div>
233
+ );
234
+ }
235
+
236
+ export function AgentLogViewer({ agentName, level }: AgentLogViewerProps) {
237
+ const [selectedLevel, setSelectedLevel] = useState(level || 'all');
238
+ const [selectedAgentName, setSelectedAgentName] = useState(agentName || 'all');
239
+ const [searchQuery, setSearchQuery] = useState('');
240
+ const [timeRange, setTimeRange] = useState('7 days');
241
+ const [isLive, setIsLive] = useState(true);
242
+ const [isClearing, setIsClearing] = useState(false);
243
+ const [wsLogs, setWsLogs] = useState<LogEntry[]>([]);
244
+ const [useWebSocket, setUseWebSocket] = useState(false);
245
+ const queryClient = useQueryClient();
246
+
247
+ const { confirm, isOpen, onOpenChange, onConfirm, options } = useConfirmation();
248
+
249
+ // Use real hooks from the existing codebase
250
+ const {
251
+ data: logResponse,
252
+ isLoading,
253
+ error,
254
+ refetch,
255
+ } = useQuery<LogResponse>({
256
+ queryKey: ['logs', selectedLevel, selectedAgentName],
257
+ queryFn: async () => {
258
+ const elizaClient = createElizaClient();
259
+ return await elizaClient.system.getGlobalLogs({
260
+ level: selectedLevel === 'all' ? '' : selectedLevel,
261
+ agentName: selectedAgentName === 'all' ? undefined : selectedAgentName,
262
+ });
263
+ },
264
+ refetchInterval: isLive && !useWebSocket ? 5000 : false,
265
+ staleTime: 1000,
266
+ enabled: true, // Always enable the query
267
+ });
268
+
269
+ const { data: agents } = useAgents();
270
+
271
+ // WebSocket log streaming integration
272
+ const handleLogStream = useCallback(
273
+ (logEntry: LogStreamData) => {
274
+ // Filter logs based on current filters
275
+ const shouldInclude =
276
+ (selectedLevel === 'all' ||
277
+ getLevelName(logEntry.level).toLowerCase() === selectedLevel.toLowerCase()) &&
278
+ (selectedAgentName === 'all' || logEntry.agentName === selectedAgentName);
279
+
280
+ if (shouldInclude) {
281
+ setWsLogs((prev) => {
282
+ const newLogs = [logEntry, ...prev].slice(0, 1000); // Keep last 1000 logs
283
+ return newLogs;
284
+ });
285
+ }
286
+ },
287
+ [selectedLevel, selectedAgentName]
288
+ );
289
+
290
+ // Setup WebSocket event listeners
291
+ useEffect(() => {
292
+ if (isLive && useWebSocket) {
293
+ const socketManager = SocketIOManager.getInstance();
294
+
295
+ // Subscribe to log stream
296
+ socketManager.subscribeToLogStream().catch(console.error);
297
+
298
+ // Listen for log events
299
+ socketManager.on('logStream', handleLogStream);
300
+
301
+ return () => {
302
+ socketManager.off('logStream', handleLogStream);
303
+ socketManager.unsubscribeFromLogStream().catch(console.error);
304
+ };
305
+ }
306
+ }, [isLive, useWebSocket, handleLogStream]);
307
+
308
+ // Toggle WebSocket usage when live mode changes
309
+ useEffect(() => {
310
+ if (isLive) {
311
+ // When enabling live mode, try to start WebSocket. Fallback to polling if WebSocket fails
312
+ if (SocketIOManager.isConnected()) {
313
+ setUseWebSocket(true);
314
+ } else {
315
+ // WebSocket not available, continue with API polling
316
+ setUseWebSocket(false);
317
+ }
318
+ } else {
319
+ // When disabling live mode, stop WebSocket and clear WebSocket logs
320
+ setUseWebSocket(false);
321
+ setWsLogs([]);
322
+ }
323
+ }, [isLive]);
324
+
325
+ // Update WebSocket filters when selectedAgentName or selectedLevel changes
326
+ useEffect(() => {
327
+ if (useWebSocket && isLive) {
328
+ const socketManager = SocketIOManager.getInstance();
329
+ socketManager
330
+ .updateLogStreamFilters({
331
+ agentName: selectedAgentName,
332
+ level: selectedLevel,
333
+ })
334
+ .catch(console.error);
335
+ }
336
+ }, [selectedAgentName, selectedLevel, useWebSocket, isLive]);
337
+
338
+ // Combine API logs and WebSocket logs
339
+ const apiLogs = logResponse?.logs || [];
340
+
341
+ // Smart fallback: If WebSocket has significantly fewer logs than API, use API logs
342
+ // This handles cases where WebSocket streaming isn't working properly
343
+ let combinedLogs;
344
+ if (useWebSocket && isLive && wsLogs.length > 0) {
345
+ // If WebSocket has logs, use them
346
+ combinedLogs = wsLogs;
347
+ } else {
348
+ // Use API logs (either not using WebSocket, or WebSocket has no logs)
349
+ combinedLogs = apiLogs;
350
+ }
351
+
352
+ // Sort logs by timestamp in descending order (newest first)
353
+ const logs = combinedLogs.sort((a, b) => b.time - a.time);
354
+ const levels = logResponse?.levels || [];
355
+ const agentNames = agents?.data?.agents?.map((agent) => agent.name) || [];
356
+
357
+ // Filter and search logs
358
+ const filteredLogs = logs.filter((log: LogEntry) => {
359
+ if (searchQuery.trim()) {
360
+ const query = searchQuery.toLowerCase();
361
+ const searchableText = [
362
+ log.msg,
363
+ log.agentName,
364
+ log.roomId,
365
+ getLevelName(log.level),
366
+ ...Object.entries(log)
367
+ .filter(([key]) => !Number.isNaN(Number(key)))
368
+ .map(([_, value]) => String(value)),
369
+ ]
370
+ .filter(Boolean)
371
+ .join(' ')
372
+ .toLowerCase();
373
+
374
+ return searchableText.includes(query);
375
+ }
376
+ return true;
377
+ });
378
+
379
+ const chartData = generateLogChart(filteredLogs);
380
+
381
+ const handleClearLogs = () => {
382
+ confirm(
383
+ {
384
+ title: 'Clear All Logs',
385
+ description:
386
+ 'Are you sure you want to permanently delete all system logs? This action cannot be undone.',
387
+ confirmText: 'Delete',
388
+ variant: 'destructive',
389
+ },
390
+ async () => {
391
+ try {
392
+ setIsClearing(true);
393
+ const elizaClient = createElizaClient();
394
+ await elizaClient.system.deleteGlobalLogs();
395
+ queryClient.invalidateQueries({ queryKey: ['logs'] });
396
+
397
+ // Also clear WebSocket logs if in WebSocket mode
398
+ if (useWebSocket) {
399
+ setWsLogs([]);
400
+ }
401
+ } catch (error) {
402
+ console.error('Failed to clear logs:', error);
403
+ } finally {
404
+ setIsClearing(false);
405
+ }
406
+ }
407
+ );
408
+ };
409
+
410
+ const handleRefresh = () => {
411
+ if (useWebSocket && isLive) {
412
+ // In WebSocket mode, clear current logs and let them re-populate
413
+ setWsLogs([]);
414
+ } else if (refetch) {
415
+ // In API mode, refetch from server
416
+ refetch();
417
+ }
418
+ };
419
+
420
+ // Loading state
421
+ if (isLoading && logs.length === 0) {
422
+ return (
423
+ <div className="flex flex-col h-[calc(100vh-100px)] min-h-[400px] w-full">
424
+ <LoadingIndicator />
425
+ </div>
426
+ );
427
+ }
428
+
429
+ // Error state
430
+ if (error) {
431
+ const isEndpointNotFound =
432
+ error.message?.includes('404') ||
433
+ error.message?.includes('endpoint not found') ||
434
+ error.message?.includes('Not Found');
435
+ return (
436
+ <div className="flex flex-col h-[calc(100vh-100px)] min-h-[400px] w-full">
437
+ <div className="flex items-center justify-center flex-1">
438
+ <div className="text-center max-w-md">
439
+ <Database className="h-12 w-12 text-destructive mx-auto mb-4" />
440
+ <h3 className="font-medium mb-2">
441
+ {isEndpointNotFound ? 'Global Logs API Not Available' : 'Failed to Load Logs'}
442
+ </h3>
443
+ <p className="text-sm text-muted-foreground mb-4">
444
+ {isEndpointNotFound
445
+ ? 'The server does not have the global logs API endpoint configured. You can still view individual agent logs from the agent details pages.'
446
+ : `There was an error loading the system logs: ${error.message}`}
447
+ </p>
448
+ {!isEndpointNotFound && (
449
+ <Button variant="outline" size="sm" onClick={() => refetch?.()}>
450
+ <RefreshCw className="h-4 w-4 mr-2" />
451
+ Try Again
452
+ </Button>
453
+ )}
454
+ </div>
455
+ </div>
456
+ </div>
457
+ );
458
+ }
459
+
460
+ return (
461
+ <>
462
+ <div className="flex flex-col h-[calc(100vh-100px)] min-h-[400px] w-full">
463
+ {/* Header Controls */}
464
+ <div className="flex items-center gap-3 mb-6 px-4 pt-4 flex-none">
465
+ {/* Filter dropdown */}
466
+ <Select value={selectedLevel} onValueChange={setSelectedLevel}>
467
+ <SelectTrigger className="w-32 h-9">
468
+ <Filter className="h-4 w-4 mr-2" />
469
+ <SelectValue placeholder="Filter" />
470
+ </SelectTrigger>
471
+ <SelectContent>
472
+ <SelectItem value="all">ALL</SelectItem>
473
+ {levels.map((level) => (
474
+ <SelectItem key={level} value={level}>
475
+ {level.toUpperCase()}
476
+ </SelectItem>
477
+ ))}
478
+ </SelectContent>
479
+ </Select>
480
+
481
+ {/* Search */}
482
+ <div className="relative flex-1 max-w-md">
483
+ <Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
484
+ <Input
485
+ placeholder="Full-text log search"
486
+ value={searchQuery}
487
+ onChange={(e) => setSearchQuery(e.target.value)}
488
+ className="pl-10 h-9"
489
+ />
490
+ </div>
491
+
492
+ {/* Agent filter */}
493
+ {agentNames && agentNames.length > 0 && !agentName && (
494
+ <Select value={selectedAgentName} onValueChange={setSelectedAgentName}>
495
+ <SelectTrigger className="w-40 h-9">
496
+ <SelectValue placeholder="Agent" />
497
+ </SelectTrigger>
498
+ <SelectContent>
499
+ <SelectItem value="all">ALL AGENTS</SelectItem>
500
+ {agentNames?.map((name) => (
501
+ <SelectItem key={name} value={name!}>
502
+ {name}
503
+ </SelectItem>
504
+ ))}
505
+ </SelectContent>
506
+ </Select>
507
+ )}
508
+
509
+ {/* Time range */}
510
+ <Select value={timeRange} onValueChange={setTimeRange}>
511
+ <SelectTrigger className="w-32 h-9">
512
+ <Clock className="h-4 w-4 mr-2" />
513
+ <SelectValue />
514
+ </SelectTrigger>
515
+ <SelectContent>
516
+ <SelectItem value="1 hour">1 hour</SelectItem>
517
+ <SelectItem value="6 hours">6 hours</SelectItem>
518
+ <SelectItem value="24 hours">24 hours</SelectItem>
519
+ <SelectItem value="7 days">7 days</SelectItem>
520
+ <SelectItem value="30 days">30 days</SelectItem>
521
+ </SelectContent>
522
+ </Select>
523
+
524
+ {/* Refresh */}
525
+ <Button variant="outline" size="sm" onClick={handleRefresh} className="h-9 w-9 p-0">
526
+ <RefreshCw className="h-4 w-4" />
527
+ </Button>
528
+
529
+ {/* Live toggle */}
530
+ <Button
531
+ variant={isLive ? 'default' : 'outline'}
532
+ size="sm"
533
+ onClick={() => setIsLive(!isLive)}
534
+ className="h-9 px-3"
535
+ title={
536
+ isLive
537
+ ? useWebSocket
538
+ ? 'Live mode (WebSocket)'
539
+ : 'Live mode (Polling)'
540
+ : 'Live mode disabled'
541
+ }
542
+ >
543
+ <div
544
+ className={`w-2 h-2 rounded-full mr-2 ${isLive ? 'bg-emerald-500 animate-pulse' : 'bg-slate-400'}`}
545
+ />
546
+ Live
547
+ </Button>
548
+ </div>
549
+
550
+ {/* Content */}
551
+ <div className="flex-1 overflow-hidden px-4 pb-4">
552
+ {filteredLogs.length === 0 ? (
553
+ <EmptyState selectedLevel={selectedLevel} searchQuery={searchQuery} />
554
+ ) : (
555
+ <div className="space-y-6">
556
+ {/* Analytics Chart */}
557
+ <LogChart data={chartData} />
558
+
559
+ {/* Log Table */}
560
+ <div className="border rounded-lg overflow-hidden">
561
+ {/* Table Header */}
562
+ <div className="bg-muted/50 border-b px-4 py-3">
563
+ <div className="grid grid-cols-[200px_1fr] gap-4">
564
+ <div className="text-sm font-medium text-muted-foreground">Timestamp</div>
565
+ <div className="text-sm font-medium text-muted-foreground">Message</div>
566
+ </div>
567
+ </div>
568
+
569
+ {/* Table Body */}
570
+ <div className="divide-y max-h-[400px] overflow-y-auto">
571
+ {filteredLogs.slice(0, 100).map((log, index) => (
572
+ <div
573
+ key={`${log.time}-${log.msg}-${index}`}
574
+ className="grid grid-cols-[200px_1fr] gap-4 px-4 py-3 hover:bg-muted/30 transition-colors"
575
+ >
576
+ {/* Timestamp with level indicator */}
577
+ <div className="flex items-center gap-3">
578
+ <div className={`w-1 h-6 rounded-full ${getLevelColor(log.level)}`} />
579
+ <div className="text-sm font-mono text-muted-foreground">
580
+ {formatTimestamp(log.time)}
581
+ </div>
582
+ </div>
583
+
584
+ {/* Message */}
585
+ <div className="flex items-start gap-2 min-w-0">
586
+ <div className="font-mono text-sm leading-relaxed break-all">{log.msg}</div>
587
+ {log.agentName && (
588
+ <Badge variant="secondary" className="text-xs flex-shrink-0">
589
+ {log.agentName}
590
+ </Badge>
591
+ )}
592
+ </div>
593
+ </div>
594
+ ))}
595
+ </div>
596
+
597
+ {/* Show more indicator */}
598
+ {filteredLogs.length > 100 && (
599
+ <div className="border-t bg-muted/30 px-4 py-3 text-center">
600
+ <span className="text-sm text-muted-foreground">
601
+ Showing first 100 of {filteredLogs.length.toLocaleString()} logs
602
+ </span>
603
+ </div>
604
+ )}
605
+ </div>
606
+ </div>
607
+ )}
608
+ </div>
609
+
610
+ {/* Actions Bar */}
611
+ <div className="border-t bg-muted/30 px-4 py-3 flex items-center justify-between">
612
+ <div className="flex items-center gap-2 text-sm text-muted-foreground">
613
+ {isLive && (
614
+ <>
615
+ <div className="w-2 h-2 rounded-full bg-emerald-500 animate-pulse" />
616
+ <span>Live updates enabled {useWebSocket ? '(streaming)' : '(polling)'}</span>
617
+ </>
618
+ )}
619
+ </div>
620
+ <Button
621
+ variant="destructive"
622
+ size="sm"
623
+ onClick={handleClearLogs}
624
+ disabled={isClearing}
625
+ className="h-8 px-3 text-xs"
626
+ >
627
+ <Trash2 className="h-3 w-3 mr-1" />
628
+ {isClearing ? 'Clearing...' : 'Clear All Logs'}
629
+ </Button>
630
+ </div>
631
+ </div>
632
+
633
+ <ConfirmationDialog
634
+ open={isOpen}
635
+ onOpenChange={onOpenChange}
636
+ title={options?.title || ''}
637
+ description={options?.description || ''}
638
+ confirmText={options?.confirmText}
639
+ cancelText={options?.cancelText}
640
+ variant={options?.variant}
641
+ onConfirm={onConfirm}
642
+ />
643
+ </>
644
+ );
645
+ }