agentflow-dashboard 0.1.0

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.
@@ -0,0 +1,342 @@
1
+ class AgentFlowDashboard {
2
+ constructor() {
3
+ this.ws = null;
4
+ this.reconnectAttempts = 0;
5
+ this.maxReconnectAttempts = 10;
6
+ this.reconnectDelay = 1000;
7
+ this.selectedAgent = null;
8
+ this.traces = [];
9
+ this.agents = [];
10
+ this.stats = null;
11
+
12
+ this.init();
13
+ }
14
+
15
+ init() {
16
+ this.connectWebSocket();
17
+ this.setupEventListeners();
18
+ this.loadInitialData();
19
+ }
20
+
21
+ connectWebSocket() {
22
+ const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
23
+ const wsUrl = `${protocol}//${window.location.host}`;
24
+
25
+ this.ws = new WebSocket(wsUrl);
26
+
27
+ this.ws.onopen = () => {
28
+ console.log('Connected to AgentFlow Dashboard');
29
+ this.reconnectAttempts = 0;
30
+ this.updateConnectionStatus(true);
31
+ };
32
+
33
+ this.ws.onmessage = (event) => {
34
+ try {
35
+ const message = JSON.parse(event.data);
36
+ this.handleWebSocketMessage(message);
37
+ } catch (error) {
38
+ console.error('Error parsing WebSocket message:', error);
39
+ }
40
+ };
41
+
42
+ this.ws.onclose = () => {
43
+ console.log('Disconnected from AgentFlow Dashboard');
44
+ this.updateConnectionStatus(false);
45
+ this.attemptReconnect();
46
+ };
47
+
48
+ this.ws.onerror = (error) => {
49
+ console.error('WebSocket error:', error);
50
+ this.updateConnectionStatus(false);
51
+ };
52
+ }
53
+
54
+ attemptReconnect() {
55
+ if (this.reconnectAttempts >= this.maxReconnectAttempts) {
56
+ console.log('Max reconnection attempts reached');
57
+ return;
58
+ }
59
+
60
+ this.reconnectAttempts++;
61
+ const delay = this.reconnectDelay * Math.pow(1.5, this.reconnectAttempts - 1);
62
+
63
+ setTimeout(() => {
64
+ console.log(`Reconnection attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts}`);
65
+ this.connectWebSocket();
66
+ }, delay);
67
+ }
68
+
69
+ handleWebSocketMessage(message) {
70
+ switch (message.type) {
71
+ case 'init':
72
+ this.traces = message.data.traces || [];
73
+ this.stats = message.data.stats || null;
74
+ this.updateUI();
75
+ break;
76
+
77
+ case 'trace-added':
78
+ this.traces.unshift(message.data);
79
+ this.updateTraces();
80
+ this.refreshStats();
81
+ break;
82
+
83
+ case 'trace-updated':
84
+ const index = this.traces.findIndex(t => t.filename === message.data.filename);
85
+ if (index >= 0) {
86
+ this.traces[index] = message.data;
87
+ this.updateTraces();
88
+ }
89
+ break;
90
+
91
+ case 'stats-updated':
92
+ this.stats = message.data;
93
+ this.updateStats();
94
+ this.updateAgents();
95
+ break;
96
+
97
+ default:
98
+ console.log('Unknown message type:', message.type);
99
+ }
100
+ }
101
+
102
+ async loadInitialData() {
103
+ try {
104
+ const [tracesRes, statsRes] = await Promise.all([
105
+ fetch('/api/traces'),
106
+ fetch('/api/stats')
107
+ ]);
108
+
109
+ if (tracesRes.ok) {
110
+ this.traces = await tracesRes.json();
111
+ }
112
+
113
+ if (statsRes.ok) {
114
+ this.stats = await statsRes.json();
115
+ }
116
+
117
+ this.updateUI();
118
+ } catch (error) {
119
+ console.error('Error loading initial data:', error);
120
+ }
121
+ }
122
+
123
+ async refreshStats() {
124
+ try {
125
+ const response = await fetch('/api/stats');
126
+ if (response.ok) {
127
+ this.stats = await response.json();
128
+ this.updateStats();
129
+ this.updateAgents();
130
+ }
131
+ } catch (error) {
132
+ console.error('Error refreshing stats:', error);
133
+ }
134
+ }
135
+
136
+ updateUI() {
137
+ this.updateStats();
138
+ this.updateAgents();
139
+ this.updateTraces();
140
+ }
141
+
142
+ updateConnectionStatus(connected) {
143
+ const statusEl = document.getElementById('connectionStatus');
144
+ if (connected) {
145
+ statusEl.textContent = 'Connected';
146
+ statusEl.className = 'connection-status connected';
147
+ } else {
148
+ statusEl.textContent = 'Disconnected';
149
+ statusEl.className = 'connection-status disconnected';
150
+ }
151
+ }
152
+
153
+ updateStats() {
154
+ const statsGrid = document.getElementById('statsGrid');
155
+
156
+ if (!this.stats) {
157
+ statsGrid.innerHTML = '<div class="loading">Loading statistics...</div>';
158
+ return;
159
+ }
160
+
161
+ const successRate = Math.round(this.stats.globalSuccessRate * 10) / 10;
162
+
163
+ statsGrid.innerHTML = `
164
+ <div class="stat-card">
165
+ <h3>Total Agents</h3>
166
+ <div class="value">${this.stats.totalAgents}</div>
167
+ </div>
168
+ <div class="stat-card">
169
+ <h3>Total Executions</h3>
170
+ <div class="value">${this.stats.totalExecutions.toLocaleString()}</div>
171
+ </div>
172
+ <div class="stat-card">
173
+ <h3>Success Rate</h3>
174
+ <div class="value">${successRate}%</div>
175
+ </div>
176
+ <div class="stat-card">
177
+ <h3>Active Agents</h3>
178
+ <div class="value">${this.stats.activeAgents}</div>
179
+ </div>
180
+ `;
181
+ }
182
+
183
+ updateAgents() {
184
+ const agentList = document.getElementById('agentList');
185
+
186
+ if (!this.stats || !this.stats.topAgents) {
187
+ agentList.innerHTML = '<div class="loading">Loading agents...</div>';
188
+ return;
189
+ }
190
+
191
+ const agentItems = this.stats.topAgents.map(agent => {
192
+ const successRate = Math.round(agent.successRate * 10) / 10;
193
+ let rateClass = 'success-rate';
194
+ if (successRate < 50) rateClass += ' critical';
195
+ else if (successRate < 80) rateClass += ' low';
196
+
197
+ return `
198
+ <div class="agent-item" data-agent-id="${agent.agentId}">
199
+ <div class="agent-name">${agent.agentId}</div>
200
+ <div class="agent-stats">
201
+ <span>${agent.executionCount} executions</span>
202
+ <span class="${rateClass}">${successRate}%</span>
203
+ </div>
204
+ </div>
205
+ `;
206
+ }).join('');
207
+
208
+ agentList.innerHTML = agentItems;
209
+ }
210
+
211
+ updateTraces() {
212
+ const tracesList = document.getElementById('tracesList');
213
+
214
+ if (!this.traces || this.traces.length === 0) {
215
+ tracesList.innerHTML = '<div class="loading">No traces available</div>';
216
+ return;
217
+ }
218
+
219
+ // Filter traces if an agent is selected
220
+ const filteredTraces = this.selectedAgent ?
221
+ this.traces.filter(trace => trace.agentId === this.selectedAgent) :
222
+ this.traces;
223
+
224
+ const traceItems = filteredTraces.slice(0, 50).map(trace => {
225
+ const timestamp = new Date(trace.timestamp).toLocaleString();
226
+ const statusClass = this.getTraceStatusClass(trace);
227
+
228
+ return `
229
+ <div class="trace-item">
230
+ <div class="trace-header">
231
+ <div class="trace-name">
232
+ <span class="status-indicator ${statusClass}"></span>
233
+ ${trace.name || `${trace.agentId} execution`}
234
+ </div>
235
+ <div class="trace-timestamp">${timestamp}</div>
236
+ </div>
237
+ <div class="trace-details">
238
+ <div class="trace-agent">${trace.agentId}</div>
239
+ <div class="trace-trigger">${trace.trigger}</div>
240
+ </div>
241
+ </div>
242
+ `;
243
+ }).join('');
244
+
245
+ tracesList.innerHTML = traceItems || '<div class="loading">No traces found</div>';
246
+ }
247
+
248
+ getTraceStatusClass(trace) {
249
+ // Try to determine status from the trace structure
250
+ if (trace.nodes) {
251
+ const nodes = Array.isArray(trace.nodes) ?
252
+ trace.nodes.map(([, node]) => node) :
253
+ trace.nodes instanceof Map ?
254
+ Array.from(trace.nodes.values()) :
255
+ Object.values(trace.nodes);
256
+
257
+ const hasFailures = nodes.some(node =>
258
+ node.status === 'failed' ||
259
+ node.error ||
260
+ (node.metadata && node.metadata.error)
261
+ );
262
+
263
+ if (hasFailures) return 'status-failure';
264
+
265
+ const hasCompleted = nodes.some(node =>
266
+ node.status === 'completed' ||
267
+ node.endTime ||
268
+ (node.metadata && node.metadata.status === 'success')
269
+ );
270
+
271
+ if (hasCompleted) return 'status-success';
272
+ }
273
+
274
+ return 'status-unknown';
275
+ }
276
+
277
+ setupEventListeners() {
278
+ // Agent selection
279
+ document.addEventListener('click', (event) => {
280
+ const agentItem = event.target.closest('.agent-item');
281
+ if (agentItem) {
282
+ const agentId = agentItem.dataset.agentId;
283
+ this.selectAgent(agentId);
284
+ }
285
+ });
286
+
287
+ // Auto-refresh every 30 seconds
288
+ setInterval(() => {
289
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
290
+ this.refreshStats();
291
+ }
292
+ }, 30000);
293
+ }
294
+
295
+ selectAgent(agentId) {
296
+ // Update UI selection
297
+ document.querySelectorAll('.agent-item').forEach(item => {
298
+ item.classList.remove('active');
299
+ });
300
+
301
+ const selectedItem = document.querySelector(`[data-agent-id="${agentId}"]`);
302
+ if (selectedItem) {
303
+ selectedItem.classList.add('active');
304
+ this.selectedAgent = agentId;
305
+ } else {
306
+ this.selectedAgent = null;
307
+ }
308
+
309
+ // Update traces view
310
+ this.updateTraces();
311
+
312
+ // Update page title
313
+ document.title = this.selectedAgent ?
314
+ `AgentFlow Dashboard - ${this.selectedAgent}` :
315
+ 'AgentFlow Dashboard';
316
+ }
317
+
318
+ // Public methods for debugging
319
+ getStats() {
320
+ return this.stats;
321
+ }
322
+
323
+ getTraces() {
324
+ return this.traces;
325
+ }
326
+
327
+ reconnect() {
328
+ if (this.ws) {
329
+ this.ws.close();
330
+ }
331
+ this.reconnectAttempts = 0;
332
+ this.connectWebSocket();
333
+ }
334
+ }
335
+
336
+ // Initialize dashboard when page loads
337
+ document.addEventListener('DOMContentLoaded', () => {
338
+ window.dashboard = new AgentFlowDashboard();
339
+ });
340
+
341
+ // Expose dashboard for debugging
342
+ window.AgentFlowDashboard = AgentFlowDashboard;
@@ -0,0 +1,274 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>AgentFlow Dashboard</title>
7
+ <style>
8
+ * {
9
+ margin: 0;
10
+ padding: 0;
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ body {
15
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
16
+ background: #0f1419;
17
+ color: #e6e6e6;
18
+ line-height: 1.6;
19
+ }
20
+
21
+ .header {
22
+ background: #1a1f2e;
23
+ padding: 1rem 2rem;
24
+ border-bottom: 1px solid #2a2f3e;
25
+ }
26
+
27
+ .header h1 {
28
+ color: #4f96ff;
29
+ font-size: 1.5rem;
30
+ font-weight: 600;
31
+ }
32
+
33
+ .header .subtitle {
34
+ color: #9ca3af;
35
+ font-size: 0.9rem;
36
+ margin-top: 0.25rem;
37
+ }
38
+
39
+ .main {
40
+ display: grid;
41
+ grid-template-columns: 1fr 2fr;
42
+ height: calc(100vh - 80px);
43
+ }
44
+
45
+ .sidebar {
46
+ background: #1a1f2e;
47
+ border-right: 1px solid #2a2f3e;
48
+ padding: 1rem;
49
+ overflow-y: auto;
50
+ }
51
+
52
+ .content {
53
+ padding: 1rem;
54
+ overflow-y: auto;
55
+ }
56
+
57
+ .stats-grid {
58
+ display: grid;
59
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
60
+ gap: 1rem;
61
+ margin-bottom: 2rem;
62
+ }
63
+
64
+ .stat-card {
65
+ background: #1a1f2e;
66
+ border: 1px solid #2a2f3e;
67
+ border-radius: 8px;
68
+ padding: 1rem;
69
+ }
70
+
71
+ .stat-card h3 {
72
+ font-size: 0.8rem;
73
+ color: #9ca3af;
74
+ text-transform: uppercase;
75
+ margin-bottom: 0.5rem;
76
+ }
77
+
78
+ .stat-card .value {
79
+ font-size: 1.8rem;
80
+ font-weight: 700;
81
+ color: #4f96ff;
82
+ }
83
+
84
+ .agent-list {
85
+ margin-bottom: 2rem;
86
+ }
87
+
88
+ .agent-item {
89
+ background: #262b3d;
90
+ border: 1px solid #363b4d;
91
+ border-radius: 6px;
92
+ padding: 1rem;
93
+ margin-bottom: 0.5rem;
94
+ cursor: pointer;
95
+ transition: all 0.2s;
96
+ }
97
+
98
+ .agent-item:hover {
99
+ border-color: #4f96ff;
100
+ background: #2a2f3e;
101
+ }
102
+
103
+ .agent-item.active {
104
+ border-color: #4f96ff;
105
+ background: #2a2f3e;
106
+ }
107
+
108
+ .agent-name {
109
+ font-weight: 600;
110
+ color: #e6e6e6;
111
+ margin-bottom: 0.25rem;
112
+ }
113
+
114
+ .agent-stats {
115
+ display: flex;
116
+ justify-content: space-between;
117
+ font-size: 0.8rem;
118
+ color: #9ca3af;
119
+ }
120
+
121
+ .success-rate {
122
+ color: #10b981;
123
+ }
124
+
125
+ .success-rate.low {
126
+ color: #f59e0b;
127
+ }
128
+
129
+ .success-rate.critical {
130
+ color: #ef4444;
131
+ }
132
+
133
+ .traces-section {
134
+ margin-bottom: 2rem;
135
+ }
136
+
137
+ .section-title {
138
+ font-size: 1.2rem;
139
+ font-weight: 600;
140
+ margin-bottom: 1rem;
141
+ color: #e6e6e6;
142
+ }
143
+
144
+ .trace-item {
145
+ background: #1a1f2e;
146
+ border: 1px solid #2a2f3e;
147
+ border-radius: 6px;
148
+ padding: 1rem;
149
+ margin-bottom: 0.75rem;
150
+ }
151
+
152
+ .trace-header {
153
+ display: flex;
154
+ justify-content: space-between;
155
+ align-items: center;
156
+ margin-bottom: 0.5rem;
157
+ }
158
+
159
+ .trace-name {
160
+ font-weight: 600;
161
+ color: #e6e6e6;
162
+ }
163
+
164
+ .trace-timestamp {
165
+ font-size: 0.8rem;
166
+ color: #9ca3af;
167
+ }
168
+
169
+ .trace-details {
170
+ display: flex;
171
+ justify-content: space-between;
172
+ font-size: 0.8rem;
173
+ }
174
+
175
+ .trace-agent {
176
+ color: #4f96ff;
177
+ }
178
+
179
+ .trace-trigger {
180
+ color: #9ca3af;
181
+ }
182
+
183
+ .status-indicator {
184
+ width: 8px;
185
+ height: 8px;
186
+ border-radius: 50%;
187
+ display: inline-block;
188
+ margin-right: 0.5rem;
189
+ }
190
+
191
+ .status-success {
192
+ background: #10b981;
193
+ }
194
+
195
+ .status-failure {
196
+ background: #ef4444;
197
+ }
198
+
199
+ .status-unknown {
200
+ background: #6b7280;
201
+ }
202
+
203
+ .connection-status {
204
+ position: fixed;
205
+ top: 1rem;
206
+ right: 1rem;
207
+ padding: 0.5rem 1rem;
208
+ border-radius: 6px;
209
+ font-size: 0.8rem;
210
+ font-weight: 600;
211
+ }
212
+
213
+ .connected {
214
+ background: #065f46;
215
+ color: #10b981;
216
+ }
217
+
218
+ .disconnected {
219
+ background: #7f1d1d;
220
+ color: #ef4444;
221
+ }
222
+
223
+ .loading {
224
+ text-align: center;
225
+ padding: 2rem;
226
+ color: #9ca3af;
227
+ }
228
+
229
+ @media (max-width: 768px) {
230
+ .main {
231
+ grid-template-columns: 1fr;
232
+ }
233
+
234
+ .sidebar {
235
+ display: none;
236
+ }
237
+ }
238
+ </style>
239
+ </head>
240
+ <body>
241
+ <div class="header">
242
+ <h1>AgentFlow Dashboard</h1>
243
+ <div class="subtitle">Real-time monitoring for AI agent executions</div>
244
+ </div>
245
+
246
+ <div class="connection-status disconnected" id="connectionStatus">
247
+ Connecting...
248
+ </div>
249
+
250
+ <div class="main">
251
+ <div class="sidebar">
252
+ <h3 class="section-title">Agents</h3>
253
+ <div class="agent-list" id="agentList">
254
+ <div class="loading">Loading agents...</div>
255
+ </div>
256
+ </div>
257
+
258
+ <div class="content">
259
+ <div class="stats-grid" id="statsGrid">
260
+ <div class="loading">Loading statistics...</div>
261
+ </div>
262
+
263
+ <div class="traces-section">
264
+ <h3 class="section-title">Recent Executions</h3>
265
+ <div class="traces-list" id="tracesList">
266
+ <div class="loading">Loading traces...</div>
267
+ </div>
268
+ </div>
269
+ </div>
270
+ </div>
271
+
272
+ <script src="dashboard.js"></script>
273
+ </body>
274
+ </html>
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "agentflow-dashboard",
3
+ "version": "0.1.0",
4
+ "description": "Real-time monitoring dashboard for AgentFlow - Visualize agent execution graphs and performance",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js",
12
+ "require": "./dist/index.cjs"
13
+ }
14
+ },
15
+ "bin": {
16
+ "agentflow-dashboard": "./bin/dashboard.js"
17
+ },
18
+ "files": [
19
+ "dist",
20
+ "bin",
21
+ "public"
22
+ ],
23
+ "scripts": {
24
+ "build": "tsup src/index.ts --format esm,cjs --clean && npm run build:public",
25
+ "build:public": "cp -r public dist/",
26
+ "dev": "tsx src/server.ts",
27
+ "start": "node dist/server.js"
28
+ },
29
+ "dependencies": {
30
+ "express": "^4.18.2",
31
+ "ws": "^8.16.0",
32
+ "chokidar": "^3.5.3"
33
+ },
34
+ "devDependencies": {
35
+ "@types/express": "^4.17.21",
36
+ "@types/ws": "^8.5.10",
37
+ "tsup": "^8.4.0",
38
+ "tsx": "^4.19.0"
39
+ },
40
+ "keywords": [
41
+ "agentflow",
42
+ "dashboard",
43
+ "monitoring",
44
+ "ai",
45
+ "agent",
46
+ "tracing",
47
+ "observability",
48
+ "real-time"
49
+ ],
50
+ "license": "MIT"
51
+ }