mcp-vector-search 0.12.1__py3-none-any.whl → 0.12.3__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.
Potentially problematic release.
This version of mcp-vector-search might be problematic. Click here for more details.
- mcp_vector_search/__init__.py +2 -2
- mcp_vector_search/cli/commands/visualize.py +22 -12
- mcp_vector_search/visualization/favicon-v1-1024.png +0 -0
- mcp_vector_search/visualization/favicon-v1-128.png +0 -0
- mcp_vector_search/visualization/favicon-v1-16.png +0 -0
- mcp_vector_search/visualization/favicon-v1-256.png +0 -0
- mcp_vector_search/visualization/favicon-v1-32.png +0 -0
- mcp_vector_search/visualization/favicon-v1-512.png +0 -0
- mcp_vector_search/visualization/favicon-v1-64.png +0 -0
- mcp_vector_search/visualization/favicon-v1.ico +0 -0
- mcp_vector_search/visualization/favicon-v2-1024.png +0 -0
- mcp_vector_search/visualization/favicon-v2-128.png +0 -0
- mcp_vector_search/visualization/favicon-v2-16.png +0 -0
- mcp_vector_search/visualization/favicon-v2-256.png +0 -0
- mcp_vector_search/visualization/favicon-v2-32.png +0 -0
- mcp_vector_search/visualization/favicon-v2-512.png +0 -0
- mcp_vector_search/visualization/favicon-v2-64.png +0 -0
- mcp_vector_search/visualization/favicon-v2.ico +0 -0
- mcp_vector_search/visualization/favicon-v3-1024.png +0 -0
- mcp_vector_search/visualization/favicon-v3-128.png +0 -0
- mcp_vector_search/visualization/favicon-v3-16.png +0 -0
- mcp_vector_search/visualization/favicon-v3-256.png +0 -0
- mcp_vector_search/visualization/favicon-v3-32.png +0 -0
- mcp_vector_search/visualization/favicon-v3-512.png +0 -0
- mcp_vector_search/visualization/favicon-v3-64.png +0 -0
- mcp_vector_search/visualization/favicon-v3.ico +0 -0
- mcp_vector_search/visualization/index.html +1592 -193
- {mcp_vector_search-0.12.1.dist-info → mcp_vector_search-0.12.3.dist-info}/METADATA +62 -2
- {mcp_vector_search-0.12.1.dist-info → mcp_vector_search-0.12.3.dist-info}/RECORD +32 -8
- {mcp_vector_search-0.12.1.dist-info → mcp_vector_search-0.12.3.dist-info}/WHEEL +0 -0
- {mcp_vector_search-0.12.1.dist-info → mcp_vector_search-0.12.3.dist-info}/entry_points.txt +0 -0
- {mcp_vector_search-0.12.1.dist-info → mcp_vector_search-0.12.3.dist-info}/licenses/LICENSE +0 -0
|
@@ -2,10 +2,15 @@
|
|
|
2
2
|
<html>
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="utf-8">
|
|
5
|
-
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
|
|
6
|
-
<meta http-equiv="Pragma" content="no-cache">
|
|
7
|
-
<meta http-equiv="Expires" content="0">
|
|
8
5
|
<title>Code Chunk Relationship Graph</title>
|
|
6
|
+
|
|
7
|
+
<!-- Favicon -->
|
|
8
|
+
<link rel="icon" type="image/x-icon" href="favicon-v1.ico">
|
|
9
|
+
<link rel="icon" type="image/png" sizes="16x16" href="favicon-v1-16.png">
|
|
10
|
+
<link rel="icon" type="image/png" sizes="32x32" href="favicon-v1-32.png">
|
|
11
|
+
<link rel="icon" type="image/png" sizes="64x64" href="favicon-v1-64.png">
|
|
12
|
+
<link rel="apple-touch-icon" sizes="180x180" href="favicon-v1-512.png">
|
|
13
|
+
|
|
9
14
|
<script src="https://d3js.org/d3.v7.min.js"></script>
|
|
10
15
|
<style>
|
|
11
16
|
body {
|
|
@@ -80,26 +85,43 @@
|
|
|
80
85
|
cursor: pointer;
|
|
81
86
|
stroke: #c9d1d9;
|
|
82
87
|
stroke-width: 1.5px;
|
|
88
|
+
transition: all 0.2s ease;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.node circle:hover,
|
|
92
|
+
.node rect:hover {
|
|
93
|
+
stroke-width: 3px !important;
|
|
94
|
+
filter: brightness(1.2);
|
|
95
|
+
cursor: pointer;
|
|
83
96
|
}
|
|
84
97
|
|
|
85
|
-
.node.directory circle { fill: #ffa657; }
|
|
86
|
-
.node.file circle { fill: #58a6ff; }
|
|
87
98
|
.node.module circle { fill: #238636; }
|
|
88
99
|
.node.class circle { fill: #1f6feb; }
|
|
89
100
|
.node.function circle { fill: #d29922; }
|
|
90
101
|
.node.method circle { fill: #8957e5; }
|
|
91
|
-
.node.imports circle { fill: #6e7681; }
|
|
92
|
-
.node.text circle { fill: #6e7681; }
|
|
93
102
|
.node.code circle { fill: #6e7681; }
|
|
103
|
+
.node.file circle {
|
|
104
|
+
fill: none;
|
|
105
|
+
stroke: #58a6ff;
|
|
106
|
+
stroke-width: 2px;
|
|
107
|
+
stroke-dasharray: 5,3;
|
|
108
|
+
opacity: 0.6;
|
|
109
|
+
}
|
|
110
|
+
.node.directory circle {
|
|
111
|
+
fill: none;
|
|
112
|
+
stroke: #79c0ff;
|
|
113
|
+
stroke-width: 2px;
|
|
114
|
+
stroke-dasharray: 3,3;
|
|
115
|
+
opacity: 0.5;
|
|
116
|
+
}
|
|
94
117
|
.node.subproject circle { fill: #da3633; stroke-width: 3px; }
|
|
95
118
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
font-weight: bold;
|
|
119
|
+
/* Non-code document nodes - squares */
|
|
120
|
+
.node.docstring rect { fill: #8b949e; }
|
|
121
|
+
.node.comment rect { fill: #6e7681; }
|
|
122
|
+
.node rect {
|
|
123
|
+
stroke: #c9d1d9;
|
|
124
|
+
stroke-width: 1.5px;
|
|
103
125
|
}
|
|
104
126
|
|
|
105
127
|
.node text {
|
|
@@ -110,16 +132,25 @@
|
|
|
110
132
|
user-select: none;
|
|
111
133
|
}
|
|
112
134
|
|
|
135
|
+
.node-icon {
|
|
136
|
+
font-size: 35px !important;
|
|
137
|
+
text-anchor: middle;
|
|
138
|
+
pointer-events: none;
|
|
139
|
+
user-select: none;
|
|
140
|
+
fill: #0d1117;
|
|
141
|
+
font-weight: bold;
|
|
142
|
+
}
|
|
143
|
+
|
|
113
144
|
.link {
|
|
114
145
|
stroke: #58a6ff;
|
|
115
|
-
stroke-opacity: 0.
|
|
116
|
-
stroke-width:
|
|
146
|
+
stroke-opacity: 0.8;
|
|
147
|
+
stroke-width: 3px;
|
|
117
148
|
}
|
|
118
149
|
|
|
119
150
|
.link.dependency {
|
|
120
151
|
stroke: #d29922;
|
|
121
152
|
stroke-opacity: 0.8;
|
|
122
|
-
stroke-width:
|
|
153
|
+
stroke-width: 3px;
|
|
123
154
|
stroke-dasharray: 5,5;
|
|
124
155
|
}
|
|
125
156
|
|
|
@@ -144,77 +175,167 @@
|
|
|
144
175
|
color: #8b949e;
|
|
145
176
|
}
|
|
146
177
|
|
|
147
|
-
#
|
|
148
|
-
position:
|
|
149
|
-
top:
|
|
150
|
-
right:
|
|
151
|
-
width:
|
|
152
|
-
|
|
153
|
-
background: rgba(13, 17, 23, 0.
|
|
154
|
-
border: 1px solid #30363d;
|
|
155
|
-
border-radius: 6px;
|
|
156
|
-
padding: 16px;
|
|
178
|
+
#content-pane {
|
|
179
|
+
position: fixed;
|
|
180
|
+
top: 0;
|
|
181
|
+
right: 0;
|
|
182
|
+
width: 600px;
|
|
183
|
+
height: 100vh;
|
|
184
|
+
background: rgba(13, 17, 23, 0.98);
|
|
185
|
+
border-left: 1px solid #30363d;
|
|
157
186
|
overflow-y: auto;
|
|
158
|
-
box-shadow: 0
|
|
159
|
-
|
|
187
|
+
box-shadow: -4px 0 24px rgba(0, 0, 0, 0.5);
|
|
188
|
+
transform: translateX(100%);
|
|
189
|
+
transition: transform 0.3s ease-in-out;
|
|
190
|
+
z-index: 1000;
|
|
160
191
|
}
|
|
161
192
|
|
|
162
|
-
#
|
|
163
|
-
|
|
193
|
+
#content-pane.visible {
|
|
194
|
+
transform: translateX(0);
|
|
164
195
|
}
|
|
165
196
|
|
|
166
|
-
#
|
|
167
|
-
|
|
168
|
-
|
|
197
|
+
#content-pane .pane-header {
|
|
198
|
+
position: sticky;
|
|
199
|
+
top: 0;
|
|
200
|
+
background: rgba(13, 17, 23, 0.98);
|
|
201
|
+
padding: 20px;
|
|
169
202
|
border-bottom: 1px solid #30363d;
|
|
203
|
+
z-index: 1;
|
|
170
204
|
}
|
|
171
205
|
|
|
172
|
-
#
|
|
173
|
-
font-size:
|
|
206
|
+
#content-pane .pane-title {
|
|
207
|
+
font-size: 16px;
|
|
174
208
|
font-weight: bold;
|
|
175
209
|
color: #58a6ff;
|
|
176
|
-
margin-bottom:
|
|
210
|
+
margin-bottom: 8px;
|
|
211
|
+
padding-right: 30px;
|
|
177
212
|
}
|
|
178
213
|
|
|
179
|
-
#
|
|
180
|
-
font-size:
|
|
214
|
+
#content-pane .pane-meta {
|
|
215
|
+
font-size: 12px;
|
|
181
216
|
color: #8b949e;
|
|
182
217
|
}
|
|
183
218
|
|
|
184
|
-
#
|
|
185
|
-
|
|
219
|
+
#content-pane .collapse-btn {
|
|
220
|
+
position: absolute;
|
|
221
|
+
top: 20px;
|
|
222
|
+
right: 20px;
|
|
186
223
|
cursor: pointer;
|
|
187
224
|
color: #8b949e;
|
|
188
|
-
font-size:
|
|
225
|
+
font-size: 24px;
|
|
189
226
|
line-height: 1;
|
|
190
|
-
|
|
227
|
+
background: none;
|
|
228
|
+
border: none;
|
|
229
|
+
padding: 0;
|
|
230
|
+
transition: color 0.2s;
|
|
191
231
|
}
|
|
192
232
|
|
|
193
|
-
#
|
|
233
|
+
#content-pane .collapse-btn:hover {
|
|
194
234
|
color: #c9d1d9;
|
|
195
235
|
}
|
|
196
236
|
|
|
197
|
-
#
|
|
237
|
+
#content-pane .pane-content {
|
|
238
|
+
padding: 20px;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
#content-pane pre {
|
|
198
242
|
margin: 0;
|
|
199
|
-
padding:
|
|
243
|
+
padding: 16px;
|
|
200
244
|
background: #0d1117;
|
|
201
245
|
border: 1px solid #30363d;
|
|
202
246
|
border-radius: 6px;
|
|
203
247
|
overflow-x: auto;
|
|
204
|
-
font-size:
|
|
205
|
-
line-height: 1.
|
|
248
|
+
font-size: 12px;
|
|
249
|
+
line-height: 1.6;
|
|
206
250
|
}
|
|
207
251
|
|
|
208
|
-
#
|
|
252
|
+
#content-pane code {
|
|
209
253
|
color: #c9d1d9;
|
|
210
254
|
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
|
211
255
|
}
|
|
212
256
|
|
|
213
|
-
.
|
|
257
|
+
#content-pane .directory-list {
|
|
258
|
+
list-style: none;
|
|
259
|
+
padding: 0;
|
|
260
|
+
margin: 0;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
#content-pane .directory-list li {
|
|
264
|
+
padding: 8px 12px;
|
|
265
|
+
margin: 4px 0;
|
|
266
|
+
background: #161b22;
|
|
267
|
+
border: 1px solid #30363d;
|
|
268
|
+
border-radius: 4px;
|
|
269
|
+
font-size: 12px;
|
|
270
|
+
display: flex;
|
|
271
|
+
align-items: center;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
#content-pane .directory-list .item-icon {
|
|
275
|
+
margin-right: 8px;
|
|
276
|
+
font-size: 14px;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
#content-pane .directory-list .item-type {
|
|
280
|
+
margin-left: auto;
|
|
281
|
+
padding-left: 12px;
|
|
282
|
+
font-size: 10px;
|
|
283
|
+
color: #8b949e;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
#content-pane .import-details {
|
|
287
|
+
background: #161b22;
|
|
288
|
+
border: 1px solid #30363d;
|
|
289
|
+
border-radius: 6px;
|
|
290
|
+
padding: 16px;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
#content-pane .import-details .import-statement {
|
|
294
|
+
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
|
295
|
+
font-size: 12px;
|
|
296
|
+
color: #79c0ff;
|
|
297
|
+
margin-bottom: 12px;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
#content-pane .import-details .detail-row {
|
|
301
|
+
font-size: 11px;
|
|
302
|
+
color: #8b949e;
|
|
303
|
+
margin: 4px 0;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
#content-pane .import-details .detail-label {
|
|
307
|
+
color: #c9d1d9;
|
|
308
|
+
font-weight: 600;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
.node.highlighted circle,
|
|
312
|
+
.node.highlighted rect {
|
|
214
313
|
stroke: #f0e68c;
|
|
215
314
|
stroke-width: 3px;
|
|
216
315
|
filter: drop-shadow(0 0 8px #f0e68c);
|
|
217
316
|
}
|
|
317
|
+
|
|
318
|
+
.control-button {
|
|
319
|
+
padding: 8px 12px;
|
|
320
|
+
background: #21262d;
|
|
321
|
+
border: 1px solid #30363d;
|
|
322
|
+
border-radius: 6px;
|
|
323
|
+
color: #c9d1d9;
|
|
324
|
+
font-size: 12px;
|
|
325
|
+
cursor: pointer;
|
|
326
|
+
transition: background-color 0.2s;
|
|
327
|
+
margin-top: 8px;
|
|
328
|
+
width: 100%;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
.control-button:hover {
|
|
332
|
+
background: #30363d;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
.control-button:disabled {
|
|
336
|
+
opacity: 0.5;
|
|
337
|
+
cursor: not-allowed;
|
|
338
|
+
}
|
|
218
339
|
</style>
|
|
219
340
|
</head>
|
|
220
341
|
<body>
|
|
@@ -225,28 +346,44 @@
|
|
|
225
346
|
<label>⏳ Loading graph data...</label>
|
|
226
347
|
</div>
|
|
227
348
|
|
|
349
|
+
<div class="control-group">
|
|
350
|
+
<button id="focus-button" class="control-button" disabled>📍 Focus on Selected</button>
|
|
351
|
+
</div>
|
|
352
|
+
|
|
228
353
|
<h3>Legend</h3>
|
|
229
354
|
<div class="legend">
|
|
230
355
|
<div class="legend-item">
|
|
231
|
-
|
|
356
|
+
<span class="legend-color" style="background: #da3633;"></span> Subproject
|
|
232
357
|
</div>
|
|
233
358
|
<div class="legend-item">
|
|
234
|
-
|
|
359
|
+
<span class="legend-color" style="border: 2px dashed #79c0ff; border-radius: 50%; background: transparent;"></span> Directory
|
|
235
360
|
</div>
|
|
236
361
|
<div class="legend-item">
|
|
237
|
-
|
|
362
|
+
<span class="legend-color" style="border: 2px dashed #58a6ff; border-radius: 50%; background: transparent;"></span> File
|
|
238
363
|
</div>
|
|
239
364
|
<div class="legend-item">
|
|
240
|
-
|
|
365
|
+
<span class="legend-color" style="background: #238636;"></span> Module
|
|
241
366
|
</div>
|
|
242
367
|
<div class="legend-item">
|
|
243
|
-
|
|
368
|
+
<span class="legend-color" style="background: #1f6feb;"></span> Class
|
|
244
369
|
</div>
|
|
245
370
|
<div class="legend-item">
|
|
246
|
-
|
|
371
|
+
<span class="legend-color" style="background: #d29922;"></span> Function
|
|
247
372
|
</div>
|
|
248
373
|
<div class="legend-item">
|
|
249
|
-
|
|
374
|
+
<span class="legend-color" style="background: #8957e5;"></span> Method
|
|
375
|
+
</div>
|
|
376
|
+
<div class="legend-item">
|
|
377
|
+
<span class="legend-color" style="background: #6e7681;"></span> Code
|
|
378
|
+
</div>
|
|
379
|
+
<div class="legend-item" style="font-style: italic; color: #79c0ff;">
|
|
380
|
+
<span class="legend-color" style="background: #6e7681;"></span> Import
|
|
381
|
+
</div>
|
|
382
|
+
<div class="legend-item">
|
|
383
|
+
<span class="legend-color" style="background: #8b949e; border-radius: 2px;"></span> Docstring ▢
|
|
384
|
+
</div>
|
|
385
|
+
<div class="legend-item">
|
|
386
|
+
<span class="legend-color" style="background: #6e7681; border-radius: 2px;"></span> Comment ▢
|
|
250
387
|
</div>
|
|
251
388
|
</div>
|
|
252
389
|
|
|
@@ -261,13 +398,13 @@
|
|
|
261
398
|
<svg id="graph"></svg>
|
|
262
399
|
<div id="tooltip" class="tooltip"></div>
|
|
263
400
|
|
|
264
|
-
<div id="
|
|
265
|
-
<div class="header">
|
|
266
|
-
<
|
|
267
|
-
<div class="title" id="
|
|
268
|
-
<div class="meta" id="
|
|
401
|
+
<div id="content-pane">
|
|
402
|
+
<div class="pane-header">
|
|
403
|
+
<button class="collapse-btn" onclick="closeContentPane()">×</button>
|
|
404
|
+
<div class="pane-title" id="pane-title"></div>
|
|
405
|
+
<div class="pane-meta" id="pane-meta"></div>
|
|
269
406
|
</div>
|
|
270
|
-
<
|
|
407
|
+
<div class="pane-content" id="pane-content"></div>
|
|
271
408
|
</div>
|
|
272
409
|
|
|
273
410
|
<script>
|
|
@@ -276,28 +413,52 @@
|
|
|
276
413
|
|
|
277
414
|
const svg = d3.select("#graph")
|
|
278
415
|
.attr("width", width)
|
|
279
|
-
.attr("height", height)
|
|
280
|
-
.call(d3.zoom().on("zoom", (event) => {
|
|
281
|
-
g.attr("transform", event.transform);
|
|
282
|
-
}));
|
|
416
|
+
.attr("height", height);
|
|
283
417
|
|
|
284
418
|
const g = svg.append("g");
|
|
285
419
|
const tooltip = d3.select("#tooltip");
|
|
420
|
+
|
|
421
|
+
const zoom = d3.zoom().on("zoom", (event) => {
|
|
422
|
+
g.attr("transform", event.transform);
|
|
423
|
+
});
|
|
424
|
+
svg.call(zoom);
|
|
425
|
+
|
|
286
426
|
let simulation;
|
|
287
427
|
let allNodes = [];
|
|
288
428
|
let allLinks = [];
|
|
289
|
-
let allLinksOriginal = []; // Keep original link structure before D3 modifies it
|
|
290
429
|
let visibleNodes = new Set();
|
|
291
430
|
let collapsedNodes = new Set();
|
|
292
431
|
let highlightedNode = null;
|
|
432
|
+
let currentNode = null; // Track currently selected node
|
|
433
|
+
|
|
434
|
+
// Add arrow markers for different relationship types
|
|
435
|
+
const defs = svg.append("defs");
|
|
436
|
+
|
|
437
|
+
const markerTypes = [
|
|
438
|
+
{id: "arrow-directory", color: "#58a6ff"}, // Blue for folder→file
|
|
439
|
+
{id: "arrow-file", color: "#8b949e"}, // Gray for file→section
|
|
440
|
+
{id: "arrow-ast", color: "#a371f7"} // Purple for section→AST
|
|
441
|
+
];
|
|
442
|
+
|
|
443
|
+
markerTypes.forEach(type => {
|
|
444
|
+
defs.append("marker")
|
|
445
|
+
.attr("id", type.id)
|
|
446
|
+
.attr("viewBox", "0 -5 10 10")
|
|
447
|
+
.attr("refX", 20)
|
|
448
|
+
.attr("refY", 0)
|
|
449
|
+
.attr("markerWidth", 6)
|
|
450
|
+
.attr("markerHeight", 6)
|
|
451
|
+
.attr("orient", "auto")
|
|
452
|
+
.append("path")
|
|
453
|
+
.attr("d", "M0,-5L10,0L0,5")
|
|
454
|
+
.attr("fill", type.color);
|
|
455
|
+
});
|
|
293
456
|
|
|
294
457
|
function visualizeGraph(data) {
|
|
295
458
|
g.selectAll("*").remove();
|
|
296
459
|
|
|
297
460
|
allNodes = data.nodes;
|
|
298
461
|
allLinks = data.links;
|
|
299
|
-
// Deep copy links before D3 modifies them
|
|
300
|
-
allLinksOriginal = JSON.parse(JSON.stringify(data.links));
|
|
301
462
|
|
|
302
463
|
// Find root nodes - start with only top-level nodes
|
|
303
464
|
let rootNodes;
|
|
@@ -305,21 +466,36 @@
|
|
|
305
466
|
// In monorepos, subproject nodes are roots
|
|
306
467
|
rootNodes = allNodes.filter(n => n.type === 'subproject');
|
|
307
468
|
} else {
|
|
308
|
-
// Regular projects:
|
|
309
|
-
|
|
310
|
-
const
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
469
|
+
// Regular projects: Find root nodes by checking graph structure
|
|
470
|
+
// Root nodes are nodes with no incoming links (no parent)
|
|
471
|
+
const targetIds = new Set(allLinks.map(link => link.target));
|
|
472
|
+
const allRootNodes = allNodes.filter(node => !targetIds.has(node.id));
|
|
473
|
+
|
|
474
|
+
console.log(`All root nodes found via links: ${allRootNodes.length}`, allRootNodes.map(n => `${n.name} (${n.type})`));
|
|
475
|
+
|
|
476
|
+
// Filter to show only directories and files at root
|
|
477
|
+
// Exclude orphaned files (files with no directory parent) from initial view
|
|
478
|
+
// These are typically config/doc files at project root
|
|
479
|
+
rootNodes = allRootNodes.filter(n => {
|
|
480
|
+
// Include all directories
|
|
481
|
+
if (n.type === 'directory') return true;
|
|
482
|
+
|
|
483
|
+
// Include files only if they're in a directory structure
|
|
484
|
+
// Exclude standalone files with no dir_path (project root files)
|
|
485
|
+
if (n.type === 'file' && n.dir_path === null) {
|
|
486
|
+
console.log(`Filtering out orphaned root file: ${n.name}`);
|
|
487
|
+
return false;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
return false; // Exclude all other types (classes, functions, etc.)
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
console.log(`Filtered root nodes (directories only): ${rootNodes.length}`, rootNodes.map(n => n.name));
|
|
494
|
+
|
|
495
|
+
// Fallback only if we got 0 root nodes (shouldn't happen with correct data)
|
|
496
|
+
if (rootNodes.length === 0) {
|
|
497
|
+
console.warn('No root nodes after filtering, using all root nodes');
|
|
498
|
+
rootNodes = allRootNodes;
|
|
323
499
|
}
|
|
324
500
|
}
|
|
325
501
|
|
|
@@ -327,58 +503,36 @@
|
|
|
327
503
|
visibleNodes = new Set(rootNodes.map(n => n.id));
|
|
328
504
|
collapsedNodes = new Set(rootNodes.map(n => n.id));
|
|
329
505
|
|
|
506
|
+
console.log('=== INITIAL STATE ===');
|
|
507
|
+
console.log(`Total nodes: ${allNodes.length}`);
|
|
508
|
+
console.log(`Total links: ${allLinks.length}`);
|
|
509
|
+
console.log(`Root nodes: ${rootNodes.length}`, rootNodes.map(n => n.name));
|
|
510
|
+
console.log(`Initial visibleNodes:`, Array.from(visibleNodes));
|
|
511
|
+
console.log(`Initial collapsedNodes:`, Array.from(collapsedNodes));
|
|
512
|
+
|
|
330
513
|
renderGraph();
|
|
331
514
|
}
|
|
332
515
|
|
|
333
516
|
function renderGraph() {
|
|
517
|
+
console.log('=== renderGraph() called ===');
|
|
518
|
+
console.log(`visibleNodes.size: ${visibleNodes.size}`);
|
|
519
|
+
console.log(`collapsedNodes.size: ${collapsedNodes.size}`);
|
|
520
|
+
|
|
334
521
|
const visibleNodesList = allNodes.filter(n => visibleNodes.has(n.id));
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
if (d.type === 'file') return 32;
|
|
344
|
-
if (d.type === 'subproject') return 38;
|
|
345
|
-
return d.complexity ? Math.min(20 + d.complexity * 2, 35) : 25;
|
|
346
|
-
};
|
|
347
|
-
|
|
348
|
-
// Dynamic link distance based on node types
|
|
349
|
-
const getLinkDistance = (link) => {
|
|
350
|
-
const source = typeof link.source === 'object' ? link.source : visibleNodesList.find(n => n.id === link.source);
|
|
351
|
-
const target = typeof link.target === 'object' ? link.target : visibleNodesList.find(n => n.id === link.target);
|
|
352
|
-
|
|
353
|
-
if (!source || !target) return 150;
|
|
354
|
-
|
|
355
|
-
// Longer distances for directory relationships
|
|
356
|
-
if (source.type === 'directory' || target.type === 'directory') return 200;
|
|
357
|
-
if (source.type === 'file' || target.type === 'file') return 150;
|
|
358
|
-
return 100;
|
|
359
|
-
};
|
|
522
|
+
console.log(`Rendering ${visibleNodesList.length} visible nodes`);
|
|
523
|
+
|
|
524
|
+
const visibleLinks = allLinks.filter(l => {
|
|
525
|
+
const sourceId = typeof l.source === 'object' ? l.source.id : l.source;
|
|
526
|
+
const targetId = typeof l.target === 'object' ? l.target.id : l.target;
|
|
527
|
+
return visibleNodes.has(sourceId) && visibleNodes.has(targetId);
|
|
528
|
+
});
|
|
529
|
+
console.log(`Rendering ${visibleLinks.length} visible links`);
|
|
360
530
|
|
|
361
531
|
simulation = d3.forceSimulation(visibleNodesList)
|
|
362
|
-
.force("link", d3.forceLink(visibleLinks)
|
|
363
|
-
|
|
364
|
-
.distance(getLinkDistance)
|
|
365
|
-
.strength(0.5))
|
|
366
|
-
.force("charge", d3.forceManyBody()
|
|
367
|
-
.strength(d => {
|
|
368
|
-
// Stronger repulsion for directories and files
|
|
369
|
-
if (d.type === 'directory') return -800;
|
|
370
|
-
if (d.type === 'file') return -600;
|
|
371
|
-
return -400;
|
|
372
|
-
})
|
|
373
|
-
.distanceMax(500))
|
|
532
|
+
.force("link", d3.forceLink(visibleLinks).id(d => d.id).distance(100))
|
|
533
|
+
.force("charge", d3.forceManyBody().strength(-400))
|
|
374
534
|
.force("center", d3.forceCenter(width / 2, height / 2))
|
|
375
|
-
.force("collision", d3.forceCollide()
|
|
376
|
-
.radius(getNodeRadius)
|
|
377
|
-
.strength(0.9))
|
|
378
|
-
.force("x", d3.forceX(width / 2).strength(0.05))
|
|
379
|
-
.force("y", d3.forceY(height / 2).strength(0.05))
|
|
380
|
-
.alphaDecay(0.02)
|
|
381
|
-
.velocityDecay(0.3);
|
|
535
|
+
.force("collision", d3.forceCollide().radius(40));
|
|
382
536
|
|
|
383
537
|
g.selectAll("*").remove();
|
|
384
538
|
|
|
@@ -386,7 +540,33 @@
|
|
|
386
540
|
.selectAll("line")
|
|
387
541
|
.data(visibleLinks)
|
|
388
542
|
.join("line")
|
|
389
|
-
.attr("class", d => d.type === "dependency" ? "link dependency" : "link")
|
|
543
|
+
.attr("class", d => d.type === "dependency" ? "link dependency" : "link")
|
|
544
|
+
.attr("marker-end", d => {
|
|
545
|
+
const sourceId = typeof d.source === 'object' ? d.source.id : d.source;
|
|
546
|
+
const targetId = typeof d.target === 'object' ? d.target.id : d.target;
|
|
547
|
+
const source = allNodes.find(n => n.id === sourceId);
|
|
548
|
+
const target = allNodes.find(n => n.id === targetId);
|
|
549
|
+
|
|
550
|
+
if (source && target) {
|
|
551
|
+
if (source.type === 'directory' && target.type === 'file') {
|
|
552
|
+
return "url(#arrow-directory)";
|
|
553
|
+
} else if (source.type === 'file' && ['class', 'function', 'method', 'imports'].includes(target.type)) {
|
|
554
|
+
return "url(#arrow-file)";
|
|
555
|
+
} else {
|
|
556
|
+
return "url(#arrow-ast)";
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
return "url(#arrow-ast)";
|
|
560
|
+
})
|
|
561
|
+
.attr("stroke", d => {
|
|
562
|
+
const sourceId = typeof d.source === 'object' ? d.source.id : d.source;
|
|
563
|
+
const source = allNodes.find(n => n.id === sourceId);
|
|
564
|
+
if (source) {
|
|
565
|
+
if (source.type === 'directory') return "#58a6ff";
|
|
566
|
+
if (source.type === 'file') return "#8b949e";
|
|
567
|
+
}
|
|
568
|
+
return "#a371f7";
|
|
569
|
+
});
|
|
390
570
|
|
|
391
571
|
const node = g.append("g")
|
|
392
572
|
.selectAll("g")
|
|
@@ -404,15 +584,73 @@
|
|
|
404
584
|
.on("mouseover", showTooltip)
|
|
405
585
|
.on("mouseout", hideTooltip);
|
|
406
586
|
|
|
407
|
-
// Add circles
|
|
408
|
-
|
|
587
|
+
// Add shapes based on node type (circles for code, squares for docs)
|
|
588
|
+
const isDocNode = d => ['docstring', 'comment'].includes(d.type);
|
|
589
|
+
|
|
590
|
+
// Add circles for code nodes
|
|
591
|
+
node.filter(d => !isDocNode(d))
|
|
592
|
+
.append("circle")
|
|
409
593
|
.attr("r", d => {
|
|
410
|
-
if (d.type === '
|
|
411
|
-
if (d.type === '
|
|
412
|
-
if (d.type === '
|
|
413
|
-
return d.complexity ? Math.min(
|
|
594
|
+
if (d.type === 'subproject') return 20;
|
|
595
|
+
if (d.type === 'directory') return 40; // Largest for directory containers
|
|
596
|
+
if (d.type === 'file') return 30; // Larger transparent circle for files
|
|
597
|
+
return d.complexity ? Math.min(8 + d.complexity * 2, 25) : 12;
|
|
598
|
+
})
|
|
599
|
+
.attr("stroke", d => hasChildren(d) ? "#ffffff" : "none")
|
|
600
|
+
.attr("stroke-width", d => hasChildren(d) ? 3 : 0)
|
|
601
|
+
.style("fill", d => d.color || null) // Use custom color if available
|
|
602
|
+
.style("cursor", "pointer"); // Ensure cursor shows pointer on all nodes
|
|
603
|
+
|
|
604
|
+
// Add rectangles for document nodes
|
|
605
|
+
node.filter(d => isDocNode(d))
|
|
606
|
+
.append("rect")
|
|
607
|
+
.attr("width", d => {
|
|
608
|
+
const size = d.complexity ? Math.min(8 + d.complexity * 2, 25) : 12;
|
|
609
|
+
return size * 2;
|
|
610
|
+
})
|
|
611
|
+
.attr("height", d => {
|
|
612
|
+
const size = d.complexity ? Math.min(8 + d.complexity * 2, 25) : 12;
|
|
613
|
+
return size * 2;
|
|
614
|
+
})
|
|
615
|
+
.attr("x", d => {
|
|
616
|
+
const size = d.complexity ? Math.min(8 + d.complexity * 2, 25) : 12;
|
|
617
|
+
return -size;
|
|
618
|
+
})
|
|
619
|
+
.attr("y", d => {
|
|
620
|
+
const size = d.complexity ? Math.min(8 + d.complexity * 2, 25) : 12;
|
|
621
|
+
return -size;
|
|
414
622
|
})
|
|
415
|
-
.
|
|
623
|
+
.attr("rx", 2) // Rounded corners
|
|
624
|
+
.attr("ry", 2)
|
|
625
|
+
.attr("stroke", d => hasChildren(d) ? "#ffffff" : "none")
|
|
626
|
+
.attr("stroke-width", d => hasChildren(d) ? 3 : 0)
|
|
627
|
+
.style("fill", d => d.color || null)
|
|
628
|
+
.style("cursor", "pointer"); // Ensure cursor shows pointer on all nodes
|
|
629
|
+
|
|
630
|
+
// Add expand/collapse indicator - circle background below node name
|
|
631
|
+
node.filter(d => hasChildren(d))
|
|
632
|
+
.append("circle")
|
|
633
|
+
.attr("class", "expand-circle")
|
|
634
|
+
.attr("r", 14)
|
|
635
|
+
.attr("cy", 35) // Position below the text
|
|
636
|
+
.attr("fill", "#2c2c2c")
|
|
637
|
+
.attr("stroke", "#ffeb3b")
|
|
638
|
+
.attr("stroke-width", 2.5)
|
|
639
|
+
.style("cursor", "pointer")
|
|
640
|
+
.style("pointer-events", "all");
|
|
641
|
+
|
|
642
|
+
// Add +/- text inside the circle
|
|
643
|
+
node.filter(d => hasChildren(d))
|
|
644
|
+
.append("text")
|
|
645
|
+
.attr("class", "expand-indicator")
|
|
646
|
+
.attr("y", 35) // Same position as circle
|
|
647
|
+
.attr("dy", 6) // Center vertically within circle
|
|
648
|
+
.attr("text-anchor", "middle")
|
|
649
|
+
.style("font-size", "22px")
|
|
650
|
+
.style("font-weight", "bold")
|
|
651
|
+
.style("fill", "#ffeb3b")
|
|
652
|
+
.style("pointer-events", "none")
|
|
653
|
+
.text(d => collapsedNodes.has(d.id) ? "+" : "−");
|
|
416
654
|
|
|
417
655
|
// Add icons based on node type
|
|
418
656
|
node.append("text")
|
|
@@ -426,24 +664,66 @@
|
|
|
426
664
|
if (d.type === 'method') return 'm';
|
|
427
665
|
if (d.type === 'module') return 'M';
|
|
428
666
|
if (d.type === 'imports') return '⇄';
|
|
667
|
+
if (d.type === 'docstring') return '📝';
|
|
668
|
+
if (d.type === 'comment') return '💬';
|
|
669
|
+
if (d.type === 'subproject') return '📦';
|
|
429
670
|
return '•';
|
|
430
671
|
});
|
|
431
672
|
|
|
432
|
-
// Add
|
|
433
|
-
node.filter(d => hasChildren(d))
|
|
434
|
-
.append("text")
|
|
435
|
-
.attr("class", "expand-indicator")
|
|
436
|
-
.attr("text-anchor", "middle")
|
|
437
|
-
.attr("dy", -20)
|
|
438
|
-
.style("font-size", "14px")
|
|
439
|
-
.style("font-weight", "bold")
|
|
440
|
-
.style("fill", "#58a6ff")
|
|
441
|
-
.style("pointer-events", "none")
|
|
442
|
-
.text(d => collapsedNodes.has(d.id) ? "+" : "−");
|
|
443
|
-
|
|
444
|
-
// Add labels
|
|
673
|
+
// Add labels (show actual import statement for import nodes, better names for text chunks)
|
|
445
674
|
node.append("text")
|
|
446
|
-
.text(d =>
|
|
675
|
+
.text(d => {
|
|
676
|
+
// Import nodes have type === 'imports'
|
|
677
|
+
if (d.type === 'imports') {
|
|
678
|
+
if (d.content) {
|
|
679
|
+
// Extract first line of import statement
|
|
680
|
+
const importLine = d.content.split('\n')[0].trim();
|
|
681
|
+
// Truncate if too long (max 60 chars)
|
|
682
|
+
return importLine.length > 60 ? importLine.substring(0, 57) + '...' : importLine;
|
|
683
|
+
}
|
|
684
|
+
return d.name; // Fallback to name if no content
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
// Text chunk nodes - show content preview instead of just line numbers
|
|
688
|
+
if (d.type === 'text' && d.content) {
|
|
689
|
+
// Extract first line of content
|
|
690
|
+
const firstLine = d.content.split('\n')[0].trim();
|
|
691
|
+
if (firstLine.length > 0) {
|
|
692
|
+
// Truncate if too long (max 40 chars for graph labels)
|
|
693
|
+
const preview = firstLine.length > 40 ? firstLine.substring(0, 37) + '...' : firstLine;
|
|
694
|
+
return preview;
|
|
695
|
+
}
|
|
696
|
+
// Fallback to line numbers if content is empty
|
|
697
|
+
if (d.start_line) {
|
|
698
|
+
return `L${d.start_line}-${d.end_line}`;
|
|
699
|
+
}
|
|
700
|
+
return d.name;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
// Code chunk nodes - show parent context if available
|
|
704
|
+
if (d.type === 'code' && d.start_line) {
|
|
705
|
+
// Try to find parent class/function by looking at incoming links
|
|
706
|
+
const parentLink = allLinks.find(l => {
|
|
707
|
+
const targetId = typeof l.target === 'object' ? l.target.id : l.target;
|
|
708
|
+
return targetId === d.id;
|
|
709
|
+
});
|
|
710
|
+
|
|
711
|
+
if (parentLink) {
|
|
712
|
+
const sourceId = typeof parentLink.source === 'object' ? parentLink.source.id : parentLink.source;
|
|
713
|
+
const parentNode = allNodes.find(n => n.id === sourceId);
|
|
714
|
+
|
|
715
|
+
if (parentNode && ['class', 'function', 'method'].includes(parentNode.type)) {
|
|
716
|
+
// Show parent context with line numbers
|
|
717
|
+
return `${parentNode.name}:L${d.start_line}-${d.end_line}`;
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
// Fallback: just show line numbers if no parent found
|
|
722
|
+
return `L${d.start_line}-${d.end_line}`;
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
return d.name;
|
|
726
|
+
})
|
|
447
727
|
.attr("dy", 30);
|
|
448
728
|
|
|
449
729
|
simulation.on("tick", () => {
|
|
@@ -459,60 +739,235 @@
|
|
|
459
739
|
updateStats({nodes: visibleNodesList, links: visibleLinks, metadata: {total_files: allNodes.length}});
|
|
460
740
|
}
|
|
461
741
|
|
|
742
|
+
function highlightNode(node) {
|
|
743
|
+
// Remove previous highlights - reset all to default stroke
|
|
744
|
+
d3.selectAll('.node circle, .node rect')
|
|
745
|
+
.attr('stroke-width', d => hasChildren(d) ? (d.type === 'file' || d.type === 'directory' ? 2 : 3) : 1.5)
|
|
746
|
+
.attr('stroke', d => {
|
|
747
|
+
if (hasChildren(d)) {
|
|
748
|
+
return '#ffffff';
|
|
749
|
+
}
|
|
750
|
+
return d.type === 'file' || d.type === 'directory' ? (d.type === 'file' ? '#58a6ff' : '#79c0ff') : '#30363d';
|
|
751
|
+
});
|
|
752
|
+
|
|
753
|
+
// Highlight the current node with blue stroke
|
|
754
|
+
const currentNodeElement = d3.selectAll('.node')
|
|
755
|
+
.filter(d => d.id === node.id);
|
|
756
|
+
|
|
757
|
+
currentNodeElement.select('circle, rect')
|
|
758
|
+
.attr('stroke', '#58a6ff')
|
|
759
|
+
.attr('stroke-width', 4);
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
function centerOnNode(node) {
|
|
763
|
+
const nodeElement = d3.selectAll('.node')
|
|
764
|
+
.filter(d => d.id === node.id);
|
|
765
|
+
|
|
766
|
+
if (nodeElement.empty()) return;
|
|
767
|
+
|
|
768
|
+
const nodeData = nodeElement.datum();
|
|
769
|
+
const transform = d3.zoomTransform(svg.node());
|
|
770
|
+
|
|
771
|
+
// Calculate center position
|
|
772
|
+
const x = -nodeData.x * transform.k + width / 2;
|
|
773
|
+
const y = -nodeData.y * transform.k + height / 2;
|
|
774
|
+
|
|
775
|
+
svg.transition()
|
|
776
|
+
.duration(750)
|
|
777
|
+
.call(zoom.transform, d3.zoomIdentity.translate(x, y).scale(transform.k));
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
function updateGraphForNode(node) {
|
|
781
|
+
if (!node) {
|
|
782
|
+
// Show default root view
|
|
783
|
+
renderGraph();
|
|
784
|
+
return;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
// Find parent
|
|
788
|
+
const parentLinks = allLinks.filter(l => {
|
|
789
|
+
const targetId = typeof l.target === 'object' ? l.target.id : l.target;
|
|
790
|
+
return targetId === node.id;
|
|
791
|
+
});
|
|
792
|
+
|
|
793
|
+
if (parentLinks.length === 0) {
|
|
794
|
+
// This is a root node - show all roots
|
|
795
|
+
renderGraph();
|
|
796
|
+
highlightNode(node);
|
|
797
|
+
return;
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
// Get parent
|
|
801
|
+
const parentId = typeof parentLinks[0].source === 'object'
|
|
802
|
+
? parentLinks[0].source.id
|
|
803
|
+
: parentLinks[0].source;
|
|
804
|
+
const parent = allNodes.find(n => n.id === parentId);
|
|
805
|
+
|
|
806
|
+
// Get all siblings (children of same parent)
|
|
807
|
+
const siblingLinks = allLinks.filter(l => {
|
|
808
|
+
const sourceId = typeof l.source === 'object' ? l.source.id : l.source;
|
|
809
|
+
return sourceId === parentId;
|
|
810
|
+
});
|
|
811
|
+
|
|
812
|
+
const siblings = siblingLinks.map(l => {
|
|
813
|
+
const targetId = typeof l.target === 'object' ? l.target.id : l.target;
|
|
814
|
+
return allNodes.find(n => n.id === targetId);
|
|
815
|
+
}).filter(n => n);
|
|
816
|
+
|
|
817
|
+
// Update visible nodes to show parent + siblings + current node's children
|
|
818
|
+
visibleNodes.clear();
|
|
819
|
+
visibleNodes.add(parentId); // Parent
|
|
820
|
+
siblings.forEach(s => visibleNodes.add(s.id)); // All siblings including current
|
|
821
|
+
|
|
822
|
+
// If current node is expanded, show its children too
|
|
823
|
+
if (!collapsedNodes.has(node.id)) {
|
|
824
|
+
const childLinks = allLinks.filter(l => {
|
|
825
|
+
const sourceId = typeof l.source === 'object' ? l.source.id : l.source;
|
|
826
|
+
return sourceId === node.id;
|
|
827
|
+
});
|
|
828
|
+
childLinks.forEach(l => {
|
|
829
|
+
const targetId = typeof l.target === 'object' ? l.target.id : l.target;
|
|
830
|
+
visibleNodes.add(targetId);
|
|
831
|
+
});
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
renderGraph();
|
|
835
|
+
highlightNode(node);
|
|
836
|
+
}
|
|
837
|
+
|
|
462
838
|
function hasChildren(node) {
|
|
463
|
-
//
|
|
464
|
-
|
|
839
|
+
// Handle both pre-mutation (string IDs) and post-mutation (object references)
|
|
840
|
+
const result = allLinks.some(l => {
|
|
841
|
+
const sourceId = typeof l.source === 'object' ? l.source.id : l.source;
|
|
842
|
+
return sourceId === node.id;
|
|
843
|
+
});
|
|
844
|
+
console.log(`hasChildren(${node.name}):`, result,
|
|
845
|
+
`Checking ${allLinks.length} links for node ID: ${node.id}`);
|
|
846
|
+
return result;
|
|
465
847
|
}
|
|
466
848
|
|
|
467
849
|
function handleNodeClick(event, d) {
|
|
850
|
+
console.log('=== NODE CLICKED ===');
|
|
851
|
+
console.log('Node:', d.name, 'Type:', d.type, 'ID:', d.id);
|
|
852
|
+
console.log('Event:', event);
|
|
853
|
+
|
|
468
854
|
event.stopPropagation();
|
|
469
855
|
|
|
470
|
-
//
|
|
471
|
-
|
|
472
|
-
|
|
856
|
+
// Track current node
|
|
857
|
+
currentNode = d;
|
|
858
|
+
|
|
859
|
+
// Always show content pane when clicking any node
|
|
860
|
+
showContentPane(d);
|
|
861
|
+
|
|
862
|
+
// If node has children, also toggle expansion
|
|
863
|
+
const nodeHasChildren = hasChildren(d);
|
|
864
|
+
console.log('Node has children:', nodeHasChildren);
|
|
865
|
+
|
|
866
|
+
if (nodeHasChildren) {
|
|
867
|
+
const isCollapsed = collapsedNodes.has(d.id);
|
|
868
|
+
console.log('Node is collapsed:', isCollapsed);
|
|
869
|
+
|
|
870
|
+
if (isCollapsed) {
|
|
871
|
+
console.log('Expanding node...');
|
|
473
872
|
expandNode(d);
|
|
474
873
|
} else {
|
|
874
|
+
console.log('Collapsing node...');
|
|
475
875
|
collapseNode(d);
|
|
476
876
|
}
|
|
877
|
+
console.log('Calling renderGraph()...');
|
|
477
878
|
renderGraph();
|
|
879
|
+
console.log('renderGraph() complete');
|
|
478
880
|
} else {
|
|
479
|
-
|
|
480
|
-
showCodeViewer(d);
|
|
881
|
+
console.log('Node has no children, skipping expansion');
|
|
481
882
|
}
|
|
883
|
+
|
|
884
|
+
// Highlight in graph
|
|
885
|
+
highlightNode(d);
|
|
482
886
|
}
|
|
483
887
|
|
|
484
888
|
function expandNode(node) {
|
|
889
|
+
console.log(`expandNode(${node.name}):`, 'Removing from collapsedNodes');
|
|
485
890
|
collapsedNodes.delete(node.id);
|
|
486
891
|
|
|
487
|
-
// Find direct children
|
|
488
|
-
const
|
|
489
|
-
|
|
490
|
-
|
|
892
|
+
// Find direct children
|
|
893
|
+
const childLinks = allLinks.filter(l => {
|
|
894
|
+
const sourceId = typeof l.source === 'object' ? l.source.id : l.source;
|
|
895
|
+
return sourceId === node.id;
|
|
896
|
+
});
|
|
897
|
+
console.log(` Found ${childLinks.length} child links for node ${node.id}`);
|
|
898
|
+
|
|
899
|
+
const children = childLinks
|
|
900
|
+
.map(l => {
|
|
901
|
+
const targetId = typeof l.target === 'object' ? l.target.id : l.target;
|
|
902
|
+
const child = allNodes.find(n => n.id === targetId);
|
|
903
|
+
if (!child) {
|
|
904
|
+
console.warn(` Could not find child node with ID: ${targetId}`);
|
|
905
|
+
}
|
|
906
|
+
return child;
|
|
907
|
+
})
|
|
491
908
|
.filter(n => n);
|
|
492
909
|
|
|
910
|
+
console.log(` Found ${children.length} child nodes:`, children.map(c => c.name));
|
|
911
|
+
|
|
493
912
|
children.forEach(child => {
|
|
913
|
+
console.log(` Adding child to visibleNodes: ${child.name} (${child.id})`);
|
|
494
914
|
visibleNodes.add(child.id);
|
|
495
915
|
collapsedNodes.add(child.id); // Children start collapsed
|
|
496
916
|
});
|
|
917
|
+
|
|
918
|
+
console.log(` visibleNodes now has ${visibleNodes.size} items`);
|
|
497
919
|
}
|
|
498
920
|
|
|
499
921
|
function collapseNode(node) {
|
|
922
|
+
console.log(`Collapsing node: ${node.name}`);
|
|
500
923
|
collapsedNodes.add(node.id);
|
|
501
924
|
|
|
502
|
-
//
|
|
503
|
-
|
|
504
|
-
const
|
|
505
|
-
|
|
506
|
-
|
|
925
|
+
// Check if this is a root node
|
|
926
|
+
const isRootNode = !allLinks.some(l => {
|
|
927
|
+
const targetId = typeof l.target === 'object' ? l.target.id : l.target;
|
|
928
|
+
return targetId === node.id;
|
|
929
|
+
});
|
|
507
930
|
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
931
|
+
if (isRootNode) {
|
|
932
|
+
// Collapsing a root node - return to root-only view
|
|
933
|
+
console.log(' Collapsing root node - resetting to root view');
|
|
934
|
+
|
|
935
|
+
// Clear all visible nodes except root nodes
|
|
936
|
+
const rootNodes = allNodes.filter(n => {
|
|
937
|
+
const hasIncomingLinks = allLinks.some(l => {
|
|
938
|
+
const targetId = typeof l.target === 'object' ? l.target.id : l.target;
|
|
939
|
+
return targetId === n.id;
|
|
940
|
+
});
|
|
941
|
+
return !hasIncomingLinks && n.type === 'directory';
|
|
512
942
|
});
|
|
513
|
-
}
|
|
514
943
|
|
|
515
|
-
|
|
944
|
+
visibleNodes.clear();
|
|
945
|
+
rootNodes.forEach(n => visibleNodes.add(n.id));
|
|
946
|
+
|
|
947
|
+
// Clear all collapsed states
|
|
948
|
+
collapsedNodes.clear();
|
|
949
|
+
// Re-add collapsed state to all root nodes
|
|
950
|
+
rootNodes.forEach(n => collapsedNodes.add(n.id));
|
|
951
|
+
} else {
|
|
952
|
+
// Non-root node - existing recursive collapse logic
|
|
953
|
+
// Hide all descendants recursively
|
|
954
|
+
function hideDescendants(parentId) {
|
|
955
|
+
const children = allLinks
|
|
956
|
+
.filter(l => {
|
|
957
|
+
const sourceId = typeof l.source === 'object' ? l.source.id : l.source;
|
|
958
|
+
return sourceId === parentId;
|
|
959
|
+
})
|
|
960
|
+
.map(l => typeof l.target === 'object' ? l.target.id : l.target);
|
|
961
|
+
|
|
962
|
+
children.forEach(childId => {
|
|
963
|
+
visibleNodes.delete(childId);
|
|
964
|
+
collapsedNodes.delete(childId);
|
|
965
|
+
hideDescendants(childId);
|
|
966
|
+
});
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
hideDescendants(node.id);
|
|
970
|
+
}
|
|
516
971
|
}
|
|
517
972
|
|
|
518
973
|
function showTooltip(event, d) {
|
|
@@ -586,19 +1041,67 @@
|
|
|
586
1041
|
}
|
|
587
1042
|
}
|
|
588
1043
|
|
|
589
|
-
function
|
|
590
|
-
//
|
|
1044
|
+
function findReferences(node) {
|
|
1045
|
+
// Find all nodes that link TO this node (reverse lookup)
|
|
1046
|
+
const incomingLinks = allLinks.filter(l => {
|
|
1047
|
+
const targetId = typeof l.target === 'object' ? l.target.id : l.target;
|
|
1048
|
+
return targetId === node.id;
|
|
1049
|
+
});
|
|
1050
|
+
|
|
1051
|
+
const callers = incomingLinks.map(l => {
|
|
1052
|
+
const sourceId = typeof l.source === 'object' ? l.source.id : l.source;
|
|
1053
|
+
return allNodes.find(n => n.id === sourceId);
|
|
1054
|
+
}).filter(n => n);
|
|
1055
|
+
|
|
1056
|
+
return callers;
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
function findSemanticReferences(node) {
|
|
1060
|
+
// For semantic search, we'd ideally use the MCP vector search
|
|
1061
|
+
// For now, use simple heuristics based on name/content similarity
|
|
1062
|
+
|
|
1063
|
+
if (!node.content && !node.name) return [];
|
|
1064
|
+
|
|
1065
|
+
const searchTerms = node.name.toLowerCase().split(/[_\s]/);
|
|
1066
|
+
|
|
1067
|
+
const semanticMatches = allNodes.filter(other => {
|
|
1068
|
+
if (other.id === node.id) return false;
|
|
1069
|
+
if (!other.content && !other.name) return false;
|
|
1070
|
+
|
|
1071
|
+
const otherText = (other.content || other.name).toLowerCase();
|
|
1072
|
+
return searchTerms.some(term => term.length > 2 && otherText.includes(term));
|
|
1073
|
+
});
|
|
1074
|
+
|
|
1075
|
+
return semanticMatches.slice(0, 10); // Limit to top 10
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
function showContentPane(node) {
|
|
1079
|
+
// Track and highlight the node
|
|
1080
|
+
currentNode = node;
|
|
591
1081
|
highlightedNode = node;
|
|
592
|
-
renderGraph();
|
|
593
1082
|
|
|
594
|
-
//
|
|
595
|
-
|
|
596
|
-
const title = document.getElementById('viewer-title');
|
|
597
|
-
const meta = document.getElementById('viewer-meta');
|
|
598
|
-
const code = document.getElementById('viewer-code');
|
|
1083
|
+
// Enable focus button
|
|
1084
|
+
document.getElementById('focus-button').disabled = false;
|
|
599
1085
|
|
|
600
|
-
|
|
1086
|
+
// Populate content pane
|
|
1087
|
+
const pane = document.getElementById('content-pane');
|
|
1088
|
+
const title = document.getElementById('pane-title');
|
|
1089
|
+
const meta = document.getElementById('pane-meta');
|
|
1090
|
+
const content = document.getElementById('pane-content');
|
|
1091
|
+
|
|
1092
|
+
// Set title with actual import statement for import nodes
|
|
1093
|
+
if (node.type === 'imports') {
|
|
1094
|
+
if (node.content) {
|
|
1095
|
+
const importLine = node.content.split('\n')[0].trim();
|
|
1096
|
+
title.textContent = importLine;
|
|
1097
|
+
} else {
|
|
1098
|
+
title.textContent = `Import: ${node.name}`;
|
|
1099
|
+
}
|
|
1100
|
+
} else {
|
|
1101
|
+
title.textContent = node.name;
|
|
1102
|
+
}
|
|
601
1103
|
|
|
1104
|
+
// Set metadata
|
|
602
1105
|
let metaText = `${node.type} • ${node.file_path}`;
|
|
603
1106
|
if (node.start_line) {
|
|
604
1107
|
metaText += ` • Lines ${node.start_line}-${node.end_line}`;
|
|
@@ -608,33 +1111,929 @@
|
|
|
608
1111
|
}
|
|
609
1112
|
meta.textContent = metaText;
|
|
610
1113
|
|
|
611
|
-
//
|
|
1114
|
+
// Display content based on node type
|
|
1115
|
+
if (node.type === 'directory') {
|
|
1116
|
+
showDirectoryContents(node, content);
|
|
1117
|
+
} else if (node.type === 'file') {
|
|
1118
|
+
showFileContents(node, content);
|
|
1119
|
+
} else if (node.type === 'imports') {
|
|
1120
|
+
// Import nodes show import details
|
|
1121
|
+
showImportDetails(node, content);
|
|
1122
|
+
} else {
|
|
1123
|
+
// Class, function, method, code nodes
|
|
1124
|
+
showCodeContent(node, content);
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
pane.classList.add('visible');
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
function showDirectoryContents(node, container) {
|
|
1131
|
+
container.innerHTML = ''; // Clear first
|
|
1132
|
+
|
|
1133
|
+
// Add "Up" button if node has parent
|
|
1134
|
+
const parentLinks = allLinks.filter(l => {
|
|
1135
|
+
const targetId = typeof l.target === 'object' ? l.target.id : l.target;
|
|
1136
|
+
return targetId === node.id;
|
|
1137
|
+
});
|
|
1138
|
+
|
|
1139
|
+
if (parentLinks.length > 0) {
|
|
1140
|
+
const parentId = typeof parentLinks[0].source === 'object'
|
|
1141
|
+
? parentLinks[0].source.id
|
|
1142
|
+
: parentLinks[0].source;
|
|
1143
|
+
const parent = allNodes.find(n => n.id === parentId);
|
|
1144
|
+
|
|
1145
|
+
if (parent) {
|
|
1146
|
+
const upButton = document.createElement('div');
|
|
1147
|
+
upButton.style.cssText = 'margin-bottom: 15px; padding: 8px 12px; background-color: #21262d; border-radius: 6px; border: 1px solid #30363d; cursor: pointer; display: flex; align-items: center; gap: 8px; transition: background-color 0.2s;';
|
|
1148
|
+
|
|
1149
|
+
const upArrow = document.createElement('span');
|
|
1150
|
+
upArrow.textContent = '↑';
|
|
1151
|
+
upArrow.style.cssText = 'font-size: 20px; color: #58a6ff;';
|
|
1152
|
+
|
|
1153
|
+
const upText = document.createElement('span');
|
|
1154
|
+
upText.textContent = `Up to ${parent.name}`;
|
|
1155
|
+
upText.style.color = '#c9d1d9';
|
|
1156
|
+
|
|
1157
|
+
upButton.appendChild(upArrow);
|
|
1158
|
+
upButton.appendChild(upText);
|
|
1159
|
+
|
|
1160
|
+
upButton.addEventListener('mouseover', function() {
|
|
1161
|
+
this.style.backgroundColor = '#30363d';
|
|
1162
|
+
});
|
|
1163
|
+
upButton.addEventListener('mouseout', function() {
|
|
1164
|
+
this.style.backgroundColor = '#21262d';
|
|
1165
|
+
});
|
|
1166
|
+
upButton.addEventListener('click', () => {
|
|
1167
|
+
showContentPane(parent);
|
|
1168
|
+
updateGraphForNode(parent);
|
|
1169
|
+
});
|
|
1170
|
+
|
|
1171
|
+
container.appendChild(upButton);
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
// Find all direct children of this directory
|
|
1176
|
+
const children = allLinks
|
|
1177
|
+
.filter(l => {
|
|
1178
|
+
const sourceId = typeof l.source === 'object' ? l.source.id : l.source;
|
|
1179
|
+
return sourceId === node.id;
|
|
1180
|
+
})
|
|
1181
|
+
.map(l => {
|
|
1182
|
+
const targetId = typeof l.target === 'object' ? l.target.id : l.target;
|
|
1183
|
+
return allNodes.find(n => n.id === targetId);
|
|
1184
|
+
})
|
|
1185
|
+
.filter(n => n);
|
|
1186
|
+
|
|
1187
|
+
// Re-find siblings (already found parentLinks above)
|
|
1188
|
+
// const parentLinks = allLinks.filter(l => {
|
|
1189
|
+
// const targetId = typeof l.target === 'object' ? l.target.id : l.target;
|
|
1190
|
+
// return targetId === node.id;
|
|
1191
|
+
// });
|
|
1192
|
+
|
|
1193
|
+
let siblings = [];
|
|
1194
|
+
if (parentLinks.length > 0) {
|
|
1195
|
+
const parentId = typeof parentLinks[0].source === 'object'
|
|
1196
|
+
? parentLinks[0].source.id
|
|
1197
|
+
: parentLinks[0].source;
|
|
1198
|
+
|
|
1199
|
+
const siblingLinks = allLinks.filter(l => {
|
|
1200
|
+
const sourceId = typeof l.source === 'object' ? l.source.id : l.source;
|
|
1201
|
+
return sourceId === parentId;
|
|
1202
|
+
});
|
|
1203
|
+
|
|
1204
|
+
siblings = siblingLinks
|
|
1205
|
+
.map(l => {
|
|
1206
|
+
const targetId = typeof l.target === 'object' ? l.target.id : l.target;
|
|
1207
|
+
return allNodes.find(n => n.id === targetId);
|
|
1208
|
+
})
|
|
1209
|
+
.filter(n => n && n.id !== node.id); // Exclude self
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
// Display siblings first if they exist
|
|
1213
|
+
if (siblings.length > 0) {
|
|
1214
|
+
const siblingsHeader = document.createElement('h4');
|
|
1215
|
+
siblingsHeader.textContent = 'Siblings';
|
|
1216
|
+
siblingsHeader.style.color = '#8b949e';
|
|
1217
|
+
siblingsHeader.style.marginTop = '0';
|
|
1218
|
+
container.appendChild(siblingsHeader);
|
|
1219
|
+
|
|
1220
|
+
const siblingsList = document.createElement('ul');
|
|
1221
|
+
siblingsList.className = 'directory-list';
|
|
1222
|
+
|
|
1223
|
+
siblings.forEach(sibling => {
|
|
1224
|
+
const listItem = document.createElement('li');
|
|
1225
|
+
listItem.style.cursor = 'pointer';
|
|
1226
|
+
listItem.style.transition = 'background-color 0.2s';
|
|
1227
|
+
|
|
1228
|
+
const icon = sibling.type === 'directory' ? '📁' : sibling.type === 'file' ? '📄' : '📝';
|
|
1229
|
+
listItem.innerHTML = `
|
|
1230
|
+
<span class="item-icon">${icon}</span>
|
|
1231
|
+
${sibling.name}
|
|
1232
|
+
<span class="item-type">${sibling.type}</span>
|
|
1233
|
+
`;
|
|
1234
|
+
|
|
1235
|
+
listItem.addEventListener('mouseover', function() {
|
|
1236
|
+
this.style.backgroundColor = '#30363d';
|
|
1237
|
+
});
|
|
1238
|
+
listItem.addEventListener('mouseout', function() {
|
|
1239
|
+
this.style.backgroundColor = 'transparent';
|
|
1240
|
+
});
|
|
1241
|
+
listItem.addEventListener('click', function(event) {
|
|
1242
|
+
event.stopPropagation();
|
|
1243
|
+
if (hasChildren(sibling)) {
|
|
1244
|
+
if (collapsedNodes.has(sibling.id)) {
|
|
1245
|
+
expandNode(sibling);
|
|
1246
|
+
} else {
|
|
1247
|
+
collapseNode(sibling);
|
|
1248
|
+
}
|
|
1249
|
+
renderGraph();
|
|
1250
|
+
}
|
|
1251
|
+
showContentPane(sibling);
|
|
1252
|
+
updateGraphForNode(sibling);
|
|
1253
|
+
});
|
|
1254
|
+
|
|
1255
|
+
siblingsList.appendChild(listItem);
|
|
1256
|
+
});
|
|
1257
|
+
|
|
1258
|
+
container.appendChild(siblingsList);
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
// Add references section for applicable node types
|
|
1262
|
+
if (node.type === 'function' || node.type === 'class' || node.type === 'method' || node.type === 'code') {
|
|
1263
|
+
const directCallers = findReferences(node);
|
|
1264
|
+
const semanticRefs = findSemanticReferences(node);
|
|
1265
|
+
|
|
1266
|
+
if (directCallers.length > 0 || semanticRefs.length > 0) {
|
|
1267
|
+
const refsHeader = document.createElement('h4');
|
|
1268
|
+
refsHeader.textContent = 'References';
|
|
1269
|
+
refsHeader.style.color = '#8b949e';
|
|
1270
|
+
refsHeader.style.marginTop = siblings.length > 0 ? '20px' : '0';
|
|
1271
|
+
container.appendChild(refsHeader);
|
|
1272
|
+
|
|
1273
|
+
if (directCallers.length > 0) {
|
|
1274
|
+
const callersSubheader = document.createElement('h5');
|
|
1275
|
+
callersSubheader.textContent = 'Direct Callers (AST)';
|
|
1276
|
+
callersSubheader.style.color = '#58a6ff';
|
|
1277
|
+
callersSubheader.style.fontSize = '14px';
|
|
1278
|
+
callersSubheader.style.marginTop = '10px';
|
|
1279
|
+
container.appendChild(callersSubheader);
|
|
1280
|
+
|
|
1281
|
+
const callersList = document.createElement('ul');
|
|
1282
|
+
callersList.className = 'directory-list';
|
|
1283
|
+
|
|
1284
|
+
directCallers.forEach(caller => {
|
|
1285
|
+
const listItem = document.createElement('li');
|
|
1286
|
+
listItem.style.cursor = 'pointer';
|
|
1287
|
+
listItem.style.transition = 'background-color 0.2s';
|
|
1288
|
+
listItem.innerHTML = `
|
|
1289
|
+
<span class="item-icon">🔗</span>
|
|
1290
|
+
${caller.name}
|
|
1291
|
+
<span class="item-type">${caller.type}</span>
|
|
1292
|
+
`;
|
|
1293
|
+
|
|
1294
|
+
listItem.addEventListener('mouseover', function() {
|
|
1295
|
+
this.style.backgroundColor = '#30363d';
|
|
1296
|
+
});
|
|
1297
|
+
listItem.addEventListener('mouseout', function() {
|
|
1298
|
+
this.style.backgroundColor = 'transparent';
|
|
1299
|
+
});
|
|
1300
|
+
listItem.addEventListener('click', () => {
|
|
1301
|
+
showContentPane(caller);
|
|
1302
|
+
updateGraphForNode(caller);
|
|
1303
|
+
});
|
|
1304
|
+
|
|
1305
|
+
callersList.appendChild(listItem);
|
|
1306
|
+
});
|
|
1307
|
+
|
|
1308
|
+
container.appendChild(callersList);
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
if (semanticRefs.length > 0) {
|
|
1312
|
+
const semanticSubheader = document.createElement('h5');
|
|
1313
|
+
semanticSubheader.textContent = 'Semantic References';
|
|
1314
|
+
semanticSubheader.style.color = '#a371f7';
|
|
1315
|
+
semanticSubheader.style.fontSize = '14px';
|
|
1316
|
+
semanticSubheader.style.marginTop = '10px';
|
|
1317
|
+
container.appendChild(semanticSubheader);
|
|
1318
|
+
|
|
1319
|
+
const semanticList = document.createElement('ul');
|
|
1320
|
+
semanticList.className = 'directory-list';
|
|
1321
|
+
|
|
1322
|
+
semanticRefs.forEach(ref => {
|
|
1323
|
+
const listItem = document.createElement('li');
|
|
1324
|
+
listItem.style.cursor = 'pointer';
|
|
1325
|
+
listItem.style.transition = 'background-color 0.2s';
|
|
1326
|
+
listItem.style.borderLeft = '2px dotted #a371f7';
|
|
1327
|
+
listItem.style.paddingLeft = '10px';
|
|
1328
|
+
listItem.innerHTML = `
|
|
1329
|
+
<span class="item-icon">🔗</span>
|
|
1330
|
+
${ref.name}
|
|
1331
|
+
<span class="item-type">${ref.type}</span>
|
|
1332
|
+
`;
|
|
1333
|
+
|
|
1334
|
+
listItem.addEventListener('mouseover', function() {
|
|
1335
|
+
this.style.backgroundColor = '#30363d';
|
|
1336
|
+
});
|
|
1337
|
+
listItem.addEventListener('mouseout', function() {
|
|
1338
|
+
this.style.backgroundColor = 'transparent';
|
|
1339
|
+
});
|
|
1340
|
+
listItem.addEventListener('click', () => {
|
|
1341
|
+
showContentPane(ref);
|
|
1342
|
+
updateGraphForNode(ref);
|
|
1343
|
+
});
|
|
1344
|
+
|
|
1345
|
+
semanticList.appendChild(listItem);
|
|
1346
|
+
});
|
|
1347
|
+
|
|
1348
|
+
container.appendChild(semanticList);
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
// Display children
|
|
1354
|
+
if (children.length === 0) {
|
|
1355
|
+
const emptyMsg = document.createElement('p');
|
|
1356
|
+
emptyMsg.textContent = 'Empty directory';
|
|
1357
|
+
emptyMsg.style.color = '#8b949e';
|
|
1358
|
+
container.appendChild(emptyMsg);
|
|
1359
|
+
return;
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
const childrenHeader = document.createElement('h4');
|
|
1363
|
+
childrenHeader.textContent = 'Contents';
|
|
1364
|
+
childrenHeader.style.color = '#8b949e';
|
|
1365
|
+
if (siblings.length > 0) {
|
|
1366
|
+
childrenHeader.style.marginTop = '20px';
|
|
1367
|
+
} else {
|
|
1368
|
+
childrenHeader.style.marginTop = '0';
|
|
1369
|
+
}
|
|
1370
|
+
container.appendChild(childrenHeader);
|
|
1371
|
+
|
|
1372
|
+
// Group by type
|
|
1373
|
+
const files = children.filter(n => n.type === 'file');
|
|
1374
|
+
const subdirs = children.filter(n => n.type === 'directory');
|
|
1375
|
+
const chunks = children.filter(n => n.type !== 'file' && n.type !== 'directory');
|
|
1376
|
+
|
|
1377
|
+
const childrenList = document.createElement('ul');
|
|
1378
|
+
childrenList.className = 'directory-list';
|
|
1379
|
+
|
|
1380
|
+
// Show subdirectories first
|
|
1381
|
+
subdirs.forEach(child => {
|
|
1382
|
+
const listItem = document.createElement('li');
|
|
1383
|
+
listItem.style.cursor = 'pointer';
|
|
1384
|
+
listItem.style.transition = 'background-color 0.2s';
|
|
1385
|
+
listItem.innerHTML = `
|
|
1386
|
+
<span class="item-icon">📁</span>
|
|
1387
|
+
${child.name}
|
|
1388
|
+
<span class="item-type">directory</span>
|
|
1389
|
+
`;
|
|
1390
|
+
|
|
1391
|
+
listItem.addEventListener('mouseover', function() {
|
|
1392
|
+
this.style.backgroundColor = '#30363d';
|
|
1393
|
+
});
|
|
1394
|
+
listItem.addEventListener('mouseout', function() {
|
|
1395
|
+
this.style.backgroundColor = 'transparent';
|
|
1396
|
+
});
|
|
1397
|
+
listItem.addEventListener('click', function(event) {
|
|
1398
|
+
event.stopPropagation();
|
|
1399
|
+
if (hasChildren(child)) {
|
|
1400
|
+
if (collapsedNodes.has(child.id)) {
|
|
1401
|
+
expandNode(child);
|
|
1402
|
+
} else {
|
|
1403
|
+
collapseNode(child);
|
|
1404
|
+
}
|
|
1405
|
+
renderGraph();
|
|
1406
|
+
}
|
|
1407
|
+
showContentPane(child);
|
|
1408
|
+
updateGraphForNode(child);
|
|
1409
|
+
});
|
|
1410
|
+
|
|
1411
|
+
childrenList.appendChild(listItem);
|
|
1412
|
+
});
|
|
1413
|
+
|
|
1414
|
+
// Then files
|
|
1415
|
+
files.forEach(child => {
|
|
1416
|
+
const listItem = document.createElement('li');
|
|
1417
|
+
listItem.style.cursor = 'pointer';
|
|
1418
|
+
listItem.style.transition = 'background-color 0.2s';
|
|
1419
|
+
listItem.innerHTML = `
|
|
1420
|
+
<span class="item-icon">📄</span>
|
|
1421
|
+
${child.name}
|
|
1422
|
+
<span class="item-type">file</span>
|
|
1423
|
+
`;
|
|
1424
|
+
|
|
1425
|
+
listItem.addEventListener('mouseover', function() {
|
|
1426
|
+
this.style.backgroundColor = '#30363d';
|
|
1427
|
+
});
|
|
1428
|
+
listItem.addEventListener('mouseout', function() {
|
|
1429
|
+
this.style.backgroundColor = 'transparent';
|
|
1430
|
+
});
|
|
1431
|
+
listItem.addEventListener('click', function(event) {
|
|
1432
|
+
event.stopPropagation();
|
|
1433
|
+
if (hasChildren(child)) {
|
|
1434
|
+
if (collapsedNodes.has(child.id)) {
|
|
1435
|
+
expandNode(child);
|
|
1436
|
+
} else {
|
|
1437
|
+
collapseNode(child);
|
|
1438
|
+
}
|
|
1439
|
+
renderGraph();
|
|
1440
|
+
}
|
|
1441
|
+
showContentPane(child);
|
|
1442
|
+
updateGraphForNode(child);
|
|
1443
|
+
});
|
|
1444
|
+
|
|
1445
|
+
childrenList.appendChild(listItem);
|
|
1446
|
+
});
|
|
1447
|
+
|
|
1448
|
+
// Then code chunks
|
|
1449
|
+
chunks.forEach(child => {
|
|
1450
|
+
const icon = child.type === 'class' ? '🔷' : child.type === 'function' ? '⚡' : '📝';
|
|
1451
|
+
const listItem = document.createElement('li');
|
|
1452
|
+
listItem.style.cursor = 'pointer';
|
|
1453
|
+
listItem.style.transition = 'background-color 0.2s';
|
|
1454
|
+
|
|
1455
|
+
// Improve display name for text chunks
|
|
1456
|
+
let displayName = child.name;
|
|
1457
|
+
if (child.type === 'text' && child.content) {
|
|
1458
|
+
// For text chunks, show first line of content as preview
|
|
1459
|
+
const firstLine = child.content.split('\n')[0].trim();
|
|
1460
|
+
if (firstLine.length > 0) {
|
|
1461
|
+
displayName = firstLine.length > 50 ? firstLine.substring(0, 47) + '...' : firstLine;
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
listItem.innerHTML = `
|
|
1466
|
+
<span class="item-icon">${icon}</span>
|
|
1467
|
+
${displayName}
|
|
1468
|
+
<span class="item-type">${child.type}</span>
|
|
1469
|
+
`;
|
|
1470
|
+
|
|
1471
|
+
listItem.addEventListener('mouseover', function() {
|
|
1472
|
+
this.style.backgroundColor = '#30363d';
|
|
1473
|
+
});
|
|
1474
|
+
listItem.addEventListener('mouseout', function() {
|
|
1475
|
+
this.style.backgroundColor = 'transparent';
|
|
1476
|
+
});
|
|
1477
|
+
listItem.addEventListener('click', function(event) {
|
|
1478
|
+
event.stopPropagation();
|
|
1479
|
+
if (hasChildren(child)) {
|
|
1480
|
+
if (collapsedNodes.has(child.id)) {
|
|
1481
|
+
expandNode(child);
|
|
1482
|
+
} else {
|
|
1483
|
+
collapseNode(child);
|
|
1484
|
+
}
|
|
1485
|
+
renderGraph();
|
|
1486
|
+
}
|
|
1487
|
+
showContentPane(child);
|
|
1488
|
+
updateGraphForNode(child);
|
|
1489
|
+
});
|
|
1490
|
+
|
|
1491
|
+
childrenList.appendChild(listItem);
|
|
1492
|
+
});
|
|
1493
|
+
|
|
1494
|
+
container.appendChild(childrenList);
|
|
1495
|
+
|
|
1496
|
+
// Add summary
|
|
1497
|
+
const summary = document.createElement('p');
|
|
1498
|
+
summary.style.color = '#8b949e';
|
|
1499
|
+
summary.style.fontSize = '11px';
|
|
1500
|
+
summary.style.marginTop = '16px';
|
|
1501
|
+
summary.textContent = `Total: ${children.length} items (${subdirs.length} directories, ${files.length} files, ${chunks.length} code chunks)`;
|
|
1502
|
+
container.appendChild(summary);
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1505
|
+
function showFileContents(node, container) {
|
|
1506
|
+
container.innerHTML = ''; // Clear first
|
|
1507
|
+
|
|
1508
|
+
// Add "Up" button if node has parent
|
|
1509
|
+
const parentLinks = allLinks.filter(l => {
|
|
1510
|
+
const targetId = typeof l.target === 'object' ? l.target.id : l.target;
|
|
1511
|
+
return targetId === node.id;
|
|
1512
|
+
});
|
|
1513
|
+
|
|
1514
|
+
if (parentLinks.length > 0) {
|
|
1515
|
+
const parentId = typeof parentLinks[0].source === 'object'
|
|
1516
|
+
? parentLinks[0].source.id
|
|
1517
|
+
: parentLinks[0].source;
|
|
1518
|
+
const parent = allNodes.find(n => n.id === parentId);
|
|
1519
|
+
|
|
1520
|
+
if (parent) {
|
|
1521
|
+
const upButton = document.createElement('div');
|
|
1522
|
+
upButton.style.cssText = 'margin-bottom: 15px; padding: 8px 12px; background-color: #21262d; border-radius: 6px; border: 1px solid #30363d; cursor: pointer; display: flex; align-items: center; gap: 8px; transition: background-color 0.2s;';
|
|
1523
|
+
|
|
1524
|
+
const upArrow = document.createElement('span');
|
|
1525
|
+
upArrow.textContent = '↑';
|
|
1526
|
+
upArrow.style.cssText = 'font-size: 20px; color: #58a6ff;';
|
|
1527
|
+
|
|
1528
|
+
const upText = document.createElement('span');
|
|
1529
|
+
upText.textContent = `Up to ${parent.name}`;
|
|
1530
|
+
upText.style.color = '#c9d1d9';
|
|
1531
|
+
|
|
1532
|
+
upButton.appendChild(upArrow);
|
|
1533
|
+
upButton.appendChild(upText);
|
|
1534
|
+
|
|
1535
|
+
upButton.addEventListener('mouseover', function() {
|
|
1536
|
+
this.style.backgroundColor = '#30363d';
|
|
1537
|
+
});
|
|
1538
|
+
upButton.addEventListener('mouseout', function() {
|
|
1539
|
+
this.style.backgroundColor = '#21262d';
|
|
1540
|
+
});
|
|
1541
|
+
upButton.addEventListener('click', () => {
|
|
1542
|
+
showContentPane(parent);
|
|
1543
|
+
updateGraphForNode(parent);
|
|
1544
|
+
});
|
|
1545
|
+
|
|
1546
|
+
container.appendChild(upButton);
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1550
|
+
// Re-find siblings (already found parentLinks above)
|
|
1551
|
+
// const parentLinks = allLinks.filter(l => {
|
|
1552
|
+
// const targetId = typeof l.target === 'object' ? l.target.id : l.target;
|
|
1553
|
+
// return targetId === node.id;
|
|
1554
|
+
// });
|
|
1555
|
+
|
|
1556
|
+
let siblings = [];
|
|
1557
|
+
if (parentLinks.length > 0) {
|
|
1558
|
+
const parentId = typeof parentLinks[0].source === 'object'
|
|
1559
|
+
? parentLinks[0].source.id
|
|
1560
|
+
: parentLinks[0].source;
|
|
1561
|
+
|
|
1562
|
+
const siblingLinks = allLinks.filter(l => {
|
|
1563
|
+
const sourceId = typeof l.source === 'object' ? l.source.id : l.source;
|
|
1564
|
+
return sourceId === parentId;
|
|
1565
|
+
});
|
|
1566
|
+
|
|
1567
|
+
siblings = siblingLinks
|
|
1568
|
+
.map(l => {
|
|
1569
|
+
const targetId = typeof l.target === 'object' ? l.target.id : l.target;
|
|
1570
|
+
return allNodes.find(n => n.id === targetId);
|
|
1571
|
+
})
|
|
1572
|
+
.filter(n => n && n.id !== node.id); // Exclude self
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1575
|
+
// Display siblings first if they exist
|
|
1576
|
+
if (siblings.length > 0) {
|
|
1577
|
+
const siblingsHeader = document.createElement('h4');
|
|
1578
|
+
siblingsHeader.textContent = 'Siblings';
|
|
1579
|
+
siblingsHeader.style.color = '#8b949e';
|
|
1580
|
+
siblingsHeader.style.marginTop = '0';
|
|
1581
|
+
container.appendChild(siblingsHeader);
|
|
1582
|
+
|
|
1583
|
+
const siblingsList = document.createElement('ul');
|
|
1584
|
+
siblingsList.className = 'directory-list';
|
|
1585
|
+
|
|
1586
|
+
siblings.forEach(sibling => {
|
|
1587
|
+
const listItem = document.createElement('li');
|
|
1588
|
+
listItem.style.cursor = 'pointer';
|
|
1589
|
+
listItem.style.transition = 'background-color 0.2s';
|
|
1590
|
+
|
|
1591
|
+
const icon = sibling.type === 'directory' ? '📁' : sibling.type === 'file' ? '📄' : '📝';
|
|
1592
|
+
listItem.innerHTML = `
|
|
1593
|
+
<span class="item-icon">${icon}</span>
|
|
1594
|
+
${sibling.name}
|
|
1595
|
+
<span class="item-type">${sibling.type}</span>
|
|
1596
|
+
`;
|
|
1597
|
+
|
|
1598
|
+
listItem.addEventListener('mouseover', function() {
|
|
1599
|
+
this.style.backgroundColor = '#30363d';
|
|
1600
|
+
});
|
|
1601
|
+
listItem.addEventListener('mouseout', function() {
|
|
1602
|
+
this.style.backgroundColor = 'transparent';
|
|
1603
|
+
});
|
|
1604
|
+
listItem.addEventListener('click', function(event) {
|
|
1605
|
+
event.stopPropagation();
|
|
1606
|
+
if (hasChildren(sibling)) {
|
|
1607
|
+
if (collapsedNodes.has(sibling.id)) {
|
|
1608
|
+
expandNode(sibling);
|
|
1609
|
+
} else {
|
|
1610
|
+
collapseNode(sibling);
|
|
1611
|
+
}
|
|
1612
|
+
renderGraph();
|
|
1613
|
+
}
|
|
1614
|
+
showContentPane(sibling);
|
|
1615
|
+
updateGraphForNode(sibling);
|
|
1616
|
+
});
|
|
1617
|
+
|
|
1618
|
+
siblingsList.appendChild(listItem);
|
|
1619
|
+
});
|
|
1620
|
+
|
|
1621
|
+
container.appendChild(siblingsList);
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1624
|
+
// Add references section for applicable node types
|
|
1625
|
+
if (node.type === 'function' || node.type === 'class' || node.type === 'method' || node.type === 'code') {
|
|
1626
|
+
const directCallers = findReferences(node);
|
|
1627
|
+
const semanticRefs = findSemanticReferences(node);
|
|
1628
|
+
|
|
1629
|
+
if (directCallers.length > 0 || semanticRefs.length > 0) {
|
|
1630
|
+
const refsHeader = document.createElement('h4');
|
|
1631
|
+
refsHeader.textContent = 'References';
|
|
1632
|
+
refsHeader.style.color = '#8b949e';
|
|
1633
|
+
refsHeader.style.marginTop = siblings.length > 0 ? '20px' : '0';
|
|
1634
|
+
container.appendChild(refsHeader);
|
|
1635
|
+
|
|
1636
|
+
if (directCallers.length > 0) {
|
|
1637
|
+
const callersSubheader = document.createElement('h5');
|
|
1638
|
+
callersSubheader.textContent = 'Direct Callers (AST)';
|
|
1639
|
+
callersSubheader.style.color = '#58a6ff';
|
|
1640
|
+
callersSubheader.style.fontSize = '14px';
|
|
1641
|
+
callersSubheader.style.marginTop = '10px';
|
|
1642
|
+
container.appendChild(callersSubheader);
|
|
1643
|
+
|
|
1644
|
+
const callersList = document.createElement('ul');
|
|
1645
|
+
callersList.className = 'directory-list';
|
|
1646
|
+
|
|
1647
|
+
directCallers.forEach(caller => {
|
|
1648
|
+
const listItem = document.createElement('li');
|
|
1649
|
+
listItem.style.cursor = 'pointer';
|
|
1650
|
+
listItem.style.transition = 'background-color 0.2s';
|
|
1651
|
+
listItem.innerHTML = `
|
|
1652
|
+
<span class="item-icon">🔗</span>
|
|
1653
|
+
${caller.name}
|
|
1654
|
+
<span class="item-type">${caller.type}</span>
|
|
1655
|
+
`;
|
|
1656
|
+
|
|
1657
|
+
listItem.addEventListener('mouseover', function() {
|
|
1658
|
+
this.style.backgroundColor = '#30363d';
|
|
1659
|
+
});
|
|
1660
|
+
listItem.addEventListener('mouseout', function() {
|
|
1661
|
+
this.style.backgroundColor = 'transparent';
|
|
1662
|
+
});
|
|
1663
|
+
listItem.addEventListener('click', () => {
|
|
1664
|
+
showContentPane(caller);
|
|
1665
|
+
updateGraphForNode(caller);
|
|
1666
|
+
});
|
|
1667
|
+
|
|
1668
|
+
callersList.appendChild(listItem);
|
|
1669
|
+
});
|
|
1670
|
+
|
|
1671
|
+
container.appendChild(callersList);
|
|
1672
|
+
}
|
|
1673
|
+
|
|
1674
|
+
if (semanticRefs.length > 0) {
|
|
1675
|
+
const semanticSubheader = document.createElement('h5');
|
|
1676
|
+
semanticSubheader.textContent = 'Semantic References';
|
|
1677
|
+
semanticSubheader.style.color = '#a371f7';
|
|
1678
|
+
semanticSubheader.style.fontSize = '14px';
|
|
1679
|
+
semanticSubheader.style.marginTop = '10px';
|
|
1680
|
+
container.appendChild(semanticSubheader);
|
|
1681
|
+
|
|
1682
|
+
const semanticList = document.createElement('ul');
|
|
1683
|
+
semanticList.className = 'directory-list';
|
|
1684
|
+
|
|
1685
|
+
semanticRefs.forEach(ref => {
|
|
1686
|
+
const listItem = document.createElement('li');
|
|
1687
|
+
listItem.style.cursor = 'pointer';
|
|
1688
|
+
listItem.style.transition = 'background-color 0.2s';
|
|
1689
|
+
listItem.style.borderLeft = '2px dotted #a371f7';
|
|
1690
|
+
listItem.style.paddingLeft = '10px';
|
|
1691
|
+
listItem.innerHTML = `
|
|
1692
|
+
<span class="item-icon">🔗</span>
|
|
1693
|
+
${ref.name}
|
|
1694
|
+
<span class="item-type">${ref.type}</span>
|
|
1695
|
+
`;
|
|
1696
|
+
|
|
1697
|
+
listItem.addEventListener('mouseover', function() {
|
|
1698
|
+
this.style.backgroundColor = '#30363d';
|
|
1699
|
+
});
|
|
1700
|
+
listItem.addEventListener('mouseout', function() {
|
|
1701
|
+
this.style.backgroundColor = 'transparent';
|
|
1702
|
+
});
|
|
1703
|
+
listItem.addEventListener('click', () => {
|
|
1704
|
+
showContentPane(ref);
|
|
1705
|
+
updateGraphForNode(ref);
|
|
1706
|
+
});
|
|
1707
|
+
|
|
1708
|
+
semanticList.appendChild(listItem);
|
|
1709
|
+
});
|
|
1710
|
+
|
|
1711
|
+
container.appendChild(semanticList);
|
|
1712
|
+
}
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1716
|
+
// Find all chunks in this file
|
|
1717
|
+
const fileChunks = allLinks
|
|
1718
|
+
.filter(l => {
|
|
1719
|
+
const sourceId = typeof l.source === 'object' ? l.source.id : l.source;
|
|
1720
|
+
return sourceId === node.id;
|
|
1721
|
+
})
|
|
1722
|
+
.map(l => {
|
|
1723
|
+
const targetId = typeof l.target === 'object' ? l.target.id : l.target;
|
|
1724
|
+
return allNodes.find(n => n.id === targetId);
|
|
1725
|
+
})
|
|
1726
|
+
.filter(n => n);
|
|
1727
|
+
|
|
1728
|
+
if (fileChunks.length === 0) {
|
|
1729
|
+
const emptyMsg = document.createElement('p');
|
|
1730
|
+
emptyMsg.textContent = 'No code chunks found in this file';
|
|
1731
|
+
emptyMsg.style.color = '#8b949e';
|
|
1732
|
+
if (siblings.length > 0) {
|
|
1733
|
+
emptyMsg.style.marginTop = '20px';
|
|
1734
|
+
}
|
|
1735
|
+
container.appendChild(emptyMsg);
|
|
1736
|
+
return;
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
// Add chunks header
|
|
1740
|
+
const chunksHeader = document.createElement('h4');
|
|
1741
|
+
chunksHeader.textContent = 'Code Chunks';
|
|
1742
|
+
chunksHeader.style.color = '#8b949e';
|
|
1743
|
+
if (siblings.length > 0) {
|
|
1744
|
+
chunksHeader.style.marginTop = '20px';
|
|
1745
|
+
} else {
|
|
1746
|
+
chunksHeader.style.marginTop = '0';
|
|
1747
|
+
}
|
|
1748
|
+
container.appendChild(chunksHeader);
|
|
1749
|
+
|
|
1750
|
+
// Create clickable list of chunks
|
|
1751
|
+
const chunksList = document.createElement('ul');
|
|
1752
|
+
chunksList.className = 'directory-list';
|
|
1753
|
+
|
|
1754
|
+
fileChunks.forEach(chunk => {
|
|
1755
|
+
const icon = chunk.type === 'class' ? '🔷' :
|
|
1756
|
+
chunk.type === 'function' ? '⚡' :
|
|
1757
|
+
chunk.type === 'imports' ? '⇄' : '📝';
|
|
1758
|
+
const listItem = document.createElement('li');
|
|
1759
|
+
listItem.style.cursor = 'pointer';
|
|
1760
|
+
listItem.style.transition = 'background-color 0.2s';
|
|
1761
|
+
|
|
1762
|
+
// Improve display name for text chunks
|
|
1763
|
+
let displayName = chunk.name;
|
|
1764
|
+
if (chunk.type === 'text' && chunk.content) {
|
|
1765
|
+
// For text chunks, show first line of content as preview
|
|
1766
|
+
const firstLine = chunk.content.split('\n')[0].trim();
|
|
1767
|
+
if (firstLine.length > 0) {
|
|
1768
|
+
displayName = firstLine.length > 50 ? firstLine.substring(0, 47) + '...' : firstLine;
|
|
1769
|
+
}
|
|
1770
|
+
if (chunk.start_line) {
|
|
1771
|
+
displayName += ` (L${chunk.start_line}-${chunk.end_line})`;
|
|
1772
|
+
}
|
|
1773
|
+
} else if (chunk.start_line) {
|
|
1774
|
+
displayName += ` (L${chunk.start_line}-${chunk.end_line})`;
|
|
1775
|
+
}
|
|
1776
|
+
|
|
1777
|
+
listItem.innerHTML = `
|
|
1778
|
+
<span class="item-icon">${icon}</span>
|
|
1779
|
+
${displayName}
|
|
1780
|
+
<span class="item-type">${chunk.type}</span>
|
|
1781
|
+
`;
|
|
1782
|
+
|
|
1783
|
+
listItem.addEventListener('mouseover', function() {
|
|
1784
|
+
this.style.backgroundColor = '#30363d';
|
|
1785
|
+
});
|
|
1786
|
+
listItem.addEventListener('mouseout', function() {
|
|
1787
|
+
this.style.backgroundColor = 'transparent';
|
|
1788
|
+
});
|
|
1789
|
+
listItem.addEventListener('click', function(event) {
|
|
1790
|
+
event.stopPropagation();
|
|
1791
|
+
if (hasChildren(chunk)) {
|
|
1792
|
+
if (collapsedNodes.has(chunk.id)) {
|
|
1793
|
+
expandNode(chunk);
|
|
1794
|
+
} else {
|
|
1795
|
+
collapseNode(chunk);
|
|
1796
|
+
}
|
|
1797
|
+
renderGraph();
|
|
1798
|
+
}
|
|
1799
|
+
showContentPane(chunk);
|
|
1800
|
+
updateGraphForNode(chunk);
|
|
1801
|
+
});
|
|
1802
|
+
|
|
1803
|
+
chunksList.appendChild(listItem);
|
|
1804
|
+
});
|
|
1805
|
+
|
|
1806
|
+
container.appendChild(chunksList);
|
|
1807
|
+
|
|
1808
|
+
// Collect all content from chunks and sort by line number
|
|
1809
|
+
const sortedChunks = fileChunks
|
|
1810
|
+
.filter(c => c.content)
|
|
1811
|
+
.sort((a, b) => a.start_line - b.start_line);
|
|
1812
|
+
|
|
1813
|
+
if (sortedChunks.length > 0) {
|
|
1814
|
+
// Add full content section
|
|
1815
|
+
const contentHeader = document.createElement('h4');
|
|
1816
|
+
contentHeader.textContent = 'Full File Content';
|
|
1817
|
+
contentHeader.style.color = '#8b949e';
|
|
1818
|
+
contentHeader.style.marginTop = '20px';
|
|
1819
|
+
container.appendChild(contentHeader);
|
|
1820
|
+
|
|
1821
|
+
const contentInfo = document.createElement('p');
|
|
1822
|
+
contentInfo.textContent = `Contains ${fileChunks.length} code chunks`;
|
|
1823
|
+
contentInfo.style.color = '#8b949e';
|
|
1824
|
+
contentInfo.style.fontSize = '11px';
|
|
1825
|
+
contentInfo.style.marginBottom = '12px';
|
|
1826
|
+
container.appendChild(contentInfo);
|
|
1827
|
+
|
|
1828
|
+
// Combine all chunks to show full file
|
|
1829
|
+
const fullContent = sortedChunks.map(c => c.content).join('\n\n');
|
|
1830
|
+
const pre = document.createElement('pre');
|
|
1831
|
+
const code = document.createElement('code');
|
|
1832
|
+
code.textContent = fullContent;
|
|
1833
|
+
pre.appendChild(code);
|
|
1834
|
+
container.appendChild(pre);
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1837
|
+
|
|
1838
|
+
function showImportDetails(node, container) {
|
|
1839
|
+
// Import nodes (type === 'imports') - show import content prominently
|
|
1840
|
+
const importHtml = `
|
|
1841
|
+
<div class="import-details">
|
|
1842
|
+
${node.content ? `
|
|
1843
|
+
<div style="margin-bottom: 16px;">
|
|
1844
|
+
<div class="detail-label" style="margin-bottom: 8px;">Import Statement:</div>
|
|
1845
|
+
<pre><code>${escapeHtml(node.content)}</code></pre>
|
|
1846
|
+
</div>
|
|
1847
|
+
` : '<p style="color: #8b949e;">No import content available</p>'}
|
|
1848
|
+
<div class="detail-row">
|
|
1849
|
+
<span class="detail-label">File:</span> ${node.file_path}
|
|
1850
|
+
</div>
|
|
1851
|
+
${node.start_line ? `
|
|
1852
|
+
<div class="detail-row">
|
|
1853
|
+
<span class="detail-label">Location:</span> Lines ${node.start_line}-${node.end_line}
|
|
1854
|
+
</div>
|
|
1855
|
+
` : ''}
|
|
1856
|
+
${node.language ? `
|
|
1857
|
+
<div class="detail-row">
|
|
1858
|
+
<span class="detail-label">Language:</span> ${node.language}
|
|
1859
|
+
</div>
|
|
1860
|
+
` : ''}
|
|
1861
|
+
</div>
|
|
1862
|
+
`;
|
|
1863
|
+
|
|
1864
|
+
container.innerHTML = importHtml;
|
|
1865
|
+
}
|
|
1866
|
+
|
|
1867
|
+
function showCodeContent(node, container) {
|
|
1868
|
+
container.innerHTML = ''; // Clear first
|
|
1869
|
+
|
|
1870
|
+
// Add references section for applicable node types
|
|
1871
|
+
if (node.type === 'function' || node.type === 'class' || node.type === 'method' || node.type === 'code') {
|
|
1872
|
+
const directCallers = findReferences(node);
|
|
1873
|
+
const semanticRefs = findSemanticReferences(node);
|
|
1874
|
+
|
|
1875
|
+
if (directCallers.length > 0 || semanticRefs.length > 0) {
|
|
1876
|
+
const refsHeader = document.createElement('h4');
|
|
1877
|
+
refsHeader.textContent = 'References';
|
|
1878
|
+
refsHeader.style.color = '#8b949e';
|
|
1879
|
+
refsHeader.style.marginTop = '0';
|
|
1880
|
+
refsHeader.style.marginBottom = '10px';
|
|
1881
|
+
container.appendChild(refsHeader);
|
|
1882
|
+
|
|
1883
|
+
if (directCallers.length > 0) {
|
|
1884
|
+
const callersSubheader = document.createElement('h5');
|
|
1885
|
+
callersSubheader.textContent = 'Direct Callers (AST)';
|
|
1886
|
+
callersSubheader.style.color = '#58a6ff';
|
|
1887
|
+
callersSubheader.style.fontSize = '14px';
|
|
1888
|
+
callersSubheader.style.marginTop = '10px';
|
|
1889
|
+
container.appendChild(callersSubheader);
|
|
1890
|
+
|
|
1891
|
+
const callersList = document.createElement('ul');
|
|
1892
|
+
callersList.className = 'directory-list';
|
|
1893
|
+
|
|
1894
|
+
directCallers.forEach(caller => {
|
|
1895
|
+
const listItem = document.createElement('li');
|
|
1896
|
+
listItem.style.cursor = 'pointer';
|
|
1897
|
+
listItem.style.transition = 'background-color 0.2s';
|
|
1898
|
+
listItem.innerHTML = `
|
|
1899
|
+
<span class="item-icon">🔗</span>
|
|
1900
|
+
${caller.name}
|
|
1901
|
+
<span class="item-type">${caller.type}</span>
|
|
1902
|
+
`;
|
|
1903
|
+
|
|
1904
|
+
listItem.addEventListener('mouseover', function() {
|
|
1905
|
+
this.style.backgroundColor = '#30363d';
|
|
1906
|
+
});
|
|
1907
|
+
listItem.addEventListener('mouseout', function() {
|
|
1908
|
+
this.style.backgroundColor = 'transparent';
|
|
1909
|
+
});
|
|
1910
|
+
listItem.addEventListener('click', () => {
|
|
1911
|
+
showContentPane(caller);
|
|
1912
|
+
updateGraphForNode(caller);
|
|
1913
|
+
});
|
|
1914
|
+
|
|
1915
|
+
callersList.appendChild(listItem);
|
|
1916
|
+
});
|
|
1917
|
+
|
|
1918
|
+
container.appendChild(callersList);
|
|
1919
|
+
}
|
|
1920
|
+
|
|
1921
|
+
if (semanticRefs.length > 0) {
|
|
1922
|
+
const semanticSubheader = document.createElement('h5');
|
|
1923
|
+
semanticSubheader.textContent = 'Semantic References';
|
|
1924
|
+
semanticSubheader.style.color = '#a371f7';
|
|
1925
|
+
semanticSubheader.style.fontSize = '14px';
|
|
1926
|
+
semanticSubheader.style.marginTop = '10px';
|
|
1927
|
+
container.appendChild(semanticSubheader);
|
|
1928
|
+
|
|
1929
|
+
const semanticList = document.createElement('ul');
|
|
1930
|
+
semanticList.className = 'directory-list';
|
|
1931
|
+
|
|
1932
|
+
semanticRefs.forEach(ref => {
|
|
1933
|
+
const listItem = document.createElement('li');
|
|
1934
|
+
listItem.style.cursor = 'pointer';
|
|
1935
|
+
listItem.style.transition = 'background-color 0.2s';
|
|
1936
|
+
listItem.style.borderLeft = '2px dotted #a371f7';
|
|
1937
|
+
listItem.style.paddingLeft = '10px';
|
|
1938
|
+
listItem.innerHTML = `
|
|
1939
|
+
<span class="item-icon">🔗</span>
|
|
1940
|
+
${ref.name}
|
|
1941
|
+
<span class="item-type">${ref.type}</span>
|
|
1942
|
+
`;
|
|
1943
|
+
|
|
1944
|
+
listItem.addEventListener('mouseover', function() {
|
|
1945
|
+
this.style.backgroundColor = '#30363d';
|
|
1946
|
+
});
|
|
1947
|
+
listItem.addEventListener('mouseout', function() {
|
|
1948
|
+
this.style.backgroundColor = 'transparent';
|
|
1949
|
+
});
|
|
1950
|
+
listItem.addEventListener('click', () => {
|
|
1951
|
+
showContentPane(ref);
|
|
1952
|
+
updateGraphForNode(ref);
|
|
1953
|
+
});
|
|
1954
|
+
|
|
1955
|
+
semanticList.appendChild(listItem);
|
|
1956
|
+
});
|
|
1957
|
+
|
|
1958
|
+
container.appendChild(semanticList);
|
|
1959
|
+
}
|
|
1960
|
+
|
|
1961
|
+
// Add separator
|
|
1962
|
+
const separator = document.createElement('h4');
|
|
1963
|
+
separator.textContent = 'Code Content';
|
|
1964
|
+
separator.style.color = '#8b949e';
|
|
1965
|
+
separator.style.marginTop = '20px';
|
|
1966
|
+
separator.style.marginBottom = '10px';
|
|
1967
|
+
container.appendChild(separator);
|
|
1968
|
+
}
|
|
1969
|
+
}
|
|
1970
|
+
|
|
1971
|
+
// Show code for function, class, method, or code chunks
|
|
1972
|
+
if (node.docstring) {
|
|
1973
|
+
const docstringDiv = document.createElement('div');
|
|
1974
|
+
docstringDiv.style.cssText = 'margin-bottom: 16px; padding: 12px; background: #161b22; border: 1px solid #30363d; border-radius: 6px;';
|
|
1975
|
+
|
|
1976
|
+
const docLabel = document.createElement('div');
|
|
1977
|
+
docLabel.textContent = 'DOCSTRING';
|
|
1978
|
+
docLabel.style.cssText = 'font-size: 11px; color: #8b949e; margin-bottom: 8px; font-weight: 600;';
|
|
1979
|
+
|
|
1980
|
+
const docPre = document.createElement('pre');
|
|
1981
|
+
docPre.style.cssText = 'margin: 0; padding: 0; background: transparent; border: none;';
|
|
1982
|
+
const docCode = document.createElement('code');
|
|
1983
|
+
docCode.textContent = node.docstring;
|
|
1984
|
+
docPre.appendChild(docCode);
|
|
1985
|
+
|
|
1986
|
+
docstringDiv.appendChild(docLabel);
|
|
1987
|
+
docstringDiv.appendChild(docPre);
|
|
1988
|
+
container.appendChild(docstringDiv);
|
|
1989
|
+
}
|
|
1990
|
+
|
|
612
1991
|
if (node.content) {
|
|
1992
|
+
const pre = document.createElement('pre');
|
|
1993
|
+
const code = document.createElement('code');
|
|
613
1994
|
code.textContent = node.content;
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
${node.docstring}`;
|
|
1995
|
+
pre.appendChild(code);
|
|
1996
|
+
container.appendChild(pre);
|
|
617
1997
|
} else {
|
|
618
|
-
|
|
1998
|
+
const noContent = document.createElement('p');
|
|
1999
|
+
noContent.textContent = 'No content available';
|
|
2000
|
+
noContent.style.color = '#8b949e';
|
|
2001
|
+
container.appendChild(noContent);
|
|
619
2002
|
}
|
|
2003
|
+
}
|
|
620
2004
|
|
|
621
|
-
|
|
2005
|
+
function escapeHtml(text) {
|
|
2006
|
+
const div = document.createElement('div');
|
|
2007
|
+
div.textContent = text;
|
|
2008
|
+
return div.innerHTML;
|
|
622
2009
|
}
|
|
623
2010
|
|
|
624
|
-
function
|
|
625
|
-
const
|
|
626
|
-
|
|
2011
|
+
function closeContentPane() {
|
|
2012
|
+
const pane = document.getElementById('content-pane');
|
|
2013
|
+
pane.classList.remove('visible');
|
|
627
2014
|
|
|
628
|
-
// Remove highlight
|
|
2015
|
+
// Remove highlight and current node
|
|
2016
|
+
currentNode = null;
|
|
629
2017
|
highlightedNode = null;
|
|
2018
|
+
|
|
2019
|
+
// Disable focus button
|
|
2020
|
+
document.getElementById('focus-button').disabled = true;
|
|
2021
|
+
|
|
630
2022
|
renderGraph();
|
|
631
2023
|
}
|
|
632
2024
|
|
|
2025
|
+
// Wire up Focus button
|
|
2026
|
+
document.getElementById('focus-button').addEventListener('click', () => {
|
|
2027
|
+
if (currentNode) {
|
|
2028
|
+
centerOnNode(currentNode);
|
|
2029
|
+
}
|
|
2030
|
+
});
|
|
2031
|
+
|
|
633
2032
|
// Auto-load graph data on page load
|
|
634
2033
|
window.addEventListener('DOMContentLoaded', () => {
|
|
635
2034
|
const loadingEl = document.getElementById('loading');
|
|
636
2035
|
|
|
637
|
-
fetch(
|
|
2036
|
+
fetch("chunk-graph.json")
|
|
638
2037
|
.then(response => {
|
|
639
2038
|
if (!response.ok) {
|
|
640
2039
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|