mcp-vector-search 0.12.2__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/visualization/index.html +1165 -116
- {mcp_vector_search-0.12.2.dist-info → mcp_vector_search-0.12.3.dist-info}/METADATA +1 -1
- {mcp_vector_search-0.12.2.dist-info → mcp_vector_search-0.12.3.dist-info}/RECORD +7 -7
- {mcp_vector_search-0.12.2.dist-info → mcp_vector_search-0.12.3.dist-info}/WHEEL +0 -0
- {mcp_vector_search-0.12.2.dist-info → mcp_vector_search-0.12.3.dist-info}/entry_points.txt +0 -0
- {mcp_vector_search-0.12.2.dist-info → mcp_vector_search-0.12.3.dist-info}/licenses/LICENSE +0 -0
|
@@ -142,15 +142,15 @@
|
|
|
142
142
|
}
|
|
143
143
|
|
|
144
144
|
.link {
|
|
145
|
-
stroke: #
|
|
146
|
-
stroke-opacity: 0.
|
|
147
|
-
stroke-width:
|
|
145
|
+
stroke: #58a6ff;
|
|
146
|
+
stroke-opacity: 0.8;
|
|
147
|
+
stroke-width: 3px;
|
|
148
148
|
}
|
|
149
149
|
|
|
150
150
|
.link.dependency {
|
|
151
151
|
stroke: #d29922;
|
|
152
152
|
stroke-opacity: 0.8;
|
|
153
|
-
stroke-width:
|
|
153
|
+
stroke-width: 3px;
|
|
154
154
|
stroke-dasharray: 5,5;
|
|
155
155
|
}
|
|
156
156
|
|
|
@@ -314,6 +314,28 @@
|
|
|
314
314
|
stroke-width: 3px;
|
|
315
315
|
filter: drop-shadow(0 0 8px #f0e68c);
|
|
316
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
|
+
}
|
|
317
339
|
</style>
|
|
318
340
|
</head>
|
|
319
341
|
<body>
|
|
@@ -324,6 +346,10 @@
|
|
|
324
346
|
<label>⏳ Loading graph data...</label>
|
|
325
347
|
</div>
|
|
326
348
|
|
|
349
|
+
<div class="control-group">
|
|
350
|
+
<button id="focus-button" class="control-button" disabled>📍 Focus on Selected</button>
|
|
351
|
+
</div>
|
|
352
|
+
|
|
327
353
|
<h3>Legend</h3>
|
|
328
354
|
<div class="legend">
|
|
329
355
|
<div class="legend-item">
|
|
@@ -387,19 +413,46 @@
|
|
|
387
413
|
|
|
388
414
|
const svg = d3.select("#graph")
|
|
389
415
|
.attr("width", width)
|
|
390
|
-
.attr("height", height)
|
|
391
|
-
.call(d3.zoom().on("zoom", (event) => {
|
|
392
|
-
g.attr("transform", event.transform);
|
|
393
|
-
}));
|
|
416
|
+
.attr("height", height);
|
|
394
417
|
|
|
395
418
|
const g = svg.append("g");
|
|
396
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
|
+
|
|
397
426
|
let simulation;
|
|
398
427
|
let allNodes = [];
|
|
399
428
|
let allLinks = [];
|
|
400
429
|
let visibleNodes = new Set();
|
|
401
430
|
let collapsedNodes = new Set();
|
|
402
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
|
+
});
|
|
403
456
|
|
|
404
457
|
function visualizeGraph(data) {
|
|
405
458
|
g.selectAll("*").remove();
|
|
@@ -413,36 +466,36 @@
|
|
|
413
466
|
// In monorepos, subproject nodes are roots
|
|
414
467
|
rootNodes = allNodes.filter(n => n.type === 'subproject');
|
|
415
468
|
} else {
|
|
416
|
-
// Regular projects: Find root nodes by graph structure
|
|
417
|
-
const dirNodes = allNodes.filter(n => n.type === 'directory');
|
|
418
|
-
const fileNodes = allNodes.filter(n => n.type === 'file');
|
|
419
|
-
|
|
420
|
-
// Find minimum depth for directories and files (for fallback)
|
|
421
|
-
let minDirDepth = Infinity;
|
|
422
|
-
let minFileDepth = Infinity;
|
|
423
|
-
|
|
424
|
-
dirNodes.forEach(d => {
|
|
425
|
-
if (d.depth < minDirDepth) minDirDepth = d.depth;
|
|
426
|
-
});
|
|
427
|
-
|
|
428
|
-
fileNodes.forEach(f => {
|
|
429
|
-
if (f.depth < minFileDepth) minFileDepth = f.depth;
|
|
430
|
-
});
|
|
431
|
-
|
|
432
|
-
// CORRECT: Find root nodes by checking graph structure
|
|
469
|
+
// Regular projects: Find root nodes by checking graph structure
|
|
433
470
|
// Root nodes are nodes with no incoming links (no parent)
|
|
434
471
|
const targetIds = new Set(allLinks.map(link => link.target));
|
|
435
|
-
|
|
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
|
+
});
|
|
436
492
|
|
|
437
|
-
console.log(`
|
|
493
|
+
console.log(`Filtered root nodes (directories only): ${rootNodes.length}`, rootNodes.map(n => n.name));
|
|
438
494
|
|
|
439
495
|
// Fallback only if we got 0 root nodes (shouldn't happen with correct data)
|
|
440
496
|
if (rootNodes.length === 0) {
|
|
441
|
-
console.warn('No root nodes
|
|
442
|
-
rootNodes =
|
|
443
|
-
...dirNodes.filter(n => n.depth === minDirDepth),
|
|
444
|
-
...fileNodes.filter(n => n.depth === minFileDepth)
|
|
445
|
-
];
|
|
497
|
+
console.warn('No root nodes after filtering, using all root nodes');
|
|
498
|
+
rootNodes = allRootNodes;
|
|
446
499
|
}
|
|
447
500
|
}
|
|
448
501
|
|
|
@@ -468,10 +521,11 @@
|
|
|
468
521
|
const visibleNodesList = allNodes.filter(n => visibleNodes.has(n.id));
|
|
469
522
|
console.log(`Rendering ${visibleNodesList.length} visible nodes`);
|
|
470
523
|
|
|
471
|
-
const visibleLinks = allLinks.filter(l =>
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
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
|
+
});
|
|
475
529
|
console.log(`Rendering ${visibleLinks.length} visible links`);
|
|
476
530
|
|
|
477
531
|
simulation = d3.forceSimulation(visibleNodesList)
|
|
@@ -486,7 +540,33 @@
|
|
|
486
540
|
.selectAll("line")
|
|
487
541
|
.data(visibleLinks)
|
|
488
542
|
.join("line")
|
|
489
|
-
.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
|
+
});
|
|
490
570
|
|
|
491
571
|
const node = g.append("g")
|
|
492
572
|
.selectAll("g")
|
|
@@ -518,7 +598,8 @@
|
|
|
518
598
|
})
|
|
519
599
|
.attr("stroke", d => hasChildren(d) ? "#ffffff" : "none")
|
|
520
600
|
.attr("stroke-width", d => hasChildren(d) ? 3 : 0)
|
|
521
|
-
.style("fill", d => d.color || null)
|
|
601
|
+
.style("fill", d => d.color || null) // Use custom color if available
|
|
602
|
+
.style("cursor", "pointer"); // Ensure cursor shows pointer on all nodes
|
|
522
603
|
|
|
523
604
|
// Add rectangles for document nodes
|
|
524
605
|
node.filter(d => isDocNode(d))
|
|
@@ -543,17 +624,31 @@
|
|
|
543
624
|
.attr("ry", 2)
|
|
544
625
|
.attr("stroke", d => hasChildren(d) ? "#ffffff" : "none")
|
|
545
626
|
.attr("stroke-width", d => hasChildren(d) ? 3 : 0)
|
|
546
|
-
.style("fill", d => d.color || null)
|
|
627
|
+
.style("fill", d => d.color || null)
|
|
628
|
+
.style("cursor", "pointer"); // Ensure cursor shows pointer on all nodes
|
|
547
629
|
|
|
548
|
-
// Add expand/collapse indicator
|
|
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
|
|
549
643
|
node.filter(d => hasChildren(d))
|
|
550
644
|
.append("text")
|
|
551
645
|
.attr("class", "expand-indicator")
|
|
646
|
+
.attr("y", 35) // Same position as circle
|
|
647
|
+
.attr("dy", 6) // Center vertically within circle
|
|
552
648
|
.attr("text-anchor", "middle")
|
|
553
|
-
.
|
|
554
|
-
.style("font-size", "16px")
|
|
649
|
+
.style("font-size", "22px")
|
|
555
650
|
.style("font-weight", "bold")
|
|
556
|
-
.style("fill", "#
|
|
651
|
+
.style("fill", "#ffeb3b")
|
|
557
652
|
.style("pointer-events", "none")
|
|
558
653
|
.text(d => collapsedNodes.has(d.id) ? "+" : "−");
|
|
559
654
|
|
|
@@ -575,7 +670,7 @@
|
|
|
575
670
|
return '•';
|
|
576
671
|
});
|
|
577
672
|
|
|
578
|
-
// Add labels (show actual import statement for import nodes)
|
|
673
|
+
// Add labels (show actual import statement for import nodes, better names for text chunks)
|
|
579
674
|
node.append("text")
|
|
580
675
|
.text(d => {
|
|
581
676
|
// Import nodes have type === 'imports'
|
|
@@ -588,6 +683,45 @@
|
|
|
588
683
|
}
|
|
589
684
|
return d.name; // Fallback to name if no content
|
|
590
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
|
+
|
|
591
725
|
return d.name;
|
|
592
726
|
})
|
|
593
727
|
.attr("dy", 30);
|
|
@@ -605,6 +739,102 @@
|
|
|
605
739
|
updateStats({nodes: visibleNodesList, links: visibleLinks, metadata: {total_files: allNodes.length}});
|
|
606
740
|
}
|
|
607
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
|
+
|
|
608
838
|
function hasChildren(node) {
|
|
609
839
|
// Handle both pre-mutation (string IDs) and post-mutation (object references)
|
|
610
840
|
const result = allLinks.some(l => {
|
|
@@ -623,6 +853,9 @@
|
|
|
623
853
|
|
|
624
854
|
event.stopPropagation();
|
|
625
855
|
|
|
856
|
+
// Track current node
|
|
857
|
+
currentNode = d;
|
|
858
|
+
|
|
626
859
|
// Always show content pane when clicking any node
|
|
627
860
|
showContentPane(d);
|
|
628
861
|
|
|
@@ -647,6 +880,9 @@
|
|
|
647
880
|
} else {
|
|
648
881
|
console.log('Node has no children, skipping expansion');
|
|
649
882
|
}
|
|
883
|
+
|
|
884
|
+
// Highlight in graph
|
|
885
|
+
highlightNode(d);
|
|
650
886
|
}
|
|
651
887
|
|
|
652
888
|
function expandNode(node) {
|
|
@@ -654,12 +890,15 @@
|
|
|
654
890
|
collapsedNodes.delete(node.id);
|
|
655
891
|
|
|
656
892
|
// Find direct children
|
|
657
|
-
const childLinks = allLinks.filter(l =>
|
|
893
|
+
const childLinks = allLinks.filter(l => {
|
|
894
|
+
const sourceId = typeof l.source === 'object' ? l.source.id : l.source;
|
|
895
|
+
return sourceId === node.id;
|
|
896
|
+
});
|
|
658
897
|
console.log(` Found ${childLinks.length} child links for node ${node.id}`);
|
|
659
898
|
|
|
660
899
|
const children = childLinks
|
|
661
900
|
.map(l => {
|
|
662
|
-
const targetId = l.target.id
|
|
901
|
+
const targetId = typeof l.target === 'object' ? l.target.id : l.target;
|
|
663
902
|
const child = allNodes.find(n => n.id === targetId);
|
|
664
903
|
if (!child) {
|
|
665
904
|
console.warn(` Could not find child node with ID: ${targetId}`);
|
|
@@ -680,22 +919,55 @@
|
|
|
680
919
|
}
|
|
681
920
|
|
|
682
921
|
function collapseNode(node) {
|
|
922
|
+
console.log(`Collapsing node: ${node.name}`);
|
|
683
923
|
collapsedNodes.add(node.id);
|
|
684
924
|
|
|
685
|
-
//
|
|
686
|
-
|
|
687
|
-
const
|
|
688
|
-
|
|
689
|
-
|
|
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
|
+
});
|
|
690
930
|
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
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';
|
|
695
942
|
});
|
|
696
|
-
}
|
|
697
943
|
|
|
698
|
-
|
|
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
|
+
}
|
|
699
971
|
}
|
|
700
972
|
|
|
701
973
|
function showTooltip(event, d) {
|
|
@@ -769,10 +1041,47 @@
|
|
|
769
1041
|
}
|
|
770
1042
|
}
|
|
771
1043
|
|
|
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
|
+
|
|
772
1078
|
function showContentPane(node) {
|
|
773
|
-
//
|
|
1079
|
+
// Track and highlight the node
|
|
1080
|
+
currentNode = node;
|
|
774
1081
|
highlightedNode = node;
|
|
775
|
-
|
|
1082
|
+
|
|
1083
|
+
// Enable focus button
|
|
1084
|
+
document.getElementById('focus-button').disabled = false;
|
|
776
1085
|
|
|
777
1086
|
// Populate content pane
|
|
778
1087
|
const pane = document.getElementById('content-pane');
|
|
@@ -819,99 +1128,711 @@
|
|
|
819
1128
|
}
|
|
820
1129
|
|
|
821
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
|
+
|
|
822
1175
|
// Find all direct children of this directory
|
|
823
1176
|
const children = allLinks
|
|
824
|
-
.filter(l =>
|
|
825
|
-
|
|
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
|
+
})
|
|
826
1185
|
.filter(n => n);
|
|
827
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
|
|
828
1354
|
if (children.length === 0) {
|
|
829
|
-
|
|
1355
|
+
const emptyMsg = document.createElement('p');
|
|
1356
|
+
emptyMsg.textContent = 'Empty directory';
|
|
1357
|
+
emptyMsg.style.color = '#8b949e';
|
|
1358
|
+
container.appendChild(emptyMsg);
|
|
830
1359
|
return;
|
|
831
1360
|
}
|
|
832
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
|
+
|
|
833
1372
|
// Group by type
|
|
834
1373
|
const files = children.filter(n => n.type === 'file');
|
|
835
1374
|
const subdirs = children.filter(n => n.type === 'directory');
|
|
836
1375
|
const chunks = children.filter(n => n.type !== 'file' && n.type !== 'directory');
|
|
837
1376
|
|
|
838
|
-
|
|
1377
|
+
const childrenList = document.createElement('ul');
|
|
1378
|
+
childrenList.className = 'directory-list';
|
|
839
1379
|
|
|
840
1380
|
// Show subdirectories first
|
|
841
1381
|
subdirs.forEach(child => {
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
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>
|
|
848
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);
|
|
849
1412
|
});
|
|
850
1413
|
|
|
851
1414
|
// Then files
|
|
852
1415
|
files.forEach(child => {
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
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>
|
|
859
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);
|
|
860
1446
|
});
|
|
861
1447
|
|
|
862
1448
|
// Then code chunks
|
|
863
1449
|
chunks.forEach(child => {
|
|
864
1450
|
const icon = child.type === 'class' ? '🔷' : child.type === 'function' ? '⚡' : '📝';
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
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>
|
|
871
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);
|
|
872
1492
|
});
|
|
873
1493
|
|
|
874
|
-
|
|
1494
|
+
container.appendChild(childrenList);
|
|
875
1495
|
|
|
876
1496
|
// Add summary
|
|
877
|
-
const summary =
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
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);
|
|
882
1503
|
}
|
|
883
1504
|
|
|
884
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
|
+
|
|
885
1716
|
// Find all chunks in this file
|
|
886
1717
|
const fileChunks = allLinks
|
|
887
|
-
.filter(l =>
|
|
888
|
-
|
|
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
|
+
})
|
|
889
1726
|
.filter(n => n);
|
|
890
1727
|
|
|
891
1728
|
if (fileChunks.length === 0) {
|
|
892
|
-
|
|
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);
|
|
893
1736
|
return;
|
|
894
1737
|
}
|
|
895
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
|
+
|
|
896
1808
|
// Collect all content from chunks and sort by line number
|
|
897
1809
|
const sortedChunks = fileChunks
|
|
898
1810
|
.filter(c => c.content)
|
|
899
1811
|
.sort((a, b) => a.start_line - b.start_line);
|
|
900
1812
|
|
|
901
|
-
if (sortedChunks.length
|
|
902
|
-
|
|
903
|
-
|
|
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);
|
|
904
1835
|
}
|
|
905
|
-
|
|
906
|
-
// Combine all chunks to show full file
|
|
907
|
-
const fullContent = sortedChunks.map(c => c.content).join('\n\n');
|
|
908
|
-
|
|
909
|
-
container.innerHTML = `
|
|
910
|
-
<p style="color: #8b949e; font-size: 11px; margin-bottom: 12px;">
|
|
911
|
-
Contains ${fileChunks.length} code chunks
|
|
912
|
-
</p>
|
|
913
|
-
<pre><code>${escapeHtml(fullContent)}</code></pre>
|
|
914
|
-
`;
|
|
915
1836
|
}
|
|
916
1837
|
|
|
917
1838
|
function showImportDetails(node, container) {
|
|
@@ -944,25 +1865,141 @@
|
|
|
944
1865
|
}
|
|
945
1866
|
|
|
946
1867
|
function showCodeContent(node, container) {
|
|
947
|
-
|
|
948
|
-
|
|
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
|
+
}
|
|
949
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
|
|
950
1972
|
if (node.docstring) {
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
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);
|
|
957
1989
|
}
|
|
958
1990
|
|
|
959
1991
|
if (node.content) {
|
|
960
|
-
|
|
1992
|
+
const pre = document.createElement('pre');
|
|
1993
|
+
const code = document.createElement('code');
|
|
1994
|
+
code.textContent = node.content;
|
|
1995
|
+
pre.appendChild(code);
|
|
1996
|
+
container.appendChild(pre);
|
|
961
1997
|
} else {
|
|
962
|
-
|
|
1998
|
+
const noContent = document.createElement('p');
|
|
1999
|
+
noContent.textContent = 'No content available';
|
|
2000
|
+
noContent.style.color = '#8b949e';
|
|
2001
|
+
container.appendChild(noContent);
|
|
963
2002
|
}
|
|
964
|
-
|
|
965
|
-
container.innerHTML = html;
|
|
966
2003
|
}
|
|
967
2004
|
|
|
968
2005
|
function escapeHtml(text) {
|
|
@@ -975,11 +2012,23 @@
|
|
|
975
2012
|
const pane = document.getElementById('content-pane');
|
|
976
2013
|
pane.classList.remove('visible');
|
|
977
2014
|
|
|
978
|
-
// Remove highlight
|
|
2015
|
+
// Remove highlight and current node
|
|
2016
|
+
currentNode = null;
|
|
979
2017
|
highlightedNode = null;
|
|
2018
|
+
|
|
2019
|
+
// Disable focus button
|
|
2020
|
+
document.getElementById('focus-button').disabled = true;
|
|
2021
|
+
|
|
980
2022
|
renderGraph();
|
|
981
2023
|
}
|
|
982
2024
|
|
|
2025
|
+
// Wire up Focus button
|
|
2026
|
+
document.getElementById('focus-button').addEventListener('click', () => {
|
|
2027
|
+
if (currentNode) {
|
|
2028
|
+
centerOnNode(currentNode);
|
|
2029
|
+
}
|
|
2030
|
+
});
|
|
2031
|
+
|
|
983
2032
|
// Auto-load graph data on page load
|
|
984
2033
|
window.addEventListener('DOMContentLoaded', () => {
|
|
985
2034
|
const loadingEl = document.getElementById('loading');
|