@plumpslabs/kuma 2.3.20 → 2.3.23
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/README.md +18 -0
- package/dist/index.js +10316 -380
- package/package.json +2 -2
- package/packages/ide/studio/dist/index.js +90 -9
- package/packages/ide/studio/public/index.html +528 -233
- package/dist/agentDetector-YOWQVJFR.js +0 -186
- package/dist/chunk-3BRBJZ7P.js +0 -1055
- package/dist/chunk-3OHYYXYN.js +0 -71
- package/dist/chunk-ABKE45T4.js +0 -1264
- package/dist/chunk-E2KFPEBT.js +0 -183
- package/dist/chunk-FKRSI5U5.js +0 -282
- package/dist/chunk-GFLSAXAH.js +0 -155
- package/dist/chunk-L7F67KUP.js +0 -172
- package/dist/chunk-LVKOGXLC.js +0 -658
- package/dist/chunk-PRUTTZBS.js +0 -1113
- package/dist/contextDigest-QB5XHPXE.js +0 -177
- package/dist/domainRules-QLPAQASB.js +0 -20
- package/dist/init-PL4XL662.js +0 -15
- package/dist/kumaAstValidator-CNM7FHYA.js +0 -150
- package/dist/kumaCheckpoint-J2LDQMEO.js +0 -207
- package/dist/kumaCodeScanner-J6B2EHGI.js +0 -563
- package/dist/kumaContractEngine-KX27T4N7.js +0 -305
- package/dist/kumaDb-4XZ5S2LH.js +0 -65
- package/dist/kumaDriftDetector-TOORILSZ.js +0 -237
- package/dist/kumaGotchas-XRGFFBTA.js +0 -151
- package/dist/kumaGraph-UMXZNGYF.js +0 -44
- package/dist/kumaMemory-FBJMV77G.js +0 -16
- package/dist/kumaMiner-XJETL7TL.js +0 -176
- package/dist/kumaPolicyEngine-2QDJDLM7.js +0 -311
- package/dist/kumaProgressiveContext-MWEDRXOH.js +0 -231
- package/dist/kumaSearch-PV4QTKE7.js +0 -321
- package/dist/kumaTrajectory-7NOAVNG2.js +0 -460
- package/dist/kumaVerifier-6YEGC77M.js +0 -265
- package/dist/kumaVisualize-264OEBGJ.js +0 -264
- package/dist/pathValidator-V4DC6U6Z.js +0 -22
- package/dist/safetyAudit-O45SPNTS.js +0 -12
- package/dist/safetyScore-TMMRD2MV.js +0 -333
- package/dist/sessionMemory-YPKVIOMV.js +0 -6
- package/dist/skillGenerator-PWEJKZNX.js +0 -304
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
<title>Kuma Studio</title>
|
|
7
7
|
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>♢</text></svg>">
|
|
8
8
|
<script src="https://unpkg.com/vis-network@9.1.9/dist/vis-network.min.js"></script>
|
|
9
|
+
<script src="https://unpkg.com/lucide@0.344.0/dist/umd/lucide.min.js"></script>
|
|
9
10
|
<style>
|
|
10
11
|
:root {
|
|
11
12
|
--bg: #1a1a2e; --surface: #16213e; --border: #2a2a4a;
|
|
@@ -13,9 +14,15 @@
|
|
|
13
14
|
--green: #22c55e; --yellow: #eab308; --orange: #f97316; --red: #ef4444;
|
|
14
15
|
}
|
|
15
16
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
16
|
-
body {
|
|
17
|
+
html, body {
|
|
17
18
|
font-family: -apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;
|
|
18
19
|
background: var(--bg); color: var(--text); height: 100vh; overflow: hidden;
|
|
20
|
+
touch-action: none; -webkit-user-select: none; user-select: none;
|
|
21
|
+
}
|
|
22
|
+
header {
|
|
23
|
+
display: flex; align-items: center; gap: 16px; padding: 12px 24px;
|
|
24
|
+
background: var(--surface); border-bottom: 1px solid var(--border);
|
|
25
|
+
-webkit-app-region: drag;
|
|
19
26
|
}
|
|
20
27
|
header {
|
|
21
28
|
display: flex; align-items: center; gap: 16px; padding: 12px 24px;
|
|
@@ -38,7 +45,7 @@ header .status .dot.off { background: var(--red); }
|
|
|
38
45
|
#main { display: flex; height: calc(100vh - 100px); }
|
|
39
46
|
#graph-panel { flex: 1; position: relative; min-width: 0; overflow: hidden; }
|
|
40
47
|
#graph-container { width: 100%; height: 100%; background: radial-gradient(ellipse at center, #1e1e3a 0%, var(--bg) 70%); }
|
|
41
|
-
#sidebar { width:
|
|
48
|
+
#sidebar { width: 360px; min-width: 360px; background: var(--surface); border-left: 1px solid var(--border); display: flex; flex-direction: column; }
|
|
42
49
|
.tabs { display: flex; border-bottom: 1px solid var(--border); }
|
|
43
50
|
.tab {
|
|
44
51
|
flex: 1; padding: 10px; text-align: center; cursor: pointer; font-size: 12px;
|
|
@@ -49,46 +56,83 @@ header .status .dot.off { background: var(--red); }
|
|
|
49
56
|
.tab-content { flex: 1; overflow-y: auto; padding: 12px; }
|
|
50
57
|
.panel { display: none; }
|
|
51
58
|
.panel.active { display: block; }
|
|
59
|
+
|
|
60
|
+
/* Gotcha items */
|
|
52
61
|
.gotcha-item {
|
|
53
62
|
padding: 10px 12px; margin-bottom: 8px; border-radius: 8px;
|
|
54
|
-
background: var(--bg); border-left: 3px solid var(--muted);
|
|
63
|
+
background: var(--bg); border-left: 3px solid var(--muted); cursor: pointer;
|
|
64
|
+
transition: background 0.15s;
|
|
55
65
|
}
|
|
66
|
+
.gotcha-item:hover { background: #1e293b; }
|
|
56
67
|
.gotcha-item .file { font-size: 11px; color: var(--accent2); margin-bottom: 4px; word-break: break-all; }
|
|
57
|
-
.gotcha-item .desc { font-size:
|
|
68
|
+
.gotcha-item .desc { font-size: 12px; margin-bottom: 4px; overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; }
|
|
58
69
|
.gotcha-item .sev-tag { display: inline-block; font-size: 10px; padding: 2px 6px; border-radius: 4px; font-weight: 600; }
|
|
59
70
|
.sev-critical { background: #ef444420; color: var(--red); border-color: var(--red); }
|
|
60
71
|
.sev-high { background: #f9731620; color: var(--orange); border-color: var(--orange); }
|
|
61
72
|
.sev-medium { background: #eab30820; color: var(--yellow); border-color: var(--yellow); }
|
|
62
73
|
.sev-low { background: #22c55e20; color: var(--green); border-color: var(--green); }
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
.
|
|
74
|
+
|
|
75
|
+
/* Feature items */
|
|
76
|
+
.feature-item {
|
|
77
|
+
padding: 10px 12px; margin-bottom: 8px; border-radius: 8px;
|
|
78
|
+
background: var(--bg); border-left: 3px solid var(--accent); cursor: pointer;
|
|
79
|
+
transition: background 0.15s;
|
|
80
|
+
}
|
|
81
|
+
.feature-item:hover { background: #1e293b; }
|
|
82
|
+
.feature-item .name { font-size: 14px; font-weight: 600; color: var(--accent); margin-bottom: 4px; }
|
|
83
|
+
.feature-item .meta { font-size: 11px; color: var(--muted); }
|
|
84
|
+
.feature-item .tags { display: flex; gap: 4px; flex-wrap: wrap; margin-top: 4px; }
|
|
85
|
+
.feature-item .tag { font-size: 10px; padding: 1px 6px; border-radius: 4px; background: rgba(124,58,237,0.15); color: var(--accent); }
|
|
86
|
+
|
|
87
|
+
/* Health items */
|
|
66
88
|
.health-item { padding: 8px 12px; margin-bottom: 6px; background: var(--bg); border-radius: 6px; }
|
|
67
89
|
.health-item .score { font-size: 20px; font-weight: 700; }
|
|
68
90
|
.health-item .score.good { color: var(--green); }
|
|
69
91
|
.health-item .score.ok { color: var(--yellow); }
|
|
70
92
|
.health-item .score.bad { color: var(--red); }
|
|
71
93
|
.health-item .meta { font-size: 11px; color: var(--muted); }
|
|
94
|
+
|
|
72
95
|
.empty { text-align: center; color: var(--muted); padding: 40px 20px; font-size: 14px; }
|
|
96
|
+
|
|
97
|
+
/* Legend */
|
|
73
98
|
#legend {
|
|
74
|
-
position:
|
|
99
|
+
position: fixed; bottom: 16px; left: 16px; background: rgba(22,33,62,0.95);
|
|
75
100
|
border: 1px solid var(--border); border-radius: 8px; padding: 10px 14px;
|
|
76
101
|
font-size: 11px; z-index: 10; max-height: 200px; overflow-y: auto;
|
|
77
|
-
backdrop-filter: blur(8px);
|
|
102
|
+
backdrop-filter: blur(8px); color: #f8fafc;
|
|
78
103
|
}
|
|
79
|
-
#legend .title { font-weight: 600; margin-bottom: 6px; color:
|
|
80
|
-
.legend-item { display: flex; align-items: center; gap: 6px; margin-bottom: 3px; }
|
|
104
|
+
#legend .title { font-weight: 600; margin-bottom: 6px; color: #94a3b8; }
|
|
105
|
+
.legend-item { display: flex; align-items: center; gap: 6px; margin-bottom: 3px; color: #f1f5f9; }
|
|
81
106
|
.legend-dot { width: 10px; height: 10px; border-radius: 50%; }
|
|
107
|
+
|
|
108
|
+
/* Tooltip */
|
|
82
109
|
#graph-tooltip {
|
|
83
|
-
position: absolute; display: none; background: rgba(
|
|
110
|
+
position: absolute; display: none; background: rgba(15, 23, 42, 0.96);
|
|
84
111
|
border: 1px solid var(--border); border-radius: 8px; padding: 10px 14px;
|
|
85
|
-
font-size: 12px; z-index: 20; max-width:
|
|
86
|
-
|
|
112
|
+
font-size: 12px; z-index: 20; max-width: 420px; word-break: break-all;
|
|
113
|
+
overflow-wrap: anywhere; white-space: normal; color: #f8fafc;
|
|
114
|
+
box-shadow: 0 8px 32px rgba(0,0,0,0.6); backdrop-filter: blur(12px);
|
|
87
115
|
pointer-events: none;
|
|
88
116
|
}
|
|
89
|
-
|
|
90
|
-
|
|
117
|
+
|
|
118
|
+
/* Search */
|
|
119
|
+
#search-box { position: absolute; top: 16px; right: 16px; z-index: 10; }
|
|
120
|
+
|
|
121
|
+
/* Context Menu */
|
|
122
|
+
#ctx-menu {
|
|
123
|
+
position: fixed; display: none; z-index: 60; background: var(--surface);
|
|
124
|
+
border: 1px solid var(--border); border-radius: 8px; padding: 4px;
|
|
125
|
+
box-shadow: 0 8px 32px rgba(0,0,0,0.5); min-width: 160px;
|
|
126
|
+
backdrop-filter: blur(12px);
|
|
91
127
|
}
|
|
128
|
+
#ctx-menu .ctx-item {
|
|
129
|
+
display: flex; align-items: center; gap: 8px; padding: 7px 12px;
|
|
130
|
+
border-radius: 6px; cursor: pointer; font-size: 12px; color: var(--text);
|
|
131
|
+
transition: background 0.1s;
|
|
132
|
+
}
|
|
133
|
+
#ctx-menu .ctx-item:hover { background: rgba(124,58,237,0.15); color: var(--accent); }
|
|
134
|
+
#ctx-menu .ctx-item i { width: 14px; height: 14px; flex-shrink: 0; }
|
|
135
|
+
#ctx-menu .ctx-sep { height: 1px; background: var(--border); margin: 4px 8px; }
|
|
92
136
|
#search-box input {
|
|
93
137
|
background: rgba(22,33,62,0.92); border: 1px solid var(--border);
|
|
94
138
|
border-radius: 8px; padding: 8px 12px; color: var(--text); font-size: 12px;
|
|
@@ -97,8 +141,10 @@ header .status .dot.off { background: var(--red); }
|
|
|
97
141
|
}
|
|
98
142
|
#search-box input:focus { border-color: var(--accent); }
|
|
99
143
|
#search-box input::placeholder { color: var(--muted); }
|
|
144
|
+
|
|
145
|
+
/* Graph controls */
|
|
100
146
|
#graph-controls {
|
|
101
|
-
position:
|
|
147
|
+
position: fixed; bottom: 16px; right: 376px; z-index: 10;
|
|
102
148
|
display: flex; gap: 6px;
|
|
103
149
|
}
|
|
104
150
|
#graph-controls button {
|
|
@@ -108,6 +154,15 @@ header .status .dot.off { background: var(--red); }
|
|
|
108
154
|
transition: background 0.2s;
|
|
109
155
|
}
|
|
110
156
|
#graph-controls button:hover { background: var(--accent); border-color: var(--accent); }
|
|
157
|
+
#graph-controls button.active {
|
|
158
|
+
background: var(--accent) !important; border-color: var(--accent) !important;
|
|
159
|
+
color: #ffffff !important; box-shadow: 0 0 10px rgba(124, 58, 237, 0.4);
|
|
160
|
+
}
|
|
161
|
+
#graph-controls .ctrl-sep { width: 1px; background: var(--border); margin: 0 2px; }
|
|
162
|
+
.depth-btn { min-width: 28px !important; padding: 4px 6px !important; font-size: 11px !important; font-weight: 600; }
|
|
163
|
+
.depth-btn.active { background: rgba(124,58,237,0.3) !important; border-color: var(--accent) !important; color: var(--accent) !important; }
|
|
164
|
+
|
|
165
|
+
/* Loading */
|
|
111
166
|
#loading {
|
|
112
167
|
position: fixed; inset: 0; background: var(--bg); display: flex;
|
|
113
168
|
align-items: center; justify-content: center; z-index: 100;
|
|
@@ -115,6 +170,48 @@ header .status .dot.off { background: var(--red); }
|
|
|
115
170
|
}
|
|
116
171
|
#loading .spinner { width: 32px; height: 32px; border: 3px solid var(--border); border-top-color: var(--accent); border-radius: 50%; animation: spin 0.8s linear infinite; }
|
|
117
172
|
@keyframes spin { to { transform: rotate(360deg); } }
|
|
173
|
+
|
|
174
|
+
/* Node Detail Modal */
|
|
175
|
+
.modal-overlay {
|
|
176
|
+
position: fixed; inset: 0; background: rgba(0,0,0,0.6); z-index: 50;
|
|
177
|
+
display: flex; align-items: center; justify-content: center;
|
|
178
|
+
backdrop-filter: blur(4px); animation: fadeIn 0.15s;
|
|
179
|
+
}
|
|
180
|
+
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
|
|
181
|
+
.modal {
|
|
182
|
+
background: var(--surface); border: 1px solid var(--border); border-radius: 12px;
|
|
183
|
+
width: 560px; max-width: 90vw; max-height: 85vh; overflow: hidden; box-shadow: 0 16px 64px rgba(0,0,0,0.5);
|
|
184
|
+
display: flex; flex-direction: column; animation: slideUp 0.2s;
|
|
185
|
+
}
|
|
186
|
+
@keyframes slideUp { from { transform: translateY(20px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
|
|
187
|
+
.modal-header {
|
|
188
|
+
display: flex; align-items: center; justify-content: space-between;
|
|
189
|
+
padding: 14px 20px; border-bottom: 1px solid var(--border); flex-shrink: 0;
|
|
190
|
+
}
|
|
191
|
+
.modal-header h2 { font-size: 14px; font-weight: 700; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
|
|
192
|
+
.modal-header .close {
|
|
193
|
+
background: none; border: none; color: var(--muted); font-size: 18px;
|
|
194
|
+
cursor: pointer; width: 28px; height: 28px; display: flex; align-items: center; justify-content: center;
|
|
195
|
+
border-radius: 6px; transition: all 0.15s; flex-shrink: 0;
|
|
196
|
+
}
|
|
197
|
+
.modal-header .close:hover { background: rgba(255,255,255,0.1); color: var(--text); }
|
|
198
|
+
.modal-body { padding: 16px 20px; overflow-y: auto; flex: 1; min-height: 0; }
|
|
199
|
+
.modal-section { margin-bottom: 14px; }
|
|
200
|
+
.modal-section h3 { font-size: 11px; font-weight: 600; color: var(--muted); text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 6px; }
|
|
201
|
+
.modal-kv { display: flex; gap: 8px; margin-bottom: 5px; font-size: 12px; line-height: 1.4; }
|
|
202
|
+
.modal-kv .key { color: var(--muted); min-width: 72px; flex-shrink: 0; }
|
|
203
|
+
.modal-kv .val { color: var(--text); word-break: break-word; overflow-wrap: anywhere; min-width: 0; }
|
|
204
|
+
.relation-item {
|
|
205
|
+
display: flex; align-items: center; gap: 6px; padding: 5px 8px;
|
|
206
|
+
margin-bottom: 3px; border-radius: 6px; background: var(--bg);
|
|
207
|
+
font-size: 11px; cursor: pointer; transition: background 0.15s;
|
|
208
|
+
}
|
|
209
|
+
.relation-item:hover { background: #1e293b; }
|
|
210
|
+
.relation-item .arrow { color: var(--accent); font-weight: 700; flex-shrink: 0; }
|
|
211
|
+
.relation-item .name { color: var(--text); flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
|
|
212
|
+
.relation-item .type { color: var(--muted); font-size: 10px; flex-shrink: 0; max-width: 80px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
213
|
+
.relation-item .weight { color: var(--accent2); font-size: 10px; flex-shrink: 0; }
|
|
214
|
+
|
|
118
215
|
::-webkit-scrollbar { width: 6px; }
|
|
119
216
|
::-webkit-scrollbar-track { background: transparent; }
|
|
120
217
|
::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
|
|
@@ -123,9 +220,13 @@ header .status .dot.off { background: var(--red); }
|
|
|
123
220
|
<body>
|
|
124
221
|
<div id="loading"><div class="spinner"></div>Connecting to Kuma...</div>
|
|
125
222
|
<header>
|
|
126
|
-
<h1
|
|
223
|
+
<h1><i data-lucide="brain" style="width:20px;height:20px;vertical-align:middle;margin-right:6px"></i> Kuma Studio</h1>
|
|
127
224
|
<div class="status"><span class="dot" id="status-dot"></span><span id="status-text">connecting...</span></div>
|
|
128
|
-
<
|
|
225
|
+
<div style="margin-left:auto;display:flex;gap:8px;-webkit-app-region:no-drag">
|
|
226
|
+
<button onclick="refetch()" id="refetch-btn" style="background:rgba(124,58,237,0.15);border:1px solid rgba(124,58,237,0.3);border-radius:6px;color:var(--accent);padding:5px 12px;font-size:11px;cursor:pointer;transition:all 0.2s"><i data-lucide="refresh-cw" style="width:13px;height:13px;vertical-align:middle;margin-right:3px"></i> Refetch</button>
|
|
227
|
+
<button onclick="resetKumaDb()" style="background:rgba(239,68,68,0.15);border:1px solid rgba(239,68,68,0.3);border-radius:6px;color:var(--red);padding:5px 12px;font-size:11px;cursor:pointer;transition:all 0.2s"><i data-lucide="trash-2" style="width:13px;height:13px;vertical-align:middle;margin-right:3px"></i> Reset DB</button>
|
|
228
|
+
<button onclick="stopKumaStudio()" style="background:rgba(148,163,184,0.15);border:1px solid rgba(148,163,184,0.3);border-radius:6px;color:#94a3b8;padding:5px 12px;font-size:11px;cursor:pointer;transition:all 0.2s"><i data-lucide="square" style="width:13px;height:13px;vertical-align:middle;margin-right:3px"></i> Stop</button>
|
|
229
|
+
</div>
|
|
129
230
|
</header>
|
|
130
231
|
<div class="stats-bar" id="stats-bar"></div>
|
|
131
232
|
<div id="main">
|
|
@@ -133,37 +234,66 @@ header .status .dot.off { background: var(--red); }
|
|
|
133
234
|
<div id="graph-container"></div>
|
|
134
235
|
<div id="search-box"><input id="node-search" type="text" placeholder="Search nodes..." /></div>
|
|
135
236
|
<div id="graph-controls">
|
|
136
|
-
<button onclick="resetGraphFocus()"
|
|
137
|
-
<button onclick="if(network)network.fit()"
|
|
138
|
-
<
|
|
139
|
-
<button onclick="
|
|
140
|
-
<button onclick="
|
|
237
|
+
<button onclick="resetGraphFocus()" title="Reset focus"><i data-lucide="x" style="width:12px;height:12px"></i></button>
|
|
238
|
+
<button onclick="if(network)network.fit()" title="Fit to viewport"><i data-lucide="maximize-2" style="width:12px;height:12px"></i></button>
|
|
239
|
+
<div class="ctrl-sep"></div>
|
|
240
|
+
<button class="depth-btn" data-depth="1" onclick="setDepth(1)">1</button>
|
|
241
|
+
<button class="depth-btn active" data-depth="2" onclick="setDepth(2)">2</button>
|
|
242
|
+
<button class="depth-btn" data-depth="3" onclick="setDepth(3)">3</button>
|
|
243
|
+
<button class="depth-btn" data-depth="99" onclick="setDepth(99)">All</button>
|
|
244
|
+
<div class="ctrl-sep"></div>
|
|
245
|
+
<button id="btn-freeze-physics" onclick="stopPhysics()"><i data-lucide="pause" style="width:12px;height:12px"></i></button>
|
|
246
|
+
<button id="btn-unfreeze-physics" class="active" onclick="restartPhysics()"><i data-lucide="play" style="width:12px;height:12px"></i></button>
|
|
141
247
|
</div>
|
|
142
248
|
<div id="graph-tooltip"></div>
|
|
143
249
|
<div id="legend"></div>
|
|
144
250
|
</div>
|
|
145
251
|
<div id="sidebar">
|
|
146
252
|
<div class="tabs">
|
|
147
|
-
<div class="tab active" data-tab="
|
|
148
|
-
<div class="tab" data-tab="
|
|
149
|
-
<div class="tab" data-tab="health"
|
|
253
|
+
<div class="tab active" data-tab="features"><i data-lucide="star" style="width:13px;height:13px;vertical-align:middle;margin-right:3px"></i> Features</div>
|
|
254
|
+
<div class="tab" data-tab="gotchas"><i data-lucide="alert-triangle" style="width:13px;height:13px;vertical-align:middle;margin-right:3px"></i> Gotchas</div>
|
|
255
|
+
<div class="tab" data-tab="health"><i data-lucide="heart-pulse" style="width:13px;height:13px;vertical-align:middle;margin-right:3px"></i> Health</div>
|
|
150
256
|
</div>
|
|
151
257
|
<div class="tab-content" id="tab-content"></div>
|
|
152
258
|
</div>
|
|
153
259
|
</div>
|
|
260
|
+
<div id="modal-root"></div>
|
|
261
|
+
<div id="ctx-menu">
|
|
262
|
+
<div class="ctx-item" onclick="ctxViewDetail()"><i data-lucide="eye"></i> View Details</div>
|
|
263
|
+
<div class="ctx-item" onclick="ctxCopyId()"><i data-lucide="copy"></i> Copy ID</div>
|
|
264
|
+
<div class="ctx-sep"></div>
|
|
265
|
+
<div class="ctx-item" onclick="ctxZoomTo()"><i data-lucide="focus"></i> Zoom to Node</div>
|
|
266
|
+
</div>
|
|
154
267
|
|
|
155
268
|
<script>
|
|
156
|
-
|
|
157
|
-
|
|
269
|
+
var API = "/api";
|
|
270
|
+
var network = null, nodesData = null, allNodes = null, allEdges = null, currentDbSize = "0 KB";
|
|
271
|
+
var edgeDefaults = {};
|
|
272
|
+
|
|
273
|
+
async function resetKumaDb() {
|
|
274
|
+
var confirmation = prompt("⚠️ TYPE 'DELETE' TO CONFIRM RESETTING KUMA DB\nThis will permanently wipe all knowledge:");
|
|
275
|
+
if (confirmation !== "DELETE") { if (confirmation !== null) alert("Reset cancelled."); return; }
|
|
276
|
+
try {
|
|
277
|
+
var res = await fetch(API + "/reset-db", { method: "POST" }).then(function(r){return r.json()});
|
|
278
|
+
if (res.ok) { alert("Kuma DB reset."); refetch(); }
|
|
279
|
+
} catch (e) { alert("Reset failed: " + e.message); }
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
async function stopKumaStudio() {
|
|
283
|
+
if (!confirm("Stop Kuma Studio?")) return;
|
|
284
|
+
try {
|
|
285
|
+
await fetch(API + "/stop-server", { method: "POST" });
|
|
286
|
+
document.body.innerHTML = '<div style="display:flex;height:100vh;align-items:center;justify-content:center;color:#94a3b8;font-size:16px;">Server stopped. Close this tab.</div>';
|
|
287
|
+
} catch (e) { alert("Stop failed: " + e.message); }
|
|
288
|
+
}
|
|
158
289
|
|
|
159
290
|
function resetGraphFocus() {
|
|
160
291
|
if (!network) return;
|
|
161
292
|
network.body.data.nodes.forEach(function(n) {
|
|
162
|
-
network.body.data.nodes.update({ id: n.id, opacity: 1.0 });
|
|
293
|
+
network.body.data.nodes.update({ id: n.id, opacity: 1.0, font: { color: '#ffffff', bold: true } });
|
|
163
294
|
});
|
|
164
295
|
document.getElementById("graph-tooltip").style.display = 'none';
|
|
165
|
-
var
|
|
166
|
-
if (searchInput) searchInput.value = "";
|
|
296
|
+
var s = document.getElementById("node-search"); if (s) s.value = "";
|
|
167
297
|
}
|
|
168
298
|
|
|
169
299
|
function exportGraphPNG() {
|
|
@@ -176,32 +306,45 @@ function exportGraphPNG() {
|
|
|
176
306
|
link.click();
|
|
177
307
|
}
|
|
178
308
|
|
|
309
|
+
function restartPhysics() {
|
|
310
|
+
if (!network) return;
|
|
311
|
+
network.setOptions({ physics: { enabled: true } });
|
|
312
|
+
network.startSimulation();
|
|
313
|
+
document.getElementById('btn-unfreeze-physics').classList.add('active');
|
|
314
|
+
document.getElementById('btn-freeze-physics').classList.remove('active');
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function stopPhysics() {
|
|
318
|
+
if (!network) return;
|
|
319
|
+
network.stopSimulation();
|
|
320
|
+
network.setOptions({ physics: { enabled: false } });
|
|
321
|
+
document.getElementById('btn-freeze-physics').classList.add('active');
|
|
322
|
+
document.getElementById('btn-unfreeze-physics').classList.remove('active');
|
|
323
|
+
}
|
|
324
|
+
|
|
179
325
|
async function init() {
|
|
180
326
|
var status = await fetch(API+"/status").then(function(r){return r.json()});
|
|
181
327
|
var dot = document.getElementById("status-dot");
|
|
182
328
|
var txt = document.getElementById("status-text");
|
|
183
|
-
if (status.ok) {
|
|
184
|
-
|
|
185
|
-
txt.textContent = status.project || "connected";
|
|
186
|
-
} else {
|
|
187
|
-
dot.className = "dot off";
|
|
188
|
-
txt.textContent = "no kuma.db found";
|
|
189
|
-
}
|
|
329
|
+
if (status.ok) { dot.className = "dot on"; txt.textContent = status.project || "connected"; currentDbSize = status.dbSize || "0 KB"; }
|
|
330
|
+
else { dot.className = "dot off"; txt.textContent = "no kuma.db found"; }
|
|
190
331
|
document.getElementById("loading").style.display = "none";
|
|
191
332
|
var data = await fetch(API+"/dashboard").then(function(r){return r.json()});
|
|
192
333
|
if (data.error) return showError(data.error);
|
|
193
334
|
setupTabs();
|
|
194
335
|
renderStats(data);
|
|
195
336
|
renderGraph(data);
|
|
337
|
+
renderFeatures(data);
|
|
196
338
|
renderGotchas(data);
|
|
197
|
-
renderTrajectories(data);
|
|
198
339
|
renderHealth(data);
|
|
199
340
|
setupSearch();
|
|
341
|
+
lucide.createIcons();
|
|
200
342
|
}
|
|
201
343
|
init();
|
|
202
344
|
|
|
203
345
|
function showError(msg) {
|
|
204
|
-
document.getElementById("graph-container").innerHTML = '<div class="empty"
|
|
346
|
+
document.getElementById("graph-container").innerHTML = '<div class="empty"><i data-lucide="alert-circle" style="width:18px;height:18px;vertical-align:middle;margin-right:4px"></i> '+msg+'</div>';
|
|
347
|
+
lucide.createIcons();
|
|
205
348
|
}
|
|
206
349
|
|
|
207
350
|
function renderStats(data) {
|
|
@@ -211,36 +354,36 @@ function renderStats(data) {
|
|
|
211
354
|
document.getElementById("stats-bar").innerHTML =
|
|
212
355
|
'<div class="stat"><span class="num">'+(s.node_count||0)+'</span><span class="lbl">nodes</span></div>'+
|
|
213
356
|
'<div class="stat"><span class="num">'+(s.edge_count||0)+'</span><span class="lbl">edges</span></div>'+
|
|
357
|
+
'<div class="stat"><span class="num" style="color:var(--accent)">'+(s.feature_count||0)+'</span><span class="lbl">features</span></div>'+
|
|
214
358
|
'<div class="stat"><span class="num">'+(s.gotcha_count||0)+'</span><span class="lbl">gotchas</span></div>'+
|
|
215
|
-
'<div class="stat"><span class="num">'+
|
|
359
|
+
'<div class="stat"><span class="num" style="color:var(--accent)">'+currentDbSize+'</span><span class="lbl">kuma.db</span></div>'+
|
|
216
360
|
'<div class="stat"><span class="num" style="color:'+hcColor+'">'+(hc||0)+'</span><span class="lbl">health</span></div>';
|
|
217
361
|
}
|
|
218
362
|
|
|
219
|
-
/** Truncate long labels for display */
|
|
220
363
|
function truncateLabel(name, maxLen) {
|
|
221
364
|
if (!name) return "";
|
|
222
|
-
|
|
223
|
-
return name.substring(0, maxLen-2) + "..";
|
|
365
|
+
return name.length <= maxLen ? name : name.substring(0, maxLen-2) + "..";
|
|
224
366
|
}
|
|
225
367
|
|
|
226
|
-
/** Calculate node size based on label length and type */
|
|
227
368
|
function calcNodeSize(name, type) {
|
|
228
|
-
var
|
|
229
|
-
if (
|
|
230
|
-
if (type === "
|
|
231
|
-
|
|
369
|
+
var isGotcha = name && name.toLowerCase().indexOf("gotcha:") === 0;
|
|
370
|
+
if (isGotcha) return 14;
|
|
371
|
+
if (type === "variable") return 14;
|
|
372
|
+
var base = 18;
|
|
373
|
+
if (type === "feature") base = 32;
|
|
374
|
+
if (type === "feature_domain") base = 30;
|
|
375
|
+
if (type === "workflow") base = 24;
|
|
376
|
+
if (type === "cross_service_link") base = 20;
|
|
232
377
|
if (type === "function") base = 16;
|
|
233
|
-
if (type === "class") base =
|
|
234
|
-
if (type === "module") base =
|
|
235
|
-
if (type === "component") base =
|
|
236
|
-
if (type === "
|
|
237
|
-
if (type === "test") base =
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
return Math.min(32, Math.max(base, base + len * 0.4));
|
|
378
|
+
if (type === "class") base = 20;
|
|
379
|
+
if (type === "module") base = 22;
|
|
380
|
+
if (type === "component") base = 20;
|
|
381
|
+
if (type === "api_route") base = 18;
|
|
382
|
+
if (type === "test") base = 18;
|
|
383
|
+
var len = name ? Math.min(name.length, 25) : 4;
|
|
384
|
+
return Math.min(36, Math.max(base, base + len * 0.4));
|
|
241
385
|
}
|
|
242
386
|
|
|
243
|
-
|
|
244
387
|
function renderGraph(data) {
|
|
245
388
|
allNodes = data.nodes || [];
|
|
246
389
|
allEdges = data.edges || [];
|
|
@@ -249,223 +392,377 @@ function renderGraph(data) {
|
|
|
249
392
|
return;
|
|
250
393
|
}
|
|
251
394
|
|
|
252
|
-
var
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
function:'
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
395
|
+
var colors = {
|
|
396
|
+
feature: '#a855f7',
|
|
397
|
+
feature_domain: '#dc2626',
|
|
398
|
+
workflow: '#2563eb',
|
|
399
|
+
cross_service_link: '#059669',
|
|
400
|
+
function: '#7c3aed',
|
|
401
|
+
class: '#0891b2',
|
|
402
|
+
module: '#16a34a',
|
|
403
|
+
component: '#ea580c',
|
|
404
|
+
file: '#d97706',
|
|
405
|
+
api_route: '#db2777',
|
|
406
|
+
test: '#0d9488',
|
|
407
|
+
variable: '#6366f1',
|
|
408
|
+
gotcha: '#f97316',
|
|
409
|
+
};
|
|
410
|
+
var shapes = {
|
|
411
|
+
feature: 'star',
|
|
412
|
+
feature_domain: 'star',
|
|
413
|
+
workflow: 'ellipse',
|
|
414
|
+
cross_service_link: 'box',
|
|
415
|
+
function: 'box',
|
|
416
|
+
class: 'hexagon',
|
|
417
|
+
module: 'ellipse',
|
|
418
|
+
component: 'diamond',
|
|
419
|
+
file: 'box',
|
|
420
|
+
api_route: 'triangleDown',
|
|
421
|
+
test: 'star',
|
|
422
|
+
variable: 'box',
|
|
423
|
+
};
|
|
424
|
+
|
|
261
425
|
var maxNodes = 300;
|
|
262
426
|
var displayNodes = allNodes;
|
|
263
427
|
var truncated = allNodes.length > maxNodes;
|
|
264
|
-
if (truncated)
|
|
265
|
-
displayNodes = allNodes.slice(0, maxNodes);
|
|
266
|
-
}
|
|
428
|
+
if (truncated) displayNodes = allNodes.slice(0, maxNodes);
|
|
267
429
|
|
|
268
430
|
var visNodes = new vis.DataSet(displayNodes.map(function(n){
|
|
269
|
-
var
|
|
270
|
-
var
|
|
271
|
-
var
|
|
431
|
+
var isGotcha = n.name && n.name.toLowerCase().indexOf("gotcha:") === 0;
|
|
432
|
+
var shape = isGotcha ? 'triangle' : (shapes[n.type] || 'dot');
|
|
433
|
+
var maxLen = isGotcha ? 20 : (shape === 'ellipse' ? 18 : 32);
|
|
434
|
+
var label = truncateLabel(n.name, maxLen);
|
|
435
|
+
var size = isGotcha ? 12 : calcNodeSize(n.name, n.type);
|
|
436
|
+
var color = isGotcha ? '#f97316' : (colors[n.type] || '#64748b');
|
|
272
437
|
return {
|
|
273
|
-
id: n.id,
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
font: { color: '#e0e0e0', size: Math.min(14, Math.max(10, size * 0.5)), face: 'monospace' },
|
|
279
|
-
borderWidth: 0,
|
|
280
|
-
size: size,
|
|
281
|
-
mass: 1 + (size / 40),
|
|
438
|
+
id: n.id, label: label,
|
|
439
|
+
color: { background: color, border: color, highlight: { background: color, border: '#38bdf8' }, hover: { background: color, border: '#a855f7' } },
|
|
440
|
+
shape: shape,
|
|
441
|
+
font: { color: '#ffffff', size: Math.min(13, Math.max(10, size * 0.55)), face: 'sans-serif', multi: false, bold: true },
|
|
442
|
+
borderWidth: 0, size: size, mass: 1 + (size / 40),
|
|
282
443
|
};
|
|
283
444
|
}));
|
|
284
445
|
|
|
285
446
|
var nodeIds = new Set(displayNodes.map(function(n){return n.id}));
|
|
286
|
-
var
|
|
287
|
-
return nodeIds.has(e.source) && nodeIds.has(e.target);
|
|
288
|
-
}).slice(0, 500);
|
|
447
|
+
var edgeList = allEdges.slice();
|
|
289
448
|
|
|
290
|
-
var
|
|
291
|
-
|
|
449
|
+
var displayEdges = edgeList.filter(function(e){ return nodeIds.has(e.source) && nodeIds.has(e.target); }).slice(0, 500);
|
|
450
|
+
|
|
451
|
+
edgeDefaults = {};
|
|
452
|
+
var visEdges = new vis.DataSet(displayEdges.map(function(e, idx){
|
|
453
|
+
var edgeColor = '#4a4a6a';
|
|
292
454
|
var width = 1.5;
|
|
293
455
|
var dashes = false;
|
|
294
456
|
if (e.relation === 'flows_through') { edgeColor = '#3b82f6'; width = 2.5; }
|
|
457
|
+
if (e.relation === 'affects') { edgeColor = '#ef4444'; width = 2; dashes = [5, 5]; }
|
|
295
458
|
if (e.relation === 'triggers') { edgeColor = '#ef4444'; width = 2; dashes = true; }
|
|
296
459
|
if (e.relation === 'syncs_with') { edgeColor = '#10b981'; width = 2; }
|
|
297
|
-
if (e.relation === '
|
|
460
|
+
if (e.relation === 'owns') { edgeColor = '#a855f7'; width = 2; }
|
|
461
|
+
if (e.relation === 'contains') { edgeColor = '#64748b'; width = 1; dashes = true; }
|
|
462
|
+
if (e.relation === 'depends_on') { edgeColor = '#eab308'; width = 1.5; }
|
|
298
463
|
|
|
464
|
+
edgeDefaults['e'+idx] = { color: edgeColor, width: width };
|
|
299
465
|
return {
|
|
300
|
-
from: e.source, to: e.target,
|
|
466
|
+
id: 'e'+idx, from: e.source, to: e.target,
|
|
301
467
|
label: e.relation || '',
|
|
302
468
|
arrows: { to: { enabled: true, scaleFactor: 0.7, type: 'arrow' } },
|
|
303
469
|
color: { color: edgeColor, highlight: '#a855f7', hover: '#38bdf8', opacity: 0.85 },
|
|
304
470
|
font: { size: 10, color: '#94a3b8', strokeWidth: 0, background: 'transparent' },
|
|
305
471
|
dashes: dashes,
|
|
306
472
|
smooth: { type: 'continuous', roundness: 0.3 },
|
|
307
|
-
width: width,
|
|
308
|
-
hoverWidth: 3,
|
|
309
|
-
selectionWidth: 3,
|
|
473
|
+
width: width, hoverWidth: 3, selectionWidth: 3,
|
|
310
474
|
};
|
|
311
475
|
}));
|
|
312
476
|
|
|
313
477
|
var container = document.getElementById("graph-container");
|
|
314
478
|
network = new vis.Network(container, {nodes: visNodes, edges: visEdges}, {
|
|
315
479
|
physics: {
|
|
316
|
-
enabled: true,
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
gravitationalConstant: -2000,
|
|
320
|
-
centralGravity: 0.3,
|
|
321
|
-
springLength: 150,
|
|
322
|
-
springConstant: 0.04,
|
|
323
|
-
damping: 0.6,
|
|
324
|
-
avoidOverlap: 0.5,
|
|
325
|
-
},
|
|
326
|
-
stabilization: { iterations: 400, updateInterval: 25, onlyDynamicEdges: false },
|
|
327
|
-
adaptiveTimestep: true,
|
|
328
|
-
},
|
|
329
|
-
interaction: {
|
|
330
|
-
hover: true,
|
|
331
|
-
tooltipDelay: 300,
|
|
332
|
-
zoomView: true,
|
|
333
|
-
dragView: true,
|
|
334
|
-
dragNodes: true,
|
|
335
|
-
keyboard: true,
|
|
336
|
-
multiselect: true,
|
|
337
|
-
selectable: true,
|
|
338
|
-
selectConnectedEdges: false,
|
|
339
|
-
},
|
|
340
|
-
nodes: {
|
|
341
|
-
borderWidth: 0,
|
|
342
|
-
font: { color: '#e0e0e0', size: 12, face: 'monospace' },
|
|
343
|
-
margin: { top: 4, bottom: 4, left: 6, right: 6 },
|
|
344
|
-
shadow: { enabled: true, color: 'rgba(124,58,237,0.15)', size: 6, x: 0, y: 2 },
|
|
345
|
-
},
|
|
346
|
-
edges: {
|
|
347
|
-
smooth: { type: 'continuous', roundness: 0.3 },
|
|
348
|
-
width: 1.5,
|
|
349
|
-
color: { color: '#4a4a6a', highlight: '#7c3aed' },
|
|
480
|
+
enabled: true, solver: 'barnesHut',
|
|
481
|
+
barnesHut: { gravitationalConstant: -3000, centralGravity: 0.05, springLength: 150, springConstant: 0.02, damping: 0.4, avoidOverlap: 0.5 },
|
|
482
|
+
stabilization: { iterations: 120, updateInterval: 50, onlyDynamicEdges: false, fit: false },
|
|
350
483
|
},
|
|
484
|
+
interaction: { hover: true, tooltipDelay: 300, zoomView: true, dragView: true, dragNodes: true, keyboard: true, multiselect: true, selectable: true, selectConnectedEdges: false },
|
|
485
|
+
nodes: { borderWidth: 0, font: { color: '#ffffff', size: 11, face: 'sans-serif', bold: true }, margin: { top: 6, bottom: 6, left: 10, right: 10 }, shapeProperties: { borderRadius: 6 } },
|
|
486
|
+
edges: { smooth: { type: 'continuous', roundness: 0.2 }, width: 1, color: { color: '#4a4a6a', highlight: '#7c3aed' } },
|
|
351
487
|
configure: { enabled: false },
|
|
352
488
|
});
|
|
353
489
|
|
|
354
|
-
//
|
|
490
|
+
// Auto-freeze physics after stabilization (saves CPU)
|
|
491
|
+
network.once('stabilizationIterationsDone', function() {
|
|
492
|
+
network.setOptions({ physics: { enabled: false } });
|
|
493
|
+
document.getElementById('btn-freeze-physics').classList.add('active');
|
|
494
|
+
document.getElementById('btn-unfreeze-physics').classList.remove('active');
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
// Left-click node → focus only (no modal)
|
|
498
|
+
var lastFocusedNode = null;
|
|
499
|
+
var currentFocusDepth = 2;
|
|
500
|
+
window.setDepth = function(d) {
|
|
501
|
+
currentFocusDepth = d;
|
|
502
|
+
document.querySelectorAll('.depth-btn').forEach(function(b) { b.classList.toggle('active', parseInt(b.dataset.depth) === d); });
|
|
503
|
+
if (lastFocusedNode) focusNode(lastFocusedNode);
|
|
504
|
+
};
|
|
505
|
+
function focusNode(selectedId) {
|
|
506
|
+
// BFS with depth limit
|
|
507
|
+
var connected = new Map();
|
|
508
|
+
var queue = [[selectedId, 0]];
|
|
509
|
+
connected.set(selectedId, 0);
|
|
510
|
+
while (queue.length) {
|
|
511
|
+
var pair = queue.shift();
|
|
512
|
+
var cur = pair[0], depth = pair[1];
|
|
513
|
+
if (depth >= currentFocusDepth) continue;
|
|
514
|
+
network.getConnectedNodes(cur).forEach(function(nid) {
|
|
515
|
+
if (!connected.has(nid)) { connected.set(nid, depth + 1); queue.push([nid, depth + 1]); }
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
var nodeUpdates = [], edgeUpdates = [];
|
|
519
|
+
network.body.data.nodes.forEach(function(n) {
|
|
520
|
+
var hit = connected.has(n.id);
|
|
521
|
+
nodeUpdates.push({ id: n.id, opacity: hit ? 1.0 : 0.08, font: { color: hit ? '#ffffff' : 'rgba(255,255,255,0.04)', bold: hit } });
|
|
522
|
+
});
|
|
523
|
+
network.body.data.nodes.update(nodeUpdates);
|
|
524
|
+
network.body.data.edges.forEach(function(e) {
|
|
525
|
+
var hit = connected.has(e.from) && connected.has(e.to);
|
|
526
|
+
var def = edgeDefaults[e.id] || {};
|
|
527
|
+
edgeUpdates.push({ id: e.id, color: { color: hit ? (def.color || '#64748b') : 'rgba(100,116,139,0.03)', highlight: '#a855f7', hover: '#38bdf8', opacity: hit ? 0.9 : 0.03 }, width: hit ? (def.width || 1) : 0.15 });
|
|
528
|
+
});
|
|
529
|
+
network.body.data.edges.update(edgeUpdates);
|
|
530
|
+
}
|
|
355
531
|
network.on('click', function(params) {
|
|
356
|
-
|
|
532
|
+
hideCtxMenu();
|
|
357
533
|
if (params.nodes.length) {
|
|
358
534
|
var selectedId = params.nodes[0];
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
network.body.data.nodes.forEach(function(n) {
|
|
363
|
-
network.body.data.nodes.update({
|
|
364
|
-
id: n.id,
|
|
365
|
-
opacity: connectedNodeIds.has(n.id) ? 1.0 : 0.15
|
|
366
|
-
});
|
|
367
|
-
});
|
|
368
|
-
|
|
369
|
-
var n = allNodes.find(function(x){return x.id===selectedId});
|
|
370
|
-
if (n) {
|
|
371
|
-
tip.style.display = 'block';
|
|
372
|
-
tip.innerHTML = '<b>' + n.name + '</b> <span style="color:var(--muted)">' + n.type + '</span>' +
|
|
373
|
-
(n.file_path ? '<br><span style="color:var(--accent2);font-size:11px;word-break:break-all">' + n.file_path + '</span>' : '');
|
|
374
|
-
// Position tooltip near the clicked node
|
|
375
|
-
var pos = network.getPositions([n.id]);
|
|
376
|
-
if (pos && pos[n.id]) {
|
|
377
|
-
var canvasPos = network.canvasToDOM(pos[n.id]);
|
|
378
|
-
var panelRect = container.getBoundingClientRect();
|
|
379
|
-
var tipX = Math.min(canvasPos.x + 12, panelRect.width - 340);
|
|
380
|
-
var tipY = Math.max(canvasPos.y - 10, 0);
|
|
381
|
-
tip.style.left = Math.max(tipX, 8) + 'px';
|
|
382
|
-
tip.style.top = tipY + 'px';
|
|
383
|
-
}
|
|
384
|
-
}
|
|
535
|
+
if (lastFocusedNode === selectedId) { lastFocusedNode = null; resetHighlight(); return; }
|
|
536
|
+
lastFocusedNode = selectedId;
|
|
537
|
+
focusNode(selectedId);
|
|
385
538
|
} else {
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
network.body.data.nodes.update({ id: n.id, opacity: 1.0 });
|
|
389
|
-
});
|
|
539
|
+
lastFocusedNode = null;
|
|
540
|
+
resetHighlight();
|
|
390
541
|
}
|
|
391
542
|
});
|
|
392
543
|
|
|
393
|
-
//
|
|
544
|
+
// Right-click node → context menu (document level, capture phase)
|
|
545
|
+
document.addEventListener('contextmenu', function(e) {
|
|
546
|
+
var containerEl = document.getElementById("graph-container");
|
|
547
|
+
if (!containerEl || !containerEl.contains(e.target)) return;
|
|
548
|
+
e.preventDefault();
|
|
549
|
+
var rect = containerEl.getBoundingClientRect();
|
|
550
|
+
var x = e.clientX - rect.left;
|
|
551
|
+
var y = e.clientY - rect.top;
|
|
552
|
+
var nodeId = network.getNodeAt({ x: x, y: y });
|
|
553
|
+
if (nodeId && typeof nodeId === 'string') {
|
|
554
|
+
var menu = document.getElementById('ctx-menu');
|
|
555
|
+
menu.style.display = 'block';
|
|
556
|
+
menu.style.left = e.clientX + 'px';
|
|
557
|
+
menu.style.top = e.clientY + 'px';
|
|
558
|
+
menu.dataset.nodeId = nodeId;
|
|
559
|
+
lucide.createIcons();
|
|
560
|
+
}
|
|
561
|
+
}, true);
|
|
562
|
+
// Close context menu on mousedown outside
|
|
563
|
+
document.addEventListener('mousedown', function(e) {
|
|
564
|
+
if (!e.target.closest('#ctx-menu')) hideCtxMenu();
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
// Hover → tooltip
|
|
394
568
|
network.on('hoverNode', function(params) {
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
569
|
+
network.body.data.nodes.updateOnly({ id: params.node, borderWidth: 2, borderWidthSelected: 2 });
|
|
570
|
+
var n = allNodes.find(function(x){return x.id===params.node});
|
|
571
|
+
if (n) {
|
|
572
|
+
var tip = document.getElementById("graph-tooltip");
|
|
573
|
+
tip.style.display = 'block';
|
|
574
|
+
tip.innerHTML = '<div style="word-break:break-all;overflow-wrap:anywhere;max-width:390px;white-space:normal;line-height:1.4"><b style="color:#f8fafc">' + n.name + '</b> <span style="color:#94a3b8">' + n.type + '</span>' +
|
|
575
|
+
(n.file_path ? '<br><span style="color:#38bdf8;font-size:11px;word-break:break-all;overflow-wrap:anywhere">' + n.file_path + '</span>' : '') +
|
|
576
|
+
'<br><span style="font-size:10px;color:#64748b">Right-click for options</span></div>';
|
|
577
|
+
var pos = network.getPositions([n.id]);
|
|
578
|
+
if (pos && pos[n.id]) {
|
|
579
|
+
var canvasPos = network.canvasToDOM(pos[n.id]);
|
|
580
|
+
var panelRect = container.getBoundingClientRect();
|
|
581
|
+
tip.style.left = Math.max(Math.min(canvasPos.x + 12, panelRect.width - 340), 8) + 'px';
|
|
582
|
+
tip.style.top = Math.max(canvasPos.y - 10, 0) + 'px';
|
|
583
|
+
}
|
|
584
|
+
}
|
|
401
585
|
});
|
|
402
586
|
network.on('blurNode', function(params) {
|
|
403
|
-
network.body.data.nodes.updateOnly({
|
|
404
|
-
|
|
405
|
-
borderWidth: 0,
|
|
406
|
-
borderWidthSelected: 0,
|
|
407
|
-
});
|
|
587
|
+
network.body.data.nodes.updateOnly({ id: params.node, borderWidth: 0, borderWidthSelected: 0 });
|
|
588
|
+
document.getElementById("graph-tooltip").style.display = 'none';
|
|
408
589
|
});
|
|
409
590
|
|
|
591
|
+
var types = [...new Set(allNodes.map(function(n){return n.type||'unknown'}))];
|
|
410
592
|
renderLegend(types, colors);
|
|
411
593
|
|
|
412
|
-
// Show truncation warning
|
|
413
594
|
if (truncated) {
|
|
414
595
|
var warn = document.createElement('div');
|
|
415
596
|
warn.style.cssText = 'position:absolute;top:16px;left:16px;z-index:10;background:rgba(234,179,8,0.15);color:var(--yellow);border:1px solid rgba(234,179,8,0.3);border-radius:8px;padding:8px 14px;font-size:12px;backdrop-filter:blur(8px);pointer-events:none';
|
|
416
|
-
warn.innerHTML = '
|
|
597
|
+
warn.innerHTML = '<i data-lucide="alert-triangle" style="width:14px;height:14px;vertical-align:middle;margin-right:4px"></i> Showing ' + maxNodes + ' / ' + allNodes.length + ' nodes';
|
|
417
598
|
document.getElementById('graph-panel').appendChild(warn);
|
|
418
599
|
}
|
|
600
|
+
}
|
|
419
601
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
602
|
+
function hideCtxMenu() { document.getElementById('ctx-menu').style.display = 'none'; }
|
|
603
|
+
function resetHighlight() {
|
|
604
|
+
if (!network) return;
|
|
605
|
+
var nUp = [], eUp = [];
|
|
606
|
+
network.body.data.nodes.forEach(function(n) { nUp.push({ id: n.id, opacity: 1.0, font: { color: '#ffffff', bold: true } }); });
|
|
607
|
+
network.body.data.nodes.update(nUp);
|
|
608
|
+
network.body.data.edges.forEach(function(e) {
|
|
609
|
+
var def = edgeDefaults[e.id];
|
|
610
|
+
eUp.push({ id: e.id, color: { color: def ? def.color : '#64748b', highlight: '#a855f7', hover: '#38bdf8', opacity: 0.85 }, width: def ? def.width : 1 });
|
|
423
611
|
});
|
|
612
|
+
network.body.data.edges.update(eUp);
|
|
613
|
+
document.getElementById("graph-tooltip").style.display = 'none';
|
|
614
|
+
}
|
|
615
|
+
function ctxViewDetail() {
|
|
616
|
+
var id = document.getElementById('ctx-menu').dataset.nodeId;
|
|
617
|
+
hideCtxMenu();
|
|
618
|
+
if (id) openNodeModal(id);
|
|
424
619
|
}
|
|
620
|
+
function ctxCopyId() {
|
|
621
|
+
var id = document.getElementById('ctx-menu').dataset.nodeId;
|
|
622
|
+
hideCtxMenu();
|
|
623
|
+
if (id) navigator.clipboard.writeText(id).catch(function(){});
|
|
624
|
+
}
|
|
625
|
+
function ctxZoomTo() {
|
|
626
|
+
var id = document.getElementById('ctx-menu').dataset.nodeId;
|
|
627
|
+
hideCtxMenu();
|
|
628
|
+
if (id && network) network.focus(id, { scale: 1.5, animation: { duration: 400, easingFunction: 'easeOutQuad' } });
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
// ============ NODE DETAIL MODAL ============
|
|
632
|
+
|
|
633
|
+
async function openNodeModal(nodeId) {
|
|
634
|
+
var node = allNodes.find(function(n){return n.id === nodeId;});
|
|
635
|
+
if (!node) return;
|
|
636
|
+
|
|
637
|
+
var root = document.getElementById("modal-root");
|
|
638
|
+
root.innerHTML = '<div class="modal-overlay" onclick="if(event.target===this)closeModal()"><div class="modal"><div class="modal-header"><h2>'+escapeHtml(node.name)+'</h2><button class="close" onclick="closeModal()">×</button></div><div class="modal-body"><div style="text-align:center;padding:20px;color:var(--muted)">Loading details...</div></div></div></div>';
|
|
639
|
+
|
|
640
|
+
try {
|
|
641
|
+
var detail = await fetch(API+"/node/"+encodeURIComponent(nodeId)).then(function(r){return r.json()});
|
|
642
|
+
var body = root.querySelector('.modal-body');
|
|
643
|
+
if (!detail || detail.error) {
|
|
644
|
+
body.innerHTML = '<div class="empty">Node not found</div>';
|
|
645
|
+
return;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
var n = typeof detail.node === 'string' ? JSON.parse(detail.node) : detail.node;
|
|
649
|
+
var outgoing = typeof detail.outgoing === 'string' ? JSON.parse(detail.outgoing) : (detail.outgoing || []);
|
|
650
|
+
var incoming = typeof detail.incoming === 'string' ? JSON.parse(detail.incoming) : (detail.incoming || []);
|
|
651
|
+
var gotchas = typeof detail.gotchas === 'string' ? JSON.parse(detail.gotchas) : (detail.gotchas || []);
|
|
652
|
+
|
|
653
|
+
var meta = {};
|
|
654
|
+
try { meta = typeof n.metadata === 'string' ? JSON.parse(n.metadata) : (n.metadata || {}); } catch(e) {}
|
|
655
|
+
|
|
656
|
+
var html = '';
|
|
657
|
+
|
|
658
|
+
// Basic info
|
|
659
|
+
html += '<div class="modal-section">';
|
|
660
|
+
html += '<h3>Info</h3>';
|
|
661
|
+
html += '<div class="modal-kv"><span class="key">Type</span><span class="val"><span style="color:var(--accent)">'+escapeHtml(n.type)+'</span></span></div>';
|
|
662
|
+
if (n.file_path) html += '<div class="modal-kv"><span class="key">Path</span><span class="val" style="color:var(--accent2)">'+escapeHtml(n.file_path)+'</span></div>';
|
|
663
|
+
if (meta.description) html += '<div class="modal-kv"><span class="key">Description</span><span class="val">'+escapeHtml(meta.description)+'</span></div>';
|
|
664
|
+
if (meta.tags && meta.tags.length) html += '<div class="modal-kv"><span class="key">Tags</span><span class="val">'+meta.tags.map(function(t){return '<span class="tag">'+escapeHtml(t)+'</span>';}).join(' ')+'</span></div>';
|
|
665
|
+
if (meta.risk) html += '<div class="modal-kv"><span class="key">Risk</span><span class="val" style="color:'+(meta.risk==='critical'?'var(--red)':meta.risk==='high'?'var(--orange)':meta.risk==='medium'?'var(--yellow)':'var(--green)')+'">'+escapeHtml(meta.risk)+'</span></div>';
|
|
666
|
+
if (n.updated_at) html += '<div class="modal-kv"><span class="key">Updated</span><span class="val">'+new Date(n.updated_at*1000).toLocaleString()+'</span></div>';
|
|
667
|
+
html += '</div>';
|
|
668
|
+
|
|
669
|
+
// Outgoing relations
|
|
670
|
+
if (outgoing.length) {
|
|
671
|
+
html += '<div class="modal-section"><h3>Outgoing ('+outgoing.length+')</h3>';
|
|
672
|
+
outgoing.forEach(function(r) {
|
|
673
|
+
var rel = typeof r === 'string' ? JSON.parse(r) : r;
|
|
674
|
+
html += '<div class="relation-item" onclick="closeModal();setTimeout(function(){openNodeModal(\''+escapeHtml(rel.target)+'\')},200)">';
|
|
675
|
+
html += '<span class="arrow"><i data-lucide="arrow-right" style="width:12px;height:12px"></i></span>';
|
|
676
|
+
html += '<span class="name">'+escapeHtml(rel.target_name || rel.target.split('::').pop())+'</span>';
|
|
677
|
+
html += '<span class="type">'+escapeHtml(rel.target_type||'')+'</span>';
|
|
678
|
+
html += '<span class="weight">w:'+((rel.weight||1).toFixed(2))+'</span>';
|
|
679
|
+
html += '<span style="color:var(--accent);font-size:10px;flex-shrink:0">'+escapeHtml(rel.relation)+'</span>';
|
|
680
|
+
html += '</div>';
|
|
681
|
+
});
|
|
682
|
+
html += '</div>';
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
// Incoming relations
|
|
686
|
+
if (incoming.length) {
|
|
687
|
+
html += '<div class="modal-section"><h3>Incoming ('+incoming.length+')</h3>';
|
|
688
|
+
incoming.forEach(function(r) {
|
|
689
|
+
var rel = typeof r === 'string' ? JSON.parse(r) : r;
|
|
690
|
+
html += '<div class="relation-item" onclick="closeModal();setTimeout(function(){openNodeModal(\''+escapeHtml(rel.source)+'\')},200)">';
|
|
691
|
+
html += '<span class="arrow"><i data-lucide="arrow-left" style="width:12px;height:12px"></i></span>';
|
|
692
|
+
html += '<span class="name">'+escapeHtml(rel.source_name || rel.source.split('::').pop())+'</span>';
|
|
693
|
+
html += '<span class="type">'+escapeHtml(rel.source_type||'')+'</span>';
|
|
694
|
+
html += '<span class="weight">w:'+((rel.weight||1).toFixed(2))+'</span>';
|
|
695
|
+
html += '<span style="color:var(--accent);font-size:10px;flex-shrink:0">'+escapeHtml(rel.relation)+'</span>';
|
|
696
|
+
html += '</div>';
|
|
697
|
+
});
|
|
698
|
+
html += '</div>';
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
// Gotchas
|
|
702
|
+
if (gotchas.length) {
|
|
703
|
+
html += '<div class="modal-section"><h3>Gotchas ('+gotchas.length+')</h3>';
|
|
704
|
+
gotchas.forEach(function(g) {
|
|
705
|
+
var got = typeof g === 'string' ? JSON.parse(g) : g;
|
|
706
|
+
html += '<div class="gotcha-item" style="border-color:var(--'+(got.severity==='critical'?'red':got.severity==='high'?'orange':got.severity==='medium'?'yellow':'green')+')">';
|
|
707
|
+
html += '<div class="desc">'+escapeHtml(got.description)+'</div>';
|
|
708
|
+
html += '<span class="sev-tag sev-'+got.severity+'">'+got.severity+'</span>';
|
|
709
|
+
html += '</div>';
|
|
710
|
+
});
|
|
711
|
+
html += '</div>';
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
if (!outgoing.length && !incoming.length && !gotchas.length) {
|
|
715
|
+
html += '<div class="empty" style="padding:20px">No relations recorded yet.</div>';
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
body.innerHTML = html;
|
|
719
|
+
lucide.createIcons();
|
|
720
|
+
} catch(e) {
|
|
721
|
+
root.querySelector('.modal-body').innerHTML = '<div class="empty">Failed to load details: '+escapeHtml(e.message)+'</div>';
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
function closeModal() {
|
|
726
|
+
document.getElementById("modal-root").innerHTML = "";
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
function escapeHtml(str) {
|
|
730
|
+
if (!str) return '';
|
|
731
|
+
return String(str).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
// ============ SIDEBAR ============
|
|
425
735
|
|
|
426
736
|
async function refetch() {
|
|
427
737
|
var btn = document.getElementById('refetch-btn');
|
|
428
|
-
btn.innerHTML = '
|
|
738
|
+
btn.innerHTML = '<i data-lucide="loader" style="width:13px;height:13px;vertical-align:middle;margin-right:3px"></i> Loading...';
|
|
429
739
|
btn.style.opacity = '0.6';
|
|
740
|
+
lucide.createIcons();
|
|
430
741
|
try {
|
|
431
|
-
// Update status too
|
|
432
742
|
var status = await fetch(API+'/status').then(function(r){return r.json()});
|
|
433
|
-
var dot = document.getElementById(
|
|
434
|
-
var txt = document.getElementById(
|
|
435
|
-
if (status.ok) {
|
|
436
|
-
dot.className = 'dot on';
|
|
437
|
-
txt.textContent = status.project || 'connected';
|
|
438
|
-
} else {
|
|
439
|
-
dot.className = 'dot off';
|
|
440
|
-
txt.textContent = 'no kuma.db found';
|
|
441
|
-
}
|
|
442
|
-
// Refetch dashboard data
|
|
743
|
+
var dot = document.getElementById("status-dot");
|
|
744
|
+
var txt = document.getElementById("status-text");
|
|
745
|
+
if (status.ok) { dot.className = 'dot on'; txt.textContent = status.project || 'connected'; } else { dot.className = 'dot off'; txt.textContent = 'no kuma.db found'; }
|
|
443
746
|
var data = await fetch(API+'/dashboard').then(function(r){return r.json()});
|
|
444
747
|
if (data.error) { showError(data.error); return; }
|
|
445
|
-
renderStats(data);
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
renderHealth(data);
|
|
450
|
-
} catch(e) {
|
|
451
|
-
showError('Refetch failed: ' + e.message);
|
|
452
|
-
}
|
|
453
|
-
btn.innerHTML = '↻ Refetch';
|
|
748
|
+
renderStats(data); renderGraph(data); renderFeatures(data); renderGotchas(data); renderHealth(data);
|
|
749
|
+
lucide.createIcons();
|
|
750
|
+
} catch(e) { showError('Refetch failed: ' + e.message); }
|
|
751
|
+
btn.innerHTML = '<i data-lucide="refresh-cw" style="width:13px;height:13px;vertical-align:middle;margin-right:3px"></i> Refetch';
|
|
454
752
|
btn.style.opacity = '1';
|
|
753
|
+
lucide.createIcons();
|
|
455
754
|
}
|
|
456
755
|
|
|
457
756
|
function renderLegend(types, colors){
|
|
458
757
|
document.getElementById("legend").innerHTML = '<div class="title">Legend</div>' +
|
|
459
|
-
types.map(function(t){
|
|
460
|
-
return '<div class="legend-item"><div class="legend-dot" style="background:'+(colors[t]||'#888')+'"></div>' + t + '</div>';
|
|
461
|
-
}).join('');
|
|
758
|
+
types.map(function(t){ return '<div class="legend-item"><div class="legend-dot" style="background:'+(colors[t]||'#888')+'"></div>' + t + '</div>'; }).join('');
|
|
462
759
|
}
|
|
463
760
|
|
|
464
761
|
function setupTabs(){
|
|
465
762
|
var content = document.getElementById("tab-content");
|
|
466
763
|
content.innerHTML =
|
|
467
|
-
'<div class="panel active" id="panel-
|
|
468
|
-
'<div class="panel" id="panel-
|
|
764
|
+
'<div class="panel active" id="panel-features"><div class="empty">Loading...</div></div>' +
|
|
765
|
+
'<div class="panel" id="panel-gotchas"><div class="empty">Loading...</div></div>' +
|
|
469
766
|
'<div class="panel" id="panel-health"><div class="empty">Loading...</div></div>';
|
|
470
767
|
document.querySelectorAll('.tab').forEach(function(tab){
|
|
471
768
|
tab.onclick = function(){
|
|
@@ -479,6 +776,28 @@ function setupTabs(){
|
|
|
479
776
|
document.querySelector('.tab.active').click();
|
|
480
777
|
}
|
|
481
778
|
|
|
779
|
+
function renderFeatures(data) {
|
|
780
|
+
var items = data.features || [];
|
|
781
|
+
var el = document.getElementById("panel-features");
|
|
782
|
+
if(!el) return;
|
|
783
|
+
if(!items.length){el.innerHTML='<div class="empty">No features recorded yet.<br><br>Use:<br><code style="color:var(--accent)">kuma_memory({action:\'feature\', title:\'Auth\'})</code></div>';return;}
|
|
784
|
+
el.innerHTML = items.map(function(f){
|
|
785
|
+
var feat = typeof f === 'string' ? JSON.parse(f) : f;
|
|
786
|
+
var meta = {};
|
|
787
|
+
try { meta = typeof feat.metadata === 'string' ? JSON.parse(feat.metadata) : (feat.metadata || {}); } catch(e) {}
|
|
788
|
+
var tags = meta.tags || [];
|
|
789
|
+
var risk = meta.risk || 'medium';
|
|
790
|
+
var riskColor = risk==='critical'?'var(--red)':risk==='high'?'var(--orange)':risk==='medium'?'var(--yellow)':'var(--green)';
|
|
791
|
+
return '<div class="feature-item" onclick="openNodeModal(\''+escapeHtml(feat.id)+'\')">' +
|
|
792
|
+
'<div class="name"><i data-lucide="star" style="width:14px;height:14px;vertical-align:middle;margin-right:4px"></i> '+escapeHtml(feat.name)+'</div>' +
|
|
793
|
+
(meta.description ? '<div class="meta">'+escapeHtml(meta.description)+'</div>' : '') +
|
|
794
|
+
'<div class="meta">Risk: <span style="color:'+riskColor+'">'+risk+'</span></div>' +
|
|
795
|
+
(tags.length ? '<div class="tags">'+tags.map(function(t){return '<span class="tag">'+escapeHtml(t)+'</span>';}).join('')+'</div>' : '') +
|
|
796
|
+
'</div>';
|
|
797
|
+
}).join('');
|
|
798
|
+
lucide.createIcons();
|
|
799
|
+
}
|
|
800
|
+
|
|
482
801
|
function renderGotchas(data){
|
|
483
802
|
var items = data.gotchas || [];
|
|
484
803
|
var el = document.getElementById("panel-gotchas");
|
|
@@ -488,24 +807,12 @@ function renderGotchas(data){
|
|
|
488
807
|
var color = g.severity==='critical'?'red':g.severity==='high'?'orange':g.severity==='medium'?'yellow':'green';
|
|
489
808
|
return '<div class="gotcha-item" style="border-color:var(--'+color+')">' +
|
|
490
809
|
'<div class="file">'+(g.file_path||'global')+'</div>' +
|
|
491
|
-
'<div class="desc">'+g.description+'</div>' +
|
|
810
|
+
'<div class="desc">'+escapeHtml(g.description)+'</div>' +
|
|
492
811
|
'<span class="sev-tag sev-'+g.severity+'">'+g.severity+'</span>' +
|
|
493
|
-
(g.workaround ? ' <span style="font-size:11px;color:var(--muted)"
|
|
812
|
+
(g.workaround ? ' <span style="font-size:11px;color:var(--muted)"><i data-lucide="lightbulb" style="width:11px;height:11px;vertical-align:middle"></i> '+escapeHtml(g.workaround)+'</span>' : '') +
|
|
494
813
|
'</div>';
|
|
495
814
|
}).join('');
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
function renderTrajectories(data){
|
|
499
|
-
var items = data.trajectories || [];
|
|
500
|
-
var el = document.getElementById("panel-trajectories");
|
|
501
|
-
if(!el) return;
|
|
502
|
-
if(!items.length){el.innerHTML='<div class="empty">No trajectories yet</div>';return;}
|
|
503
|
-
el.innerHTML = items.map(function(t){
|
|
504
|
-
var dur = t.total_duration_ms ? (t.total_duration_ms/1000).toFixed(1)+'s' : '-';
|
|
505
|
-
var rate = t.success_rate != null ? (t.success_rate*100).toFixed(0)+'%' : '-';
|
|
506
|
-
var date = t.created_at ? new Date(t.created_at*1000).toLocaleDateString() : '-';
|
|
507
|
-
return '<div class="traj-item"><div class="goal">'+t.goal+'</div><div class="meta">⏱ '+dur+' · ✔ '+rate+' · '+date+'</div></div>';
|
|
508
|
-
}).join('');
|
|
815
|
+
lucide.createIcons();
|
|
509
816
|
}
|
|
510
817
|
|
|
511
818
|
function renderHealth(data){
|
|
@@ -517,7 +824,7 @@ function renderHealth(data){
|
|
|
517
824
|
var cls = h.score>=80?'good':h.score>=50?'ok':'bad';
|
|
518
825
|
var summary = h.summary||h.risk_level||'-';
|
|
519
826
|
var date = h.created_at ? new Date(h.created_at*1000).toLocaleDateString() : '-';
|
|
520
|
-
return '<div class="health-item"><div class="score '+cls+'">'+h.score+'</div><div class="meta">'+summary+' · '+date+'</div></div>';
|
|
827
|
+
return '<div class="health-item"><div class="score '+cls+'">'+h.score+'</div><div class="meta">'+escapeHtml(summary)+' · '+date+'</div></div>';
|
|
521
828
|
}).join('');
|
|
522
829
|
}
|
|
523
830
|
|
|
@@ -525,24 +832,12 @@ function setupSearch() {
|
|
|
525
832
|
document.getElementById("node-search").oninput = function() {
|
|
526
833
|
var query = this.value.toLowerCase().trim();
|
|
527
834
|
if (!network || !allNodes) return;
|
|
528
|
-
if (!query) {
|
|
529
|
-
network.body.data.nodes.forEach(function(n){
|
|
530
|
-
network.body.data.nodes.update({id:n.id,opacity:1.0});
|
|
531
|
-
});
|
|
532
|
-
return;
|
|
533
|
-
}
|
|
835
|
+
if (!query) { network.body.data.nodes.forEach(function(n){ network.body.data.nodes.update({id:n.id,opacity:1.0}); }); return; }
|
|
534
836
|
var matched = new Set();
|
|
535
837
|
allNodes.forEach(function(n){
|
|
536
|
-
if (n.name.toLowerCase().includes(query) || (n.file_path && n.file_path.toLowerCase().includes(query)))
|
|
537
|
-
matched.add(n.id);
|
|
538
|
-
}
|
|
539
|
-
});
|
|
540
|
-
network.body.data.nodes.forEach(function(n){
|
|
541
|
-
network.body.data.nodes.update({
|
|
542
|
-
id: n.id,
|
|
543
|
-
opacity: matched.has(n.id) ? 1.0 : 0.15,
|
|
544
|
-
});
|
|
838
|
+
if (n.name.toLowerCase().includes(query) || (n.file_path && n.file_path.toLowerCase().includes(query))) matched.add(n.id);
|
|
545
839
|
});
|
|
840
|
+
network.body.data.nodes.forEach(function(n){ network.body.data.nodes.update({ id: n.id, opacity: matched.has(n.id) ? 1.0 : 0.15 }); });
|
|
546
841
|
};
|
|
547
842
|
}
|
|
548
843
|
</script>
|