claude-mpm 3.4.10__py3-none-any.whl → 3.4.14__py3-none-any.whl
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.
- claude_mpm/cli/commands/run.py +10 -10
- claude_mpm/dashboard/index.html +13 -0
- claude_mpm/dashboard/static/css/dashboard.css +2722 -0
- claude_mpm/dashboard/static/js/components/agent-inference.js +619 -0
- claude_mpm/dashboard/static/js/components/event-processor.js +641 -0
- claude_mpm/dashboard/static/js/components/event-viewer.js +914 -0
- claude_mpm/dashboard/static/js/components/export-manager.js +362 -0
- claude_mpm/dashboard/static/js/components/file-tool-tracker.js +611 -0
- claude_mpm/dashboard/static/js/components/hud-library-loader.js +211 -0
- claude_mpm/dashboard/static/js/components/hud-manager.js +671 -0
- claude_mpm/dashboard/static/js/components/hud-visualizer.js +1718 -0
- claude_mpm/dashboard/static/js/components/module-viewer.js +2701 -0
- claude_mpm/dashboard/static/js/components/session-manager.js +520 -0
- claude_mpm/dashboard/static/js/components/socket-manager.js +343 -0
- claude_mpm/dashboard/static/js/components/ui-state-manager.js +427 -0
- claude_mpm/dashboard/static/js/components/working-directory.js +866 -0
- claude_mpm/dashboard/static/js/dashboard-original.js +4134 -0
- claude_mpm/dashboard/static/js/dashboard.js +1978 -0
- claude_mpm/dashboard/static/js/socket-client.js +537 -0
- claude_mpm/dashboard/templates/index.html +346 -0
- claude_mpm/dashboard/test_dashboard.html +372 -0
- claude_mpm/scripts/socketio_daemon.py +51 -6
- claude_mpm/services/socketio_server.py +41 -5
- {claude_mpm-3.4.10.dist-info → claude_mpm-3.4.14.dist-info}/METADATA +2 -1
- {claude_mpm-3.4.10.dist-info → claude_mpm-3.4.14.dist-info}/RECORD +29 -9
- {claude_mpm-3.4.10.dist-info → claude_mpm-3.4.14.dist-info}/WHEEL +0 -0
- {claude_mpm-3.4.10.dist-info → claude_mpm-3.4.14.dist-info}/entry_points.txt +0 -0
- {claude_mpm-3.4.10.dist-info → claude_mpm-3.4.14.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-3.4.10.dist-info → claude_mpm-3.4.14.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,1718 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HUD Visualizer Component
|
|
3
|
+
* Manages the Cytoscape.js tree visualization for the HUD mode with lazy loading
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
class HUDVisualizer {
|
|
7
|
+
constructor() {
|
|
8
|
+
this.cy = null;
|
|
9
|
+
this.container = null;
|
|
10
|
+
this.nodes = new Map(); // Map of node IDs to node data
|
|
11
|
+
this.isActive = false;
|
|
12
|
+
this.librariesLoaded = false;
|
|
13
|
+
this.loadingPromise = null;
|
|
14
|
+
this.pendingEvents = []; // Store events received before libraries are loaded
|
|
15
|
+
|
|
16
|
+
// Layout configuration
|
|
17
|
+
this.layoutConfig = {
|
|
18
|
+
name: 'dagre',
|
|
19
|
+
rankDir: 'TB', // Top to bottom
|
|
20
|
+
animate: true,
|
|
21
|
+
animationDuration: 500,
|
|
22
|
+
fit: true,
|
|
23
|
+
padding: 30,
|
|
24
|
+
rankSep: 100,
|
|
25
|
+
nodeSep: 80
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// Node type configurations
|
|
29
|
+
this.nodeTypes = {
|
|
30
|
+
PM: {
|
|
31
|
+
color: '#48bb78',
|
|
32
|
+
shape: 'rectangle',
|
|
33
|
+
width: 120,
|
|
34
|
+
height: 40,
|
|
35
|
+
icon: '👤'
|
|
36
|
+
},
|
|
37
|
+
AGENT: {
|
|
38
|
+
color: '#9f7aea',
|
|
39
|
+
shape: 'ellipse',
|
|
40
|
+
width: 100,
|
|
41
|
+
height: 60,
|
|
42
|
+
icon: '🤖'
|
|
43
|
+
},
|
|
44
|
+
TOOL: {
|
|
45
|
+
color: '#4299e1',
|
|
46
|
+
shape: 'diamond',
|
|
47
|
+
width: 80,
|
|
48
|
+
height: 50,
|
|
49
|
+
icon: '🔧'
|
|
50
|
+
},
|
|
51
|
+
TODO: {
|
|
52
|
+
color: '#e53e3e',
|
|
53
|
+
shape: 'triangle',
|
|
54
|
+
width: 70,
|
|
55
|
+
height: 40,
|
|
56
|
+
icon: '📝'
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Initialize the HUD visualizer (called at startup)
|
|
63
|
+
*/
|
|
64
|
+
initialize() {
|
|
65
|
+
this.container = document.getElementById('hud-cytoscape');
|
|
66
|
+
if (!this.container) {
|
|
67
|
+
console.error('HUD container not found');
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Ensure container has proper attributes for interaction
|
|
72
|
+
this.container.style.pointerEvents = 'auto';
|
|
73
|
+
this.container.style.cursor = 'default';
|
|
74
|
+
this.container.style.position = 'relative';
|
|
75
|
+
this.container.style.zIndex = '1';
|
|
76
|
+
|
|
77
|
+
// Setup basic event handlers (not library-dependent)
|
|
78
|
+
this.setupBasicEventHandlers();
|
|
79
|
+
|
|
80
|
+
console.log('HUD Visualizer initialized (libraries will load lazily)');
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Load libraries and initialize Cytoscape when HUD is first activated
|
|
86
|
+
* @returns {Promise} - Promise that resolves when libraries are loaded and Cytoscape is initialized
|
|
87
|
+
*/
|
|
88
|
+
async loadLibrariesAndInitialize() {
|
|
89
|
+
if (this.librariesLoaded && this.cy) {
|
|
90
|
+
return Promise.resolve();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// If already loading, return the existing promise
|
|
94
|
+
if (this.loadingPromise) {
|
|
95
|
+
return this.loadingPromise;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
this.loadingPromise = this._performLazyLoading();
|
|
99
|
+
return this.loadingPromise;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Perform the actual lazy loading process
|
|
104
|
+
* @private
|
|
105
|
+
*/
|
|
106
|
+
async _performLazyLoading() {
|
|
107
|
+
try {
|
|
108
|
+
console.log('[HUD-VISUALIZER-DEBUG] _performLazyLoading() called');
|
|
109
|
+
console.log('[HUD-VISUALIZER-DEBUG] Loading HUD visualization libraries...');
|
|
110
|
+
|
|
111
|
+
// Show loading indicator
|
|
112
|
+
this.showLoadingIndicator();
|
|
113
|
+
|
|
114
|
+
// Load libraries using the HUD library loader
|
|
115
|
+
if (!window.HUDLibraryLoader) {
|
|
116
|
+
throw new Error('HUD Library Loader not available');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
console.log('[HUD-VISUALIZER-DEBUG] HUD Library Loader found, loading libraries...');
|
|
120
|
+
await window.HUDLibraryLoader.loadHUDLibraries((progress) => {
|
|
121
|
+
console.log('[HUD-VISUALIZER-DEBUG] Loading progress:', progress);
|
|
122
|
+
this.updateLoadingProgress(progress);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// Verify libraries are available
|
|
126
|
+
console.log('[HUD-VISUALIZER-DEBUG] Verifying libraries are loaded...');
|
|
127
|
+
if (typeof window.cytoscape === 'undefined') {
|
|
128
|
+
throw new Error('Cytoscape.js not loaded');
|
|
129
|
+
}
|
|
130
|
+
if (typeof window.dagre === 'undefined') {
|
|
131
|
+
throw new Error('Dagre not loaded');
|
|
132
|
+
}
|
|
133
|
+
if (typeof window.cytoscapeDagre === 'undefined') {
|
|
134
|
+
throw new Error('Cytoscape-dagre not loaded');
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
console.log('[HUD-VISUALIZER-DEBUG] All HUD libraries loaded successfully');
|
|
138
|
+
this.librariesLoaded = true;
|
|
139
|
+
|
|
140
|
+
// Initialize Cytoscape instance
|
|
141
|
+
console.log('[HUD-VISUALIZER-DEBUG] Initializing Cytoscape...');
|
|
142
|
+
this.initializeCytoscape();
|
|
143
|
+
|
|
144
|
+
// Setup library-dependent event handlers
|
|
145
|
+
console.log('[HUD-VISUALIZER-DEBUG] Setting up Cytoscape event handlers...');
|
|
146
|
+
this.setupCytoscapeEventHandlers();
|
|
147
|
+
|
|
148
|
+
// Process any pending events
|
|
149
|
+
console.log('[HUD-VISUALIZER-DEBUG] Processing pending events...');
|
|
150
|
+
this.processPendingEvents();
|
|
151
|
+
|
|
152
|
+
// Hide loading indicator
|
|
153
|
+
this.hideLoadingIndicator();
|
|
154
|
+
|
|
155
|
+
console.log('[HUD-VISUALIZER-DEBUG] HUD Visualizer fully initialized with lazy loading');
|
|
156
|
+
return true;
|
|
157
|
+
|
|
158
|
+
} catch (error) {
|
|
159
|
+
console.error('[HUD-VISUALIZER-DEBUG] Failed to load HUD libraries:', error);
|
|
160
|
+
console.error('[HUD-VISUALIZER-DEBUG] Error stack:', error.stack);
|
|
161
|
+
this.showLoadingError(error.message);
|
|
162
|
+
this.librariesLoaded = false;
|
|
163
|
+
this.loadingPromise = null;
|
|
164
|
+
throw error;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Initialize Cytoscape.js instance (called after libraries are loaded)
|
|
170
|
+
*/
|
|
171
|
+
initializeCytoscape() {
|
|
172
|
+
if (!this.librariesLoaded || !window.cytoscape) {
|
|
173
|
+
console.error('Cannot initialize Cytoscape: libraries not loaded');
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Register dagre extension for hierarchical layouts
|
|
178
|
+
if (typeof window.cytoscape !== 'undefined' && typeof window.cytoscapeDagre !== 'undefined') {
|
|
179
|
+
window.cytoscape.use(window.cytoscapeDagre);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
this.cy = window.cytoscape({
|
|
183
|
+
container: this.container,
|
|
184
|
+
|
|
185
|
+
elements: [],
|
|
186
|
+
|
|
187
|
+
// Enable user interaction
|
|
188
|
+
userZoomingEnabled: true,
|
|
189
|
+
userPanningEnabled: true,
|
|
190
|
+
boxSelectionEnabled: false,
|
|
191
|
+
autoungrabify: false,
|
|
192
|
+
autounselectify: false,
|
|
193
|
+
|
|
194
|
+
style: [
|
|
195
|
+
// Node styles
|
|
196
|
+
{
|
|
197
|
+
selector: 'node',
|
|
198
|
+
style: {
|
|
199
|
+
'background-color': 'data(color)',
|
|
200
|
+
'border-color': 'data(borderColor)',
|
|
201
|
+
'border-width': 2,
|
|
202
|
+
'color': '#ffffff',
|
|
203
|
+
'label': 'data(label)',
|
|
204
|
+
'text-valign': 'center',
|
|
205
|
+
'text-halign': 'center',
|
|
206
|
+
'font-size': '12px',
|
|
207
|
+
'font-weight': 'bold',
|
|
208
|
+
'width': 'data(width)',
|
|
209
|
+
'height': 'data(height)',
|
|
210
|
+
'shape': 'data(shape)',
|
|
211
|
+
'text-wrap': 'wrap',
|
|
212
|
+
'text-max-width': '100px'
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
|
|
216
|
+
// Edge styles
|
|
217
|
+
{
|
|
218
|
+
selector: 'edge',
|
|
219
|
+
style: {
|
|
220
|
+
'width': 2,
|
|
221
|
+
'line-color': '#718096',
|
|
222
|
+
'target-arrow-color': '#718096',
|
|
223
|
+
'target-arrow-shape': 'triangle',
|
|
224
|
+
'curve-style': 'bezier',
|
|
225
|
+
'arrow-scale': 1.2
|
|
226
|
+
}
|
|
227
|
+
},
|
|
228
|
+
|
|
229
|
+
// Node type specific styles
|
|
230
|
+
{
|
|
231
|
+
selector: '.pm-node',
|
|
232
|
+
style: {
|
|
233
|
+
'background-color': '#48bb78',
|
|
234
|
+
'border-color': '#38a169',
|
|
235
|
+
'shape': 'rectangle'
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
|
|
239
|
+
{
|
|
240
|
+
selector: '.agent-node',
|
|
241
|
+
style: {
|
|
242
|
+
'background-color': '#9f7aea',
|
|
243
|
+
'border-color': '#805ad5',
|
|
244
|
+
'shape': 'ellipse'
|
|
245
|
+
}
|
|
246
|
+
},
|
|
247
|
+
|
|
248
|
+
{
|
|
249
|
+
selector: '.tool-node',
|
|
250
|
+
style: {
|
|
251
|
+
'background-color': '#4299e1',
|
|
252
|
+
'border-color': '#3182ce',
|
|
253
|
+
'shape': 'diamond'
|
|
254
|
+
}
|
|
255
|
+
},
|
|
256
|
+
|
|
257
|
+
{
|
|
258
|
+
selector: '.todo-node',
|
|
259
|
+
style: {
|
|
260
|
+
'background-color': '#e53e3e',
|
|
261
|
+
'border-color': '#c53030',
|
|
262
|
+
'shape': 'triangle'
|
|
263
|
+
}
|
|
264
|
+
},
|
|
265
|
+
|
|
266
|
+
// Hover effects
|
|
267
|
+
{
|
|
268
|
+
selector: 'node:active',
|
|
269
|
+
style: {
|
|
270
|
+
'overlay-opacity': 0.2,
|
|
271
|
+
'overlay-color': '#000000'
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
],
|
|
275
|
+
|
|
276
|
+
layout: this.layoutConfig
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
// Setup resize handler
|
|
280
|
+
this.setupResizeHandler();
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Setup basic event handlers (not dependent on libraries)
|
|
285
|
+
*/
|
|
286
|
+
setupBasicEventHandlers() {
|
|
287
|
+
// Reset layout button
|
|
288
|
+
const resetBtn = document.getElementById('hud-reset-layout');
|
|
289
|
+
if (resetBtn) {
|
|
290
|
+
resetBtn.addEventListener('click', () => {
|
|
291
|
+
this.resetLayout();
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Center view button
|
|
296
|
+
const centerBtn = document.getElementById('hud-center-view');
|
|
297
|
+
if (centerBtn) {
|
|
298
|
+
centerBtn.addEventListener('click', () => {
|
|
299
|
+
this.centerView();
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Setup Cytoscape-dependent event handlers (called after libraries are loaded)
|
|
306
|
+
*/
|
|
307
|
+
setupCytoscapeEventHandlers() {
|
|
308
|
+
if (!this.cy) {
|
|
309
|
+
console.warn('[HUD-VISUALIZER-DEBUG] Cannot setup Cytoscape event handlers: no cy instance');
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
console.log('[HUD-VISUALIZER-DEBUG] Setting up Cytoscape event handlers...');
|
|
314
|
+
|
|
315
|
+
// Node click events
|
|
316
|
+
this.cy.on('tap', 'node', (evt) => {
|
|
317
|
+
const node = evt.target;
|
|
318
|
+
const data = node.data();
|
|
319
|
+
console.log('[HUD-VISUALIZER-DEBUG] Node clicked:', data);
|
|
320
|
+
|
|
321
|
+
// Highlight connected nodes
|
|
322
|
+
this.highlightConnectedNodes(node);
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
// Background click events
|
|
326
|
+
this.cy.on('tap', (evt) => {
|
|
327
|
+
if (evt.target === this.cy) {
|
|
328
|
+
console.log('[HUD-VISUALIZER-DEBUG] Background clicked - resetting highlights');
|
|
329
|
+
// Reset all node styles
|
|
330
|
+
this.cy.nodes().style({
|
|
331
|
+
'opacity': 1
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
this.cy.edges().style({
|
|
335
|
+
'opacity': 1
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
// Mouse events for debugging
|
|
341
|
+
this.cy.on('mouseover', 'node', (evt) => {
|
|
342
|
+
const node = evt.target;
|
|
343
|
+
node.style('opacity', 0.8);
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
this.cy.on('mouseout', 'node', (evt) => {
|
|
347
|
+
const node = evt.target;
|
|
348
|
+
node.style('opacity', 1);
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
console.log('[HUD-VISUALIZER-DEBUG] Cytoscape event handlers set up successfully');
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Setup resize handler for container
|
|
356
|
+
*/
|
|
357
|
+
setupResizeHandler() {
|
|
358
|
+
const resizeObserver = new ResizeObserver(() => {
|
|
359
|
+
if (this.cy && this.isActive) {
|
|
360
|
+
this.ensureContainerResize();
|
|
361
|
+
}
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
if (this.container) {
|
|
365
|
+
resizeObserver.observe(this.container);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Ensure container is properly resized and visible
|
|
371
|
+
*/
|
|
372
|
+
ensureContainerResize() {
|
|
373
|
+
if (!this.cy || !this.container) {
|
|
374
|
+
console.log('[HUD-VISUALIZER-DEBUG] Cannot resize: missing cy or container');
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Ensure container can receive events
|
|
379
|
+
this.ensureContainerInteractivity();
|
|
380
|
+
|
|
381
|
+
// Log container dimensions
|
|
382
|
+
const containerRect = this.container.getBoundingClientRect();
|
|
383
|
+
console.log('[HUD-VISUALIZER-DEBUG] Container dimensions:', {
|
|
384
|
+
width: containerRect.width,
|
|
385
|
+
height: containerRect.height,
|
|
386
|
+
offsetWidth: this.container.offsetWidth,
|
|
387
|
+
offsetHeight: this.container.offsetHeight,
|
|
388
|
+
isVisible: containerRect.width > 0 && containerRect.height > 0
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
// Only proceed if container is visible
|
|
392
|
+
if (containerRect.width > 0 && containerRect.height > 0) {
|
|
393
|
+
console.log('[HUD-VISUALIZER-DEBUG] Container is visible, resizing Cytoscape...');
|
|
394
|
+
|
|
395
|
+
try {
|
|
396
|
+
// Force Cytoscape to resize
|
|
397
|
+
this.cy.resize();
|
|
398
|
+
|
|
399
|
+
// Log Cytoscape elements
|
|
400
|
+
const nodeCount = this.cy.nodes().length;
|
|
401
|
+
const edgeCount = this.cy.edges().length;
|
|
402
|
+
console.log('[HUD-VISUALIZER-DEBUG] Cytoscape elements after resize:', {
|
|
403
|
+
nodes: nodeCount,
|
|
404
|
+
edges: edgeCount
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
// If we have nodes, fit and run layout
|
|
408
|
+
if (nodeCount > 0) {
|
|
409
|
+
console.log('[HUD-VISUALIZER-DEBUG] Running fit and layout...');
|
|
410
|
+
this.cy.fit();
|
|
411
|
+
this.runLayout();
|
|
412
|
+
} else {
|
|
413
|
+
console.log('[HUD-VISUALIZER-DEBUG] No nodes to display');
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
} catch (error) {
|
|
417
|
+
console.error('[HUD-VISUALIZER-DEBUG] Error during resize:', error);
|
|
418
|
+
}
|
|
419
|
+
} else {
|
|
420
|
+
console.log('[HUD-VISUALIZER-DEBUG] Container not visible yet, skipping resize');
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Ensure container can receive mouse and touch events
|
|
426
|
+
*/
|
|
427
|
+
ensureContainerInteractivity() {
|
|
428
|
+
if (!this.container) return;
|
|
429
|
+
|
|
430
|
+
// Force container to be interactive
|
|
431
|
+
this.container.style.pointerEvents = 'auto';
|
|
432
|
+
this.container.style.cursor = 'default';
|
|
433
|
+
this.container.style.userSelect = 'none';
|
|
434
|
+
this.container.style.touchAction = 'manipulation';
|
|
435
|
+
|
|
436
|
+
// Remove any overlapping elements that might block events
|
|
437
|
+
const parent = this.container.parentElement;
|
|
438
|
+
if (parent) {
|
|
439
|
+
parent.style.pointerEvents = 'auto';
|
|
440
|
+
parent.style.position = 'relative';
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
console.log('[HUD-VISUALIZER-DEBUG] Container interactivity ensured');
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* Activate the HUD visualizer (triggers lazy loading if needed)
|
|
448
|
+
*/
|
|
449
|
+
async activate() {
|
|
450
|
+
console.log('[HUD-VISUALIZER-DEBUG] activate() called');
|
|
451
|
+
this.isActive = true;
|
|
452
|
+
|
|
453
|
+
try {
|
|
454
|
+
console.log('[HUD-VISUALIZER-DEBUG] Loading libraries and initializing...');
|
|
455
|
+
// Load libraries if not already loaded
|
|
456
|
+
await this.loadLibrariesAndInitialize();
|
|
457
|
+
|
|
458
|
+
console.log('[HUD-VISUALIZER-DEBUG] Libraries loaded, cy exists:', !!this.cy);
|
|
459
|
+
|
|
460
|
+
// If Cytoscape was destroyed during clearing, recreate it
|
|
461
|
+
if (!this.cy) {
|
|
462
|
+
console.log('[HUD-VISUALIZER-DEBUG] Cytoscape instance missing, recreating...');
|
|
463
|
+
this.initializeCytoscape();
|
|
464
|
+
this.setupCytoscapeEventHandlers();
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
if (this.cy) {
|
|
468
|
+
// Wait for container to be visible, then trigger resize and fit
|
|
469
|
+
console.log('[HUD-VISUALIZER-DEBUG] Triggering resize and fit...');
|
|
470
|
+
|
|
471
|
+
// Multiple resize attempts to ensure container visibility
|
|
472
|
+
setTimeout(() => {
|
|
473
|
+
console.log('[HUD-VISUALIZER-DEBUG] First resize attempt...');
|
|
474
|
+
this.ensureContainerResize();
|
|
475
|
+
}, 50);
|
|
476
|
+
|
|
477
|
+
setTimeout(() => {
|
|
478
|
+
console.log('[HUD-VISUALIZER-DEBUG] Second resize attempt...');
|
|
479
|
+
this.ensureContainerResize();
|
|
480
|
+
}, 200);
|
|
481
|
+
|
|
482
|
+
setTimeout(() => {
|
|
483
|
+
console.log('[HUD-VISUALIZER-DEBUG] Final resize attempt...');
|
|
484
|
+
this.ensureContainerResize();
|
|
485
|
+
}, 500);
|
|
486
|
+
}
|
|
487
|
+
console.log('[HUD-VISUALIZER-DEBUG] activate() completed successfully');
|
|
488
|
+
} catch (error) {
|
|
489
|
+
console.error('[HUD-VISUALIZER-DEBUG] Failed to activate HUD:', error);
|
|
490
|
+
console.error('[HUD-VISUALIZER-DEBUG] Error stack:', error.stack);
|
|
491
|
+
// Keep isActive true so user can retry
|
|
492
|
+
throw error; // Re-throw so the promise rejects properly
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* Deactivate the HUD visualizer
|
|
498
|
+
*/
|
|
499
|
+
deactivate() {
|
|
500
|
+
this.isActive = false;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* Process pending events that were received before libraries loaded
|
|
505
|
+
*/
|
|
506
|
+
processPendingEvents() {
|
|
507
|
+
if (this.pendingEvents.length > 0) {
|
|
508
|
+
console.log(`Processing ${this.pendingEvents.length} pending events`);
|
|
509
|
+
|
|
510
|
+
for (const event of this.pendingEvents) {
|
|
511
|
+
this._processEventInternal(event);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
this.pendingEvents = [];
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* Process existing events from dashboard when HUD is activated
|
|
520
|
+
* This builds the complete tree structure from historical events
|
|
521
|
+
* @param {Array} events - Array of sorted historical events
|
|
522
|
+
*/
|
|
523
|
+
processExistingEvents(events) {
|
|
524
|
+
console.log(`[HUD-VISUALIZER-DEBUG] processExistingEvents called with ${events ? events.length : 0} events`);
|
|
525
|
+
|
|
526
|
+
if (!events) {
|
|
527
|
+
console.error('[HUD-VISUALIZER-DEBUG] No events provided to processExistingEvents');
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
if (!Array.isArray(events)) {
|
|
532
|
+
console.error('[HUD-VISUALIZER-DEBUG] Events is not an array:', typeof events);
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
console.log(`[HUD-VISUALIZER-DEBUG] Libraries loaded: ${this.librariesLoaded}, Cytoscape available: ${!!this.cy}`);
|
|
537
|
+
|
|
538
|
+
if (!this.librariesLoaded || !this.cy) {
|
|
539
|
+
console.warn('[HUD-VISUALIZER-DEBUG] HUD libraries not loaded, cannot process existing events');
|
|
540
|
+
console.log(`[HUD-VISUALIZER-DEBUG] Storing ${events.length} events as pending`);
|
|
541
|
+
this.pendingEvents = [...events];
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
console.log(`[HUD-VISUALIZER-DEBUG] 🏗️ Building HUD tree structure from ${events.length} historical events`);
|
|
546
|
+
|
|
547
|
+
// Log sample events to understand structure
|
|
548
|
+
if (events.length > 0) {
|
|
549
|
+
console.log('[HUD-VISUALIZER-DEBUG] Sample events:');
|
|
550
|
+
events.slice(0, 3).forEach((event, i) => {
|
|
551
|
+
console.log(`[HUD-VISUALIZER-DEBUG] Event ${i + 1}:`, {
|
|
552
|
+
timestamp: event.timestamp,
|
|
553
|
+
hook_event_name: event.hook_event_name,
|
|
554
|
+
type: event.type,
|
|
555
|
+
subtype: event.subtype,
|
|
556
|
+
session_id: event.session_id,
|
|
557
|
+
data_session_id: event.data?.session_id,
|
|
558
|
+
data_keys: event.data ? Object.keys(event.data) : 'no data'
|
|
559
|
+
});
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
// Clear any existing visualization
|
|
564
|
+
this.clear();
|
|
565
|
+
|
|
566
|
+
// Group events by session to build proper hierarchies
|
|
567
|
+
const sessionGroups = this.groupEventsBySession(events);
|
|
568
|
+
|
|
569
|
+
// Process each session group to build trees
|
|
570
|
+
Object.entries(sessionGroups).forEach(([sessionId, sessionEvents]) => {
|
|
571
|
+
console.log(` 📂 Processing session ${sessionId}: ${sessionEvents.length} events`);
|
|
572
|
+
this.buildSessionTree(sessionId, sessionEvents);
|
|
573
|
+
});
|
|
574
|
+
|
|
575
|
+
// Run final layout to organize the complete visualization
|
|
576
|
+
this.runLayout();
|
|
577
|
+
|
|
578
|
+
console.log(`✅ HUD tree structure built successfully`);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
/**
|
|
582
|
+
* Group events by session ID for hierarchical processing
|
|
583
|
+
* @param {Array} events - Array of events
|
|
584
|
+
* @returns {Object} Object with session IDs as keys and event arrays as values
|
|
585
|
+
*/
|
|
586
|
+
groupEventsBySession(events) {
|
|
587
|
+
const sessionGroups = {};
|
|
588
|
+
|
|
589
|
+
events.forEach(event => {
|
|
590
|
+
const sessionId = event.session_id || event.data?.session_id || 'unknown';
|
|
591
|
+
if (!sessionGroups[sessionId]) {
|
|
592
|
+
sessionGroups[sessionId] = [];
|
|
593
|
+
}
|
|
594
|
+
sessionGroups[sessionId].push(event);
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
return sessionGroups;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
/**
|
|
601
|
+
* Build a tree structure for a specific session
|
|
602
|
+
* @param {string} sessionId - Session identifier
|
|
603
|
+
* @param {Array} sessionEvents - Events for this session
|
|
604
|
+
*/
|
|
605
|
+
buildSessionTree(sessionId, sessionEvents) {
|
|
606
|
+
console.log(`[HUD-VISUALIZER-DEBUG] Building session tree for ${sessionId} with ${sessionEvents.length} events`);
|
|
607
|
+
|
|
608
|
+
const sessionNodes = new Map(); // Track nodes created for this session
|
|
609
|
+
let sessionRootNode = null;
|
|
610
|
+
|
|
611
|
+
// Sort events chronologically within the session
|
|
612
|
+
const sortedEvents = sessionEvents.sort((a, b) => {
|
|
613
|
+
return new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime();
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
console.log(`[HUD-VISUALIZER-DEBUG] Sorted ${sortedEvents.length} events chronologically`);
|
|
617
|
+
|
|
618
|
+
sortedEvents.forEach((event, index) => {
|
|
619
|
+
const nodeData = this.createNodeFromEvent(event, sessionId);
|
|
620
|
+
if (!nodeData) return;
|
|
621
|
+
|
|
622
|
+
// Add the node to visualization
|
|
623
|
+
this.addNode(nodeData.id, nodeData.type, nodeData.label, {
|
|
624
|
+
sessionId: sessionId,
|
|
625
|
+
timestamp: event.timestamp,
|
|
626
|
+
eventData: event,
|
|
627
|
+
isSessionRoot: nodeData.isSessionRoot
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
sessionNodes.set(nodeData.id, {
|
|
631
|
+
...nodeData,
|
|
632
|
+
event: event,
|
|
633
|
+
index: index
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
// Track session root node
|
|
637
|
+
if (nodeData.isSessionRoot && !sessionRootNode) {
|
|
638
|
+
sessionRootNode = nodeData.id;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
// Create relationships based on event context
|
|
642
|
+
this.createHierarchicalRelationships(nodeData.id, event, sessionNodes, sessionRootNode);
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
/**
|
|
647
|
+
* Create node data from an event
|
|
648
|
+
* @param {Object} event - Event object
|
|
649
|
+
* @param {string} sessionId - Session ID
|
|
650
|
+
* @returns {Object|null} Node data or null if event should be skipped
|
|
651
|
+
*/
|
|
652
|
+
createNodeFromEvent(event, sessionId) {
|
|
653
|
+
const eventType = event.hook_event_name || event.type || '';
|
|
654
|
+
const subtype = event.subtype || '';
|
|
655
|
+
const timestamp = new Date(event.timestamp || Date.now());
|
|
656
|
+
|
|
657
|
+
console.log(`[HUD-VISUALIZER-DEBUG] Creating node from event: ${eventType}/${subtype} for session ${sessionId}`);
|
|
658
|
+
|
|
659
|
+
let nodeId, nodeType, label, isSessionRoot = false;
|
|
660
|
+
|
|
661
|
+
// Generate a unique timestamp-based ID suffix
|
|
662
|
+
const timestampId = timestamp.getTime();
|
|
663
|
+
const randomSuffix = Math.random().toString(36).substring(2, 7);
|
|
664
|
+
|
|
665
|
+
// Determine node type and create appropriate visualization
|
|
666
|
+
if (eventType === 'session' && subtype === 'started') {
|
|
667
|
+
// Session root node
|
|
668
|
+
nodeType = 'PM';
|
|
669
|
+
label = `Session ${sessionId.substring(0, 8)}...`;
|
|
670
|
+
nodeId = `session-${sessionId.replace(/[^a-zA-Z0-9]/g, '')}`;
|
|
671
|
+
isSessionRoot = true;
|
|
672
|
+
|
|
673
|
+
} else if (eventType === 'hook' && subtype === 'user_prompt') {
|
|
674
|
+
// User prompts are major workflow nodes
|
|
675
|
+
nodeType = 'PM';
|
|
676
|
+
const promptPreview = event.data?.prompt_preview || 'User Prompt';
|
|
677
|
+
label = promptPreview.length > 20 ? promptPreview.substring(0, 20) + '...' : promptPreview;
|
|
678
|
+
nodeId = `user-prompt-${timestampId}-${randomSuffix}`;
|
|
679
|
+
|
|
680
|
+
} else if (eventType === 'hook' && subtype === 'claude_response') {
|
|
681
|
+
// Claude responses
|
|
682
|
+
nodeType = 'PM';
|
|
683
|
+
label = 'Claude Response';
|
|
684
|
+
nodeId = `claude-response-${timestampId}-${randomSuffix}`;
|
|
685
|
+
|
|
686
|
+
} else if (eventType === 'hook' && subtype === 'pre_tool') {
|
|
687
|
+
// Tool calls - pre hook
|
|
688
|
+
nodeType = 'TOOL';
|
|
689
|
+
const toolName = event.data?.tool_name || 'Unknown Tool';
|
|
690
|
+
// Clean tool name for ID
|
|
691
|
+
const cleanToolName = toolName.replace(/[^a-zA-Z0-9]/g, '');
|
|
692
|
+
label = `${toolName}`;
|
|
693
|
+
nodeId = `tool-${cleanToolName}-${timestampId}-${randomSuffix}`;
|
|
694
|
+
|
|
695
|
+
} else if (eventType === 'agent' || event.data?.agent_type) {
|
|
696
|
+
// Agent operations
|
|
697
|
+
nodeType = 'AGENT';
|
|
698
|
+
const agentName = event.data?.agent_type || event.data?.agent_name || 'Agent';
|
|
699
|
+
// Clean agent name for ID
|
|
700
|
+
const cleanAgentName = agentName.replace(/[^a-zA-Z0-9]/g, '');
|
|
701
|
+
label = agentName;
|
|
702
|
+
nodeId = `agent-${cleanAgentName}-${timestampId}-${randomSuffix}`;
|
|
703
|
+
|
|
704
|
+
} else if (eventType === 'todo' || subtype.includes('todo')) {
|
|
705
|
+
// Todo operations
|
|
706
|
+
nodeType = 'TODO';
|
|
707
|
+
label = 'Todo Update';
|
|
708
|
+
nodeId = `todo-${timestampId}-${randomSuffix}`;
|
|
709
|
+
|
|
710
|
+
} else if (eventType === 'hook' && subtype === 'notification') {
|
|
711
|
+
// Skip notifications for cleaner visualization
|
|
712
|
+
return null;
|
|
713
|
+
|
|
714
|
+
} else if (eventType === 'log') {
|
|
715
|
+
// Skip log events for cleaner visualization unless they're errors
|
|
716
|
+
const level = event.data?.level || 'info';
|
|
717
|
+
if (!['error', 'critical'].includes(level)) {
|
|
718
|
+
return null;
|
|
719
|
+
}
|
|
720
|
+
nodeType = 'PM';
|
|
721
|
+
label = `${level.toUpperCase()} Log`;
|
|
722
|
+
nodeId = `log-${level}-${timestampId}-${randomSuffix}`;
|
|
723
|
+
|
|
724
|
+
} else {
|
|
725
|
+
// Generic event node
|
|
726
|
+
nodeType = 'PM';
|
|
727
|
+
const cleanEventType = eventType.replace(/[^a-zA-Z0-9]/g, '') || 'Event';
|
|
728
|
+
label = eventType || 'Event';
|
|
729
|
+
nodeId = `generic-${cleanEventType}-${timestampId}-${randomSuffix}`;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
return {
|
|
733
|
+
id: nodeId,
|
|
734
|
+
type: nodeType,
|
|
735
|
+
label: label,
|
|
736
|
+
isSessionRoot: isSessionRoot
|
|
737
|
+
};
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
/**
|
|
741
|
+
* Create hierarchical relationships between nodes based on event context
|
|
742
|
+
* @param {string} nodeId - Current node ID
|
|
743
|
+
* @param {Object} event - Current event
|
|
744
|
+
* @param {Map} sessionNodes - Map of all nodes in this session
|
|
745
|
+
* @param {string} sessionRootNode - Root node ID for this session
|
|
746
|
+
*/
|
|
747
|
+
createHierarchicalRelationships(nodeId, event, sessionNodes, sessionRootNode) {
|
|
748
|
+
const eventType = event.hook_event_name || event.type || '';
|
|
749
|
+
const subtype = event.subtype || '';
|
|
750
|
+
|
|
751
|
+
// Find appropriate parent node based on event context
|
|
752
|
+
let parentNodeId = null;
|
|
753
|
+
|
|
754
|
+
if (eventType === 'session' && subtype === 'started') {
|
|
755
|
+
// Session start nodes have no parent
|
|
756
|
+
return;
|
|
757
|
+
|
|
758
|
+
} else if (eventType === 'hook' && subtype === 'pre_tool') {
|
|
759
|
+
// Tool calls should connect to the most recent user prompt or agent
|
|
760
|
+
parentNodeId = this.findRecentParentNode(sessionNodes, ['user-prompt', 'agent'], nodeId);
|
|
761
|
+
|
|
762
|
+
} else if (eventType === 'hook' && subtype === 'claude_response') {
|
|
763
|
+
// Claude responses should connect to user prompts
|
|
764
|
+
parentNodeId = this.findRecentParentNode(sessionNodes, ['user-prompt'], nodeId);
|
|
765
|
+
|
|
766
|
+
} else if (eventType === 'agent') {
|
|
767
|
+
// Agents should connect to user prompts or other agents (delegation)
|
|
768
|
+
parentNodeId = this.findRecentParentNode(sessionNodes, ['user-prompt', 'agent'], nodeId);
|
|
769
|
+
|
|
770
|
+
} else if (eventType === 'todo') {
|
|
771
|
+
// Todos should connect to agents or user prompts
|
|
772
|
+
parentNodeId = this.findRecentParentNode(sessionNodes, ['agent', 'user-prompt'], nodeId);
|
|
773
|
+
|
|
774
|
+
} else {
|
|
775
|
+
// Default: connect to most recent significant node
|
|
776
|
+
parentNodeId = this.findRecentParentNode(sessionNodes, ['user-prompt', 'agent', 'session'], nodeId);
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
// If no specific parent found, connect to session root
|
|
780
|
+
if (!parentNodeId && sessionRootNode && nodeId !== sessionRootNode) {
|
|
781
|
+
parentNodeId = sessionRootNode;
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
// Create the edge if parent exists
|
|
785
|
+
if (parentNodeId && parentNodeId !== nodeId) {
|
|
786
|
+
this.addEdge(parentNodeId, nodeId);
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
/**
|
|
791
|
+
* Find the most recent parent node of specified types
|
|
792
|
+
* @param {Map} sessionNodes - Map of session nodes
|
|
793
|
+
* @param {Array} nodeTypes - Array of node type prefixes to search for
|
|
794
|
+
* @param {string} currentNodeId - Current node ID to exclude from search
|
|
795
|
+
* @returns {string|null} Parent node ID or null
|
|
796
|
+
*/
|
|
797
|
+
findRecentParentNode(sessionNodes, nodeTypes, currentNodeId) {
|
|
798
|
+
const nodeEntries = Array.from(sessionNodes.entries()).reverse(); // Most recent first
|
|
799
|
+
|
|
800
|
+
for (const [nodeId, nodeData] of nodeEntries) {
|
|
801
|
+
if (nodeId === currentNodeId) continue; // Skip current node
|
|
802
|
+
|
|
803
|
+
// Check if this node matches any of the desired parent types
|
|
804
|
+
for (const typePrefix of nodeTypes) {
|
|
805
|
+
if (nodeId.startsWith(typePrefix)) {
|
|
806
|
+
return nodeId;
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
return null;
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
/**
|
|
815
|
+
* Process a socket event and add appropriate nodes/edges
|
|
816
|
+
* @param {Object} event - Socket event data
|
|
817
|
+
*/
|
|
818
|
+
processEvent(event) {
|
|
819
|
+
if (!this.isActive) return;
|
|
820
|
+
|
|
821
|
+
// If libraries aren't loaded yet, store the event for later processing
|
|
822
|
+
if (!this.librariesLoaded || !this.cy) {
|
|
823
|
+
this.pendingEvents.push(event);
|
|
824
|
+
return;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
this._processEventInternal(event);
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
/**
|
|
831
|
+
* Internal event processing (assumes libraries are loaded)
|
|
832
|
+
* @private
|
|
833
|
+
*/
|
|
834
|
+
_processEventInternal(event) {
|
|
835
|
+
const eventType = event.hook_event_name || event.type || '';
|
|
836
|
+
const sessionId = event.session_id || 'unknown';
|
|
837
|
+
const timestamp = new Date(event.timestamp || Date.now());
|
|
838
|
+
|
|
839
|
+
// Create a unique node ID based on event type and data
|
|
840
|
+
let nodeId = `${eventType}-${timestamp.getTime()}`;
|
|
841
|
+
let nodeType = 'PM';
|
|
842
|
+
let label = eventType;
|
|
843
|
+
|
|
844
|
+
// Determine node type based on event
|
|
845
|
+
if (eventType.includes('tool_call')) {
|
|
846
|
+
nodeType = 'TOOL';
|
|
847
|
+
const toolName = event.data?.tool_name || 'Unknown Tool';
|
|
848
|
+
label = toolName;
|
|
849
|
+
nodeId = `tool-${toolName}-${timestamp.getTime()}`;
|
|
850
|
+
} else if (eventType.includes('agent')) {
|
|
851
|
+
nodeType = 'AGENT';
|
|
852
|
+
const agentName = event.data?.agent_name || 'Agent';
|
|
853
|
+
label = agentName;
|
|
854
|
+
nodeId = `agent-${agentName}-${timestamp.getTime()}`;
|
|
855
|
+
} else if (eventType.includes('todo')) {
|
|
856
|
+
nodeType = 'TODO';
|
|
857
|
+
label = 'Todo List';
|
|
858
|
+
nodeId = `todo-${timestamp.getTime()}`;
|
|
859
|
+
} else if (eventType.includes('user_prompt') || eventType.includes('claude_response')) {
|
|
860
|
+
nodeType = 'PM';
|
|
861
|
+
label = eventType.includes('user_prompt') ? 'User Prompt' : 'Claude Response';
|
|
862
|
+
nodeId = `pm-${label.replace(' ', '')}-${timestamp.getTime()}`;
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
// Add the node
|
|
866
|
+
this.addNode(nodeId, nodeType, label, {
|
|
867
|
+
sessionId: sessionId,
|
|
868
|
+
timestamp: timestamp.toISOString(),
|
|
869
|
+
eventData: event
|
|
870
|
+
});
|
|
871
|
+
|
|
872
|
+
// Add edges based on relationships
|
|
873
|
+
this.createEventRelationships(nodeId, event);
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
/**
|
|
877
|
+
* Add a node to the visualization
|
|
878
|
+
* @param {string} id - Unique node identifier
|
|
879
|
+
* @param {string} type - Node type (PM, AGENT, TOOL, TODO)
|
|
880
|
+
* @param {string} label - Node label
|
|
881
|
+
* @param {Object} data - Additional node data
|
|
882
|
+
*/
|
|
883
|
+
addNode(id, type, label, data = {}) {
|
|
884
|
+
console.log(`[HUD-VISUALIZER-DEBUG] Adding node: ${id} (${type}) - ${label}`);
|
|
885
|
+
|
|
886
|
+
if (this.nodes.has(id)) {
|
|
887
|
+
console.log(`[HUD-VISUALIZER-DEBUG] Node ${id} already exists, skipping`);
|
|
888
|
+
return; // Node already exists
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
const nodeType = this.nodeTypes[type] || this.nodeTypes.PM;
|
|
892
|
+
const nodeData = {
|
|
893
|
+
id: id,
|
|
894
|
+
label: `${nodeType.icon} ${label}`,
|
|
895
|
+
type: type,
|
|
896
|
+
color: nodeType.color,
|
|
897
|
+
borderColor: this.darkenColor(nodeType.color, 20),
|
|
898
|
+
shape: nodeType.shape,
|
|
899
|
+
width: nodeType.width,
|
|
900
|
+
height: nodeType.height,
|
|
901
|
+
...data
|
|
902
|
+
};
|
|
903
|
+
|
|
904
|
+
this.nodes.set(id, nodeData);
|
|
905
|
+
|
|
906
|
+
if (this.cy) {
|
|
907
|
+
const element = {
|
|
908
|
+
group: 'nodes',
|
|
909
|
+
data: nodeData,
|
|
910
|
+
classes: `${type.toLowerCase()}-node`
|
|
911
|
+
};
|
|
912
|
+
|
|
913
|
+
console.log(`[HUD-VISUALIZER-DEBUG] Adding node element to Cytoscape:`, element);
|
|
914
|
+
this.cy.add(element);
|
|
915
|
+
console.log(`[HUD-VISUALIZER-DEBUG] Node added successfully. Total nodes in cy: ${this.cy.nodes().length}`);
|
|
916
|
+
this.runLayout();
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
/**
|
|
921
|
+
* Add an edge between two nodes
|
|
922
|
+
* @param {string} sourceId - Source node ID
|
|
923
|
+
* @param {string} targetId - Target node ID
|
|
924
|
+
* @param {string} edgeId - Unique edge identifier
|
|
925
|
+
* @param {Object} data - Additional edge data
|
|
926
|
+
*/
|
|
927
|
+
addEdge(sourceId, targetId, edgeId = null, data = {}) {
|
|
928
|
+
if (!sourceId || !targetId) {
|
|
929
|
+
console.warn(`[HUD-VISUALIZER-DEBUG] Cannot create edge: missing source (${sourceId}) or target (${targetId})`);
|
|
930
|
+
return;
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
if (sourceId === targetId) {
|
|
934
|
+
console.warn(`[HUD-VISUALIZER-DEBUG] Cannot create self-loop edge from ${sourceId} to itself`);
|
|
935
|
+
return;
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
if (!edgeId) {
|
|
939
|
+
edgeId = `edge-${sourceId}-to-${targetId}`;
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
if (this.cy) {
|
|
943
|
+
// Check if edge already exists
|
|
944
|
+
const existingEdge = this.cy.getElementById(edgeId);
|
|
945
|
+
if (existingEdge.length > 0) {
|
|
946
|
+
console.log(`[HUD-VISUALIZER-DEBUG] Edge ${edgeId} already exists, skipping`);
|
|
947
|
+
return;
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
// Check if nodes exist
|
|
951
|
+
const sourceNode = this.cy.getElementById(sourceId);
|
|
952
|
+
const targetNode = this.cy.getElementById(targetId);
|
|
953
|
+
|
|
954
|
+
if (sourceNode.length === 0) {
|
|
955
|
+
console.warn(`[HUD-VISUALIZER-DEBUG] Source node ${sourceId} does not exist, cannot create edge`);
|
|
956
|
+
return;
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
if (targetNode.length === 0) {
|
|
960
|
+
console.warn(`[HUD-VISUALIZER-DEBUG] Target node ${targetId} does not exist, cannot create edge`);
|
|
961
|
+
return;
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
const element = {
|
|
965
|
+
group: 'edges',
|
|
966
|
+
data: {
|
|
967
|
+
id: edgeId,
|
|
968
|
+
source: sourceId,
|
|
969
|
+
target: targetId,
|
|
970
|
+
...data
|
|
971
|
+
}
|
|
972
|
+
};
|
|
973
|
+
|
|
974
|
+
console.log(`[HUD-VISUALIZER-DEBUG] Adding edge element to Cytoscape:`, element);
|
|
975
|
+
|
|
976
|
+
try {
|
|
977
|
+
this.cy.add(element);
|
|
978
|
+
console.log(`[HUD-VISUALIZER-DEBUG] Edge added successfully. Total edges in cy: ${this.cy.edges().length}`);
|
|
979
|
+
this.runLayout();
|
|
980
|
+
} catch (error) {
|
|
981
|
+
console.error(`[HUD-VISUALIZER-DEBUG] Failed to add edge ${edgeId}:`, error);
|
|
982
|
+
console.error(`[HUD-VISUALIZER-DEBUG] Element details:`, element);
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
/**
|
|
988
|
+
* Create relationships between events
|
|
989
|
+
* @param {string} nodeId - Current node ID
|
|
990
|
+
* @param {Object} event - Event data
|
|
991
|
+
*/
|
|
992
|
+
createEventRelationships(nodeId, event) {
|
|
993
|
+
const eventType = event.hook_event_name || event.type || '';
|
|
994
|
+
const sessionId = event.session_id || 'unknown';
|
|
995
|
+
|
|
996
|
+
// Find parent nodes based on event relationships
|
|
997
|
+
const allNodeEntries = Array.from(this.nodes.entries());
|
|
998
|
+
|
|
999
|
+
// Tool call relationships
|
|
1000
|
+
if (eventType.includes('tool_call') && event.data?.tool_name) {
|
|
1001
|
+
// Connect tool calls to their invoking agent/PM nodes
|
|
1002
|
+
const parentNode = this.findParentNode(sessionId, ['PM', 'AGENT']);
|
|
1003
|
+
if (parentNode) {
|
|
1004
|
+
this.addEdge(parentNode, nodeId);
|
|
1005
|
+
return;
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
// Agent delegation relationships
|
|
1010
|
+
if (eventType.includes('agent') || event.data?.agent_name) {
|
|
1011
|
+
// Connect agents to PM nodes
|
|
1012
|
+
const pmNode = this.findParentNode(sessionId, ['PM']);
|
|
1013
|
+
if (pmNode) {
|
|
1014
|
+
this.addEdge(pmNode, nodeId);
|
|
1015
|
+
return;
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
// Todo relationships - connect to agent or PM nodes
|
|
1020
|
+
if (eventType.includes('todo')) {
|
|
1021
|
+
const parentNode = this.findParentNode(sessionId, ['AGENT', 'PM']);
|
|
1022
|
+
if (parentNode) {
|
|
1023
|
+
this.addEdge(parentNode, nodeId);
|
|
1024
|
+
return;
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
// Default sequential relationship
|
|
1029
|
+
const allNodes = Array.from(this.nodes.keys());
|
|
1030
|
+
const currentIndex = allNodes.indexOf(nodeId);
|
|
1031
|
+
|
|
1032
|
+
if (currentIndex > 0) {
|
|
1033
|
+
const previousNodeId = allNodes[currentIndex - 1];
|
|
1034
|
+
this.addEdge(previousNodeId, nodeId);
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
/**
|
|
1039
|
+
* Find a parent node of specific types for the same session
|
|
1040
|
+
* @param {string} sessionId - Session ID
|
|
1041
|
+
* @param {Array} nodeTypes - Array of node types to search for
|
|
1042
|
+
* @returns {string|null} - Parent node ID or null
|
|
1043
|
+
*/
|
|
1044
|
+
findParentNode(sessionId, nodeTypes) {
|
|
1045
|
+
const nodeEntries = Array.from(this.nodes.entries()).reverse(); // Start from most recent
|
|
1046
|
+
|
|
1047
|
+
for (const [nodeId, nodeData] of nodeEntries) {
|
|
1048
|
+
if (nodeData.sessionId === sessionId && nodeTypes.includes(nodeData.type)) {
|
|
1049
|
+
return nodeId;
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
return null;
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
/**
|
|
1057
|
+
* Highlight connected nodes
|
|
1058
|
+
* @param {Object} node - Cytoscape node object
|
|
1059
|
+
*/
|
|
1060
|
+
highlightConnectedNodes(node) {
|
|
1061
|
+
if (!this.cy) return;
|
|
1062
|
+
|
|
1063
|
+
// Reset all node styles
|
|
1064
|
+
this.cy.nodes().style({
|
|
1065
|
+
'opacity': 0.3
|
|
1066
|
+
});
|
|
1067
|
+
|
|
1068
|
+
this.cy.edges().style({
|
|
1069
|
+
'opacity': 0.2
|
|
1070
|
+
});
|
|
1071
|
+
|
|
1072
|
+
// Highlight selected node and its neighborhood
|
|
1073
|
+
const neighborhood = node.neighborhood();
|
|
1074
|
+
node.style('opacity', 1);
|
|
1075
|
+
neighborhood.style('opacity', 1);
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
/**
|
|
1079
|
+
* Reset layout
|
|
1080
|
+
*/
|
|
1081
|
+
resetLayout() {
|
|
1082
|
+
if (this.cy) {
|
|
1083
|
+
this.cy.layout(this.layoutConfig).run();
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
/**
|
|
1088
|
+
* Center view
|
|
1089
|
+
*/
|
|
1090
|
+
centerView() {
|
|
1091
|
+
if (this.cy) {
|
|
1092
|
+
this.cy.fit();
|
|
1093
|
+
this.cy.center();
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
/**
|
|
1098
|
+
* Run layout animation
|
|
1099
|
+
*/
|
|
1100
|
+
runLayout() {
|
|
1101
|
+
console.log(`[HUD-VISUALIZER-DEBUG] runLayout called - isActive: ${this.isActive}, cy exists: ${!!this.cy}`);
|
|
1102
|
+
if (this.cy && this.isActive) {
|
|
1103
|
+
const nodeCount = this.cy.nodes().length;
|
|
1104
|
+
const edgeCount = this.cy.edges().length;
|
|
1105
|
+
console.log(`[HUD-VISUALIZER-DEBUG] Running layout with ${nodeCount} nodes and ${edgeCount} edges`);
|
|
1106
|
+
|
|
1107
|
+
// Check container dimensions before layout
|
|
1108
|
+
if (this.container) {
|
|
1109
|
+
const rect = this.container.getBoundingClientRect();
|
|
1110
|
+
console.log(`[HUD-VISUALIZER-DEBUG] Container dimensions before layout:`, {
|
|
1111
|
+
width: rect.width,
|
|
1112
|
+
height: rect.height,
|
|
1113
|
+
offsetWidth: this.container.offsetWidth,
|
|
1114
|
+
offsetHeight: this.container.offsetHeight
|
|
1115
|
+
});
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
const layout = this.cy.layout(this.layoutConfig);
|
|
1119
|
+
|
|
1120
|
+
// Listen for layout completion
|
|
1121
|
+
layout.on('layoutstop', () => {
|
|
1122
|
+
console.log(`[HUD-VISUALIZER-DEBUG] Layout completed. Final node positions:`);
|
|
1123
|
+
this.cy.nodes().forEach((node, index) => {
|
|
1124
|
+
const position = node.position();
|
|
1125
|
+
const data = node.data();
|
|
1126
|
+
console.log(`[HUD-VISUALIZER-DEBUG] Node ${index + 1}: ${data.label} at (${position.x.toFixed(1)}, ${position.y.toFixed(1)})`);
|
|
1127
|
+
});
|
|
1128
|
+
});
|
|
1129
|
+
|
|
1130
|
+
layout.run();
|
|
1131
|
+
} else {
|
|
1132
|
+
console.log(`[HUD-VISUALIZER-DEBUG] Skipping layout - not active or no Cytoscape instance`);
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
/**
|
|
1137
|
+
* Clear all nodes and edges
|
|
1138
|
+
*/
|
|
1139
|
+
clear() {
|
|
1140
|
+
console.log(`[HUD-VISUALIZER-DEBUG] Clearing HUD: ${this.nodes.size} nodes, ${this.pendingEvents.length} pending events`);
|
|
1141
|
+
this.nodes.clear();
|
|
1142
|
+
this.pendingEvents = [];
|
|
1143
|
+
if (this.cy) {
|
|
1144
|
+
const elementCount = this.cy.elements().length;
|
|
1145
|
+
try {
|
|
1146
|
+
this.cy.elements().remove();
|
|
1147
|
+
console.log(`[HUD-VISUALIZER-DEBUG] Removed ${elementCount} Cytoscape elements`);
|
|
1148
|
+
} catch (error) {
|
|
1149
|
+
console.error(`[HUD-VISUALIZER-DEBUG] Error clearing Cytoscape elements:`, error);
|
|
1150
|
+
// Try to destroy and recreate if clearing fails
|
|
1151
|
+
try {
|
|
1152
|
+
this.cy.destroy();
|
|
1153
|
+
this.cy = null;
|
|
1154
|
+
console.log(`[HUD-VISUALIZER-DEBUG] Destroyed Cytoscape instance due to clear error`);
|
|
1155
|
+
} catch (destroyError) {
|
|
1156
|
+
console.error(`[HUD-VISUALIZER-DEBUG] Error destroying Cytoscape:`, destroyError);
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
/**
|
|
1163
|
+
* Show loading indicator
|
|
1164
|
+
*/
|
|
1165
|
+
showLoadingIndicator() {
|
|
1166
|
+
if (this.container) {
|
|
1167
|
+
this.container.innerHTML = `
|
|
1168
|
+
<div class="hud-loading-container">
|
|
1169
|
+
<div class="hud-loading-spinner"></div>
|
|
1170
|
+
<div class="hud-loading-text">Loading HUD visualization libraries...</div>
|
|
1171
|
+
<div class="hud-loading-progress" id="hud-loading-progress"></div>
|
|
1172
|
+
</div>
|
|
1173
|
+
`;
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
/**
|
|
1178
|
+
* Update loading progress
|
|
1179
|
+
*/
|
|
1180
|
+
updateLoadingProgress(progress) {
|
|
1181
|
+
const progressElement = document.getElementById('hud-loading-progress');
|
|
1182
|
+
if (progressElement) {
|
|
1183
|
+
if (progress.error) {
|
|
1184
|
+
progressElement.innerHTML = `<span class="hud-error">❌ ${progress.message}</span>`;
|
|
1185
|
+
} else {
|
|
1186
|
+
progressElement.innerHTML = `
|
|
1187
|
+
<div class="hud-progress-bar">
|
|
1188
|
+
<div class="hud-progress-fill" style="width: ${(progress.current / progress.total) * 100}%"></div>
|
|
1189
|
+
</div>
|
|
1190
|
+
<div class="hud-progress-text">${progress.message} (${progress.current}/${progress.total})</div>
|
|
1191
|
+
`;
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
/**
|
|
1197
|
+
* Hide loading indicator
|
|
1198
|
+
*/
|
|
1199
|
+
hideLoadingIndicator() {
|
|
1200
|
+
if (this.container) {
|
|
1201
|
+
this.container.innerHTML = '';
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
/**
|
|
1206
|
+
* Show loading error
|
|
1207
|
+
*/
|
|
1208
|
+
showLoadingError(message) {
|
|
1209
|
+
if (this.container) {
|
|
1210
|
+
this.container.innerHTML = `
|
|
1211
|
+
<div class="hud-error-container">
|
|
1212
|
+
<div class="hud-error-icon">⚠️</div>
|
|
1213
|
+
<div class="hud-error-text">Failed to load HUD libraries</div>
|
|
1214
|
+
<div class="hud-error-message">${message}</div>
|
|
1215
|
+
<button class="hud-retry-button" onclick="window.hudVisualizer && window.hudVisualizer.retryLoading()">
|
|
1216
|
+
Retry Loading
|
|
1217
|
+
</button>
|
|
1218
|
+
</div>
|
|
1219
|
+
`;
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
/**
|
|
1224
|
+
* Retry loading libraries (called from error UI)
|
|
1225
|
+
*/
|
|
1226
|
+
retryLoading() {
|
|
1227
|
+
this.librariesLoaded = false;
|
|
1228
|
+
this.loadingPromise = null;
|
|
1229
|
+
this.activate();
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
/**
|
|
1233
|
+
* Debug method to manually test HUD visualizer
|
|
1234
|
+
* Can be called from browser console: window.hudVisualizer.debugTest()
|
|
1235
|
+
*/
|
|
1236
|
+
debugTest() {
|
|
1237
|
+
console.log('[HUD-VISUALIZER-DEBUG] debugTest() called manually');
|
|
1238
|
+
console.log('[HUD-VISUALIZER-DEBUG] Current state:', {
|
|
1239
|
+
isActive: this.isActive,
|
|
1240
|
+
librariesLoaded: this.librariesLoaded,
|
|
1241
|
+
hasCy: !!this.cy,
|
|
1242
|
+
hasContainer: !!this.container,
|
|
1243
|
+
nodeCount: this.nodes.size,
|
|
1244
|
+
pendingEventCount: this.pendingEvents.length,
|
|
1245
|
+
hasHUDLibraryLoader: !!window.HUDLibraryLoader
|
|
1246
|
+
});
|
|
1247
|
+
|
|
1248
|
+
// Test container
|
|
1249
|
+
if (this.container) {
|
|
1250
|
+
console.log('[HUD-VISUALIZER-DEBUG] Container info:', {
|
|
1251
|
+
id: this.container.id,
|
|
1252
|
+
className: this.container.className,
|
|
1253
|
+
offsetWidth: this.container.offsetWidth,
|
|
1254
|
+
offsetHeight: this.container.offsetHeight,
|
|
1255
|
+
innerHTML: this.container.innerHTML ? 'has content' : 'empty'
|
|
1256
|
+
});
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
// Test library availability
|
|
1260
|
+
console.log('[HUD-VISUALIZER-DEBUG] Library availability:', {
|
|
1261
|
+
cytoscape: typeof window.cytoscape,
|
|
1262
|
+
dagre: typeof window.dagre,
|
|
1263
|
+
cytoscapeDagre: typeof window.cytoscapeDagre,
|
|
1264
|
+
HUDLibraryLoader: typeof window.HUDLibraryLoader
|
|
1265
|
+
});
|
|
1266
|
+
|
|
1267
|
+
return {
|
|
1268
|
+
isActive: this.isActive,
|
|
1269
|
+
librariesLoaded: this.librariesLoaded,
|
|
1270
|
+
hasCy: !!this.cy,
|
|
1271
|
+
containerFound: !!this.container
|
|
1272
|
+
};
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
/**
|
|
1276
|
+
* Comprehensive debug method to identify blank screen issues
|
|
1277
|
+
* Can be called from browser console: window.hudVisualizer.debugBlankScreen()
|
|
1278
|
+
*/
|
|
1279
|
+
debugBlankScreen() {
|
|
1280
|
+
console.log('[HUD-BLANK-SCREEN-DEBUG] =================================');
|
|
1281
|
+
console.log('[HUD-BLANK-SCREEN-DEBUG] COMPREHENSIVE BLANK SCREEN DEBUG');
|
|
1282
|
+
console.log('[HUD-BLANK-SCREEN-DEBUG] =================================');
|
|
1283
|
+
|
|
1284
|
+
// 1. Check basic state
|
|
1285
|
+
const basicState = {
|
|
1286
|
+
isActive: this.isActive,
|
|
1287
|
+
librariesLoaded: this.librariesLoaded,
|
|
1288
|
+
hasCy: !!this.cy,
|
|
1289
|
+
hasContainer: !!this.container,
|
|
1290
|
+
nodeCount: this.nodes.size,
|
|
1291
|
+
cytoscapeElementCount: this.cy ? this.cy.elements().length : 0
|
|
1292
|
+
};
|
|
1293
|
+
console.log('[HUD-BLANK-SCREEN-DEBUG] 1. Basic State:', basicState);
|
|
1294
|
+
|
|
1295
|
+
// 2. Check container visibility and dimensions
|
|
1296
|
+
if (this.container) {
|
|
1297
|
+
const containerInfo = this.getContainerDebugInfo();
|
|
1298
|
+
console.log('[HUD-BLANK-SCREEN-DEBUG] 2. Container Info:', containerInfo);
|
|
1299
|
+
|
|
1300
|
+
// Add background color to verify container is visible
|
|
1301
|
+
this.debugAddContainerBackground();
|
|
1302
|
+
} else {
|
|
1303
|
+
console.error('[HUD-BLANK-SCREEN-DEBUG] 2. Container not found!');
|
|
1304
|
+
return false;
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
// 3. Check Cytoscape state
|
|
1308
|
+
if (this.cy) {
|
|
1309
|
+
const cytoscapeInfo = this.getCytoscapeDebugInfo();
|
|
1310
|
+
console.log('[HUD-BLANK-SCREEN-DEBUG] 3. Cytoscape Info:', cytoscapeInfo);
|
|
1311
|
+
} else {
|
|
1312
|
+
console.error('[HUD-BLANK-SCREEN-DEBUG] 3. Cytoscape instance not found!');
|
|
1313
|
+
return false;
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
// 4. Check node positions
|
|
1317
|
+
this.debugNodePositions();
|
|
1318
|
+
|
|
1319
|
+
// 5. Try manual rendering triggers
|
|
1320
|
+
this.debugManualRenderingTriggers();
|
|
1321
|
+
|
|
1322
|
+
// 6. Add test nodes if none exist
|
|
1323
|
+
if (this.cy && this.cy.nodes().length === 0) {
|
|
1324
|
+
console.log('[HUD-BLANK-SCREEN-DEBUG] 6. No nodes found, adding test nodes...');
|
|
1325
|
+
this.debugAddTestNodes();
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
// 7. Force zoom fit
|
|
1329
|
+
this.debugForceZoomFit();
|
|
1330
|
+
|
|
1331
|
+
console.log('[HUD-BLANK-SCREEN-DEBUG] Debug complete. Check visual results.');
|
|
1332
|
+
return true;
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
/**
|
|
1336
|
+
* Get comprehensive container debug information
|
|
1337
|
+
*/
|
|
1338
|
+
getContainerDebugInfo() {
|
|
1339
|
+
const rect = this.container.getBoundingClientRect();
|
|
1340
|
+
const computed = window.getComputedStyle(this.container);
|
|
1341
|
+
|
|
1342
|
+
return {
|
|
1343
|
+
id: this.container.id,
|
|
1344
|
+
className: this.container.className,
|
|
1345
|
+
// Dimensions
|
|
1346
|
+
offsetWidth: this.container.offsetWidth,
|
|
1347
|
+
offsetHeight: this.container.offsetHeight,
|
|
1348
|
+
clientWidth: this.container.clientWidth,
|
|
1349
|
+
clientHeight: this.container.clientHeight,
|
|
1350
|
+
scrollWidth: this.container.scrollWidth,
|
|
1351
|
+
scrollHeight: this.container.scrollHeight,
|
|
1352
|
+
// Bounding rect
|
|
1353
|
+
boundingRect: {
|
|
1354
|
+
width: rect.width,
|
|
1355
|
+
height: rect.height,
|
|
1356
|
+
top: rect.top,
|
|
1357
|
+
left: rect.left,
|
|
1358
|
+
bottom: rect.bottom,
|
|
1359
|
+
right: rect.right
|
|
1360
|
+
},
|
|
1361
|
+
// Computed styles that affect visibility
|
|
1362
|
+
computedStyles: {
|
|
1363
|
+
display: computed.display,
|
|
1364
|
+
visibility: computed.visibility,
|
|
1365
|
+
opacity: computed.opacity,
|
|
1366
|
+
position: computed.position,
|
|
1367
|
+
overflow: computed.overflow,
|
|
1368
|
+
zIndex: computed.zIndex,
|
|
1369
|
+
backgroundColor: computed.backgroundColor,
|
|
1370
|
+
transform: computed.transform
|
|
1371
|
+
},
|
|
1372
|
+
// Check if visible
|
|
1373
|
+
isVisible: rect.width > 0 && rect.height > 0 && computed.display !== 'none' && computed.visibility !== 'hidden',
|
|
1374
|
+
// Parent info
|
|
1375
|
+
parentElement: this.container.parentElement ? {
|
|
1376
|
+
tagName: this.container.parentElement.tagName,
|
|
1377
|
+
className: this.container.parentElement.className,
|
|
1378
|
+
offsetWidth: this.container.parentElement.offsetWidth,
|
|
1379
|
+
offsetHeight: this.container.parentElement.offsetHeight
|
|
1380
|
+
} : null
|
|
1381
|
+
};
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
/**
|
|
1385
|
+
* Get comprehensive Cytoscape debug information
|
|
1386
|
+
*/
|
|
1387
|
+
getCytoscapeDebugInfo() {
|
|
1388
|
+
const extent = this.cy.extent();
|
|
1389
|
+
const zoom = this.cy.zoom();
|
|
1390
|
+
const pan = this.cy.pan();
|
|
1391
|
+
const viewport = this.cy.viewport();
|
|
1392
|
+
|
|
1393
|
+
return {
|
|
1394
|
+
// Elements
|
|
1395
|
+
nodeCount: this.cy.nodes().length,
|
|
1396
|
+
edgeCount: this.cy.edges().length,
|
|
1397
|
+
elementCount: this.cy.elements().length,
|
|
1398
|
+
// Viewport
|
|
1399
|
+
zoom: zoom,
|
|
1400
|
+
pan: pan,
|
|
1401
|
+
extent: extent,
|
|
1402
|
+
viewport: viewport,
|
|
1403
|
+
// Container
|
|
1404
|
+
containerWidth: this.cy.width(),
|
|
1405
|
+
containerHeight: this.cy.height(),
|
|
1406
|
+
// Check if initialized
|
|
1407
|
+
isInitialized: this.cy.scratch('_cytoscape-initialized') !== undefined,
|
|
1408
|
+
// Renderer info
|
|
1409
|
+
renderer: this.cy.renderer() ? {
|
|
1410
|
+
name: this.cy.renderer().name,
|
|
1411
|
+
options: this.cy.renderer().options
|
|
1412
|
+
} : null
|
|
1413
|
+
};
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
/**
|
|
1417
|
+
* Debug node positions to check if they're outside viewport
|
|
1418
|
+
*/
|
|
1419
|
+
debugNodePositions() {
|
|
1420
|
+
if (!this.cy || this.cy.nodes().length === 0) {
|
|
1421
|
+
console.log('[HUD-BLANK-SCREEN-DEBUG] 4. No nodes to check positions');
|
|
1422
|
+
return;
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1425
|
+
console.log('[HUD-BLANK-SCREEN-DEBUG] 4. Node Positions:');
|
|
1426
|
+
const nodes = this.cy.nodes();
|
|
1427
|
+
const extent = this.cy.extent();
|
|
1428
|
+
const viewport = this.cy.viewport();
|
|
1429
|
+
|
|
1430
|
+
console.log('[HUD-BLANK-SCREEN-DEBUG] Viewport extent:', extent);
|
|
1431
|
+
console.log('[HUD-BLANK-SCREEN-DEBUG] Current viewport:', viewport);
|
|
1432
|
+
|
|
1433
|
+
nodes.forEach((node, index) => {
|
|
1434
|
+
const position = node.position();
|
|
1435
|
+
const data = node.data();
|
|
1436
|
+
const boundingBox = node.boundingBox();
|
|
1437
|
+
|
|
1438
|
+
console.log(`[HUD-BLANK-SCREEN-DEBUG] Node ${index + 1}:`, {
|
|
1439
|
+
id: data.id,
|
|
1440
|
+
label: data.label,
|
|
1441
|
+
position: position,
|
|
1442
|
+
boundingBox: boundingBox,
|
|
1443
|
+
isVisible: node.visible(),
|
|
1444
|
+
opacity: node.style('opacity'),
|
|
1445
|
+
width: node.style('width'),
|
|
1446
|
+
height: node.style('height')
|
|
1447
|
+
});
|
|
1448
|
+
});
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
/**
|
|
1452
|
+
* Add background color to container to verify it's visible
|
|
1453
|
+
*/
|
|
1454
|
+
debugAddContainerBackground() {
|
|
1455
|
+
if (this.container) {
|
|
1456
|
+
this.container.style.backgroundColor = '#ff000020'; // Light red background
|
|
1457
|
+
this.container.style.border = '2px solid #ff0000'; // Red border
|
|
1458
|
+
this.container.style.minHeight = '400px'; // Ensure minimum height
|
|
1459
|
+
console.log('[HUD-BLANK-SCREEN-DEBUG] Added red background and border to container for visibility test');
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
/**
|
|
1464
|
+
* Manual rendering triggers to force Cytoscape to render
|
|
1465
|
+
*/
|
|
1466
|
+
debugManualRenderingTriggers() {
|
|
1467
|
+
if (!this.cy) {
|
|
1468
|
+
console.log('[HUD-BLANK-SCREEN-DEBUG] 5. No Cytoscape instance for manual rendering');
|
|
1469
|
+
return;
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
console.log('[HUD-BLANK-SCREEN-DEBUG] 5. Triggering manual rendering operations...');
|
|
1473
|
+
|
|
1474
|
+
try {
|
|
1475
|
+
// Force resize
|
|
1476
|
+
console.log('[HUD-BLANK-SCREEN-DEBUG] - Forcing resize...');
|
|
1477
|
+
this.cy.resize();
|
|
1478
|
+
|
|
1479
|
+
// Force redraw
|
|
1480
|
+
console.log('[HUD-BLANK-SCREEN-DEBUG] - Forcing redraw...');
|
|
1481
|
+
this.cy.forceRender();
|
|
1482
|
+
|
|
1483
|
+
// Force layout
|
|
1484
|
+
if (this.cy.nodes().length > 0) {
|
|
1485
|
+
console.log('[HUD-BLANK-SCREEN-DEBUG] - Running layout...');
|
|
1486
|
+
this.cy.layout(this.layoutConfig).run();
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
// Force viewport update
|
|
1490
|
+
console.log('[HUD-BLANK-SCREEN-DEBUG] - Updating viewport...');
|
|
1491
|
+
this.cy.viewport({
|
|
1492
|
+
zoom: this.cy.zoom(),
|
|
1493
|
+
pan: this.cy.pan()
|
|
1494
|
+
});
|
|
1495
|
+
|
|
1496
|
+
console.log('[HUD-BLANK-SCREEN-DEBUG] Manual rendering triggers completed');
|
|
1497
|
+
} catch (error) {
|
|
1498
|
+
console.error('[HUD-BLANK-SCREEN-DEBUG] Error during manual rendering:', error);
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
/**
|
|
1503
|
+
* Add test nodes to verify Cytoscape is working
|
|
1504
|
+
*/
|
|
1505
|
+
debugAddTestNodes() {
|
|
1506
|
+
if (!this.cy) return;
|
|
1507
|
+
|
|
1508
|
+
console.log('[HUD-BLANK-SCREEN-DEBUG] Adding test nodes...');
|
|
1509
|
+
|
|
1510
|
+
try {
|
|
1511
|
+
// Clear existing elements
|
|
1512
|
+
this.cy.elements().remove();
|
|
1513
|
+
|
|
1514
|
+
// Add test nodes
|
|
1515
|
+
const testNodes = [
|
|
1516
|
+
{
|
|
1517
|
+
group: 'nodes',
|
|
1518
|
+
data: {
|
|
1519
|
+
id: 'test-node-1',
|
|
1520
|
+
label: '🤖 Test Node 1',
|
|
1521
|
+
color: '#48bb78',
|
|
1522
|
+
borderColor: '#38a169',
|
|
1523
|
+
shape: 'rectangle',
|
|
1524
|
+
width: 120,
|
|
1525
|
+
height: 40
|
|
1526
|
+
},
|
|
1527
|
+
classes: 'pm-node'
|
|
1528
|
+
},
|
|
1529
|
+
{
|
|
1530
|
+
group: 'nodes',
|
|
1531
|
+
data: {
|
|
1532
|
+
id: 'test-node-2',
|
|
1533
|
+
label: '🔧 Test Node 2',
|
|
1534
|
+
color: '#4299e1',
|
|
1535
|
+
borderColor: '#3182ce',
|
|
1536
|
+
shape: 'diamond',
|
|
1537
|
+
width: 80,
|
|
1538
|
+
height: 50
|
|
1539
|
+
},
|
|
1540
|
+
classes: 'tool-node'
|
|
1541
|
+
},
|
|
1542
|
+
{
|
|
1543
|
+
group: 'nodes',
|
|
1544
|
+
data: {
|
|
1545
|
+
id: 'test-node-3',
|
|
1546
|
+
label: '📝 Test Node 3',
|
|
1547
|
+
color: '#e53e3e',
|
|
1548
|
+
borderColor: '#c53030',
|
|
1549
|
+
shape: 'triangle',
|
|
1550
|
+
width: 70,
|
|
1551
|
+
height: 40
|
|
1552
|
+
},
|
|
1553
|
+
classes: 'todo-node'
|
|
1554
|
+
}
|
|
1555
|
+
];
|
|
1556
|
+
|
|
1557
|
+
// Add test edges
|
|
1558
|
+
const testEdges = [
|
|
1559
|
+
{
|
|
1560
|
+
group: 'edges',
|
|
1561
|
+
data: {
|
|
1562
|
+
id: 'test-edge-1',
|
|
1563
|
+
source: 'test-node-1',
|
|
1564
|
+
target: 'test-node-2'
|
|
1565
|
+
}
|
|
1566
|
+
},
|
|
1567
|
+
{
|
|
1568
|
+
group: 'edges',
|
|
1569
|
+
data: {
|
|
1570
|
+
id: 'test-edge-2',
|
|
1571
|
+
source: 'test-node-2',
|
|
1572
|
+
target: 'test-node-3'
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
];
|
|
1576
|
+
|
|
1577
|
+
// Add elements to Cytoscape
|
|
1578
|
+
this.cy.add(testNodes);
|
|
1579
|
+
this.cy.add(testEdges);
|
|
1580
|
+
|
|
1581
|
+
console.log('[HUD-BLANK-SCREEN-DEBUG] Added 3 test nodes and 2 test edges');
|
|
1582
|
+
|
|
1583
|
+
// Update our internal nodes map
|
|
1584
|
+
testNodes.forEach(nodeElement => {
|
|
1585
|
+
this.nodes.set(nodeElement.data.id, nodeElement.data);
|
|
1586
|
+
});
|
|
1587
|
+
|
|
1588
|
+
// Run layout
|
|
1589
|
+
this.runLayout();
|
|
1590
|
+
|
|
1591
|
+
} catch (error) {
|
|
1592
|
+
console.error('[HUD-BLANK-SCREEN-DEBUG] Error adding test nodes:', error);
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
/**
|
|
1597
|
+
* Force zoom fit after layout with multiple attempts
|
|
1598
|
+
*/
|
|
1599
|
+
debugForceZoomFit() {
|
|
1600
|
+
if (!this.cy) return;
|
|
1601
|
+
|
|
1602
|
+
console.log('[HUD-BLANK-SCREEN-DEBUG] 7. Forcing zoom fit...');
|
|
1603
|
+
|
|
1604
|
+
const attemptZoomFit = (attemptNumber) => {
|
|
1605
|
+
try {
|
|
1606
|
+
console.log(`[HUD-BLANK-SCREEN-DEBUG] Zoom fit attempt ${attemptNumber}...`);
|
|
1607
|
+
|
|
1608
|
+
// Get current state before fit
|
|
1609
|
+
const beforeZoom = this.cy.zoom();
|
|
1610
|
+
const beforePan = this.cy.pan();
|
|
1611
|
+
const elements = this.cy.elements();
|
|
1612
|
+
|
|
1613
|
+
console.log('[HUD-BLANK-SCREEN-DEBUG] Before fit:', {
|
|
1614
|
+
zoom: beforeZoom,
|
|
1615
|
+
pan: beforePan,
|
|
1616
|
+
elementCount: elements.length
|
|
1617
|
+
});
|
|
1618
|
+
|
|
1619
|
+
if (elements.length > 0) {
|
|
1620
|
+
// Try fit with specific options
|
|
1621
|
+
this.cy.fit(elements, 50); // 50px padding
|
|
1622
|
+
|
|
1623
|
+
// Get state after fit
|
|
1624
|
+
const afterZoom = this.cy.zoom();
|
|
1625
|
+
const afterPan = this.cy.pan();
|
|
1626
|
+
|
|
1627
|
+
console.log('[HUD-BLANK-SCREEN-DEBUG] After fit:', {
|
|
1628
|
+
zoom: afterZoom,
|
|
1629
|
+
pan: afterPan,
|
|
1630
|
+
changed: beforeZoom !== afterZoom || beforePan.x !== afterPan.x || beforePan.y !== afterPan.y
|
|
1631
|
+
});
|
|
1632
|
+
|
|
1633
|
+
// Force center
|
|
1634
|
+
this.cy.center(elements);
|
|
1635
|
+
|
|
1636
|
+
} else {
|
|
1637
|
+
console.log('[HUD-BLANK-SCREEN-DEBUG] No elements to fit');
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1640
|
+
} catch (error) {
|
|
1641
|
+
console.error(`[HUD-BLANK-SCREEN-DEBUG] Zoom fit attempt ${attemptNumber} failed:`, error);
|
|
1642
|
+
}
|
|
1643
|
+
};
|
|
1644
|
+
|
|
1645
|
+
// Multiple attempts with delays
|
|
1646
|
+
attemptZoomFit(1);
|
|
1647
|
+
setTimeout(() => attemptZoomFit(2), 100);
|
|
1648
|
+
setTimeout(() => attemptZoomFit(3), 500);
|
|
1649
|
+
setTimeout(() => attemptZoomFit(4), 1000);
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1652
|
+
/**
|
|
1653
|
+
* Quick test to draw a simple shape to verify Cytoscape canvas is working
|
|
1654
|
+
*/
|
|
1655
|
+
debugDrawSimpleShape() {
|
|
1656
|
+
if (!this.cy) {
|
|
1657
|
+
console.log('[HUD-CANVAS-TEST] No Cytoscape instance');
|
|
1658
|
+
return false;
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1661
|
+
console.log('[HUD-CANVAS-TEST] Testing Cytoscape canvas rendering...');
|
|
1662
|
+
|
|
1663
|
+
try {
|
|
1664
|
+
// Clear everything
|
|
1665
|
+
this.cy.elements().remove();
|
|
1666
|
+
|
|
1667
|
+
// Add a single, simple node at center
|
|
1668
|
+
this.cy.add({
|
|
1669
|
+
group: 'nodes',
|
|
1670
|
+
data: {
|
|
1671
|
+
id: 'canvas-test',
|
|
1672
|
+
label: '✅ CANVAS TEST',
|
|
1673
|
+
color: '#ff0000',
|
|
1674
|
+
borderColor: '#000000',
|
|
1675
|
+
width: 200,
|
|
1676
|
+
height: 100,
|
|
1677
|
+
shape: 'rectangle'
|
|
1678
|
+
},
|
|
1679
|
+
position: { x: 200, y: 200 } // Fixed position
|
|
1680
|
+
});
|
|
1681
|
+
|
|
1682
|
+
// Force immediate render
|
|
1683
|
+
this.cy.forceRender();
|
|
1684
|
+
|
|
1685
|
+
// Zoom to fit this single node
|
|
1686
|
+
this.cy.fit(this.cy.$('#canvas-test'), 50);
|
|
1687
|
+
|
|
1688
|
+
console.log('[HUD-CANVAS-TEST] Canvas test node added and positioned');
|
|
1689
|
+
console.log('[HUD-CANVAS-TEST] If you see a red rectangle with "CANVAS TEST", rendering works!');
|
|
1690
|
+
|
|
1691
|
+
return true;
|
|
1692
|
+
|
|
1693
|
+
} catch (error) {
|
|
1694
|
+
console.error('[HUD-CANVAS-TEST] Canvas test failed:', error);
|
|
1695
|
+
return false;
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1698
|
+
|
|
1699
|
+
/**
|
|
1700
|
+
* Utility function to darken a color
|
|
1701
|
+
* @param {string} color - Hex color
|
|
1702
|
+
* @param {number} percent - Percentage to darken
|
|
1703
|
+
* @returns {string} - Darkened hex color
|
|
1704
|
+
*/
|
|
1705
|
+
darkenColor(color, percent) {
|
|
1706
|
+
const num = parseInt(color.replace("#", ""), 16);
|
|
1707
|
+
const amt = Math.round(2.55 * percent);
|
|
1708
|
+
const R = (num >> 16) - amt;
|
|
1709
|
+
const G = (num >> 8 & 0x00FF) - amt;
|
|
1710
|
+
const B = (num & 0x0000FF) - amt;
|
|
1711
|
+
return "#" + (0x1000000 + (R < 255 ? R < 1 ? 0 : R : 255) * 0x10000 +
|
|
1712
|
+
(G < 255 ? G < 1 ? 0 : G : 255) * 0x100 +
|
|
1713
|
+
(B < 255 ? B < 1 ? 0 : B : 255)).toString(16).slice(1);
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1717
|
+
// Export for use in dashboard
|
|
1718
|
+
window.HUDVisualizer = HUDVisualizer;
|