@rpcajr/smart-graph-indexer 1.0.0
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.
- package/.agents/rules/antigravity-rtk-rules.md +32 -0
- package/README.md +116 -0
- package/dist/graph.d.ts +32 -0
- package/dist/graph.js +86 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +227 -0
- package/dist/mcp_server.d.ts +48 -0
- package/dist/mcp_server.js +433 -0
- package/dist/parser.d.ts +42 -0
- package/dist/parser.js +529 -0
- package/dist/stats.d.ts +43 -0
- package/dist/stats.js +146 -0
- package/dist/ui.d.ts +2 -0
- package/dist/ui.js +1003 -0
- package/dist/vector.d.ts +66 -0
- package/dist/vector.js +144 -0
- package/dist/wasm/tree-sitter-c.wasm +0 -0
- package/dist/wasm/tree-sitter-c_sharp.wasm +0 -0
- package/dist/wasm/tree-sitter-cpp.wasm +0 -0
- package/dist/wasm/tree-sitter-go.wasm +0 -0
- package/dist/wasm/tree-sitter-java.wasm +0 -0
- package/dist/wasm/tree-sitter-python.wasm +0 -0
- package/dist/wasm/tree-sitter-ruby.wasm +0 -0
- package/dist/wasm/tree-sitter-rust.wasm +0 -0
- package/dist/wasm/tree-sitter-typescript.wasm +0 -0
- package/dist/wasm/tree-sitter.wasm +0 -0
- package/dist/watcher.d.ts +36 -0
- package/dist/watcher.js +166 -0
- package/mcp-config-example.json +12 -0
- package/mcp_smart_indexer_implementation_spec.md +366 -0
- package/package.json +35 -0
- package/src/graph.ts +93 -0
- package/src/index.ts +216 -0
- package/src/mcp_server.ts +454 -0
- package/src/parser.ts +484 -0
- package/src/stats.ts +156 -0
- package/src/ui.ts +956 -0
- package/src/vector.ts +166 -0
- package/src/watcher.ts +144 -0
- package/test_project/App.java +16 -0
- package/test_project/BillingService.cs +31 -0
- package/test_project/auth.ts +18 -0
- package/test_project/config.ts +11 -0
- package/test_project/database.ts +21 -0
- package/test_project/index.ts +13 -0
- package/test_project/main.go +11 -0
- package/test_project/main.py +21 -0
- package/test_project/processor.py +12 -0
- package/test_project/utils.py +13 -0
- package/tsconfig.json +16 -0
- package/wasm/tree-sitter-c.wasm +0 -0
- package/wasm/tree-sitter-c_sharp.wasm +0 -0
- package/wasm/tree-sitter-cpp.wasm +0 -0
- package/wasm/tree-sitter-go.wasm +0 -0
- package/wasm/tree-sitter-java.wasm +0 -0
- package/wasm/tree-sitter-python.wasm +0 -0
- package/wasm/tree-sitter-ruby.wasm +0 -0
- package/wasm/tree-sitter-rust.wasm +0 -0
- package/wasm/tree-sitter-typescript.wasm +0 -0
- package/wasm/tree-sitter.wasm +0 -0
package/dist/ui.js
ADDED
|
@@ -0,0 +1,1003 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.generateGraphHtml = generateGraphHtml;
|
|
37
|
+
const path = __importStar(require("path"));
|
|
38
|
+
function generateGraphHtml(engine) {
|
|
39
|
+
const report = engine.getReport();
|
|
40
|
+
const projectName = path.basename(engine.projectPath) || 'Project';
|
|
41
|
+
// Format data for Vis.js
|
|
42
|
+
const nodes = [];
|
|
43
|
+
const edges = [];
|
|
44
|
+
const nodeMap = new Map();
|
|
45
|
+
let idCounter = 1;
|
|
46
|
+
// Add all files as nodes
|
|
47
|
+
for (const file of report.files) {
|
|
48
|
+
const id = idCounter++;
|
|
49
|
+
nodeMap.set(file, id);
|
|
50
|
+
// Determine language based on extension for icon/color
|
|
51
|
+
const ext = path.extname(file).toLowerCase();
|
|
52
|
+
let group = 'other';
|
|
53
|
+
let color = '#7f8c8d'; // gray
|
|
54
|
+
if (['.ts', '.tsx', '.js', '.jsx'].includes(ext)) {
|
|
55
|
+
group = 'typescript';
|
|
56
|
+
color = '#3178c6';
|
|
57
|
+
}
|
|
58
|
+
else if (ext === '.py') {
|
|
59
|
+
group = 'python';
|
|
60
|
+
color = '#3776ab';
|
|
61
|
+
}
|
|
62
|
+
else if (ext === '.cs') {
|
|
63
|
+
group = 'csharp';
|
|
64
|
+
color = '#178600';
|
|
65
|
+
}
|
|
66
|
+
else if (ext === '.java') {
|
|
67
|
+
group = 'java';
|
|
68
|
+
color = '#b07219';
|
|
69
|
+
}
|
|
70
|
+
else if (ext === '.go') {
|
|
71
|
+
group = 'go';
|
|
72
|
+
color = '#00add8';
|
|
73
|
+
}
|
|
74
|
+
else if (ext === '.rs') {
|
|
75
|
+
group = 'rust';
|
|
76
|
+
color = '#dee5e7';
|
|
77
|
+
}
|
|
78
|
+
else if (ext === '.rb') {
|
|
79
|
+
group = 'ruby';
|
|
80
|
+
color = '#701516';
|
|
81
|
+
}
|
|
82
|
+
else if (['.cpp', '.hpp', '.cc', '.h', '.c'].includes(ext)) {
|
|
83
|
+
group = 'cpp';
|
|
84
|
+
color = '#f34b7d';
|
|
85
|
+
}
|
|
86
|
+
const skeleton = engine.getFileSkeleton(file);
|
|
87
|
+
const symbolsCount = skeleton?.symbols.length ?? 0;
|
|
88
|
+
const tooltipText = `${file}\nSymbols: ${symbolsCount}\nImports: ${skeleton?.dependencies.length ?? 0}`;
|
|
89
|
+
nodes.push({
|
|
90
|
+
id,
|
|
91
|
+
label: path.basename(file),
|
|
92
|
+
title: tooltipText,
|
|
93
|
+
value: Math.max(5, symbolsCount), // size based on symbol count
|
|
94
|
+
color: {
|
|
95
|
+
background: '#1e1e2e',
|
|
96
|
+
border: color,
|
|
97
|
+
highlight: {
|
|
98
|
+
background: color,
|
|
99
|
+
border: '#ffffff'
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
font: { color: '#cdd6f4' },
|
|
103
|
+
borderWidth: 2,
|
|
104
|
+
shape: 'dot',
|
|
105
|
+
filePath: file,
|
|
106
|
+
symbols: skeleton?.symbols || [],
|
|
107
|
+
dependencies: skeleton?.dependencies || []
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
// Add edges
|
|
111
|
+
for (const [sourceFile, data] of Object.entries(report.dependencyGraph)) {
|
|
112
|
+
const sourceId = nodeMap.get(sourceFile);
|
|
113
|
+
if (!sourceId)
|
|
114
|
+
continue;
|
|
115
|
+
for (const targetName of data.imports) {
|
|
116
|
+
// Find if target matches a file in our project
|
|
117
|
+
const targetFile = report.files.find(f => {
|
|
118
|
+
const baseF = path.basename(f, path.extname(f));
|
|
119
|
+
const baseT = path.basename(targetName, path.extname(targetName));
|
|
120
|
+
return f === targetName || baseF === baseT;
|
|
121
|
+
});
|
|
122
|
+
if (targetFile) {
|
|
123
|
+
const targetId = nodeMap.get(targetFile);
|
|
124
|
+
if (targetId && sourceId !== targetId) {
|
|
125
|
+
edges.push({
|
|
126
|
+
from: sourceId,
|
|
127
|
+
to: targetId,
|
|
128
|
+
arrows: 'to',
|
|
129
|
+
color: { color: '#a6adc8', highlight: '#f5c2e7' },
|
|
130
|
+
width: 1
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return `
|
|
137
|
+
<!DOCTYPE html>
|
|
138
|
+
<html lang="en">
|
|
139
|
+
<head>
|
|
140
|
+
<meta charset="UTF-8">
|
|
141
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
142
|
+
<title>SmartGraphIndexer - ${projectName}</title>
|
|
143
|
+
<!-- Vis.js for Network Graph -->
|
|
144
|
+
<script type="text/javascript" src="https://unpkg.com/vis-network/standalone/umd/vis-network.min.js"></script>
|
|
145
|
+
<!-- Google Fonts Outfit -->
|
|
146
|
+
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;600;800&display=swap" rel="stylesheet">
|
|
147
|
+
<style>
|
|
148
|
+
:root {
|
|
149
|
+
--bg-base: #0f0f16;
|
|
150
|
+
--bg-surface: #1e1e2e;
|
|
151
|
+
--bg-panel: rgba(30, 30, 46, 0.7);
|
|
152
|
+
--text-main: #cdd6f4;
|
|
153
|
+
--text-muted: #a6adc8;
|
|
154
|
+
--accent: #cba6f7;
|
|
155
|
+
--accent-glow: rgba(203, 166, 247, 0.4);
|
|
156
|
+
--border: #313244;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
* {
|
|
160
|
+
box-sizing: border-box;
|
|
161
|
+
margin: 0;
|
|
162
|
+
padding: 0;
|
|
163
|
+
font-family: 'Outfit', sans-serif;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
body {
|
|
167
|
+
background-color: var(--bg-base);
|
|
168
|
+
color: var(--text-main);
|
|
169
|
+
height: 100vh;
|
|
170
|
+
overflow: hidden;
|
|
171
|
+
display: flex;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/* Glassmorphism sidebar */
|
|
175
|
+
.sidebar {
|
|
176
|
+
width: 400px;
|
|
177
|
+
background: var(--bg-panel);
|
|
178
|
+
backdrop-filter: blur(16px);
|
|
179
|
+
-webkit-backdrop-filter: blur(16px);
|
|
180
|
+
border-right: 1px solid var(--border);
|
|
181
|
+
display: flex;
|
|
182
|
+
flex-direction: column;
|
|
183
|
+
height: 100%;
|
|
184
|
+
z-index: 10;
|
|
185
|
+
transition: transform 0.3s ease;
|
|
186
|
+
position: relative;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.header {
|
|
190
|
+
padding: 20px 24px;
|
|
191
|
+
border-bottom: 1px solid var(--border);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
.header h1 {
|
|
195
|
+
font-size: 22px;
|
|
196
|
+
font-weight: 800;
|
|
197
|
+
background: linear-gradient(45deg, #cba6f7, #f5c2e7);
|
|
198
|
+
-webkit-background-clip: text;
|
|
199
|
+
-webkit-text-fill-color: transparent;
|
|
200
|
+
margin-bottom: 4px;
|
|
201
|
+
word-break: break-all;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.header p {
|
|
205
|
+
font-size: 12px;
|
|
206
|
+
color: var(--text-muted);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/* Token Stats Panel */
|
|
210
|
+
.stats-panel {
|
|
211
|
+
padding: 16px 24px;
|
|
212
|
+
border-bottom: 1px solid var(--border);
|
|
213
|
+
background: rgba(203, 166, 247, 0.02);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
.stats-title {
|
|
217
|
+
font-size: 10px;
|
|
218
|
+
text-transform: uppercase;
|
|
219
|
+
letter-spacing: 1px;
|
|
220
|
+
color: var(--accent);
|
|
221
|
+
margin-bottom: 10px;
|
|
222
|
+
font-weight: 600;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
.stats-grid {
|
|
226
|
+
display: flex;
|
|
227
|
+
gap: 12px;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
.stat-card {
|
|
231
|
+
flex: 1;
|
|
232
|
+
background: rgba(15, 15, 22, 0.4);
|
|
233
|
+
border: 1px solid var(--border);
|
|
234
|
+
border-radius: 6px;
|
|
235
|
+
padding: 8px;
|
|
236
|
+
text-align: center;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
.stat-value {
|
|
240
|
+
font-size: 15px;
|
|
241
|
+
font-weight: 800;
|
|
242
|
+
color: #a6e3a1; /* green */
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
.stat-label {
|
|
246
|
+
font-size: 9.5px;
|
|
247
|
+
color: var(--text-muted);
|
|
248
|
+
margin-top: 2px;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/* Premium Tabs styling */
|
|
252
|
+
.tabs-header {
|
|
253
|
+
display: flex;
|
|
254
|
+
background: rgba(15, 15, 22, 0.3);
|
|
255
|
+
border-bottom: 1px solid var(--border);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
.tab-btn {
|
|
259
|
+
flex: 1;
|
|
260
|
+
background: none;
|
|
261
|
+
border: none;
|
|
262
|
+
color: var(--text-muted);
|
|
263
|
+
padding: 12px;
|
|
264
|
+
font-size: 13px;
|
|
265
|
+
font-weight: 600;
|
|
266
|
+
cursor: pointer;
|
|
267
|
+
transition: all 0.3s ease;
|
|
268
|
+
position: relative;
|
|
269
|
+
outline: none;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
.tab-btn.active {
|
|
273
|
+
color: var(--accent);
|
|
274
|
+
background: rgba(203, 166, 247, 0.03);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
.tab-btn.active::after {
|
|
278
|
+
content: '';
|
|
279
|
+
position: absolute;
|
|
280
|
+
bottom: -1px;
|
|
281
|
+
left: 0;
|
|
282
|
+
width: 100%;
|
|
283
|
+
height: 2px;
|
|
284
|
+
background: var(--accent);
|
|
285
|
+
box-shadow: 0 0 8px var(--accent);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
.search-box {
|
|
289
|
+
padding: 14px 24px;
|
|
290
|
+
border-bottom: 1px solid var(--border);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
.search-input {
|
|
294
|
+
width: 100%;
|
|
295
|
+
padding: 10px 14px;
|
|
296
|
+
background: rgba(15, 15, 22, 0.6);
|
|
297
|
+
border: 1px solid var(--border);
|
|
298
|
+
border-radius: 8px;
|
|
299
|
+
color: var(--text-main);
|
|
300
|
+
font-size: 13px;
|
|
301
|
+
outline: none;
|
|
302
|
+
transition: all 0.3s ease;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
.search-input:focus {
|
|
306
|
+
border-color: var(--accent);
|
|
307
|
+
box-shadow: 0 0 10px var(--accent-glow);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
.content-panel {
|
|
311
|
+
flex: 1;
|
|
312
|
+
overflow-y: auto;
|
|
313
|
+
padding: 20px 24px 60px 24px; /* leaves space for footer */
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
.tab-panel {
|
|
317
|
+
display: none;
|
|
318
|
+
animation: fadeIn 0.3s ease forwards;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
.tab-panel.active-panel {
|
|
322
|
+
display: block;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
.empty-state {
|
|
326
|
+
text-align: center;
|
|
327
|
+
color: var(--text-muted);
|
|
328
|
+
margin-top: 40px;
|
|
329
|
+
font-weight: 300;
|
|
330
|
+
font-size: 13px;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/* Node Details Panel */
|
|
334
|
+
.detail-card {
|
|
335
|
+
display: none;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
.detail-title {
|
|
339
|
+
font-size: 18px;
|
|
340
|
+
font-weight: 600;
|
|
341
|
+
color: #ffffff;
|
|
342
|
+
margin-bottom: 6px;
|
|
343
|
+
word-break: break-all;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
.detail-path {
|
|
347
|
+
font-size: 11px;
|
|
348
|
+
color: var(--text-muted);
|
|
349
|
+
background: rgba(0, 0, 0, 0.2);
|
|
350
|
+
padding: 4px 8px;
|
|
351
|
+
border-radius: 4px;
|
|
352
|
+
font-family: monospace;
|
|
353
|
+
margin-bottom: 16px;
|
|
354
|
+
display: inline-block;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
.section-title {
|
|
358
|
+
font-size: 12.5px;
|
|
359
|
+
text-transform: uppercase;
|
|
360
|
+
letter-spacing: 1px;
|
|
361
|
+
color: var(--accent);
|
|
362
|
+
margin-bottom: 10px;
|
|
363
|
+
margin-top: 20px;
|
|
364
|
+
font-weight: 600;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
.symbol-list {
|
|
368
|
+
list-style: none;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
.symbol-item {
|
|
372
|
+
background: rgba(255, 255, 255, 0.03);
|
|
373
|
+
border: 1px solid var(--border);
|
|
374
|
+
border-radius: 6px;
|
|
375
|
+
padding: 8px 10px;
|
|
376
|
+
margin-bottom: 8px;
|
|
377
|
+
font-size: 12.5px;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
.symbol-header {
|
|
381
|
+
display: flex;
|
|
382
|
+
justify-content: space-between;
|
|
383
|
+
margin-bottom: 4px;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
.symbol-name {
|
|
387
|
+
font-weight: 600;
|
|
388
|
+
color: #f5e0dc;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
.symbol-kind {
|
|
392
|
+
font-size: 10px;
|
|
393
|
+
padding: 1px 5px;
|
|
394
|
+
border-radius: 4px;
|
|
395
|
+
background: rgba(203, 166, 247, 0.15);
|
|
396
|
+
color: var(--accent);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
.symbol-sig {
|
|
400
|
+
font-family: monospace;
|
|
401
|
+
font-size: 10.5px;
|
|
402
|
+
color: var(--text-muted);
|
|
403
|
+
overflow-x: auto;
|
|
404
|
+
white-space: nowrap;
|
|
405
|
+
padding-top: 2px;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
.symbol-doc {
|
|
409
|
+
font-size: 11px;
|
|
410
|
+
color: #a6e3a1;
|
|
411
|
+
font-style: italic;
|
|
412
|
+
margin-top: 4px;
|
|
413
|
+
border-left: 2px solid #a6e3a1;
|
|
414
|
+
padding-left: 6px;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
.dep-badge {
|
|
418
|
+
display: inline-block;
|
|
419
|
+
padding: 5px 8px;
|
|
420
|
+
border-radius: 20px;
|
|
421
|
+
background: rgba(245, 194, 231, 0.08);
|
|
422
|
+
border: 1px solid rgba(245, 194, 231, 0.2);
|
|
423
|
+
color: #f5c2e7;
|
|
424
|
+
font-size: 11.5px;
|
|
425
|
+
margin-right: 6px;
|
|
426
|
+
margin-bottom: 6px;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/* Detailed UI History Layout */
|
|
430
|
+
.session-card {
|
|
431
|
+
background: rgba(255, 255, 255, 0.02);
|
|
432
|
+
border: 1px solid var(--border);
|
|
433
|
+
border-radius: 8px;
|
|
434
|
+
margin-bottom: 16px;
|
|
435
|
+
overflow: hidden;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
.session-header {
|
|
439
|
+
background: rgba(255, 255, 255, 0.03);
|
|
440
|
+
padding: 12px 16px;
|
|
441
|
+
cursor: pointer;
|
|
442
|
+
display: flex;
|
|
443
|
+
justify-content: space-between;
|
|
444
|
+
align-items: center;
|
|
445
|
+
border-bottom: 1px solid var(--border);
|
|
446
|
+
transition: background 0.3s ease;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
.session-header:hover {
|
|
450
|
+
background: rgba(255, 255, 255, 0.06);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
.session-meta {
|
|
454
|
+
display: flex;
|
|
455
|
+
flex-direction: column;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
.session-date {
|
|
459
|
+
font-size: 13px;
|
|
460
|
+
font-weight: 600;
|
|
461
|
+
color: #ffffff;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
.session-summary {
|
|
465
|
+
font-size: 11px;
|
|
466
|
+
color: var(--text-muted);
|
|
467
|
+
margin-top: 2px;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
.session-savings {
|
|
471
|
+
font-size: 14px;
|
|
472
|
+
font-weight: 800;
|
|
473
|
+
color: #a6e3a1;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
.session-body {
|
|
477
|
+
padding: 14px;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
.history-item {
|
|
481
|
+
padding: 12px 0;
|
|
482
|
+
border-bottom: 1px solid rgba(49, 50, 68, 0.2);
|
|
483
|
+
display: flex;
|
|
484
|
+
flex-direction: column;
|
|
485
|
+
gap: 6px;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
.history-item:last-child {
|
|
489
|
+
border-bottom: none;
|
|
490
|
+
padding-bottom: 0;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
.history-row-top {
|
|
494
|
+
display: flex;
|
|
495
|
+
justify-content: space-between;
|
|
496
|
+
align-items: center;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
.history-tool-badge {
|
|
500
|
+
font-size: 9.5px;
|
|
501
|
+
font-weight: 700;
|
|
502
|
+
color: var(--accent);
|
|
503
|
+
background: rgba(203, 166, 247, 0.12);
|
|
504
|
+
padding: 2px 6px;
|
|
505
|
+
border-radius: 4px;
|
|
506
|
+
text-transform: uppercase;
|
|
507
|
+
letter-spacing: 0.5px;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
.history-time {
|
|
511
|
+
font-size: 10px;
|
|
512
|
+
color: var(--text-muted);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
.history-target {
|
|
516
|
+
font-family: monospace;
|
|
517
|
+
font-size: 10.5px;
|
|
518
|
+
color: #b4befe;
|
|
519
|
+
background: rgba(0, 0, 0, 0.2);
|
|
520
|
+
padding: 4px 8px;
|
|
521
|
+
border-radius: 4px;
|
|
522
|
+
word-break: break-all;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
.history-metrics {
|
|
526
|
+
display: flex;
|
|
527
|
+
justify-content: space-between;
|
|
528
|
+
align-items: center;
|
|
529
|
+
font-size: 11px;
|
|
530
|
+
color: var(--text-muted);
|
|
531
|
+
margin-top: 2px;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
.history-saved {
|
|
535
|
+
font-weight: 700;
|
|
536
|
+
color: #a6e3a1; /* green */
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
.progress-bar-container {
|
|
540
|
+
width: 100%;
|
|
541
|
+
height: 4px;
|
|
542
|
+
background: rgba(49, 50, 68, 0.4);
|
|
543
|
+
border-radius: 2px;
|
|
544
|
+
overflow: hidden;
|
|
545
|
+
margin-top: 2px;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
.progress-bar-fill {
|
|
549
|
+
height: 100%;
|
|
550
|
+
background: linear-gradient(90deg, #cba6f7, #a6e3a1);
|
|
551
|
+
border-radius: 2px;
|
|
552
|
+
transition: width 0.4s ease;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
/* Main view area */
|
|
556
|
+
.view-area {
|
|
557
|
+
flex: 1;
|
|
558
|
+
position: relative;
|
|
559
|
+
height: 100%;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
#network-container {
|
|
563
|
+
width: 100%;
|
|
564
|
+
height: 100%;
|
|
565
|
+
background-color: var(--bg-base);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
/* Float controls */
|
|
569
|
+
.controls {
|
|
570
|
+
position: absolute;
|
|
571
|
+
bottom: 24px;
|
|
572
|
+
right: 24px;
|
|
573
|
+
display: flex;
|
|
574
|
+
gap: 12px;
|
|
575
|
+
z-index: 5;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
.btn {
|
|
579
|
+
background: var(--bg-surface);
|
|
580
|
+
border: 1px solid var(--border);
|
|
581
|
+
color: var(--text-main);
|
|
582
|
+
padding: 10px 16px;
|
|
583
|
+
border-radius: 8px;
|
|
584
|
+
cursor: pointer;
|
|
585
|
+
font-weight: 600;
|
|
586
|
+
font-size: 13px;
|
|
587
|
+
display: flex;
|
|
588
|
+
align-items: center;
|
|
589
|
+
gap: 8px;
|
|
590
|
+
transition: all 0.3s ease;
|
|
591
|
+
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
.btn:hover {
|
|
595
|
+
border-color: var(--accent);
|
|
596
|
+
color: #ffffff;
|
|
597
|
+
box-shadow: 0 4px 16px var(--accent-glow);
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
/* Sidebar Footer Logo */
|
|
601
|
+
.sidebar-footer {
|
|
602
|
+
position: absolute;
|
|
603
|
+
bottom: 0;
|
|
604
|
+
left: 0;
|
|
605
|
+
width: 100%;
|
|
606
|
+
padding: 16px 24px;
|
|
607
|
+
background: linear-gradient(180deg, rgba(30, 30, 46, 0) 0%, rgba(30, 30, 46, 0.95) 100%);
|
|
608
|
+
border-top: 1px solid rgba(49, 50, 68, 0.4);
|
|
609
|
+
display: flex;
|
|
610
|
+
justify-content: space-between;
|
|
611
|
+
align-items: center;
|
|
612
|
+
font-size: 11px;
|
|
613
|
+
color: var(--text-muted);
|
|
614
|
+
z-index: 12;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
.sidebar-footer span {
|
|
618
|
+
font-weight: 600;
|
|
619
|
+
color: var(--accent);
|
|
620
|
+
}
|
|
621
|
+
</style>
|
|
622
|
+
</head>
|
|
623
|
+
<body>
|
|
624
|
+
|
|
625
|
+
<!-- Sidebar details panel -->
|
|
626
|
+
<div class="sidebar">
|
|
627
|
+
<div class="header">
|
|
628
|
+
<h1>${projectName}</h1>
|
|
629
|
+
<p>Project Dependencies Map</p>
|
|
630
|
+
</div>
|
|
631
|
+
|
|
632
|
+
<!-- Token Stats persistent widget -->
|
|
633
|
+
<div class="stats-panel">
|
|
634
|
+
<div class="stats-title">Token Savings (RTK)</div>
|
|
635
|
+
<div class="stats-grid">
|
|
636
|
+
<div class="stat-card">
|
|
637
|
+
<div class="stat-value" id="stat-saved-tokens">0</div>
|
|
638
|
+
<div class="stat-label">Tokens Saved</div>
|
|
639
|
+
</div>
|
|
640
|
+
<div class="stat-card">
|
|
641
|
+
<div class="stat-value" id="stat-efficiency">0%</div>
|
|
642
|
+
<div class="stat-label">Efficiency</div>
|
|
643
|
+
</div>
|
|
644
|
+
</div>
|
|
645
|
+
</div>
|
|
646
|
+
|
|
647
|
+
<!-- Tab selectors -->
|
|
648
|
+
<div class="tabs-header">
|
|
649
|
+
<button class="tab-btn active" id="tab-graph" onclick="switchTab('graph')">Graph</button>
|
|
650
|
+
<button class="tab-btn" id="tab-history" onclick="switchTab('history')">History (RTK)</button>
|
|
651
|
+
</div>
|
|
652
|
+
|
|
653
|
+
<!-- Tab panels wrapper -->
|
|
654
|
+
<div class="content-panel">
|
|
655
|
+
|
|
656
|
+
<!-- Panel 1: Graph/AST Details -->
|
|
657
|
+
<div id="panel-graph" class="tab-panel active-panel">
|
|
658
|
+
<div class="search-box" style="padding: 0 0 16px 0; border-bottom: none;">
|
|
659
|
+
<input type="text" id="search" class="search-input" placeholder="Search file or symbol...">
|
|
660
|
+
</div>
|
|
661
|
+
|
|
662
|
+
<div id="empty-state" class="empty-state">
|
|
663
|
+
<p>Select a file node in the graph to view structural signatures and skeletons.</p>
|
|
664
|
+
</div>
|
|
665
|
+
|
|
666
|
+
<div id="detail-card" class="detail-card">
|
|
667
|
+
<div id="detail-title" class="detail-title">Parser.ts</div>
|
|
668
|
+
<div id="detail-path" class="detail-path">src/parser.ts</div>
|
|
669
|
+
|
|
670
|
+
<div class="section-title">Imports & Dependencies</div>
|
|
671
|
+
<div id="detail-deps"></div>
|
|
672
|
+
|
|
673
|
+
<div class="section-title">Symbols & Signatures (AST)</div>
|
|
674
|
+
<ul id="detail-symbols" class="symbol-list"></ul>
|
|
675
|
+
</div>
|
|
676
|
+
</div>
|
|
677
|
+
|
|
678
|
+
<!-- Panel 2: Persistent History Log -->
|
|
679
|
+
<div id="panel-history" class="tab-panel">
|
|
680
|
+
<div id="history-container">
|
|
681
|
+
<!-- Populated dynamically via JS -->
|
|
682
|
+
</div>
|
|
683
|
+
</div>
|
|
684
|
+
|
|
685
|
+
</div>
|
|
686
|
+
|
|
687
|
+
<!-- Graphical Footer Branding -->
|
|
688
|
+
<div class="sidebar-footer">
|
|
689
|
+
<div>Mapped by <span>SmartGraphIndexer</span></div>
|
|
690
|
+
<div>v1.0.0</div>
|
|
691
|
+
</div>
|
|
692
|
+
</div>
|
|
693
|
+
|
|
694
|
+
<!-- Graph interactive area -->
|
|
695
|
+
<div class="view-area">
|
|
696
|
+
<div id="network-container"></div>
|
|
697
|
+
<div class="controls">
|
|
698
|
+
<button class="btn" onclick="fitGraph()">Fit Graph</button>
|
|
699
|
+
<button class="btn" onclick="togglePhysics()">Pause Physics</button>
|
|
700
|
+
</div>
|
|
701
|
+
</div>
|
|
702
|
+
|
|
703
|
+
<script type="text/javascript">
|
|
704
|
+
const rawNodes = ${JSON.stringify(nodes)};
|
|
705
|
+
const rawEdges = ${JSON.stringify(edges)};
|
|
706
|
+
const tokenStats = ${JSON.stringify(report.tokenStats || {})};
|
|
707
|
+
|
|
708
|
+
// Populate token stats in UI
|
|
709
|
+
function formatNumber(num) {
|
|
710
|
+
if (num === undefined || isNaN(num)) return '0';
|
|
711
|
+
if (num >= 1000000) return (num / 1000000).toFixed(1) + 'M';
|
|
712
|
+
if (num >= 1000) return (num / 1000).toFixed(1) + 'K';
|
|
713
|
+
return num.toString();
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
if (tokenStats && tokenStats.totalTokensSaved !== undefined) {
|
|
717
|
+
document.getElementById('stat-saved-tokens').innerText = formatNumber(tokenStats.totalTokensSaved);
|
|
718
|
+
document.getElementById('stat-efficiency').innerText = (tokenStats.efficiencyPercentage || 0) + '%';
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
// Switch Tabs
|
|
722
|
+
function switchTab(tabName) {
|
|
723
|
+
document.querySelectorAll('.tab-btn').forEach(btn => btn.classList.remove('active'));
|
|
724
|
+
document.querySelectorAll('.tab-panel').forEach(panel => panel.classList.remove('active-panel'));
|
|
725
|
+
|
|
726
|
+
if (tabName === 'graph') {
|
|
727
|
+
document.getElementById('tab-graph').classList.add('active');
|
|
728
|
+
document.getElementById('panel-graph').classList.add('active-panel');
|
|
729
|
+
} else {
|
|
730
|
+
document.getElementById('tab-history').classList.add('active');
|
|
731
|
+
document.getElementById('panel-history').classList.add('active-panel');
|
|
732
|
+
renderHistory();
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
// Render history grouped by session
|
|
737
|
+
function renderHistory() {
|
|
738
|
+
const container = document.getElementById('history-container');
|
|
739
|
+
container.innerHTML = '';
|
|
740
|
+
|
|
741
|
+
if (!tokenStats || !tokenStats.history || tokenStats.history.length === 0) {
|
|
742
|
+
container.innerHTML = '<div class="empty-state">No stats records found. Start using the MCP to log savings.</div>';
|
|
743
|
+
return;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
// Group by sessionId
|
|
747
|
+
const sessions = {};
|
|
748
|
+
tokenStats.history.forEach(item => {
|
|
749
|
+
if (!sessions[item.sessionId]) {
|
|
750
|
+
sessions[item.sessionId] = [];
|
|
751
|
+
}
|
|
752
|
+
sessions[item.sessionId].push(item);
|
|
753
|
+
});
|
|
754
|
+
|
|
755
|
+
// Sort sessions descending by date
|
|
756
|
+
const sortedSessionIds = Object.keys(sessions).sort((a, b) => new Date(b).getTime() - new Date(a).getTime());
|
|
757
|
+
|
|
758
|
+
sortedSessionIds.forEach((sessId, index) => {
|
|
759
|
+
const items = sessions[sessId];
|
|
760
|
+
const date = new Date(sessId);
|
|
761
|
+
const dateStr = date.toLocaleDateString('en-US') + ' ' + date.toLocaleTimeString('en-US', {hour: '2-digit', minute:'2-digit'});
|
|
762
|
+
|
|
763
|
+
let sessionSaved = 0;
|
|
764
|
+
let sessionUsed = 0;
|
|
765
|
+
let sessionRaw = 0;
|
|
766
|
+
let sessionTime = 0;
|
|
767
|
+
let sessionCalls = items.length;
|
|
768
|
+
|
|
769
|
+
items.forEach(it => {
|
|
770
|
+
sessionSaved += it.saved;
|
|
771
|
+
sessionUsed += it.actual;
|
|
772
|
+
sessionRaw += it.alternative;
|
|
773
|
+
sessionTime += (it.execTimeMs || 0);
|
|
774
|
+
});
|
|
775
|
+
|
|
776
|
+
const sessionEff = sessionRaw > 0 ? Math.round((sessionSaved / sessionRaw) * 100) : 0;
|
|
777
|
+
const avgTime = sessionCalls > 0 ? Math.round(sessionTime / sessionCalls) : 0;
|
|
778
|
+
|
|
779
|
+
const card = document.createElement('div');
|
|
780
|
+
card.className = 'session-card';
|
|
781
|
+
|
|
782
|
+
const header = document.createElement('div');
|
|
783
|
+
header.className = 'session-header';
|
|
784
|
+
header.onclick = () => {
|
|
785
|
+
const body = card.querySelector('.session-body');
|
|
786
|
+
body.style.display = body.style.display === 'none' ? 'block' : 'none';
|
|
787
|
+
};
|
|
788
|
+
|
|
789
|
+
header.innerHTML = \`
|
|
790
|
+
<div class="session-meta">
|
|
791
|
+
<span class="session-date">\${dateStr}</span>
|
|
792
|
+
<span class="session-summary">\${sessionCalls} optimized requests</span>
|
|
793
|
+
</div>
|
|
794
|
+
<div class="session-savings">+\${formatNumber(sessionSaved)}</div>
|
|
795
|
+
\`;
|
|
796
|
+
|
|
797
|
+
const body = document.createElement('div');
|
|
798
|
+
body.className = 'session-body';
|
|
799
|
+
body.style.display = index === 0 ? 'block' : 'none';
|
|
800
|
+
|
|
801
|
+
// Add RTK Console Output Mock
|
|
802
|
+
const consoleMock = document.createElement('div');
|
|
803
|
+
consoleMock.style.cssText = 'background: rgba(0, 0, 0, 0.35); border: 1px solid var(--border); padding: 12px; border-radius: 6px; font-family: monospace; font-size: 11.5px; margin-bottom: 12px; color: #a6e3a1; border-left: 3px solid var(--accent); line-height: 1.4;';
|
|
804
|
+
|
|
805
|
+
// Build meter block
|
|
806
|
+
const filledBlocks = Math.round(sessionEff / 10);
|
|
807
|
+
const emptyBlocks = 10 - filledBlocks;
|
|
808
|
+
const meterStr = '='.repeat(filledBlocks) + ' '.repeat(emptyBlocks);
|
|
809
|
+
|
|
810
|
+
consoleMock.innerHTML = \`
|
|
811
|
+
<div style="font-weight: 800; color: var(--accent); margin-bottom: 6px;">RTK Token Savings (Session Scope)</div>
|
|
812
|
+
<div style="border-bottom: 1px dashed rgba(49, 50, 68, 0.6); margin-bottom: 6px; padding-bottom: 6px;">
|
|
813
|
+
<div>Total requests: \${sessionCalls}</div>
|
|
814
|
+
<div>Input tokens (Used): \${sessionUsed}</div>
|
|
815
|
+
<div>Raw cost (No MCP): \${sessionRaw}</div>
|
|
816
|
+
<div>Tokens saved: \${sessionSaved} (\${sessionEff}.0%)</div>
|
|
817
|
+
<div>Total exec time: \${sessionTime}ms (avg \${avgTime}ms)</div>
|
|
818
|
+
</div>
|
|
819
|
+
<div style="display: flex; align-items: center; gap: 8px;">
|
|
820
|
+
<div>Efficiency meter:</div>
|
|
821
|
+
<div style="color: #a6e3a1; font-weight: bold;">[\${meterStr}] \${sessionEff}%</div>
|
|
822
|
+
</div>
|
|
823
|
+
\`;
|
|
824
|
+
body.appendChild(consoleMock);
|
|
825
|
+
|
|
826
|
+
// Add Divider Header
|
|
827
|
+
const requestsHeader = document.createElement('div');
|
|
828
|
+
requestsHeader.className = 'section-title';
|
|
829
|
+
requestsHeader.style.cssText = 'margin-top: 12px; margin-bottom: 8px; font-size: 11px;';
|
|
830
|
+
requestsHeader.innerText = 'By Request';
|
|
831
|
+
body.appendChild(requestsHeader);
|
|
832
|
+
|
|
833
|
+
items.forEach(it => {
|
|
834
|
+
const itDate = new Date(it.timestamp);
|
|
835
|
+
const timeStr = itDate.toLocaleTimeString('en-US', {hour: '2-digit', minute:'2-digit', second:'2-digit'});
|
|
836
|
+
const eff = it.alternative > 0 ? Math.round((it.saved / it.alternative) * 100) : 0;
|
|
837
|
+
|
|
838
|
+
const itemDiv = document.createElement('div');
|
|
839
|
+
itemDiv.className = 'history-item';
|
|
840
|
+
|
|
841
|
+
let targetHtml = it.target ? \`<div class="history-target">\${it.target}</div>\` : '';
|
|
842
|
+
|
|
843
|
+
itemDiv.innerHTML = \`
|
|
844
|
+
<div class="history-row-top">
|
|
845
|
+
<span class="history-tool-badge">\${it.toolName}</span>
|
|
846
|
+
<span class="history-time">\${timeStr}</span>
|
|
847
|
+
</div>
|
|
848
|
+
\${targetHtml}
|
|
849
|
+
<div class="history-metrics">
|
|
850
|
+
<span>Used: <strong>\${it.actual}</strong> | Raw: <strong>\${it.alternative}</strong> | Time: <strong>\${it.execTimeMs || 0}ms</strong></span>
|
|
851
|
+
<span class="history-saved">Saved: <strong>+\ \${formatNumber(it.saved)}</strong> (\${eff}%)</span>
|
|
852
|
+
</div>
|
|
853
|
+
<div class="progress-bar-container">
|
|
854
|
+
<div class="progress-bar-fill" style="width: \${eff}%"></div>
|
|
855
|
+
</div>
|
|
856
|
+
\`;
|
|
857
|
+
body.appendChild(itemDiv);
|
|
858
|
+
});
|
|
859
|
+
|
|
860
|
+
card.appendChild(header);
|
|
861
|
+
card.appendChild(body);
|
|
862
|
+
container.appendChild(card);
|
|
863
|
+
});
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
const containerNet = document.getElementById('network-container');
|
|
867
|
+
const nodesDataSet = new vis.DataSet(rawNodes);
|
|
868
|
+
const edgesDataSet = new vis.DataSet(rawEdges);
|
|
869
|
+
|
|
870
|
+
const data = {
|
|
871
|
+
nodes: nodesDataSet,
|
|
872
|
+
edges: edgesDataSet
|
|
873
|
+
};
|
|
874
|
+
|
|
875
|
+
const options = {
|
|
876
|
+
nodes: {
|
|
877
|
+
scaling: {
|
|
878
|
+
min: 10,
|
|
879
|
+
max: 30
|
|
880
|
+
}
|
|
881
|
+
},
|
|
882
|
+
edges: {
|
|
883
|
+
smooth: {
|
|
884
|
+
type: 'continuous',
|
|
885
|
+
roundness: 0.5
|
|
886
|
+
}
|
|
887
|
+
},
|
|
888
|
+
physics: {
|
|
889
|
+
stabilization: true,
|
|
890
|
+
barnesHut: {
|
|
891
|
+
gravitationalConstant: -3000,
|
|
892
|
+
centralGravity: 0.3,
|
|
893
|
+
springLength: 95,
|
|
894
|
+
springConstant: 0.04
|
|
895
|
+
}
|
|
896
|
+
},
|
|
897
|
+
interaction: {
|
|
898
|
+
hover: true,
|
|
899
|
+
tooltipDelay: 100
|
|
900
|
+
}
|
|
901
|
+
};
|
|
902
|
+
|
|
903
|
+
const network = new vis.Network(containerNet, data, options);
|
|
904
|
+
|
|
905
|
+
// Event listener for click
|
|
906
|
+
network.on("click", function (params) {
|
|
907
|
+
if (params.nodes.length > 0) {
|
|
908
|
+
const nodeId = params.nodes[0];
|
|
909
|
+
const node = nodesDataSet.get(nodeId);
|
|
910
|
+
showDetails(node);
|
|
911
|
+
} else {
|
|
912
|
+
hideDetails();
|
|
913
|
+
}
|
|
914
|
+
});
|
|
915
|
+
|
|
916
|
+
function showDetails(node) {
|
|
917
|
+
document.getElementById('empty-state').style.display = 'none';
|
|
918
|
+
const card = document.getElementById('detail-card');
|
|
919
|
+
card.style.display = 'block';
|
|
920
|
+
|
|
921
|
+
document.getElementById('detail-title').innerText = node.label;
|
|
922
|
+
document.getElementById('detail-path').innerText = node.filePath;
|
|
923
|
+
|
|
924
|
+
// Render deps
|
|
925
|
+
const depsContainer = document.getElementById('detail-deps');
|
|
926
|
+
depsContainer.innerHTML = '';
|
|
927
|
+
if (node.dependencies.length === 0) {
|
|
928
|
+
depsContainer.innerHTML = '<span style="color: var(--text-muted); font-size:13px;">No external dependencies</span>';
|
|
929
|
+
} else {
|
|
930
|
+
node.dependencies.forEach(dep => {
|
|
931
|
+
const span = document.createElement('span');
|
|
932
|
+
span.className = 'dep-badge';
|
|
933
|
+
span.innerText = dep;
|
|
934
|
+
depsContainer.appendChild(span);
|
|
935
|
+
});
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
// Render symbols
|
|
939
|
+
const symbolsContainer = document.getElementById('detail-symbols');
|
|
940
|
+
symbolsContainer.innerHTML = '';
|
|
941
|
+
if (node.symbols.length === 0) {
|
|
942
|
+
symbolsContainer.innerHTML = '<li style="color: var(--text-muted); font-size:13px;">No public symbols extracted</li>';
|
|
943
|
+
} else {
|
|
944
|
+
node.symbols.forEach(sym => {
|
|
945
|
+
const li = document.createElement('li');
|
|
946
|
+
li.className = 'symbol-item';
|
|
947
|
+
|
|
948
|
+
let docHtml = sym.docstring ? \`<div class="symbol-doc">\${sym.docstring}</div>\` : '';
|
|
949
|
+
|
|
950
|
+
li.innerHTML = \`
|
|
951
|
+
<div class="symbol-header">
|
|
952
|
+
<span class="symbol-name">\${sym.name}</span>
|
|
953
|
+
<span class="symbol-kind">\${sym.kind}</span>
|
|
954
|
+
</div>
|
|
955
|
+
<div class="symbol-sig">\${sym.signature}</div>
|
|
956
|
+
\${docHtml}
|
|
957
|
+
\`;
|
|
958
|
+
symbolsContainer.appendChild(li);
|
|
959
|
+
});
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
function hideDetails() {
|
|
964
|
+
document.getElementById('empty-state').style.display = 'block';
|
|
965
|
+
document.getElementById('detail-card').style.display = 'none';
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
// Search bar filtering
|
|
969
|
+
document.getElementById('search').addEventListener('input', function(e) {
|
|
970
|
+
const val = e.target.value.toLowerCase();
|
|
971
|
+
if (!val) {
|
|
972
|
+
nodesDataSet.forEach(node => {
|
|
973
|
+
nodesDataSet.update({ id: node.id, hidden: false });
|
|
974
|
+
});
|
|
975
|
+
return;
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
nodesDataSet.forEach(node => {
|
|
979
|
+
const fileMatch = node.filePath.toLowerCase().includes(val);
|
|
980
|
+
const symbolMatch = node.symbols.some(s => s.name.toLowerCase().includes(val));
|
|
981
|
+
|
|
982
|
+
nodesDataSet.update({
|
|
983
|
+
id: node.id,
|
|
984
|
+
hidden: !(fileMatch || symbolMatch)
|
|
985
|
+
});
|
|
986
|
+
});
|
|
987
|
+
});
|
|
988
|
+
|
|
989
|
+
function fitGraph() {
|
|
990
|
+
network.fit({ animation: true });
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
let physicsEnabled = true;
|
|
994
|
+
function togglePhysics() {
|
|
995
|
+
physicsEnabled = !physicsEnabled;
|
|
996
|
+
network.setOptions({ physics: physicsEnabled });
|
|
997
|
+
event.target.innerText = physicsEnabled ? 'Pause Physics' : 'Resume Physics';
|
|
998
|
+
}
|
|
999
|
+
</script>
|
|
1000
|
+
</body>
|
|
1001
|
+
</html>
|
|
1002
|
+
`;
|
|
1003
|
+
}
|