claude-mpm 4.1.8__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.
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/INSTRUCTIONS.md +26 -1
- claude_mpm/agents/agents_metadata.py +57 -0
- claude_mpm/agents/templates/.claude-mpm/memories/README.md +17 -0
- claude_mpm/agents/templates/.claude-mpm/memories/engineer_memories.md +3 -0
- claude_mpm/agents/templates/agent-manager.json +263 -17
- claude_mpm/agents/templates/agentic_coder_optimizer.json +222 -0
- claude_mpm/agents/templates/code_analyzer.json +18 -8
- claude_mpm/agents/templates/engineer.json +1 -1
- claude_mpm/agents/templates/logs/prompts/agent_engineer_20250826_014258_728.md +39 -0
- claude_mpm/agents/templates/qa.json +1 -1
- claude_mpm/agents/templates/research.json +1 -1
- claude_mpm/cli/__init__.py +4 -0
- claude_mpm/cli/commands/__init__.py +6 -0
- claude_mpm/cli/commands/analyze.py +547 -0
- claude_mpm/cli/commands/analyze_code.py +524 -0
- claude_mpm/cli/commands/configure.py +77 -28
- claude_mpm/cli/commands/configure_tui.py +60 -60
- claude_mpm/cli/commands/debug.py +1387 -0
- claude_mpm/cli/parsers/analyze_code_parser.py +170 -0
- claude_mpm/cli/parsers/analyze_parser.py +135 -0
- claude_mpm/cli/parsers/base_parser.py +29 -0
- claude_mpm/cli/parsers/debug_parser.py +319 -0
- claude_mpm/constants.py +3 -1
- claude_mpm/core/framework_loader.py +148 -6
- claude_mpm/core/log_manager.py +16 -13
- claude_mpm/core/logger.py +1 -1
- claude_mpm/core/unified_agent_registry.py +1 -1
- claude_mpm/dashboard/.claude-mpm/socketio-instances.json +1 -0
- claude_mpm/dashboard/analysis_runner.py +428 -0
- claude_mpm/dashboard/static/built/components/activity-tree.js +2 -0
- claude_mpm/dashboard/static/built/components/agent-inference.js +1 -1
- claude_mpm/dashboard/static/built/components/event-viewer.js +1 -1
- claude_mpm/dashboard/static/built/components/file-tool-tracker.js +1 -1
- claude_mpm/dashboard/static/built/components/module-viewer.js +1 -1
- claude_mpm/dashboard/static/built/components/session-manager.js +1 -1
- claude_mpm/dashboard/static/built/components/working-directory.js +1 -1
- claude_mpm/dashboard/static/built/dashboard.js +1 -1
- claude_mpm/dashboard/static/built/socket-client.js +1 -1
- claude_mpm/dashboard/static/css/activity.css +549 -0
- claude_mpm/dashboard/static/css/code-tree.css +846 -0
- claude_mpm/dashboard/static/css/dashboard.css +245 -0
- claude_mpm/dashboard/static/dist/components/activity-tree.js +2 -0
- claude_mpm/dashboard/static/dist/components/code-tree.js +2 -0
- claude_mpm/dashboard/static/dist/components/code-viewer.js +2 -0
- claude_mpm/dashboard/static/dist/components/event-viewer.js +1 -1
- claude_mpm/dashboard/static/dist/components/session-manager.js +1 -1
- claude_mpm/dashboard/static/dist/components/working-directory.js +1 -1
- claude_mpm/dashboard/static/dist/dashboard.js +1 -1
- claude_mpm/dashboard/static/dist/socket-client.js +1 -1
- claude_mpm/dashboard/static/js/components/activity-tree.js +1139 -0
- claude_mpm/dashboard/static/js/components/code-tree.js +1357 -0
- claude_mpm/dashboard/static/js/components/code-viewer.js +480 -0
- claude_mpm/dashboard/static/js/components/event-viewer.js +11 -0
- claude_mpm/dashboard/static/js/components/session-manager.js +40 -4
- claude_mpm/dashboard/static/js/components/socket-manager.js +12 -0
- claude_mpm/dashboard/static/js/components/ui-state-manager.js +4 -0
- claude_mpm/dashboard/static/js/components/working-directory.js +17 -1
- claude_mpm/dashboard/static/js/dashboard.js +39 -0
- claude_mpm/dashboard/static/js/socket-client.js +414 -20
- claude_mpm/dashboard/templates/index.html +184 -4
- claude_mpm/hooks/claude_hooks/hook_handler.py +182 -5
- claude_mpm/hooks/claude_hooks/installer.py +386 -113
- claude_mpm/scripts/claude-hook-handler.sh +161 -0
- claude_mpm/scripts/socketio_daemon.py +121 -8
- claude_mpm/services/agents/deployment/agent_lifecycle_manager_refactored.py +2 -2
- claude_mpm/services/agents/deployment/agent_record_service.py +1 -2
- claude_mpm/services/agents/memory/memory_format_service.py +1 -5
- claude_mpm/services/cli/agent_cleanup_service.py +1 -2
- claude_mpm/services/cli/agent_dependency_service.py +1 -1
- claude_mpm/services/cli/agent_validation_service.py +3 -4
- claude_mpm/services/cli/dashboard_launcher.py +2 -3
- claude_mpm/services/cli/startup_checker.py +0 -10
- claude_mpm/services/core/cache_manager.py +1 -2
- claude_mpm/services/core/path_resolver.py +1 -4
- claude_mpm/services/core/service_container.py +2 -2
- claude_mpm/services/diagnostics/checks/instructions_check.py +1 -2
- claude_mpm/services/infrastructure/monitoring/__init__.py +11 -11
- claude_mpm/services/infrastructure/monitoring.py +11 -11
- claude_mpm/services/project/architecture_analyzer.py +1 -1
- claude_mpm/services/project/dependency_analyzer.py +4 -4
- claude_mpm/services/project/language_analyzer.py +3 -3
- claude_mpm/services/project/metrics_collector.py +3 -6
- claude_mpm/services/socketio/handlers/__init__.py +2 -0
- claude_mpm/services/socketio/handlers/code_analysis.py +170 -0
- claude_mpm/services/socketio/handlers/registry.py +2 -0
- claude_mpm/services/socketio/server/connection_manager.py +4 -4
- claude_mpm/services/socketio/server/core.py +100 -11
- claude_mpm/services/socketio/server/main.py +8 -2
- claude_mpm/services/visualization/__init__.py +19 -0
- claude_mpm/services/visualization/mermaid_generator.py +938 -0
- claude_mpm/tools/__main__.py +208 -0
- claude_mpm/tools/code_tree_analyzer.py +778 -0
- claude_mpm/tools/code_tree_builder.py +632 -0
- claude_mpm/tools/code_tree_events.py +318 -0
- claude_mpm/tools/socketio_debug.py +671 -0
- {claude_mpm-4.1.8.dist-info → claude_mpm-4.1.10.dist-info}/METADATA +1 -1
- {claude_mpm-4.1.8.dist-info → claude_mpm-4.1.10.dist-info}/RECORD +102 -73
- claude_mpm/agents/schema/agent_schema.json +0 -314
- {claude_mpm-4.1.8.dist-info → claude_mpm-4.1.10.dist-info}/WHEEL +0 -0
- {claude_mpm-4.1.8.dist-info → claude_mpm-4.1.10.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.1.8.dist-info → claude_mpm-4.1.10.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.1.8.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;
|