claude-mpm 4.1.7__py3-none-any.whl → 4.1.10__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.
Files changed (109) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/INSTRUCTIONS.md +26 -1
  3. claude_mpm/agents/OUTPUT_STYLE.md +73 -0
  4. claude_mpm/agents/agents_metadata.py +57 -0
  5. claude_mpm/agents/templates/.claude-mpm/memories/README.md +17 -0
  6. claude_mpm/agents/templates/.claude-mpm/memories/engineer_memories.md +3 -0
  7. claude_mpm/agents/templates/agent-manager.json +263 -17
  8. claude_mpm/agents/templates/agent-manager.md +248 -10
  9. claude_mpm/agents/templates/agentic_coder_optimizer.json +222 -0
  10. claude_mpm/agents/templates/code_analyzer.json +18 -8
  11. claude_mpm/agents/templates/engineer.json +1 -1
  12. claude_mpm/agents/templates/logs/prompts/agent_engineer_20250826_014258_728.md +39 -0
  13. claude_mpm/agents/templates/qa.json +1 -1
  14. claude_mpm/agents/templates/research.json +1 -1
  15. claude_mpm/cli/__init__.py +4 -0
  16. claude_mpm/cli/commands/__init__.py +6 -0
  17. claude_mpm/cli/commands/analyze.py +547 -0
  18. claude_mpm/cli/commands/analyze_code.py +524 -0
  19. claude_mpm/cli/commands/configure.py +223 -25
  20. claude_mpm/cli/commands/configure_tui.py +65 -61
  21. claude_mpm/cli/commands/debug.py +1387 -0
  22. claude_mpm/cli/parsers/analyze_code_parser.py +170 -0
  23. claude_mpm/cli/parsers/analyze_parser.py +135 -0
  24. claude_mpm/cli/parsers/base_parser.py +29 -0
  25. claude_mpm/cli/parsers/configure_parser.py +23 -0
  26. claude_mpm/cli/parsers/debug_parser.py +319 -0
  27. claude_mpm/config/socketio_config.py +21 -21
  28. claude_mpm/constants.py +3 -1
  29. claude_mpm/core/framework_loader.py +148 -6
  30. claude_mpm/core/log_manager.py +16 -13
  31. claude_mpm/core/logger.py +1 -1
  32. claude_mpm/core/unified_agent_registry.py +1 -1
  33. claude_mpm/dashboard/.claude-mpm/socketio-instances.json +1 -0
  34. claude_mpm/dashboard/analysis_runner.py +428 -0
  35. claude_mpm/dashboard/static/built/components/activity-tree.js +2 -0
  36. claude_mpm/dashboard/static/built/components/agent-inference.js +1 -1
  37. claude_mpm/dashboard/static/built/components/event-viewer.js +1 -1
  38. claude_mpm/dashboard/static/built/components/file-tool-tracker.js +1 -1
  39. claude_mpm/dashboard/static/built/components/module-viewer.js +1 -1
  40. claude_mpm/dashboard/static/built/components/session-manager.js +1 -1
  41. claude_mpm/dashboard/static/built/components/working-directory.js +1 -1
  42. claude_mpm/dashboard/static/built/dashboard.js +1 -1
  43. claude_mpm/dashboard/static/built/socket-client.js +1 -1
  44. claude_mpm/dashboard/static/css/activity.css +549 -0
  45. claude_mpm/dashboard/static/css/code-tree.css +846 -0
  46. claude_mpm/dashboard/static/css/dashboard.css +245 -0
  47. claude_mpm/dashboard/static/dist/components/activity-tree.js +2 -0
  48. claude_mpm/dashboard/static/dist/components/code-tree.js +2 -0
  49. claude_mpm/dashboard/static/dist/components/code-viewer.js +2 -0
  50. claude_mpm/dashboard/static/dist/components/event-viewer.js +1 -1
  51. claude_mpm/dashboard/static/dist/components/session-manager.js +1 -1
  52. claude_mpm/dashboard/static/dist/components/working-directory.js +1 -1
  53. claude_mpm/dashboard/static/dist/dashboard.js +1 -1
  54. claude_mpm/dashboard/static/dist/socket-client.js +1 -1
  55. claude_mpm/dashboard/static/js/components/activity-tree.js +1139 -0
  56. claude_mpm/dashboard/static/js/components/code-tree.js +1357 -0
  57. claude_mpm/dashboard/static/js/components/code-viewer.js +480 -0
  58. claude_mpm/dashboard/static/js/components/event-viewer.js +11 -0
  59. claude_mpm/dashboard/static/js/components/session-manager.js +40 -4
  60. claude_mpm/dashboard/static/js/components/socket-manager.js +12 -0
  61. claude_mpm/dashboard/static/js/components/ui-state-manager.js +4 -0
  62. claude_mpm/dashboard/static/js/components/working-directory.js +17 -1
  63. claude_mpm/dashboard/static/js/dashboard.js +39 -0
  64. claude_mpm/dashboard/static/js/socket-client.js +414 -20
  65. claude_mpm/dashboard/templates/index.html +184 -4
  66. claude_mpm/hooks/claude_hooks/hook_handler.py +182 -5
  67. claude_mpm/hooks/claude_hooks/installer.py +728 -0
  68. claude_mpm/scripts/claude-hook-handler.sh +161 -0
  69. claude_mpm/scripts/socketio_daemon.py +121 -8
  70. claude_mpm/services/agents/deployment/agent_config_provider.py +127 -27
  71. claude_mpm/services/agents/deployment/agent_lifecycle_manager_refactored.py +2 -2
  72. claude_mpm/services/agents/deployment/agent_record_service.py +1 -2
  73. claude_mpm/services/agents/memory/memory_format_service.py +1 -5
  74. claude_mpm/services/cli/agent_cleanup_service.py +1 -2
  75. claude_mpm/services/cli/agent_dependency_service.py +1 -1
  76. claude_mpm/services/cli/agent_validation_service.py +3 -4
  77. claude_mpm/services/cli/dashboard_launcher.py +2 -3
  78. claude_mpm/services/cli/startup_checker.py +0 -10
  79. claude_mpm/services/core/cache_manager.py +1 -2
  80. claude_mpm/services/core/path_resolver.py +1 -4
  81. claude_mpm/services/core/service_container.py +2 -2
  82. claude_mpm/services/diagnostics/checks/instructions_check.py +2 -5
  83. claude_mpm/services/event_bus/direct_relay.py +98 -20
  84. claude_mpm/services/infrastructure/monitoring/__init__.py +11 -11
  85. claude_mpm/services/infrastructure/monitoring.py +11 -11
  86. claude_mpm/services/project/architecture_analyzer.py +1 -1
  87. claude_mpm/services/project/dependency_analyzer.py +4 -4
  88. claude_mpm/services/project/language_analyzer.py +3 -3
  89. claude_mpm/services/project/metrics_collector.py +3 -6
  90. claude_mpm/services/socketio/handlers/__init__.py +2 -0
  91. claude_mpm/services/socketio/handlers/code_analysis.py +170 -0
  92. claude_mpm/services/socketio/handlers/registry.py +2 -0
  93. claude_mpm/services/socketio/server/connection_manager.py +95 -65
  94. claude_mpm/services/socketio/server/core.py +125 -17
  95. claude_mpm/services/socketio/server/main.py +44 -5
  96. claude_mpm/services/visualization/__init__.py +19 -0
  97. claude_mpm/services/visualization/mermaid_generator.py +938 -0
  98. claude_mpm/tools/__main__.py +208 -0
  99. claude_mpm/tools/code_tree_analyzer.py +778 -0
  100. claude_mpm/tools/code_tree_builder.py +632 -0
  101. claude_mpm/tools/code_tree_events.py +318 -0
  102. claude_mpm/tools/socketio_debug.py +671 -0
  103. {claude_mpm-4.1.7.dist-info → claude_mpm-4.1.10.dist-info}/METADATA +1 -1
  104. {claude_mpm-4.1.7.dist-info → claude_mpm-4.1.10.dist-info}/RECORD +108 -77
  105. claude_mpm/agents/schema/agent_schema.json +0 -314
  106. {claude_mpm-4.1.7.dist-info → claude_mpm-4.1.10.dist-info}/WHEEL +0 -0
  107. {claude_mpm-4.1.7.dist-info → claude_mpm-4.1.10.dist-info}/entry_points.txt +0 -0
  108. {claude_mpm-4.1.7.dist-info → claude_mpm-4.1.10.dist-info}/licenses/LICENSE +0 -0
  109. {claude_mpm-4.1.7.dist-info → claude_mpm-4.1.10.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,1357 @@
1
+ /**
2
+ * Code Tree Component
3
+ *
4
+ * D3.js-based tree visualization for displaying AST-based code structure.
5
+ * Shows modules, classes, functions, and methods with complexity-based coloring.
6
+ * Provides real-time updates during code analysis.
7
+ */
8
+
9
+ class CodeTree {
10
+ constructor() {
11
+ this.container = null;
12
+ this.svg = null;
13
+ this.treeData = null;
14
+ this.root = null;
15
+ this.treeLayout = null;
16
+ this.treeGroup = null;
17
+ this.nodes = new Map();
18
+ this.stats = {
19
+ files: 0,
20
+ classes: 0,
21
+ functions: 0,
22
+ methods: 0,
23
+ lines: 0
24
+ };
25
+ this.margin = {top: 20, right: 150, bottom: 20, left: 150};
26
+ this.width = 960 - this.margin.left - this.margin.right;
27
+ this.height = 600 - this.margin.top - this.margin.bottom;
28
+ this.nodeId = 0;
29
+ this.duration = 750;
30
+ this.languageFilter = 'all';
31
+ this.searchTerm = '';
32
+ this.tooltip = null;
33
+ this.initialized = false;
34
+ this.analyzing = false;
35
+ this.selectedNode = null;
36
+ this.socket = null;
37
+ }
38
+
39
+ /**
40
+ * Initialize the code tree visualization
41
+ */
42
+ initialize() {
43
+ console.log('CodeTree.initialize() called');
44
+
45
+ if (this.initialized) {
46
+ console.log('Code tree already initialized');
47
+ return;
48
+ }
49
+
50
+ this.container = document.getElementById('code-tree-container');
51
+ if (!this.container) {
52
+ console.error('Code tree container not found');
53
+ return;
54
+ }
55
+
56
+ console.log('Code tree container found:', this.container);
57
+
58
+ // Check if tab is visible
59
+ const tabPanel = document.getElementById('code-tab');
60
+ if (!tabPanel) {
61
+ console.error('Code tab panel not found');
62
+ return;
63
+ }
64
+
65
+ console.log('Code tab panel found, active:', tabPanel.classList.contains('active'));
66
+
67
+ // Initialize always
68
+ this.setupControls();
69
+ this.initializeTreeData();
70
+ this.subscribeToEvents();
71
+
72
+ // Only create visualization if tab is visible
73
+ if (tabPanel.classList.contains('active')) {
74
+ console.log('Tab is active, creating visualization');
75
+ this.createVisualization();
76
+ if (this.root && this.svg) {
77
+ this.update(this.root);
78
+ }
79
+ } else {
80
+ console.log('Tab is not active, deferring visualization');
81
+ }
82
+
83
+ this.initialized = true;
84
+ console.log('Code tree initialization complete');
85
+ }
86
+
87
+ /**
88
+ * Render visualization when tab becomes visible
89
+ */
90
+ renderWhenVisible() {
91
+ console.log('CodeTree.renderWhenVisible() called');
92
+ console.log('Current state - initialized:', this.initialized, 'svg:', !!this.svg);
93
+
94
+ if (!this.initialized) {
95
+ console.log('Not initialized, calling initialize()');
96
+ this.initialize();
97
+ return;
98
+ }
99
+
100
+ if (!this.svg) {
101
+ console.log('No SVG found, creating visualization');
102
+ this.createVisualization();
103
+ if (this.svg && this.treeGroup) {
104
+ console.log('SVG created, updating tree');
105
+ this.update(this.root);
106
+ } else {
107
+ console.log('Failed to create SVG or treeGroup');
108
+ }
109
+ } else {
110
+ console.log('SVG exists, forcing update');
111
+ // Force update with current data
112
+ if (this.root && this.svg) {
113
+ this.update(this.root);
114
+ }
115
+ }
116
+ }
117
+
118
+ /**
119
+ * Setup control handlers
120
+ */
121
+ setupControls() {
122
+ // Analyze button
123
+ const analyzeBtn = document.getElementById('analyze-code');
124
+ if (analyzeBtn) {
125
+ analyzeBtn.addEventListener('click', () => this.startAnalysis());
126
+ }
127
+
128
+ // Cancel button
129
+ const cancelBtn = document.getElementById('cancel-analysis');
130
+ if (cancelBtn) {
131
+ cancelBtn.addEventListener('click', () => this.cancelAnalysis());
132
+ }
133
+
134
+ // Expand all button
135
+ const expandAllBtn = document.getElementById('code-expand-all');
136
+ if (expandAllBtn) {
137
+ expandAllBtn.addEventListener('click', () => this.expandAll());
138
+ }
139
+
140
+ // Collapse all button
141
+ const collapseAllBtn = document.getElementById('code-collapse-all');
142
+ if (collapseAllBtn) {
143
+ collapseAllBtn.addEventListener('click', () => this.collapseAll());
144
+ }
145
+
146
+ // Reset zoom button
147
+ const resetZoomBtn = document.getElementById('code-reset-zoom');
148
+ if (resetZoomBtn) {
149
+ resetZoomBtn.addEventListener('click', () => this.resetZoom());
150
+ }
151
+
152
+ // Toggle legend button
153
+ const toggleLegendBtn = document.getElementById('code-toggle-legend');
154
+ if (toggleLegendBtn) {
155
+ toggleLegendBtn.addEventListener('click', () => this.toggleLegend());
156
+ }
157
+
158
+ // Language filter
159
+ const languageFilter = document.getElementById('language-filter');
160
+ if (languageFilter) {
161
+ languageFilter.addEventListener('change', (e) => {
162
+ this.languageFilter = e.target.value;
163
+ this.filterByLanguage();
164
+ });
165
+ }
166
+
167
+ // Search input
168
+ const searchInput = document.getElementById('code-search');
169
+ if (searchInput) {
170
+ searchInput.addEventListener('input', (e) => {
171
+ this.searchTerm = e.target.value.toLowerCase();
172
+ this.highlightSearchResults();
173
+ });
174
+ }
175
+ }
176
+
177
+ /**
178
+ * Create D3 visualization
179
+ */
180
+ createVisualization() {
181
+ console.log('Creating code tree visualization');
182
+
183
+ // Check if D3 is available
184
+ if (typeof d3 === 'undefined') {
185
+ console.error('D3.js is not loaded! Cannot create code tree visualization.');
186
+ return;
187
+ }
188
+
189
+ console.log('D3 is available:', typeof d3);
190
+
191
+ const container = document.getElementById('code-tree');
192
+ if (!container) {
193
+ console.error('Code tree div not found');
194
+ return;
195
+ }
196
+
197
+ console.log('Code tree div found:', container);
198
+
199
+ // Create root if it doesn't exist
200
+ if (!this.root && this.treeData) {
201
+ console.log('Creating D3 hierarchy from tree data');
202
+ this.root = d3.hierarchy(this.treeData);
203
+ this.root.x0 = this.height / 2;
204
+ this.root.y0 = 0;
205
+ }
206
+
207
+ // Clear any existing SVG
208
+ d3.select(container).selectAll("*").remove();
209
+
210
+ // Get container dimensions
211
+ const rect = container.getBoundingClientRect();
212
+ this.width = rect.width - this.margin.left - this.margin.right;
213
+ this.height = Math.max(500, rect.height) - this.margin.top - this.margin.bottom;
214
+
215
+ // Create SVG
216
+ this.svg = d3.select(container)
217
+ .append('svg')
218
+ .attr('width', '100%')
219
+ .attr('height', '100%')
220
+ .attr('viewBox', `0 0 ${rect.width} ${rect.height}`)
221
+ .call(d3.zoom()
222
+ .scaleExtent([0.1, 3])
223
+ .on('zoom', (event) => {
224
+ this.treeGroup.attr('transform', event.transform);
225
+ }));
226
+
227
+ this.treeGroup = this.svg.append('g')
228
+ .attr('transform', `translate(${this.margin.left},${this.margin.top})`);
229
+
230
+ // Create tree layout
231
+ this.treeLayout = d3.tree()
232
+ .size([this.height, this.width]);
233
+
234
+ // Create tooltip
235
+ this.tooltip = d3.select('body').append('div')
236
+ .attr('class', 'code-tooltip')
237
+ .style('opacity', 0);
238
+ }
239
+
240
+ /**
241
+ * Initialize tree data structure
242
+ */
243
+ initializeTreeData() {
244
+ console.log('Initializing tree data...');
245
+
246
+ this.treeData = {
247
+ name: 'Project Root',
248
+ type: 'module',
249
+ path: '/',
250
+ complexity: 0,
251
+ children: []
252
+ };
253
+
254
+ // Only create D3 hierarchy if D3 is available
255
+ if (typeof d3 !== 'undefined') {
256
+ this.root = d3.hierarchy(this.treeData);
257
+ this.root.x0 = this.height / 2;
258
+ this.root.y0 = 0;
259
+ console.log('Tree root created:', this.root);
260
+ } else {
261
+ console.warn('D3 not available yet, deferring hierarchy creation');
262
+ this.root = null;
263
+ }
264
+ }
265
+
266
+ /**
267
+ * Subscribe to Socket.IO events
268
+ */
269
+ subscribeToEvents() {
270
+ // Try multiple socket sources
271
+ this.getSocket();
272
+
273
+ if (this.socket) {
274
+ console.log('CodeTree: Socket available, subscribing to events');
275
+
276
+ // Code analysis events - match the server-side event names
277
+ this.socket.on('code:analysis:start', (data) => this.handleAnalysisStart(data));
278
+ this.socket.on('code:analysis:accepted', (data) => this.handleAnalysisAccepted(data));
279
+ this.socket.on('code:analysis:queued', (data) => this.handleAnalysisQueued(data));
280
+ this.socket.on('code:analysis:cancelled', (data) => this.handleAnalysisCancelled(data));
281
+ this.socket.on('code:analysis:progress', (data) => this.handleProgress(data));
282
+ this.socket.on('code:analysis:complete', (data) => this.handleAnalysisComplete(data));
283
+ this.socket.on('code:analysis:error', (data) => this.handleAnalysisError(data));
284
+
285
+ // File and node events
286
+ this.socket.on('code:file:start', (data) => this.handleFileStart(data));
287
+ this.socket.on('code:file:complete', (data) => this.handleFileComplete(data));
288
+ this.socket.on('code:node:found', (data) => this.handleNodeFound(data));
289
+ } else {
290
+ console.warn('CodeTree: Socket not available yet, will retry on analysis');
291
+ }
292
+ }
293
+
294
+ /**
295
+ * Get socket connection from available sources
296
+ */
297
+ getSocket() {
298
+ // Try multiple sources for the socket
299
+ if (!this.socket) {
300
+ // Try window.socket first (most common)
301
+ if (window.socket) {
302
+ this.socket = window.socket;
303
+ console.log('CodeTree: Using window.socket');
304
+ }
305
+ // Try from dashboard's socketClient
306
+ else if (window.dashboard?.socketClient?.socket) {
307
+ this.socket = window.dashboard.socketClient.socket;
308
+ console.log('CodeTree: Using dashboard.socketClient.socket');
309
+ }
310
+ // Try from socketClient directly
311
+ else if (window.socketClient?.socket) {
312
+ this.socket = window.socketClient.socket;
313
+ console.log('CodeTree: Using socketClient.socket');
314
+ }
315
+ }
316
+ return this.socket;
317
+ }
318
+
319
+ /**
320
+ * Start code analysis
321
+ */
322
+ startAnalysis() {
323
+ if (this.analyzing) {
324
+ console.log('Analysis already in progress');
325
+ return;
326
+ }
327
+
328
+ console.log('Starting code analysis...');
329
+
330
+ // Ensure socket is available
331
+ this.getSocket();
332
+ if (!this.socket) {
333
+ console.error('Socket not available');
334
+ this.showNotification('Cannot connect to server. Please check connection.', 'error');
335
+ return;
336
+ }
337
+
338
+ // Re-subscribe to events if needed (in case socket reconnected)
339
+ if (!this.socket._callbacks || !this.socket._callbacks['code:analysis:start']) {
340
+ console.log('Re-subscribing to code analysis events');
341
+ this.subscribeToEvents();
342
+ }
343
+
344
+ this.analyzing = true;
345
+
346
+ // Update button state - but keep it responsive
347
+ const analyzeBtn = document.getElementById('analyze-code');
348
+ const cancelBtn = document.getElementById('cancel-analysis');
349
+ if (analyzeBtn) {
350
+ analyzeBtn.textContent = 'Analyzing...';
351
+ analyzeBtn.classList.add('analyzing');
352
+ }
353
+ if (cancelBtn) {
354
+ cancelBtn.style.display = 'inline-block';
355
+ }
356
+
357
+ // Show analysis status in footer
358
+ this.showFooterAnalysisStatus('Starting analysis...');
359
+
360
+ // Reset tree but keep visualization
361
+ this.initializeTreeData();
362
+ this.nodes.clear();
363
+ this.stats = {
364
+ files: 0,
365
+ classes: 0,
366
+ functions: 0,
367
+ methods: 0,
368
+ lines: 0
369
+ };
370
+ this.updateStats();
371
+
372
+ // Create visualization if not already created
373
+ if (!this.svg) {
374
+ this.createVisualization();
375
+ }
376
+
377
+ // Initial tree update with empty root
378
+ if (this.root && this.svg) {
379
+ this.update(this.root);
380
+ }
381
+
382
+ // Get analysis parameters from UI
383
+ const pathInput = document.getElementById('analysis-path');
384
+ let path = pathInput?.value?.trim();
385
+
386
+ // Use working directory if no path specified
387
+ if (!path || path === '') {
388
+ // Try to get working directory from various sources
389
+ path = window.workingDirectory ||
390
+ window.dashboard?.workingDirectory ||
391
+ window.socketClient?.sessions?.values()?.next()?.value?.working_directory ||
392
+ '.';
393
+
394
+ // Update the input field with the detected path
395
+ if (pathInput && path !== '.') {
396
+ pathInput.value = path;
397
+ }
398
+ }
399
+
400
+ const languages = this.getSelectedLanguages();
401
+ const maxDepth = parseInt(document.getElementById('max-depth')?.value) || null;
402
+ const ignorePatterns = this.getIgnorePatterns();
403
+
404
+ // Generate request ID
405
+ const requestId = this.generateRequestId();
406
+ this.currentRequestId = requestId;
407
+
408
+ // Build request payload
409
+ const requestPayload = {
410
+ request_id: requestId,
411
+ path: path,
412
+ languages: languages.length > 0 ? languages : null,
413
+ max_depth: maxDepth,
414
+ ignore_patterns: ignorePatterns.length > 0 ? ignorePatterns : null
415
+ };
416
+
417
+ console.log('Emitting code:analyze:request with payload:', requestPayload);
418
+
419
+ // Request analysis from server
420
+ this.socket.emit('code:analyze:request', requestPayload);
421
+
422
+ // Set a longer safety timeout (60 seconds) for truly stuck requests
423
+ // This is just a safety net - normal cancellation flow should work
424
+ this.requestTimeout = setTimeout(() => {
425
+ if (this.analyzing && this.currentRequestId === requestId) {
426
+ console.warn('Analysis appears stuck after 60 seconds');
427
+ this.showNotification('Analysis is taking longer than expected. You can cancel if needed.', 'warning');
428
+ // Don't auto-reset, let user decide to cancel
429
+ }
430
+ }, 60000); // 60 second safety timeout
431
+ }
432
+
433
+ /**
434
+ * Cancel current analysis
435
+ */
436
+ cancelAnalysis() {
437
+ if (!this.analyzing) {
438
+ return;
439
+ }
440
+
441
+ console.log('Cancelling analysis...');
442
+
443
+ if (this.socket && this.currentRequestId) {
444
+ this.socket.emit('code:analyze:cancel', {
445
+ request_id: this.currentRequestId
446
+ });
447
+ }
448
+
449
+ this.resetAnalysisState();
450
+ }
451
+
452
+ /**
453
+ * Reset analysis state
454
+ */
455
+ resetAnalysisState() {
456
+ this.analyzing = false;
457
+ this.currentRequestId = null;
458
+
459
+ // Clear any timeouts
460
+ if (this.requestTimeout) {
461
+ clearTimeout(this.requestTimeout);
462
+ this.requestTimeout = null;
463
+ }
464
+
465
+ // Update button state
466
+ const analyzeBtn = document.getElementById('analyze-code');
467
+ const cancelBtn = document.getElementById('cancel-analysis');
468
+ if (analyzeBtn) {
469
+ analyzeBtn.disabled = false;
470
+ analyzeBtn.textContent = 'Analyze';
471
+ analyzeBtn.classList.remove('analyzing');
472
+ }
473
+ if (cancelBtn) {
474
+ cancelBtn.style.display = 'none';
475
+ }
476
+
477
+ // Hide analysis status in footer
478
+ this.hideFooterAnalysisStatus();
479
+ }
480
+
481
+ /**
482
+ * Generate unique request ID
483
+ */
484
+ generateRequestId() {
485
+ return `analysis-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
486
+ }
487
+
488
+ /**
489
+ * Get selected languages from UI
490
+ */
491
+ getSelectedLanguages() {
492
+ const languages = [];
493
+ const checkboxes = document.querySelectorAll('.language-checkbox:checked');
494
+ checkboxes.forEach(cb => {
495
+ languages.push(cb.value);
496
+ });
497
+ return languages;
498
+ }
499
+
500
+ /**
501
+ * Get ignore patterns from UI
502
+ */
503
+ getIgnorePatterns() {
504
+ const patterns = [];
505
+ const input = document.getElementById('ignore-patterns');
506
+ if (input && input.value) {
507
+ patterns.push(...input.value.split(',').map(p => p.trim()).filter(p => p));
508
+ }
509
+ return patterns;
510
+ }
511
+
512
+ /**
513
+ * Handle analysis start event
514
+ */
515
+ handleAnalysisStart(data) {
516
+ console.log('Code analysis started:', data);
517
+
518
+ // Only handle if this is for our current request
519
+ if (data.request_id && data.request_id !== this.currentRequestId) {
520
+ return;
521
+ }
522
+
523
+ // Clear request timeout since we got a response
524
+ if (this.requestTimeout) {
525
+ clearTimeout(this.requestTimeout);
526
+ this.requestTimeout = null;
527
+ }
528
+
529
+ const message = `Analyzing ${data.total_files || 0} files...`;
530
+ this.updateProgress(0, message);
531
+ this.updateTicker(message, 'progress');
532
+ this.showNotification('Analysis started - building tree in real-time...', 'info');
533
+ }
534
+
535
+ /**
536
+ * Handle file start event
537
+ */
538
+ handleFileStart(data) {
539
+ console.log('Analyzing file:', data.path);
540
+ const message = `Analyzing: ${data.path}`;
541
+ this.updateProgress(data.progress || 0, message);
542
+ this.updateTicker(`📄 ${data.path}`, 'file');
543
+
544
+ // Add file node to tree
545
+ const fileNode = {
546
+ name: data.name || data.path.split('/').pop(),
547
+ type: 'file',
548
+ path: data.path,
549
+ language: data.language,
550
+ children: []
551
+ };
552
+
553
+ this.addNodeToTree(fileNode, data.path);
554
+ this.stats.files++;
555
+ this.updateStats();
556
+
557
+ // Incremental tree update - update visualization as files are discovered
558
+ if (this.svg && this.root) {
559
+ // Throttle updates to avoid performance issues
560
+ if (!this.updateThrottleTimer) {
561
+ this.updateThrottleTimer = setTimeout(() => {
562
+ this.update(this.root);
563
+ this.updateThrottleTimer = null;
564
+ }, 100); // Update every 100ms max
565
+ }
566
+ }
567
+ }
568
+
569
+ /**
570
+ * Handle file complete event
571
+ */
572
+ handleFileComplete(data) {
573
+ console.log('File analysis complete:', data.path);
574
+
575
+ // Update the file node if we have stats
576
+ if (data.stats) {
577
+ const fileNode = this.nodes.get(data.path);
578
+ if (fileNode) {
579
+ fileNode.stats = data.stats;
580
+ if (data.stats.lines) {
581
+ this.stats.lines += data.stats.lines;
582
+ this.updateStats();
583
+ }
584
+ }
585
+ }
586
+ }
587
+
588
+ /**
589
+ * Handle node found event
590
+ */
591
+ handleNodeFound(data) {
592
+ console.log('Node found:', data);
593
+
594
+ // Update ticker with node discovery
595
+ const icons = {
596
+ 'function': '⚡',
597
+ 'class': '🏛️',
598
+ 'method': '🔧',
599
+ 'module': '📦'
600
+ };
601
+ const icon = icons[data.type] || '📌';
602
+ const nodeName = data.name || 'unnamed';
603
+ this.updateTicker(`${icon} ${nodeName}`, 'node');
604
+
605
+ // Create node object
606
+ const node = {
607
+ name: data.name,
608
+ type: data.type, // module, class, function, method
609
+ path: data.path,
610
+ line: data.line,
611
+ complexity: data.complexity || 0,
612
+ docstring: data.docstring,
613
+ params: data.params,
614
+ returns: data.returns,
615
+ children: []
616
+ };
617
+
618
+ // Add to tree
619
+ this.addNodeToTree(node, data.parent_path || data.path);
620
+
621
+ // Update stats
622
+ switch (data.type) {
623
+ case 'class':
624
+ this.stats.classes++;
625
+ break;
626
+ case 'function':
627
+ this.stats.functions++;
628
+ break;
629
+ case 'method':
630
+ this.stats.methods++;
631
+ break;
632
+ }
633
+
634
+ if (data.lines) {
635
+ this.stats.lines += data.lines;
636
+ }
637
+
638
+ this.updateStats();
639
+
640
+ // Incremental tree update - batch updates for performance
641
+ if (this.svg && this.root) {
642
+ // Clear existing throttle timer
643
+ if (this.updateThrottleTimer) {
644
+ clearTimeout(this.updateThrottleTimer);
645
+ }
646
+
647
+ // Set new throttle timer - batch multiple nodes together
648
+ this.updateThrottleTimer = setTimeout(() => {
649
+ this.update(this.root);
650
+ this.updateThrottleTimer = null;
651
+ }, 200); // Update every 200ms max for node additions
652
+ }
653
+ }
654
+
655
+ /**
656
+ * Handle progress update
657
+ */
658
+ handleProgress(data) {
659
+ this.updateProgress(data.percentage, data.message);
660
+ }
661
+
662
+ /**
663
+ * Handle analysis complete
664
+ */
665
+ handleAnalysisComplete(data) {
666
+ console.log('Code analysis complete:', data);
667
+
668
+ // Only handle if this is for our current request
669
+ if (data.request_id && data.request_id !== this.currentRequestId) {
670
+ return;
671
+ }
672
+
673
+ this.resetAnalysisState();
674
+
675
+ // Final tree update
676
+ if (this.svg) {
677
+ this.update(this.root);
678
+ }
679
+
680
+ // Update final stats
681
+ if (data.stats) {
682
+ this.stats = {...this.stats, ...data.stats};
683
+ this.updateStats();
684
+ }
685
+
686
+ // Show completion message
687
+ const completeMessage = `✅ Complete: ${this.stats.files} files, ${this.stats.functions} functions, ${this.stats.classes} classes`;
688
+ this.updateTicker(completeMessage, 'progress');
689
+ this.showNotification('Analysis complete', 'success');
690
+ }
691
+
692
+ /**
693
+ * Handle analysis error
694
+ */
695
+ handleAnalysisError(data) {
696
+ console.error('Code analysis error:', data);
697
+
698
+ // Only handle if this is for our current request
699
+ if (data.request_id && data.request_id !== this.currentRequestId) {
700
+ return;
701
+ }
702
+
703
+ this.resetAnalysisState();
704
+
705
+ // Show error message
706
+ const errorMessage = data.message || 'Unknown error';
707
+ this.updateTicker(`❌ ${errorMessage}`, 'error');
708
+ this.showNotification(`Analysis failed: ${errorMessage}`, 'error');
709
+ }
710
+
711
+ /**
712
+ * Handle analysis accepted event
713
+ */
714
+ handleAnalysisAccepted(data) {
715
+ console.log('Analysis request accepted:', data);
716
+
717
+ if (data.request_id === this.currentRequestId) {
718
+ // Clear timeout since server responded
719
+ if (this.requestTimeout) {
720
+ clearTimeout(this.requestTimeout);
721
+ this.requestTimeout = null;
722
+ }
723
+ this.showNotification('Analysis request accepted by server', 'info');
724
+ }
725
+ }
726
+
727
+ /**
728
+ * Handle analysis queued event
729
+ */
730
+ handleAnalysisQueued(data) {
731
+ console.log('Analysis queued:', data);
732
+
733
+ if (data.request_id === this.currentRequestId) {
734
+ this.showNotification(`Analysis queued (position: ${data.queue_size || 1})`, 'info');
735
+ }
736
+ }
737
+
738
+ /**
739
+ * Handle analysis cancelled event
740
+ */
741
+ handleAnalysisCancelled(data) {
742
+ console.log('Analysis cancelled:', data);
743
+
744
+ if (!data.request_id || data.request_id === this.currentRequestId) {
745
+ this.resetAnalysisState();
746
+ this.showNotification('Analysis cancelled', 'warning');
747
+ }
748
+ }
749
+
750
+ /**
751
+ * Show notification message
752
+ */
753
+ showNotification(message, type = 'info') {
754
+ console.log(`CodeTree notification: ${message} (${type})`);
755
+
756
+ // Try to find existing notification area in the Code tab
757
+ let notification = document.querySelector('#code-tab .notification-area');
758
+
759
+ // If not found, create one in the Code tab
760
+ if (!notification) {
761
+ const codeTab = document.getElementById('code-tab');
762
+ if (!codeTab) {
763
+ console.error('Code tab not found for notification');
764
+ return;
765
+ }
766
+
767
+ notification = document.createElement('div');
768
+ notification.className = 'notification-area';
769
+ notification.style.cssText = `
770
+ position: absolute;
771
+ top: 10px;
772
+ right: 10px;
773
+ max-width: 400px;
774
+ z-index: 1000;
775
+ padding: 12px 16px;
776
+ border-radius: 4px;
777
+ font-size: 14px;
778
+ box-shadow: 0 2px 8px rgba(0,0,0,0.15);
779
+ transition: opacity 0.3s ease;
780
+ `;
781
+ codeTab.insertBefore(notification, codeTab.firstChild);
782
+ }
783
+
784
+ // Set colors based on type
785
+ const colors = {
786
+ info: { bg: '#e3f2fd', text: '#1976d2', border: '#90caf9' },
787
+ success: { bg: '#e8f5e9', text: '#388e3c', border: '#81c784' },
788
+ warning: { bg: '#fff3e0', text: '#f57c00', border: '#ffb74d' },
789
+ error: { bg: '#ffebee', text: '#d32f2f', border: '#ef5350' }
790
+ };
791
+
792
+ const color = colors[type] || colors.info;
793
+ notification.style.backgroundColor = color.bg;
794
+ notification.style.color = color.text;
795
+ notification.style.border = `1px solid ${color.border}`;
796
+
797
+ // Set message
798
+ notification.textContent = message;
799
+ notification.style.display = 'block';
800
+ notification.style.opacity = '1';
801
+
802
+ // Clear existing timeout
803
+ if (this.notificationTimeout) {
804
+ clearTimeout(this.notificationTimeout);
805
+ }
806
+
807
+ // Auto-hide after 5 seconds
808
+ this.notificationTimeout = setTimeout(() => {
809
+ notification.style.opacity = '0';
810
+ setTimeout(() => {
811
+ notification.style.display = 'none';
812
+ }, 300);
813
+ }, 5000);
814
+ }
815
+
816
+ /**
817
+ * Add node to tree structure
818
+ */
819
+ addNodeToTree(node, parentPath) {
820
+ // Find parent in tree
821
+ let parent = this.findNodeByPath(parentPath);
822
+ if (!parent) {
823
+ parent = this.treeData;
824
+ }
825
+
826
+ // Check if node already exists (avoid duplicates)
827
+ if (this.nodes.has(node.path)) {
828
+ console.log('Node already exists:', node.path);
829
+ return;
830
+ }
831
+
832
+ // Add node to parent's children
833
+ if (!parent.children) {
834
+ parent.children = [];
835
+ }
836
+ parent.children.push(node);
837
+
838
+ // Store node reference
839
+ this.nodes.set(node.path, node);
840
+
841
+ // Update hierarchy only if D3 is available
842
+ if (typeof d3 !== 'undefined') {
843
+ // Preserve expanded/collapsed state
844
+ const oldExpandedNodes = new Set();
845
+ if (this.root) {
846
+ this.root.descendants().forEach(d => {
847
+ if (d.children) {
848
+ oldExpandedNodes.add(d.data.path);
849
+ }
850
+ });
851
+ }
852
+
853
+ // Create new hierarchy
854
+ this.root = d3.hierarchy(this.treeData);
855
+ this.root.x0 = this.height / 2;
856
+ this.root.y0 = 0;
857
+
858
+ // Restore expanded state
859
+ this.root.descendants().forEach(d => {
860
+ if (oldExpandedNodes.has(d.data.path) && d._children) {
861
+ d.children = d._children;
862
+ d._children = null;
863
+ }
864
+ });
865
+ }
866
+ }
867
+
868
+ /**
869
+ * Find node by path
870
+ */
871
+ findNodeByPath(path) {
872
+ return this.nodes.get(path);
873
+ }
874
+
875
+ /**
876
+ * Update progress in footer
877
+ */
878
+ updateProgress(percentage, message) {
879
+ const footerStatus = document.getElementById('footer-analysis-progress');
880
+ if (footerStatus) {
881
+ // Format the message with percentage if available
882
+ let statusText = message || 'Analyzing...';
883
+ if (percentage > 0) {
884
+ statusText = `[${Math.round(percentage)}%] ${statusText}`;
885
+ }
886
+ footerStatus.textContent = statusText;
887
+ }
888
+ }
889
+
890
+ /**
891
+ * Show analysis status in footer
892
+ */
893
+ showFooterAnalysisStatus(message) {
894
+ const container = document.getElementById('footer-analysis-container');
895
+ const progress = document.getElementById('footer-analysis-progress');
896
+
897
+ if (container) {
898
+ container.style.display = 'flex';
899
+ }
900
+
901
+ if (progress) {
902
+ progress.textContent = message || 'Analyzing...';
903
+ // Add pulsing animation
904
+ progress.style.animation = 'pulse 1.5s ease-in-out infinite';
905
+ }
906
+ }
907
+
908
+ /**
909
+ * Hide analysis status in footer
910
+ */
911
+ hideFooterAnalysisStatus() {
912
+ const container = document.getElementById('footer-analysis-container');
913
+ const progress = document.getElementById('footer-analysis-progress');
914
+
915
+ if (container) {
916
+ // Fade out after a brief delay
917
+ setTimeout(() => {
918
+ container.style.display = 'none';
919
+ }, 2000);
920
+ }
921
+
922
+ if (progress) {
923
+ progress.style.animation = 'none';
924
+ }
925
+ }
926
+
927
+ /**
928
+ * Update statistics display
929
+ */
930
+ updateStats() {
931
+ const fileCount = document.getElementById('file-count');
932
+ const classCount = document.getElementById('class-count');
933
+ const functionCount = document.getElementById('function-count');
934
+ const lineCount = document.getElementById('line-count');
935
+
936
+ if (fileCount) fileCount.textContent = this.stats.files;
937
+ if (classCount) classCount.textContent = this.stats.classes;
938
+ if (functionCount) functionCount.textContent = this.stats.functions;
939
+ if (lineCount) lineCount.textContent = this.stats.lines;
940
+ }
941
+
942
+ /**
943
+ * Update tree visualization
944
+ */
945
+ update(source) {
946
+ if (!this.svg || !this.treeGroup) {
947
+ return;
948
+ }
949
+
950
+ // Compute new tree layout
951
+ const treeData = this.treeLayout(this.root);
952
+ const nodes = treeData.descendants();
953
+ const links = treeData.links();
954
+
955
+ // Normalize for fixed-depth
956
+ nodes.forEach(d => {
957
+ d.y = d.depth * 180;
958
+ });
959
+
960
+ // Update nodes
961
+ const node = this.treeGroup.selectAll('g.code-node')
962
+ .data(nodes, d => d.id || (d.id = ++this.nodeId));
963
+
964
+ // Enter new nodes
965
+ const nodeEnter = node.enter().append('g')
966
+ .attr('class', d => `code-node ${d.data.type} complexity-${this.getComplexityLevel(d.data.complexity)}`)
967
+ .attr('transform', d => `translate(${source.y0},${source.x0})`)
968
+ .on('click', (event, d) => this.toggleNode(event, d))
969
+ .on('mouseover', (event, d) => this.showTooltip(event, d))
970
+ .on('mouseout', () => this.hideTooltip());
971
+
972
+ // Add circles
973
+ nodeEnter.append('circle')
974
+ .attr('r', 1e-6)
975
+ .style('fill', d => d._children ? '#e2e8f0' : this.getNodeColor(d.data.type));
976
+
977
+ // Add text labels
978
+ nodeEnter.append('text')
979
+ .attr('dy', '.35em')
980
+ .attr('x', d => d.children || d._children ? -13 : 13)
981
+ .attr('text-anchor', d => d.children || d._children ? 'end' : 'start')
982
+ .text(d => d.data.name)
983
+ .style('fill-opacity', 1e-6);
984
+
985
+ // Add icons
986
+ nodeEnter.append('text')
987
+ .attr('class', 'node-icon')
988
+ .attr('dy', '.35em')
989
+ .attr('x', 0)
990
+ .attr('text-anchor', 'middle')
991
+ .text(d => this.getNodeIcon(d.data.type))
992
+ .style('font-size', '16px');
993
+
994
+ // Transition nodes to their new position
995
+ const nodeUpdate = nodeEnter.merge(node);
996
+
997
+ nodeUpdate.transition()
998
+ .duration(this.duration)
999
+ .attr('transform', d => `translate(${d.y},${d.x})`);
1000
+
1001
+ nodeUpdate.select('circle')
1002
+ .attr('r', 8)
1003
+ .style('fill', d => d._children ? '#e2e8f0' : this.getNodeColor(d.data.type));
1004
+
1005
+ nodeUpdate.select('text')
1006
+ .style('fill-opacity', 1);
1007
+
1008
+ // Remove exiting nodes
1009
+ const nodeExit = node.exit().transition()
1010
+ .duration(this.duration)
1011
+ .attr('transform', d => `translate(${source.y},${source.x})`)
1012
+ .remove();
1013
+
1014
+ nodeExit.select('circle')
1015
+ .attr('r', 1e-6);
1016
+
1017
+ nodeExit.select('text')
1018
+ .style('fill-opacity', 1e-6);
1019
+
1020
+ // Update links
1021
+ const link = this.treeGroup.selectAll('path.code-link')
1022
+ .data(links, d => d.target.id);
1023
+
1024
+ // Enter new links
1025
+ const linkEnter = link.enter().insert('path', 'g')
1026
+ .attr('class', 'code-link')
1027
+ .attr('d', d => {
1028
+ const o = {x: source.x0, y: source.y0};
1029
+ return this.diagonal(o, o);
1030
+ });
1031
+
1032
+ // Transition links to their new position
1033
+ const linkUpdate = linkEnter.merge(link);
1034
+
1035
+ linkUpdate.transition()
1036
+ .duration(this.duration)
1037
+ .attr('d', d => this.diagonal(d.source, d.target));
1038
+
1039
+ // Remove exiting links
1040
+ link.exit().transition()
1041
+ .duration(this.duration)
1042
+ .attr('d', d => {
1043
+ const o = {x: source.x, y: source.y};
1044
+ return this.diagonal(o, o);
1045
+ })
1046
+ .remove();
1047
+
1048
+ // Store old positions for transition
1049
+ nodes.forEach(d => {
1050
+ d.x0 = d.x;
1051
+ d.y0 = d.y;
1052
+ });
1053
+ }
1054
+
1055
+ /**
1056
+ * Create diagonal path for links
1057
+ */
1058
+ diagonal(source, target) {
1059
+ return `M ${source.y} ${source.x}
1060
+ C ${(source.y + target.y) / 2} ${source.x},
1061
+ ${(source.y + target.y) / 2} ${target.x},
1062
+ ${target.y} ${target.x}`;
1063
+ }
1064
+
1065
+ /**
1066
+ * Toggle node expansion/collapse
1067
+ */
1068
+ toggleNode(event, d) {
1069
+ if (d.children) {
1070
+ d._children = d.children;
1071
+ d.children = null;
1072
+ } else {
1073
+ d.children = d._children;
1074
+ d._children = null;
1075
+ }
1076
+
1077
+ this.update(d);
1078
+
1079
+ // Update breadcrumb
1080
+ this.updateBreadcrumb(d);
1081
+
1082
+ // Mark as selected
1083
+ this.selectNode(d);
1084
+
1085
+ // Show code viewer if it's a code node
1086
+ if (d.data.type !== 'module' && d.data.type !== 'file') {
1087
+ this.showCodeViewer(d.data);
1088
+ }
1089
+ }
1090
+
1091
+ /**
1092
+ * Select a node
1093
+ */
1094
+ selectNode(node) {
1095
+ // Remove previous selection
1096
+ if (this.selectedNode) {
1097
+ d3.select(this.selectedNode).classed('selected', false);
1098
+ }
1099
+
1100
+ // Add selection to new node
1101
+ this.selectedNode = node;
1102
+ if (node) {
1103
+ d3.select(node).classed('selected', true);
1104
+ }
1105
+ }
1106
+
1107
+ /**
1108
+ * Update breadcrumb navigation
1109
+ */
1110
+ updateBreadcrumb(node) {
1111
+ const path = [];
1112
+ let current = node;
1113
+
1114
+ while (current) {
1115
+ path.unshift(current.data.name);
1116
+ current = current.parent;
1117
+ }
1118
+
1119
+ const breadcrumbContent = document.getElementById('breadcrumb-content');
1120
+ if (breadcrumbContent) {
1121
+ breadcrumbContent.textContent = path.join(' > ');
1122
+ breadcrumbContent.className = 'ticker-file';
1123
+ }
1124
+ }
1125
+
1126
+ /**
1127
+ * Update ticker with event
1128
+ */
1129
+ updateTicker(message, type = 'info') {
1130
+ const breadcrumbContent = document.getElementById('breadcrumb-content');
1131
+ if (breadcrumbContent) {
1132
+ // Add class based on type
1133
+ let className = '';
1134
+ switch(type) {
1135
+ case 'file': className = 'ticker-file'; break;
1136
+ case 'node': className = 'ticker-node'; break;
1137
+ case 'progress': className = 'ticker-progress'; break;
1138
+ case 'error': className = 'ticker-error'; break;
1139
+ default: className = '';
1140
+ }
1141
+
1142
+ breadcrumbContent.textContent = message;
1143
+ breadcrumbContent.className = className + ' ticker-event';
1144
+
1145
+ // Trigger animation
1146
+ breadcrumbContent.style.animation = 'none';
1147
+ setTimeout(() => {
1148
+ breadcrumbContent.style.animation = '';
1149
+ }, 10);
1150
+ }
1151
+ }
1152
+
1153
+ /**
1154
+ * Show code viewer for a node
1155
+ */
1156
+ showCodeViewer(nodeData) {
1157
+ // Emit event to open code viewer
1158
+ if (window.CodeViewer) {
1159
+ window.CodeViewer.show(nodeData);
1160
+ }
1161
+ }
1162
+
1163
+ /**
1164
+ * Show tooltip
1165
+ */
1166
+ showTooltip(event, d) {
1167
+ if (!this.tooltip) return;
1168
+
1169
+ let content = `<strong>${d.data.name}</strong><br/>`;
1170
+ content += `Type: ${d.data.type}<br/>`;
1171
+
1172
+ if (d.data.complexity) {
1173
+ content += `Complexity: ${d.data.complexity}<br/>`;
1174
+ }
1175
+
1176
+ if (d.data.line) {
1177
+ content += `Line: ${d.data.line}<br/>`;
1178
+ }
1179
+
1180
+ if (d.data.docstring) {
1181
+ content += `<em>${d.data.docstring.substring(0, 100)}...</em>`;
1182
+ }
1183
+
1184
+ this.tooltip.transition()
1185
+ .duration(200)
1186
+ .style('opacity', .9);
1187
+
1188
+ this.tooltip.html(content)
1189
+ .style('left', (event.pageX + 10) + 'px')
1190
+ .style('top', (event.pageY - 28) + 'px');
1191
+ }
1192
+
1193
+ /**
1194
+ * Hide tooltip
1195
+ */
1196
+ hideTooltip() {
1197
+ if (this.tooltip) {
1198
+ this.tooltip.transition()
1199
+ .duration(500)
1200
+ .style('opacity', 0);
1201
+ }
1202
+ }
1203
+
1204
+ /**
1205
+ * Get node color based on type
1206
+ */
1207
+ getNodeColor(type) {
1208
+ const colors = {
1209
+ module: '#8b5cf6',
1210
+ file: '#6366f1',
1211
+ class: '#3b82f6',
1212
+ function: '#f59e0b',
1213
+ method: '#10b981'
1214
+ };
1215
+ return colors[type] || '#718096';
1216
+ }
1217
+
1218
+ /**
1219
+ * Get node icon based on type
1220
+ */
1221
+ getNodeIcon(type) {
1222
+ const icons = {
1223
+ module: '📦',
1224
+ file: '📄',
1225
+ class: '🏛️',
1226
+ function: '⚡',
1227
+ method: '🔧'
1228
+ };
1229
+ return icons[type] || '📌';
1230
+ }
1231
+
1232
+ /**
1233
+ * Get complexity level
1234
+ */
1235
+ getComplexityLevel(complexity) {
1236
+ if (complexity <= 5) return 'low';
1237
+ if (complexity <= 10) return 'medium';
1238
+ return 'high';
1239
+ }
1240
+
1241
+ /**
1242
+ * Expand all nodes
1243
+ */
1244
+ expandAll() {
1245
+ this.expand(this.root);
1246
+ this.update(this.root);
1247
+ }
1248
+
1249
+ /**
1250
+ * Expand node recursively
1251
+ */
1252
+ expand(node) {
1253
+ if (node._children) {
1254
+ node.children = node._children;
1255
+ node._children = null;
1256
+ }
1257
+ if (node.children) {
1258
+ node.children.forEach(child => this.expand(child));
1259
+ }
1260
+ }
1261
+
1262
+ /**
1263
+ * Collapse all nodes
1264
+ */
1265
+ collapseAll() {
1266
+ this.collapse(this.root);
1267
+ this.update(this.root);
1268
+ }
1269
+
1270
+ /**
1271
+ * Collapse node recursively
1272
+ */
1273
+ collapse(node) {
1274
+ if (node.children) {
1275
+ node._children = node.children;
1276
+ node.children.forEach(child => this.collapse(child));
1277
+ node.children = null;
1278
+ }
1279
+ }
1280
+
1281
+ /**
1282
+ * Reset zoom
1283
+ */
1284
+ resetZoom() {
1285
+ if (this.svg) {
1286
+ this.svg.transition()
1287
+ .duration(750)
1288
+ .call(d3.zoom().transform, d3.zoomIdentity);
1289
+ }
1290
+ }
1291
+
1292
+ /**
1293
+ * Toggle legend visibility
1294
+ */
1295
+ toggleLegend() {
1296
+ const legend = document.getElementById('tree-legend');
1297
+ if (legend) {
1298
+ if (legend.style.display === 'none') {
1299
+ legend.style.display = 'block';
1300
+ } else {
1301
+ legend.style.display = 'none';
1302
+ }
1303
+ }
1304
+ }
1305
+
1306
+ /**
1307
+ * Filter nodes by language
1308
+ */
1309
+ filterByLanguage() {
1310
+ // Implementation for language filtering
1311
+ console.log('Filtering by language:', this.languageFilter);
1312
+ // This would filter the tree data and update visualization
1313
+ this.update(this.root);
1314
+ }
1315
+
1316
+ /**
1317
+ * Highlight search results
1318
+ */
1319
+ highlightSearchResults() {
1320
+ if (!this.treeGroup) return;
1321
+
1322
+ // Clear previous highlights
1323
+ this.treeGroup.selectAll('.code-node').classed('highlighted', false);
1324
+
1325
+ if (!this.searchTerm) return;
1326
+
1327
+ // Highlight matching nodes
1328
+ this.treeGroup.selectAll('.code-node').each((d, i, nodes) => {
1329
+ if (d.data.name.toLowerCase().includes(this.searchTerm)) {
1330
+ d3.select(nodes[i]).classed('highlighted', true);
1331
+ }
1332
+ });
1333
+ }
1334
+ }
1335
+
1336
+ // Export for use in dashboard
1337
+ if (typeof window !== 'undefined') {
1338
+ window.CodeTree = CodeTree;
1339
+
1340
+ // Initialize when DOM is ready
1341
+ document.addEventListener('DOMContentLoaded', () => {
1342
+ const codeTree = new CodeTree();
1343
+ window.codeTree = codeTree;
1344
+
1345
+ // Listen for tab switches to initialize when Code tab is activated
1346
+ document.querySelectorAll('.tab-button').forEach(button => {
1347
+ button.addEventListener('click', () => {
1348
+ if (button.getAttribute('data-tab') === 'code') {
1349
+ console.log('Code tab activated, initializing tree...');
1350
+ codeTree.renderWhenVisible();
1351
+ }
1352
+ });
1353
+ });
1354
+ });
1355
+ }
1356
+
1357
+ export default CodeTree;