agentgui 1.0.67 → 1.0.69
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/.prd +92 -0
- package/.prd-browser +607 -0
- package/CLAUDE.md +1559 -125
- package/browser-test-harness.js +371 -0
- package/browser-test.js +409 -0
- package/execute-tests.js +164 -0
- package/lib/claude-runner.js +41 -12
- package/lib/database-service.ts +252 -0
- package/lib/sync-service.ts +275 -0
- package/lib/types.ts +168 -0
- package/package.json +1 -1
- package/readme.md +586 -0
- package/run-e2e-test.sh +88 -0
- package/server.js +274 -8
- package/static/index.html +487 -180
- package/static/js/client.js +558 -0
- package/static/js/event-filter.js +311 -0
- package/static/js/event-processor.js +454 -0
- package/static/js/streaming-renderer.js +813 -0
- package/static/js/syntax-highlighter.js +271 -0
- package/static/js/ui-components.js +433 -0
- package/static/js/websocket-manager.js +482 -0
- package/static/templates/INDEX.html +465 -0
- package/static/templates/README.md +190 -0
- package/static/templates/agent-capabilities.html +56 -0
- package/static/templates/agent-metadata-panel.html +44 -0
- package/static/templates/agent-status-badge.html +30 -0
- package/static/templates/code-annotation-panel.html +155 -0
- package/static/templates/code-suggestion-panel.html +184 -0
- package/static/templates/command-header.html +77 -0
- package/static/templates/command-output-scrollable.html +118 -0
- package/static/templates/elapsed-time.html +54 -0
- package/static/templates/error-alert.html +106 -0
- package/static/templates/error-history-timeline.html +160 -0
- package/static/templates/error-recovery-options.html +109 -0
- package/static/templates/error-stack-trace.html +95 -0
- package/static/templates/error-summary.html +80 -0
- package/static/templates/event-counter.html +48 -0
- package/static/templates/execution-actions.html +97 -0
- package/static/templates/execution-progress-bar.html +80 -0
- package/static/templates/execution-stepper.html +120 -0
- package/static/templates/file-breadcrumb.html +118 -0
- package/static/templates/file-diff-viewer.html +121 -0
- package/static/templates/file-metadata.html +133 -0
- package/static/templates/file-read-panel.html +66 -0
- package/static/templates/file-write-panel.html +120 -0
- package/static/templates/git-branch-remote.html +107 -0
- package/static/templates/git-diff-list.html +101 -0
- package/static/templates/git-log-visualization.html +153 -0
- package/static/templates/git-status-panel.html +115 -0
- package/static/templates/quality-metrics-display.html +170 -0
- package/static/templates/terminal-output-panel.html +87 -0
- package/static/templates/test-results-display.html +144 -0
- package/test-browser.js +457 -0
- package/test-runner.js +182 -0
|
@@ -0,0 +1,558 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AgentGUI Client
|
|
3
|
+
* Main application orchestrator that integrates WebSocket, event processing,
|
|
4
|
+
* and streaming renderer for real-time Claude Code execution visualization
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
class AgentGUIClient {
|
|
8
|
+
constructor(config = {}) {
|
|
9
|
+
this.config = {
|
|
10
|
+
containerId: config.containerId || 'app',
|
|
11
|
+
outputContainerId: config.outputContainerId || 'output',
|
|
12
|
+
scrollContainerId: config.scrollContainerId || 'output-scroll',
|
|
13
|
+
autoConnect: config.autoConnect !== false,
|
|
14
|
+
...config
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// Initialize components
|
|
18
|
+
this.renderer = new StreamingRenderer(config.renderer || {});
|
|
19
|
+
this.wsManager = new WebSocketManager(config.websocket || {});
|
|
20
|
+
this.eventProcessor = new EventProcessor(config.eventProcessor || {});
|
|
21
|
+
|
|
22
|
+
// Application state
|
|
23
|
+
this.state = {
|
|
24
|
+
isInitialized: false,
|
|
25
|
+
currentSession: null,
|
|
26
|
+
currentConversation: null,
|
|
27
|
+
isStreaming: false,
|
|
28
|
+
sessionEvents: [],
|
|
29
|
+
conversations: [],
|
|
30
|
+
agents: []
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// Event handlers
|
|
34
|
+
this.eventHandlers = {};
|
|
35
|
+
|
|
36
|
+
// UI state
|
|
37
|
+
this.ui = {
|
|
38
|
+
statusIndicator: null,
|
|
39
|
+
messageInput: null,
|
|
40
|
+
sendButton: null,
|
|
41
|
+
agentSelector: null
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Initialize the client
|
|
47
|
+
*/
|
|
48
|
+
async init() {
|
|
49
|
+
try {
|
|
50
|
+
console.log('Initializing AgentGUI client');
|
|
51
|
+
|
|
52
|
+
// Initialize renderer
|
|
53
|
+
this.renderer.init(this.config.outputContainerId, this.config.scrollContainerId);
|
|
54
|
+
|
|
55
|
+
// Setup event listeners
|
|
56
|
+
this.setupWebSocketListeners();
|
|
57
|
+
this.setupRendererListeners();
|
|
58
|
+
|
|
59
|
+
// Load initial data
|
|
60
|
+
await this.loadAgents();
|
|
61
|
+
await this.loadConversations();
|
|
62
|
+
|
|
63
|
+
// Setup UI elements
|
|
64
|
+
this.setupUI();
|
|
65
|
+
|
|
66
|
+
// Connect WebSocket
|
|
67
|
+
if (this.config.autoConnect) {
|
|
68
|
+
await this.connectWebSocket();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
this.state.isInitialized = true;
|
|
72
|
+
this.emit('initialized');
|
|
73
|
+
|
|
74
|
+
console.log('AgentGUI client initialized');
|
|
75
|
+
return this;
|
|
76
|
+
} catch (error) {
|
|
77
|
+
console.error('Client initialization error:', error);
|
|
78
|
+
this.showError('Failed to initialize client: ' + error.message);
|
|
79
|
+
throw error;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Setup WebSocket event listeners
|
|
85
|
+
*/
|
|
86
|
+
setupWebSocketListeners() {
|
|
87
|
+
this.wsManager.on('connected', () => {
|
|
88
|
+
console.log('WebSocket connected');
|
|
89
|
+
this.updateConnectionStatus('connected');
|
|
90
|
+
this.emit('ws:connected');
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
this.wsManager.on('disconnected', () => {
|
|
94
|
+
console.log('WebSocket disconnected');
|
|
95
|
+
this.updateConnectionStatus('disconnected');
|
|
96
|
+
this.emit('ws:disconnected');
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
this.wsManager.on('reconnecting', (data) => {
|
|
100
|
+
console.log('WebSocket reconnecting:', data);
|
|
101
|
+
this.updateConnectionStatus('reconnecting');
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
this.wsManager.on('message', (data) => {
|
|
105
|
+
this.handleWebSocketMessage(data);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
this.wsManager.on('error', (data) => {
|
|
109
|
+
console.error('WebSocket error:', data);
|
|
110
|
+
this.showError('Connection error: ' + (data.error?.message || 'unknown'));
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
this.wsManager.on('reconnect_failed', (data) => {
|
|
114
|
+
console.error('WebSocket reconnection failed:', data);
|
|
115
|
+
this.updateConnectionStatus('error');
|
|
116
|
+
this.showError('Failed to reconnect to server after ' + data.attempts + ' attempts');
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Setup renderer event listeners
|
|
122
|
+
*/
|
|
123
|
+
setupRendererListeners() {
|
|
124
|
+
this.renderer.on('batch:complete', (data) => {
|
|
125
|
+
console.log('Batch rendered:', data);
|
|
126
|
+
this.updateMetrics(data.metrics);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
this.renderer.on('error:render', (data) => {
|
|
130
|
+
console.error('Render error:', data.error);
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Setup UI elements
|
|
136
|
+
*/
|
|
137
|
+
setupUI() {
|
|
138
|
+
const container = document.getElementById(this.config.containerId);
|
|
139
|
+
if (!container) {
|
|
140
|
+
throw new Error(`Container not found: ${this.config.containerId}`);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Get references to key UI elements
|
|
144
|
+
this.ui.statusIndicator = document.querySelector('[data-status-indicator]');
|
|
145
|
+
this.ui.messageInput = document.querySelector('[data-message-input]');
|
|
146
|
+
this.ui.sendButton = document.querySelector('[data-send-button]');
|
|
147
|
+
this.ui.agentSelector = document.querySelector('[data-agent-selector]');
|
|
148
|
+
|
|
149
|
+
// Setup event listeners
|
|
150
|
+
if (this.ui.sendButton) {
|
|
151
|
+
this.ui.sendButton.addEventListener('click', () => this.startExecution());
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (this.ui.messageInput) {
|
|
155
|
+
this.ui.messageInput.addEventListener('keydown', (e) => {
|
|
156
|
+
if (e.key === 'Enter' && e.ctrlKey) {
|
|
157
|
+
this.startExecution();
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Setup theme toggle
|
|
163
|
+
const themeToggle = document.querySelector('[data-theme-toggle]');
|
|
164
|
+
if (themeToggle) {
|
|
165
|
+
themeToggle.addEventListener('click', () => this.toggleTheme());
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Connect to WebSocket
|
|
171
|
+
*/
|
|
172
|
+
async connectWebSocket() {
|
|
173
|
+
try {
|
|
174
|
+
await this.wsManager.connect();
|
|
175
|
+
this.updateConnectionStatus('connected');
|
|
176
|
+
} catch (error) {
|
|
177
|
+
console.error('WebSocket connection failed:', error);
|
|
178
|
+
this.updateConnectionStatus('error');
|
|
179
|
+
throw error;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Handle incoming WebSocket message
|
|
185
|
+
*/
|
|
186
|
+
handleWebSocketMessage(data) {
|
|
187
|
+
try {
|
|
188
|
+
// Route by message type
|
|
189
|
+
switch (data.type) {
|
|
190
|
+
case 'streaming_start':
|
|
191
|
+
this.handleStreamingStart(data);
|
|
192
|
+
break;
|
|
193
|
+
|
|
194
|
+
case 'streaming_progress':
|
|
195
|
+
this.queueEvent(data);
|
|
196
|
+
break;
|
|
197
|
+
|
|
198
|
+
case 'streaming_complete':
|
|
199
|
+
this.handleStreamingComplete(data);
|
|
200
|
+
break;
|
|
201
|
+
|
|
202
|
+
case 'file_read':
|
|
203
|
+
case 'file_write':
|
|
204
|
+
case 'command_execute':
|
|
205
|
+
case 'git_status':
|
|
206
|
+
case 'error':
|
|
207
|
+
case 'text_block':
|
|
208
|
+
case 'code_block':
|
|
209
|
+
case 'thinking_block':
|
|
210
|
+
case 'tool_use':
|
|
211
|
+
this.queueEvent(data);
|
|
212
|
+
break;
|
|
213
|
+
|
|
214
|
+
case 'conversation_created':
|
|
215
|
+
this.handleConversationCreated(data);
|
|
216
|
+
break;
|
|
217
|
+
|
|
218
|
+
case 'message_created':
|
|
219
|
+
this.handleMessageCreated(data);
|
|
220
|
+
break;
|
|
221
|
+
|
|
222
|
+
default:
|
|
223
|
+
console.log('Unhandled message type:', data.type);
|
|
224
|
+
}
|
|
225
|
+
} catch (error) {
|
|
226
|
+
console.error('Message handling error:', error);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Queue event for rendering
|
|
232
|
+
*/
|
|
233
|
+
queueEvent(data) {
|
|
234
|
+
try {
|
|
235
|
+
// Process event
|
|
236
|
+
const processed = this.eventProcessor.processEvent(data);
|
|
237
|
+
if (!processed) return;
|
|
238
|
+
|
|
239
|
+
// Queue for rendering
|
|
240
|
+
this.renderer.queueEvent(processed);
|
|
241
|
+
|
|
242
|
+
// Track session events
|
|
243
|
+
if (data.sessionId && this.state.currentSession?.id === data.sessionId) {
|
|
244
|
+
this.state.sessionEvents.push(processed);
|
|
245
|
+
}
|
|
246
|
+
} catch (error) {
|
|
247
|
+
console.error('Event queuing error:', error);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Handle streaming start
|
|
253
|
+
*/
|
|
254
|
+
handleStreamingStart(data) {
|
|
255
|
+
console.log('Streaming started:', data);
|
|
256
|
+
this.state.isStreaming = true;
|
|
257
|
+
this.state.currentSession = {
|
|
258
|
+
id: data.sessionId,
|
|
259
|
+
conversationId: data.conversationId,
|
|
260
|
+
agentId: data.agentId,
|
|
261
|
+
startTime: Date.now()
|
|
262
|
+
};
|
|
263
|
+
this.state.sessionEvents = [];
|
|
264
|
+
this.renderer.clear();
|
|
265
|
+
|
|
266
|
+
this.renderer.queueEvent({
|
|
267
|
+
type: 'streaming_start',
|
|
268
|
+
sessionId: data.sessionId,
|
|
269
|
+
conversationId: data.conversationId,
|
|
270
|
+
agentId: data.agentId,
|
|
271
|
+
timestamp: data.timestamp || Date.now()
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
this.disableControls();
|
|
275
|
+
this.emit('streaming:start', data);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Handle streaming complete
|
|
280
|
+
*/
|
|
281
|
+
handleStreamingComplete(data) {
|
|
282
|
+
console.log('Streaming completed:', data);
|
|
283
|
+
this.state.isStreaming = false;
|
|
284
|
+
|
|
285
|
+
const duration = data.duration || (Date.now() - (this.state.currentSession?.startTime || Date.now()));
|
|
286
|
+
|
|
287
|
+
this.renderer.queueEvent({
|
|
288
|
+
type: 'streaming_complete',
|
|
289
|
+
sessionId: data.sessionId,
|
|
290
|
+
duration,
|
|
291
|
+
timestamp: data.timestamp || Date.now()
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
this.enableControls();
|
|
295
|
+
this.emit('streaming:complete', {
|
|
296
|
+
...data,
|
|
297
|
+
duration,
|
|
298
|
+
eventCount: this.state.sessionEvents.length
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Handle conversation created
|
|
304
|
+
*/
|
|
305
|
+
handleConversationCreated(data) {
|
|
306
|
+
if (data.conversation) {
|
|
307
|
+
this.state.conversations.push(data.conversation);
|
|
308
|
+
this.emit('conversation:created', data.conversation);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Handle message created
|
|
314
|
+
*/
|
|
315
|
+
handleMessageCreated(data) {
|
|
316
|
+
this.emit('message:created', data);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Start execution
|
|
321
|
+
*/
|
|
322
|
+
async startExecution() {
|
|
323
|
+
if (this.state.isStreaming) {
|
|
324
|
+
this.showError('Streaming already in progress');
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const prompt = this.ui.messageInput?.value || '';
|
|
329
|
+
const agentId = this.ui.agentSelector?.value || 'claude-code';
|
|
330
|
+
|
|
331
|
+
if (!prompt.trim()) {
|
|
332
|
+
this.showError('Please enter a prompt');
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
try {
|
|
337
|
+
this.disableControls();
|
|
338
|
+
|
|
339
|
+
const response = await fetch(window.__BASE_URL + '/api/conversations', {
|
|
340
|
+
method: 'POST',
|
|
341
|
+
headers: { 'Content-Type': 'application/json' },
|
|
342
|
+
body: JSON.stringify({
|
|
343
|
+
agentId,
|
|
344
|
+
title: prompt.substring(0, 50)
|
|
345
|
+
})
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
const { conversation } = await response.json();
|
|
349
|
+
this.state.currentConversation = conversation;
|
|
350
|
+
|
|
351
|
+
// Start streaming
|
|
352
|
+
await this.streamToConversation(conversation.id, prompt, agentId);
|
|
353
|
+
} catch (error) {
|
|
354
|
+
console.error('Execution error:', error);
|
|
355
|
+
this.showError('Failed to start execution: ' + error.message);
|
|
356
|
+
this.enableControls();
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Stream execution to conversation
|
|
362
|
+
*/
|
|
363
|
+
async streamToConversation(conversationId, prompt, agentId) {
|
|
364
|
+
try {
|
|
365
|
+
const response = await fetch(`${window.__BASE_URL}/api/conversations/${conversationId}/stream`, {
|
|
366
|
+
method: 'POST',
|
|
367
|
+
headers: { 'Content-Type': 'application/json' },
|
|
368
|
+
body: JSON.stringify({
|
|
369
|
+
content: prompt,
|
|
370
|
+
agentId,
|
|
371
|
+
skipPermissions: false
|
|
372
|
+
})
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
if (!response.ok) {
|
|
376
|
+
throw new Error(`HTTP ${response.status}`);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const { session, streamId } = await response.json();
|
|
380
|
+
|
|
381
|
+
// Subscribe to session events via WebSocket
|
|
382
|
+
if (this.wsManager.isConnected) {
|
|
383
|
+
this.wsManager.subscribeToSession(session.id);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
this.emit('execution:started', { session, streamId });
|
|
387
|
+
} catch (error) {
|
|
388
|
+
console.error('Stream execution error:', error);
|
|
389
|
+
this.showError('Failed to stream execution: ' + error.message);
|
|
390
|
+
this.enableControls();
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Load agents
|
|
396
|
+
*/
|
|
397
|
+
async loadAgents() {
|
|
398
|
+
try {
|
|
399
|
+
const response = await fetch(window.__BASE_URL + '/api/agents');
|
|
400
|
+
const { agents } = await response.json();
|
|
401
|
+
this.state.agents = agents;
|
|
402
|
+
|
|
403
|
+
// Populate agent selector
|
|
404
|
+
if (this.ui.agentSelector) {
|
|
405
|
+
this.ui.agentSelector.innerHTML = agents
|
|
406
|
+
.map(agent => `<option value="${agent.id}">${agent.name}</option>`)
|
|
407
|
+
.join('');
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
return agents;
|
|
411
|
+
} catch (error) {
|
|
412
|
+
console.error('Failed to load agents:', error);
|
|
413
|
+
return [];
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Load conversations
|
|
419
|
+
*/
|
|
420
|
+
async loadConversations() {
|
|
421
|
+
try {
|
|
422
|
+
const response = await fetch(window.__BASE_URL + '/api/conversations');
|
|
423
|
+
const { conversations } = await response.json();
|
|
424
|
+
this.state.conversations = conversations;
|
|
425
|
+
return conversations;
|
|
426
|
+
} catch (error) {
|
|
427
|
+
console.error('Failed to load conversations:', error);
|
|
428
|
+
return [];
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Update connection status UI
|
|
434
|
+
*/
|
|
435
|
+
updateConnectionStatus(status) {
|
|
436
|
+
if (this.ui.statusIndicator) {
|
|
437
|
+
this.ui.statusIndicator.dataset.status = status;
|
|
438
|
+
this.ui.statusIndicator.textContent = status.charAt(0).toUpperCase() + status.slice(1);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Update metrics display
|
|
444
|
+
*/
|
|
445
|
+
updateMetrics(metrics) {
|
|
446
|
+
const metricsDisplay = document.querySelector('[data-metrics]');
|
|
447
|
+
if (metricsDisplay && metrics) {
|
|
448
|
+
metricsDisplay.textContent = `Batches: ${metrics.totalBatches} | Events: ${metrics.totalEvents} | Avg render: ${metrics.avgRenderTime.toFixed(2)}ms`;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Disable UI controls during streaming
|
|
454
|
+
*/
|
|
455
|
+
disableControls() {
|
|
456
|
+
if (this.ui.sendButton) this.ui.sendButton.disabled = true;
|
|
457
|
+
if (this.ui.messageInput) this.ui.messageInput.disabled = true;
|
|
458
|
+
if (this.ui.agentSelector) this.ui.agentSelector.disabled = true;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Enable UI controls
|
|
463
|
+
*/
|
|
464
|
+
enableControls() {
|
|
465
|
+
if (this.ui.sendButton) this.ui.sendButton.disabled = false;
|
|
466
|
+
if (this.ui.messageInput) this.ui.messageInput.disabled = false;
|
|
467
|
+
if (this.ui.agentSelector) this.ui.agentSelector.disabled = false;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* Toggle theme
|
|
472
|
+
*/
|
|
473
|
+
toggleTheme() {
|
|
474
|
+
const isDark = document.documentElement.classList.toggle('dark');
|
|
475
|
+
localStorage.setItem('theme', isDark ? 'dark' : 'light');
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Show error message
|
|
480
|
+
*/
|
|
481
|
+
showError(message) {
|
|
482
|
+
console.error(message);
|
|
483
|
+
// Could display in a toast or alert
|
|
484
|
+
alert(message);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* Add event listener
|
|
489
|
+
*/
|
|
490
|
+
on(event, callback) {
|
|
491
|
+
if (!this.eventHandlers[event]) {
|
|
492
|
+
this.eventHandlers[event] = [];
|
|
493
|
+
}
|
|
494
|
+
this.eventHandlers[event].push(callback);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* Emit event
|
|
499
|
+
*/
|
|
500
|
+
emit(event, data) {
|
|
501
|
+
if (this.eventHandlers[event]) {
|
|
502
|
+
this.eventHandlers[event].forEach(callback => {
|
|
503
|
+
try {
|
|
504
|
+
callback(data);
|
|
505
|
+
} catch (error) {
|
|
506
|
+
console.error(`Event handler error for ${event}:`, error);
|
|
507
|
+
}
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* Get application state
|
|
514
|
+
*/
|
|
515
|
+
getState() {
|
|
516
|
+
return { ...this.state };
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/**
|
|
520
|
+
* Get metrics
|
|
521
|
+
*/
|
|
522
|
+
getMetrics() {
|
|
523
|
+
return {
|
|
524
|
+
renderer: this.renderer.getMetrics(),
|
|
525
|
+
websocket: this.wsManager.getStatus(),
|
|
526
|
+
eventProcessor: this.eventProcessor.getStats(),
|
|
527
|
+
state: this.state
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/**
|
|
532
|
+
* Cleanup resources
|
|
533
|
+
*/
|
|
534
|
+
destroy() {
|
|
535
|
+
this.renderer.destroy();
|
|
536
|
+
this.wsManager.destroy();
|
|
537
|
+
this.eventHandlers = {};
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// Global instance
|
|
542
|
+
let agentGUIClient = null;
|
|
543
|
+
|
|
544
|
+
// Initialize on DOM ready
|
|
545
|
+
document.addEventListener('DOMContentLoaded', async () => {
|
|
546
|
+
try {
|
|
547
|
+
agentGUIClient = new AgentGUIClient();
|
|
548
|
+
await agentGUIClient.init();
|
|
549
|
+
console.log('AgentGUI ready');
|
|
550
|
+
} catch (error) {
|
|
551
|
+
console.error('Failed to initialize AgentGUI:', error);
|
|
552
|
+
}
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
// Export for testing
|
|
556
|
+
if (typeof module !== 'undefined' && module.exports) {
|
|
557
|
+
module.exports = AgentGUIClient;
|
|
558
|
+
}
|