@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,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
|
+
}
|