@plumpslabs/kuma 2.3.31 → 2.3.33
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 +321 -184
- package/dist/index.js +1469 -521
- package/package.json +1 -1
- package/packages/ide/studio/dist/index.js +298 -101
- package/packages/ide/studio/public/index.html +486 -74
|
@@ -4,14 +4,15 @@
|
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<title>Kuma Studio</title>
|
|
7
|
-
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><
|
|
7
|
+
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><circle cx='50' cy='50' r='48' fill='%23D97706'/><circle cx='50' cy='50' r='44' fill='%23F59E0B'/><circle cx='50' cy='45' r='30' fill='%2392400E'/><circle cx='35' cy='38' r='5' fill='%231F2937'/><circle cx='65' cy='38' r='5' fill='%231F2937'/><ellipse cx='50' cy='50' rx='10' ry='6' fill='%23B45309'/><path d='M35 58 Q50 72 65 58' stroke='%231F2937' stroke-width='3.5' fill='none' stroke-linecap='round'/></svg>">
|
|
8
8
|
<script src="https://unpkg.com/vis-network@9.1.9/dist/vis-network.min.js"></script>
|
|
9
9
|
<script src="https://unpkg.com/lucide@0.344.0/dist/umd/lucide.min.js"></script>
|
|
10
10
|
<style>
|
|
11
11
|
:root {
|
|
12
|
-
--bg: #
|
|
13
|
-
--text: #
|
|
14
|
-
--green: #
|
|
12
|
+
--bg: #0f172a; --surface: #1e293b; --border: #334155;
|
|
13
|
+
--text: #f1f5f9; --muted: #94a3b8; --accent: #a78bfa; --accent2: #22d3ee;
|
|
14
|
+
--green: #34d399; --yellow: #fbbf24; --orange: #fb923c; --red: #f87171;
|
|
15
|
+
--accent-bright: #c4b5fd; --accent2-bright: #67e8f9;
|
|
15
16
|
}
|
|
16
17
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
17
18
|
html, body {
|
|
@@ -20,46 +21,44 @@ html, body {
|
|
|
20
21
|
touch-action: none; -webkit-user-select: none; user-select: none;
|
|
21
22
|
}
|
|
22
23
|
header {
|
|
23
|
-
display: flex; align-items: center; gap:
|
|
24
|
+
display: flex; align-items: center; gap: 12px; padding: 8px 16px;
|
|
24
25
|
background: var(--surface); border-bottom: 1px solid var(--border);
|
|
25
26
|
-webkit-app-region: drag;
|
|
26
27
|
}
|
|
27
|
-
header {
|
|
28
|
-
display: flex; align-items: center; gap: 16px; padding: 12px 24px;
|
|
29
|
-
background: var(--surface); border-bottom: 1px solid var(--border);
|
|
30
|
-
-webkit-app-region: drag;
|
|
31
|
-
}
|
|
32
|
-
header h1 { font-size: 18px; font-weight: 700; color: var(--accent); letter-spacing: -0.5px; }
|
|
28
|
+
header h1 { font-size: 15px; font-weight: 700; color: var(--accent-bright); letter-spacing: -0.5px; text-shadow: 0 0 12px rgba(167,139,250,0.3); }
|
|
33
29
|
header .status { font-size: 12px; color: var(--muted); }
|
|
34
30
|
header .status .dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-right: 6px; }
|
|
35
31
|
header .status .dot.on { background: var(--green); }
|
|
36
32
|
header .status .dot.off { background: var(--red); }
|
|
37
33
|
.stats-bar {
|
|
38
|
-
display: flex; gap:
|
|
39
|
-
border-bottom: 1px solid var(--border); font-size:
|
|
40
|
-
overflow-x: auto;
|
|
41
|
-
}
|
|
42
|
-
.stat { display: flex; align-items: center; gap:
|
|
43
|
-
.stat .num { font-weight: 700; color: var(--accent2); }
|
|
44
|
-
.stat .lbl { color: var(--muted); }
|
|
45
|
-
#main { display: flex; height: calc(100vh -
|
|
34
|
+
display: flex; gap: 6px; padding: 6px 16px; background: var(--surface);
|
|
35
|
+
border-bottom: 1px solid var(--border); font-size: 11px;
|
|
36
|
+
overflow-x: auto; flex-wrap: nowrap;
|
|
37
|
+
}
|
|
38
|
+
.stat { display: flex; align-items: center; gap: 3px; padding: 3px 8px; border-radius: 5px; background: var(--bg); white-space: nowrap; }
|
|
39
|
+
.stat .num { font-weight: 700; color: var(--accent2); font-size: 12px; }
|
|
40
|
+
.stat .lbl { color: var(--muted); font-size: 10px; }
|
|
41
|
+
#main { display: flex; height: calc(100vh - 80px); }
|
|
46
42
|
#graph-panel { flex: 1; position: relative; min-width: 0; overflow: hidden; }
|
|
47
43
|
#graph-container { width: 100%; height: 100%; background: radial-gradient(ellipse at center, #1e1e3a 0%, var(--bg) 70%); }
|
|
48
|
-
#sidebar { width:
|
|
49
|
-
.tabs { display: flex; border-bottom: 1px solid var(--border); }
|
|
44
|
+
#sidebar { width: 320px; min-width: 320px; background: var(--surface); border-left: 1px solid var(--border); display: flex; flex-direction: column; }
|
|
45
|
+
.tabs { display: flex; gap: 3px; padding: 8px 12px 4px; background: var(--surface); border-bottom: 1px solid var(--border); overflow-x: auto; flex-wrap: nowrap; }
|
|
50
46
|
.tab {
|
|
51
|
-
|
|
52
|
-
color: var(--muted); border-
|
|
47
|
+
padding: 5px 10px; text-align: center; cursor: pointer; font-size: 11px;
|
|
48
|
+
color: var(--muted); border-radius: 6px; transition: all 0.15s; white-space: nowrap;
|
|
49
|
+
display: flex; align-items: center; gap: 4px; border: 1px solid transparent;
|
|
53
50
|
}
|
|
54
|
-
.tab:hover { color: var(--text); }
|
|
55
|
-
.tab.active { color: var(--accent); border-
|
|
56
|
-
.tab-content { flex: 1; overflow-y: auto; padding: 12px; }
|
|
51
|
+
.tab:hover { color: var(--text); background: rgba(167,139,250,0.08); }
|
|
52
|
+
.tab.active { color: var(--accent-bright); background: rgba(167,139,250,0.12); border-color: rgba(167,139,250,0.25); }
|
|
53
|
+
.tab-content { flex: 1; overflow-y: auto; padding: 10px 12px; }
|
|
57
54
|
.panel { display: none; }
|
|
58
|
-
.panel.active { display: block; }
|
|
55
|
+
.panel.active { display: block; animation: fadeIn 0.15s; }
|
|
56
|
+
.tab-label { font-size: 11px; }
|
|
57
|
+
@media (max-width: 400px) { .tab-label { display: none; } .tab { padding: 5px 8px; } }
|
|
59
58
|
|
|
60
59
|
/* Gotcha items */
|
|
61
60
|
.gotcha-item {
|
|
62
|
-
padding: 10px
|
|
61
|
+
padding: 8px 10px; margin-bottom: 6px; border-radius: 6px;
|
|
63
62
|
background: var(--bg); border-left: 3px solid var(--muted); cursor: pointer;
|
|
64
63
|
transition: background 0.15s;
|
|
65
64
|
}
|
|
@@ -74,19 +73,19 @@ header .status .dot.off { background: var(--red); }
|
|
|
74
73
|
|
|
75
74
|
/* Feature items */
|
|
76
75
|
.feature-item {
|
|
77
|
-
padding: 10px
|
|
76
|
+
padding: 8px 10px; margin-bottom: 6px; border-radius: 6px;
|
|
78
77
|
background: var(--bg); border-left: 3px solid var(--accent); cursor: pointer;
|
|
79
78
|
transition: background 0.15s;
|
|
80
79
|
}
|
|
81
80
|
.feature-item:hover { background: #1e293b; }
|
|
82
|
-
.feature-item .name { font-size:
|
|
81
|
+
.feature-item .name { font-size: 13px; font-weight: 600; color: var(--accent); margin-bottom: 2px; }
|
|
83
82
|
.feature-item .meta { font-size: 11px; color: var(--muted); }
|
|
84
83
|
.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(
|
|
84
|
+
.feature-item .tag { font-size: 10px; padding: 1px 6px; border-radius: 4px; background: rgba(167,139,250,0.15); color: var(--accent-bright); }
|
|
86
85
|
|
|
87
86
|
/* Health items */
|
|
88
|
-
.health-item { padding:
|
|
89
|
-
.health-item .score { font-size:
|
|
87
|
+
.health-item { padding: 6px 10px; margin-bottom: 5px; background: var(--bg); border-radius: 5px; }
|
|
88
|
+
.health-item .score { font-size: 18px; font-weight: 700; }
|
|
90
89
|
.health-item .score.good { color: var(--green); }
|
|
91
90
|
.health-item .score.ok { color: var(--yellow); }
|
|
92
91
|
.health-item .score.bad { color: var(--red); }
|
|
@@ -96,7 +95,7 @@ header .status .dot.off { background: var(--red); }
|
|
|
96
95
|
|
|
97
96
|
/* Legend */
|
|
98
97
|
#legend {
|
|
99
|
-
position: fixed; bottom: 16px; left: 16px; background: rgba(
|
|
98
|
+
position: fixed; bottom: 16px; left: 16px; background: rgba(15,23,42,0.95);
|
|
100
99
|
border: 1px solid var(--border); border-radius: 8px; padding: 10px 14px;
|
|
101
100
|
font-size: 11px; z-index: 10; max-height: 200px; overflow-y: auto;
|
|
102
101
|
backdrop-filter: blur(8px); color: #f8fafc;
|
|
@@ -130,45 +129,45 @@ header .status .dot.off { background: var(--red); }
|
|
|
130
129
|
border-radius: 6px; cursor: pointer; font-size: 12px; color: var(--text);
|
|
131
130
|
transition: background 0.1s;
|
|
132
131
|
}
|
|
133
|
-
#ctx-menu .ctx-item:hover { background: rgba(
|
|
132
|
+
#ctx-menu .ctx-item:hover { background: rgba(167,139,250,0.15); color: var(--accent-bright); }
|
|
134
133
|
#ctx-menu .ctx-item i { width: 14px; height: 14px; flex-shrink: 0; }
|
|
135
134
|
#ctx-menu .ctx-sep { height: 1px; background: var(--border); margin: 4px 8px; }
|
|
136
135
|
#search-box input {
|
|
137
|
-
background: rgba(
|
|
136
|
+
background: rgba(15,23,42,0.92); border: 1px solid var(--border);
|
|
138
137
|
border-radius: 8px; padding: 8px 12px; color: var(--text); font-size: 12px;
|
|
139
138
|
width: 200px; outline: none; backdrop-filter: blur(8px);
|
|
140
139
|
transition: border-color 0.2s;
|
|
141
140
|
}
|
|
142
|
-
#search-box input:focus { border-color: var(--accent); }
|
|
141
|
+
#search-box input:focus { border-color: var(--accent-bright); box-shadow: 0 0 8px rgba(167,139,250,0.3); }
|
|
143
142
|
#search-box input::placeholder { color: var(--muted); }
|
|
144
143
|
|
|
145
144
|
/* Graph controls */
|
|
146
145
|
#graph-controls {
|
|
147
|
-
position: fixed; bottom: 16px; right:
|
|
146
|
+
position: fixed; bottom: 16px; right: 336px; z-index: 10;
|
|
148
147
|
display: flex; gap: 6px;
|
|
149
148
|
}
|
|
150
149
|
#graph-controls button {
|
|
151
|
-
background: rgba(
|
|
150
|
+
background: rgba(15,23,42,0.92); border: 1px solid var(--border);
|
|
152
151
|
border-radius: 6px; color: var(--text); padding: 6px 10px;
|
|
153
152
|
font-size: 11px; cursor: pointer; backdrop-filter: blur(8px);
|
|
154
153
|
transition: background 0.2s;
|
|
155
154
|
}
|
|
156
|
-
#graph-controls button:hover { background: var(--accent); border-color: var(--accent); }
|
|
155
|
+
#graph-controls button:hover { background: var(--accent); border-color: var(--accent-bright); }
|
|
157
156
|
#graph-controls button.active {
|
|
158
|
-
background: var(--accent) !important; border-color: var(--accent) !important;
|
|
159
|
-
color: #ffffff !important; box-shadow: 0 0
|
|
157
|
+
background: var(--accent) !important; border-color: var(--accent-bright) !important;
|
|
158
|
+
color: #ffffff !important; box-shadow: 0 0 12px rgba(167,139,250,0.5);
|
|
160
159
|
}
|
|
161
160
|
#graph-controls .ctrl-sep { width: 1px; background: var(--border); margin: 0 2px; }
|
|
162
161
|
.depth-btn { min-width: 28px !important; padding: 4px 6px !important; font-size: 11px !important; font-weight: 600; }
|
|
163
|
-
.depth-btn.active { background: rgba(
|
|
162
|
+
.depth-btn.active { background: rgba(167,139,250,0.25) !important; border-color: var(--accent-bright) !important; color: var(--accent-bright) !important; }
|
|
164
163
|
|
|
165
164
|
/* Loading */
|
|
166
165
|
#loading {
|
|
167
166
|
position: fixed; inset: 0; background: var(--bg); display: flex;
|
|
168
167
|
align-items: center; justify-content: center; z-index: 100;
|
|
169
|
-
font-size: 18px; color: var(--accent); flex-direction: column; gap: 16px;
|
|
168
|
+
font-size: 18px; color: var(--accent-bright); flex-direction: column; gap: 16px;
|
|
170
169
|
}
|
|
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; }
|
|
170
|
+
#loading .spinner { width: 32px; height: 32px; border: 3px solid var(--border); border-top-color: var(--accent-bright); border-radius: 50%; animation: spin 0.8s linear infinite; }
|
|
172
171
|
@keyframes spin { to { transform: rotate(360deg); } }
|
|
173
172
|
|
|
174
173
|
/* Node Detail Modal */
|
|
@@ -179,31 +178,31 @@ header .status .dot.off { background: var(--red); }
|
|
|
179
178
|
}
|
|
180
179
|
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
|
|
181
180
|
.modal {
|
|
182
|
-
background: var(--surface); border: 1px solid var(--border); border-radius:
|
|
183
|
-
width:
|
|
181
|
+
background: var(--surface); border: 1px solid var(--border); border-radius: 10px;
|
|
182
|
+
width: 520px; max-width: 90vw; max-height: 85vh; overflow: hidden; box-shadow: 0 16px 64px rgba(0,0,0,0.5);
|
|
184
183
|
display: flex; flex-direction: column; animation: slideUp 0.2s;
|
|
185
184
|
}
|
|
186
185
|
@keyframes slideUp { from { transform: translateY(20px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
|
|
187
186
|
.modal-header {
|
|
188
187
|
display: flex; align-items: center; justify-content: space-between;
|
|
189
|
-
padding:
|
|
188
|
+
padding: 10px 16px; border-bottom: 1px solid var(--border); flex-shrink: 0;
|
|
190
189
|
}
|
|
191
|
-
.modal-header h2 { font-size:
|
|
190
|
+
.modal-header h2 { font-size: 13px; font-weight: 700; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
|
|
192
191
|
.modal-header .close {
|
|
193
192
|
background: none; border: none; color: var(--muted); font-size: 18px;
|
|
194
193
|
cursor: pointer; width: 28px; height: 28px; display: flex; align-items: center; justify-content: center;
|
|
195
194
|
border-radius: 6px; transition: all 0.15s; flex-shrink: 0;
|
|
196
195
|
}
|
|
197
196
|
.modal-header .close:hover { background: rgba(255,255,255,0.1); color: var(--text); }
|
|
198
|
-
.modal-body { padding: 16px
|
|
199
|
-
.modal-section { margin-bottom:
|
|
200
|
-
.modal-section h3 { font-size:
|
|
201
|
-
.modal-kv { display: flex; gap:
|
|
202
|
-
.modal-kv .key { color: var(--muted); min-width:
|
|
197
|
+
.modal-body { padding: 12px 16px; overflow-y: auto; flex: 1; min-height: 0; }
|
|
198
|
+
.modal-section { margin-bottom: 10px; }
|
|
199
|
+
.modal-section h3 { font-size: 10px; font-weight: 600; color: var(--muted); text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 4px; }
|
|
200
|
+
.modal-kv { display: flex; gap: 6px; margin-bottom: 3px; font-size: 11px; line-height: 1.3; }
|
|
201
|
+
.modal-kv .key { color: var(--muted); min-width: 64px; flex-shrink: 0; }
|
|
203
202
|
.modal-kv .val { color: var(--text); word-break: break-word; overflow-wrap: anywhere; min-width: 0; }
|
|
204
203
|
.relation-item {
|
|
205
|
-
display: flex; align-items: center; gap:
|
|
206
|
-
margin-bottom:
|
|
204
|
+
display: flex; align-items: center; gap: 5px; padding: 4px 7px;
|
|
205
|
+
margin-bottom: 2px; border-radius: 5px; background: var(--bg);
|
|
207
206
|
font-size: 11px; cursor: pointer; transition: background 0.15s;
|
|
208
207
|
}
|
|
209
208
|
.relation-item:hover { background: #1e293b; }
|
|
@@ -212,20 +211,20 @@ header .status .dot.off { background: var(--red); }
|
|
|
212
211
|
.relation-item .type { color: var(--muted); font-size: 10px; flex-shrink: 0; max-width: 80px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
213
212
|
.relation-item .weight { color: var(--accent2); font-size: 10px; flex-shrink: 0; }
|
|
214
213
|
|
|
215
|
-
::-webkit-scrollbar { width:
|
|
214
|
+
::-webkit-scrollbar { width: 4px; height: 4px; }
|
|
216
215
|
::-webkit-scrollbar-track { background: transparent; }
|
|
217
|
-
::-webkit-scrollbar-thumb { background: var(--border); border-radius:
|
|
216
|
+
::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; }
|
|
218
217
|
</style>
|
|
219
218
|
</head>
|
|
220
219
|
<body>
|
|
221
220
|
<div id="loading"><div class="spinner"></div>Connecting to Kuma...</div>
|
|
222
221
|
<header>
|
|
223
|
-
<h1><
|
|
222
|
+
<h1><svg width="22" height="22" viewBox="0 0 100 100" style="vertical-align:middle;margin-right:6px"><circle cx="50" cy="50" r="48" fill="#D97706"/><circle cx="50" cy="50" r="44" fill="#F59E0B"/><circle cx="50" cy="45" r="30" fill="#92400E"/><circle cx="35" cy="38" r="5" fill="#1F2937"/><circle cx="65" cy="38" r="5" fill="#1F2937"/><ellipse cx="50" cy="50" rx="10" ry="6" fill="#B45309"/><path d="M35 58 Q50 72 65 58" stroke="#1F2937" stroke-width="3.5" fill="none" stroke-linecap="round"/></svg> Kuma Studio</h1>
|
|
224
223
|
<div class="status"><span class="dot" id="status-dot"></span><span id="status-text">connecting...</span></div>
|
|
225
224
|
<div style="margin-left:auto;display:flex;gap:8px;-webkit-app-region:no-drag">
|
|
226
|
-
<button onclick="refetch()" id="refetch-btn" style="background:rgba(
|
|
227
|
-
<button onclick="resetKumaDb()" style="background:rgba(239,68,68,0.
|
|
228
|
-
<button onclick="stopKumaStudio()" style="background:rgba(148,163,184,0.
|
|
225
|
+
<button onclick="refetch()" id="refetch-btn" style="background:rgba(167,139,250,0.12);border:1px solid rgba(167,139,250,0.25);border-radius:5px;color:var(--accent-bright);padding:4px 10px;font-size:10px;cursor:pointer;transition:all 0.2s;display:flex;align-items:center;gap:3px"><i data-lucide="refresh-cw" style="width:11px;height:11px"></i> Refetch</button>
|
|
226
|
+
<button onclick="resetKumaDb()" style="background:rgba(239,68,68,0.12);border:1px solid rgba(239,68,68,0.25);border-radius:5px;color:var(--red);padding:4px 10px;font-size:10px;cursor:pointer;transition:all 0.2s;display:flex;align-items:center;gap:3px"><i data-lucide="trash-2" style="width:11px;height:11px"></i> Reset</button>
|
|
227
|
+
<button onclick="stopKumaStudio()" style="background:rgba(148,163,184,0.12);border:1px solid rgba(148,163,184,0.25);border-radius:5px;color:#94a3b8;padding:4px 10px;font-size:10px;cursor:pointer;transition:all 0.2s;display:flex;align-items:center;gap:3px"><i data-lucide="square" style="width:11px;height:11px"></i> Stop</button>
|
|
229
228
|
</div>
|
|
230
229
|
</header>
|
|
231
230
|
<div class="stats-bar" id="stats-bar"></div>
|
|
@@ -249,9 +248,12 @@ header .status .dot.off { background: var(--red); }
|
|
|
249
248
|
</div>
|
|
250
249
|
<div id="sidebar">
|
|
251
250
|
<div class="tabs">
|
|
252
|
-
<div class="tab active" data-tab="features"><i data-lucide="star" style="width:
|
|
253
|
-
<div class="tab" data-tab="gotchas"><i data-lucide="alert-triangle" style="width:
|
|
254
|
-
<div class="tab" data-tab="health"><i data-lucide="heart-pulse" style="width:
|
|
251
|
+
<div class="tab active" data-tab="features" title="Features"><i data-lucide="star" style="width:12px;height:12px"></i><span class="tab-label">Features</span></div>
|
|
252
|
+
<div class="tab" data-tab="gotchas" title="Gotchas"><i data-lucide="alert-triangle" style="width:12px;height:12px"></i><span class="tab-label">Gotchas</span></div>
|
|
253
|
+
<div class="tab" data-tab="health" title="Health"><i data-lucide="heart-pulse" style="width:12px;height:12px"></i><span class="tab-label">Health</span></div>
|
|
254
|
+
<div class="tab" data-tab="efficiency" title="Efficiency"><i data-lucide="zap" style="width:12px;height:12px"></i><span class="tab-label">Efficiency</span></div>
|
|
255
|
+
<div class="tab" data-tab="staleness" title="Staleness"><i data-lucide="activity" style="width:12px;height:12px"></i><span class="tab-label">Staleness</span></div>
|
|
256
|
+
<div class="tab" data-tab="activity" title="Activity"><i data-lucide="bar-chart-3" style="width:12px;height:12px"></i><span class="tab-label">Activity</span></div>
|
|
255
257
|
</div>
|
|
256
258
|
<div class="tab-content" id="tab-content"></div>
|
|
257
259
|
</div>
|
|
@@ -273,7 +275,7 @@ async function resetKumaDb() {
|
|
|
273
275
|
var confirmation = prompt("⚠️ TYPE 'DELETE' TO CONFIRM RESETTING KUMA DB\nThis will permanently wipe all knowledge:");
|
|
274
276
|
if (confirmation !== "DELETE") { if (confirmation !== null) alert("Reset cancelled."); return; }
|
|
275
277
|
try {
|
|
276
|
-
var res = await fetch(API + "/reset-db", { method: "POST" }).then(function(r){return r.json()});
|
|
278
|
+
var res = await fetch(API + "/reset-db?confirm=1", { method: "POST" }).then(function(r){return r.json()});
|
|
277
279
|
if (res.ok) { alert("Kuma DB reset."); refetch(); }
|
|
278
280
|
} catch (e) { alert("Reset failed: " + e.message); }
|
|
279
281
|
}
|
|
@@ -281,7 +283,7 @@ async function resetKumaDb() {
|
|
|
281
283
|
async function stopKumaStudio() {
|
|
282
284
|
if (!confirm("Stop Kuma Studio?")) return;
|
|
283
285
|
try {
|
|
284
|
-
await fetch(API + "/stop-server", { method: "POST" });
|
|
286
|
+
await fetch(API + "/stop-server?confirm=1", { method: "POST" });
|
|
285
287
|
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>';
|
|
286
288
|
} catch (e) { alert("Stop failed: " + e.message); }
|
|
287
289
|
}
|
|
@@ -336,11 +338,16 @@ async function init() {
|
|
|
336
338
|
renderFeatures(data);
|
|
337
339
|
renderGotchas(data);
|
|
338
340
|
renderHealth(data);
|
|
341
|
+
renderEfficiency(data);
|
|
342
|
+
renderStaleness(data);
|
|
343
|
+
renderActivity(data);
|
|
339
344
|
setupSearch();
|
|
340
345
|
lucide.createIcons();
|
|
341
346
|
}
|
|
342
347
|
init();
|
|
343
348
|
|
|
349
|
+
|
|
350
|
+
|
|
344
351
|
function showError(msg) {
|
|
345
352
|
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>';
|
|
346
353
|
lucide.createIcons();
|
|
@@ -353,7 +360,7 @@ function renderStats(data) {
|
|
|
353
360
|
document.getElementById("stats-bar").innerHTML =
|
|
354
361
|
'<div class="stat"><span class="num">'+(s.node_count||0)+'</span><span class="lbl">nodes</span></div>'+
|
|
355
362
|
'<div class="stat"><span class="num">'+(s.edge_count||0)+'</span><span class="lbl">edges</span></div>'+
|
|
356
|
-
'<div class="stat"><span class="num" style="color:var(--accent)">'+(s.feature_count||0)+'</span><span class="lbl">features</span></div>'+
|
|
363
|
+
'<div class="stat"><span class="num" style="color:var(--accent-bright)">'+(s.feature_count||0)+'</span><span class="lbl">features</span></div>'+
|
|
357
364
|
'<div class="stat"><span class="num">'+(s.gotcha_count||0)+'</span><span class="lbl">gotchas</span></div>'+
|
|
358
365
|
'<div class="stat"><span class="num" style="color:var(--accent)">'+currentDbSize+'</span><span class="lbl">kuma.db</span></div>'+
|
|
359
366
|
'<div class="stat"><span class="num" style="color:'+hcColor+'">'+(hc||0)+'</span><span class="lbl">health</span></div>';
|
|
@@ -435,7 +442,7 @@ function renderGraph(data) {
|
|
|
435
442
|
var color = isGotcha ? '#f97316' : (colors[n.type] || '#64748b');
|
|
436
443
|
return {
|
|
437
444
|
id: n.id, label: label,
|
|
438
|
-
color: { background: color, border: color, highlight: { background: color, border: '#
|
|
445
|
+
color: { background: color, border: color, highlight: { background: color, border: '#67e8f9' }, hover: { background: color, border: '#c4b5fd' } },
|
|
439
446
|
shape: shape,
|
|
440
447
|
font: { color: '#ffffff', size: Math.min(13, Math.max(10, size * 0.55)), face: 'sans-serif', multi: false, bold: true },
|
|
441
448
|
borderWidth: 0, size: size, mass: 1 + (size / 40),
|
|
@@ -465,7 +472,7 @@ function renderGraph(data) {
|
|
|
465
472
|
id: 'e'+idx, from: e.source, to: e.target,
|
|
466
473
|
label: e.relation || '',
|
|
467
474
|
arrows: { to: { enabled: true, scaleFactor: 0.7, type: 'arrow' } },
|
|
468
|
-
color: { color: edgeColor, highlight: '#
|
|
475
|
+
color: { color: edgeColor, highlight: '#c4b5fd', hover: '#67e8f9', opacity: 0.85 },
|
|
469
476
|
font: { size: 10, color: '#94a3b8', strokeWidth: 0, background: 'transparent' },
|
|
470
477
|
dashes: dashes,
|
|
471
478
|
smooth: { type: 'continuous', roundness: 0.3 },
|
|
@@ -482,7 +489,7 @@ function renderGraph(data) {
|
|
|
482
489
|
},
|
|
483
490
|
interaction: { hover: true, tooltipDelay: 300, zoomView: true, dragView: true, dragNodes: true, keyboard: true, multiselect: true, selectable: true, selectConnectedEdges: false },
|
|
484
491
|
nodes: { borderWidth: 0, font: { color: '#ffffff', size: 11, face: 'sans-serif' }, margin: { top: 6, bottom: 6, left: 10, right: 10 }, shapeProperties: { borderRadius: 6 } },
|
|
485
|
-
edges: { smooth: { type: 'continuous', roundness: 0.2 }, width: 1, color: { color: '#4a4a6a', highlight: '#
|
|
492
|
+
edges: { smooth: { type: 'continuous', roundness: 0.2 }, width: 1, color: { color: '#4a4a6a', highlight: '#a78bfa' } },
|
|
486
493
|
configure: { enabled: false },
|
|
487
494
|
});
|
|
488
495
|
|
|
@@ -606,7 +613,7 @@ function resetHighlight() {
|
|
|
606
613
|
network.body.data.nodes.update(nUp);
|
|
607
614
|
network.body.data.edges.forEach(function(e) {
|
|
608
615
|
var def = edgeDefaults[e.id];
|
|
609
|
-
eUp.push({ id: e.id, color: { color: def ? def.color : '#64748b', highlight: '#
|
|
616
|
+
eUp.push({ id: e.id, color: { color: def ? def.color : '#64748b', highlight: '#c4b5fd', hover: '#67e8f9', opacity: 0.85 }, width: def ? def.width : 1 });
|
|
610
617
|
});
|
|
611
618
|
network.body.data.edges.update(eUp);
|
|
612
619
|
document.getElementById("graph-tooltip").style.display = 'none';
|
|
@@ -784,7 +791,7 @@ async function refetch() {
|
|
|
784
791
|
if (status.ok) { dot.className = 'dot on'; txt.textContent = status.project || 'connected'; } else { dot.className = 'dot off'; txt.textContent = 'no kuma.db found'; }
|
|
785
792
|
var data = await fetch(API+'/dashboard').then(function(r){return r.json()});
|
|
786
793
|
if (data.error) { showError(data.error); return; }
|
|
787
|
-
renderStats(data); renderGraph(data); renderFeatures(data); renderGotchas(data); renderHealth(data);
|
|
794
|
+
renderStats(data); renderGraph(data); renderFeatures(data); renderGotchas(data); renderHealth(data); renderEfficiency(data); renderStaleness(data); renderActivity(data);
|
|
788
795
|
lucide.createIcons();
|
|
789
796
|
} catch(e) { showError('Refetch failed: ' + e.message); }
|
|
790
797
|
btn.innerHTML = '<i data-lucide="refresh-cw" style="width:13px;height:13px;vertical-align:middle;margin-right:3px"></i> Refetch';
|
|
@@ -802,7 +809,10 @@ function setupTabs(){
|
|
|
802
809
|
content.innerHTML =
|
|
803
810
|
'<div class="panel active" id="panel-features"><div class="empty">Loading...</div></div>' +
|
|
804
811
|
'<div class="panel" id="panel-gotchas"><div class="empty">Loading...</div></div>' +
|
|
805
|
-
'<div class="panel" id="panel-health"><div class="empty">Loading...</div></div>'
|
|
812
|
+
'<div class="panel" id="panel-health"><div class="empty">Loading...</div></div>' +
|
|
813
|
+
'<div class="panel" id="panel-efficiency"><div class="empty">Loading...</div></div>' +
|
|
814
|
+
'<div class="panel" id="panel-staleness"><div class="empty">Loading...</div></div>' +
|
|
815
|
+
'<div class="panel" id="panel-activity"><div class="empty">Loading...</div></div>';
|
|
806
816
|
document.querySelectorAll('.tab').forEach(function(tab){
|
|
807
817
|
tab.onclick = function(){
|
|
808
818
|
document.querySelectorAll('.tab').forEach(function(t){t.classList.remove('active')});
|
|
@@ -867,6 +877,137 @@ function renderHealth(data){
|
|
|
867
877
|
}).join('');
|
|
868
878
|
}
|
|
869
879
|
|
|
880
|
+
// ============ EFFICIENCY PANEL (GAP 1) ============
|
|
881
|
+
// Proves the philosophy: "the more you use Kuma, the more efficient it gets."
|
|
882
|
+
// Every saved decision / record is an asset — here is the evidence.
|
|
883
|
+
|
|
884
|
+
function fmtDuration(sec) {
|
|
885
|
+
if (!sec || sec <= 0) return "0s";
|
|
886
|
+
if (sec >= 3600) return (sec/3600).toFixed(1)+"h";
|
|
887
|
+
if (sec >= 60) return Math.round(sec/60)+"m "+(sec%60)+"s";
|
|
888
|
+
return sec+"s";
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
function renderEfficiency(data){
|
|
892
|
+
var el = document.getElementById("panel-efficiency");
|
|
893
|
+
if(!el) return;
|
|
894
|
+
var eff = data.efficiency || {};
|
|
895
|
+
var m = eff.metrics || {};
|
|
896
|
+
var r = eff.recordings || {};
|
|
897
|
+
var savedSec = m.researchTimeSaved ? Math.round(m.researchTimeSaved/1000) : 0;
|
|
898
|
+
var passRate = eff.verificationPassRate;
|
|
899
|
+
var passColor = passRate >= 80 ? 'var(--green)' : passRate >= 50 ? 'var(--yellow)' : 'var(--red)';
|
|
900
|
+
|
|
901
|
+
var stat = function(num, lbl, color){
|
|
902
|
+
return '<div style="flex:1;min-width:110px;padding:10px 12px;background:var(--bg);border-radius:8px;border:1px solid var(--border)"><div style="font-size:20px;font-weight:700;color:'+(color||'var(--accent2)')+'">'+num+'</div><div style="font-size:11px;color:var(--muted);margin-top:2px">'+lbl+'</div></div>';
|
|
903
|
+
};
|
|
904
|
+
|
|
905
|
+
var html = '';
|
|
906
|
+
html += '<div style="display:flex;flex-wrap:wrap;gap:8px;margin-bottom:12px">' +
|
|
907
|
+
stat(eff.sessions||0, 'sessions') +
|
|
908
|
+
stat(eff.toolCalls||0, 'tool calls') +
|
|
909
|
+
stat(eff.gotchas||0, 'gotchas', 'var(--orange)') +
|
|
910
|
+
stat(eff.researchCacheScopes||0, 'research cached', 'var(--green)') +
|
|
911
|
+
stat(eff.decisions||0, 'decisions', 'var(--accent)') +
|
|
912
|
+
stat(eff.verifications||0, 'verifications', passRate==null?'var(--muted)':'var(--green)') +
|
|
913
|
+
'</div>';
|
|
914
|
+
|
|
915
|
+
// The headline: time saved from cache — the compounding asset
|
|
916
|
+
html += '<div class="health-item" style="border-left:3px solid var(--green);margin-bottom:12px">' +
|
|
917
|
+
'<div style="font-size:13px;color:var(--muted)">⏱ Estimated time saved from research cache</div>' +
|
|
918
|
+
'<div class="score good" style="font-size:28px">'+fmtDuration(savedSec)+'</div>' +
|
|
919
|
+
'<div class="meta">'+ (m.filesRead||0) +' files read · '+ (m.filesEdited||0) +' files edited (current session)</div>' +
|
|
920
|
+
'</div>';
|
|
921
|
+
|
|
922
|
+
// Recording assets — what we've banked
|
|
923
|
+
var recTotal = r.total||0;
|
|
924
|
+
html += '<div style="font-size:11px;font-weight:600;color:var(--muted);text-transform:uppercase;letter-spacing:0.5px;margin:10px 0 6px">📦 Recorded Assets</div>';
|
|
925
|
+
html += '<div class="health-item" style="margin-bottom:12px">';
|
|
926
|
+
html += '<div style="display:flex;flex-wrap:wrap;gap:6px">';
|
|
927
|
+
html += '<span class="sev-tag" style="background:rgba(124,58,237,0.15);color:var(--accent)">arch_flow: '+(r.archFlows||0)+'</span>';
|
|
928
|
+
html += '<span class="sev-tag" style="background:#f9731620;color:var(--orange)">gotcha: '+(r.gotchas||0)+'</span>';
|
|
929
|
+
html += '<span class="sev-tag" style="background:#eab30820;color:var(--yellow)">decision: '+(r.decisions||0)+'</span>';
|
|
930
|
+
html += '<span class="sev-tag" style="background:#06b6d420;color:var(--accent2)">research_save: '+(r.researchSaves||0)+'</span>';
|
|
931
|
+
html += '</div>';
|
|
932
|
+
html += '<div class="meta" style="margin-top:6px">Total assets banked: <b style="color:var(--accent2)">'+recTotal+'</b> — each one saves re-research next session</div>';
|
|
933
|
+
html += '</div>';
|
|
934
|
+
|
|
935
|
+
// Verification pass rate
|
|
936
|
+
html += '<div style="font-size:11px;font-weight:600;color:var(--muted);text-transform:uppercase;letter-spacing:0.5px;margin:10px 0 6px">🧪 Verification</div>';
|
|
937
|
+
html += '<div class="health-item" style="margin-bottom:12px">' +
|
|
938
|
+
'<div class="score" style="color:'+passColor+'">'+(passRate==null?'—':passRate+'%')+'</div>' +
|
|
939
|
+
'<div class="meta">'+(eff.verifications||0)+' runs · pass rate</div>' +
|
|
940
|
+
'</div>';
|
|
941
|
+
|
|
942
|
+
// Recent sessions breakdown
|
|
943
|
+
var sess = eff.recentSessions || [];
|
|
944
|
+
if (sess.length) {
|
|
945
|
+
html += '<div style="font-size:11px;font-weight:600;color:var(--muted);text-transform:uppercase;letter-spacing:0.5px;margin:10px 0 6px">🕐 Recent Sessions</div>';
|
|
946
|
+
html += sess.map(function(s){
|
|
947
|
+
var d = s.startedAt ? new Date(s.startedAt*1000) : null;
|
|
948
|
+
var dateStr = d ? d.toLocaleDateString()+' '+d.toLocaleTimeString([],{hour:'2-digit',minute:'2-digit'}) : '-';
|
|
949
|
+
return '<div class="health-item">' +
|
|
950
|
+
'<div style="font-size:12px;color:var(--text)">'+escapeHtml(s.goal || 'untitled goal')+'</div>' +
|
|
951
|
+
'<div class="meta">'+dateStr+' · '+s.toolCalls+' calls · '+s.edits+' edits'+(s.failures?' · <span style="color:var(--red)">'+s.failures+' failures</span>':'')+'</div>' +
|
|
952
|
+
'</div>';
|
|
953
|
+
}).join('');
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
el.innerHTML = html;
|
|
957
|
+
lucide.createIcons();
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
// ============ STALENESS PANEL (GAP 4) ============
|
|
961
|
+
// Assets can become liabilities when stale. Surface them before they bite.
|
|
962
|
+
|
|
963
|
+
function renderStaleness(data){
|
|
964
|
+
var el = document.getElementById("panel-staleness");
|
|
965
|
+
if(!el) return;
|
|
966
|
+
var st = data.staleness || { checked: 0, staleNodes: 0, missing: [] };
|
|
967
|
+
var html = '';
|
|
968
|
+
|
|
969
|
+
if (!st.staleNodes) {
|
|
970
|
+
html += '<div class="health-item" style="border-left:3px solid var(--green)">' +
|
|
971
|
+
'<div class="score good" style="font-size:22px">✅ All fresh</div>' +
|
|
972
|
+
'<div class="meta">'+st.checked+' file references checked — no stale assets found.</div>' +
|
|
973
|
+
'</div>';
|
|
974
|
+
} else {
|
|
975
|
+
html += '<div class="health-item" style="border-left:3px solid var(--red)">' +
|
|
976
|
+
'<div class="score bad" style="font-size:22px">'+st.staleNodes+' stale node(s)</div>' +
|
|
977
|
+
'<div class="meta">'+st.missing.length+' file(s) referenced but missing on disk ('+st.checked+' checked).</div>' +
|
|
978
|
+
'</div>';
|
|
979
|
+
html += '<div style="display:flex;gap:6px;margin:10px 0">' +
|
|
980
|
+
'<button onclick="copyHealCommand(this)" style="background:rgba(124,58,237,0.15);border:1px solid rgba(124,58,237,0.3);border-radius:6px;color:var(--accent);padding:6px 12px;font-size:11px;cursor:pointer">🛠 Copy heal command</button>' +
|
|
981
|
+
'</div>';
|
|
982
|
+
html += st.missing.map(function(m){
|
|
983
|
+
return '<div class="gotcha-item" style="border-color:var(--red)">' +
|
|
984
|
+
'<div class="file">'+escapeHtml(m.filePath)+'</div>' +
|
|
985
|
+
'<div class="desc">Referenced by <b style="color:var(--red)">'+m.nodeCount+'</b> node(s) but the file no longer exists.</div>' +
|
|
986
|
+
'</div>';
|
|
987
|
+
}).join('');
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
el.innerHTML = html;
|
|
991
|
+
lucide.createIcons();
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
function copyHealCommand(btn) {
|
|
995
|
+
var cmd = "kuma_memory({ action: 'heal' })";
|
|
996
|
+
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
997
|
+
navigator.clipboard.writeText(cmd).then(function() {
|
|
998
|
+
if (btn) { btn.textContent = "✅ Copied!"; setTimeout(function(){ btn.textContent = "🛠 Copy heal command"; }, 1500); }
|
|
999
|
+
}).catch(function() {
|
|
1000
|
+
if (btn) { btn.textContent = "⚠️ Copy failed"; setTimeout(function(){ btn.textContent = "🛠 Copy heal command"; }, 1500); }
|
|
1001
|
+
});
|
|
1002
|
+
} else {
|
|
1003
|
+
// Fallback for non-secure contexts
|
|
1004
|
+
var ta = document.createElement("textarea");
|
|
1005
|
+
ta.value = cmd; document.body.appendChild(ta); ta.select();
|
|
1006
|
+
try { document.execCommand("copy"); if (btn) { btn.textContent = "✅ Copied!"; setTimeout(function(){ btn.textContent = "🛠 Copy heal command"; }, 1500); } } catch(e) {}
|
|
1007
|
+
document.body.removeChild(ta);
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
|
|
870
1011
|
function setupSearch() {
|
|
871
1012
|
var searchTimeout = null;
|
|
872
1013
|
document.getElementById("node-search").oninput = function() {
|
|
@@ -920,6 +1061,277 @@ function setupSearch() {
|
|
|
920
1061
|
}, 150);
|
|
921
1062
|
};
|
|
922
1063
|
}
|
|
1064
|
+
|
|
1065
|
+
// ============ ACTIVITY PANEL — Agent Usage Intensity ============
|
|
1066
|
+
// Shows how intensely agents use Kuma: tool calls per day, sessions frequency,
|
|
1067
|
+
// and activity heatmap to understand adoption patterns.
|
|
1068
|
+
|
|
1069
|
+
function renderActivity(data) {
|
|
1070
|
+
var el = document.getElementById("panel-activity");
|
|
1071
|
+
if (!el) return;
|
|
1072
|
+
var eff = data.efficiency || {};
|
|
1073
|
+
var sess = eff.recentSessions || [];
|
|
1074
|
+
var totalSessions = eff.sessions || 0;
|
|
1075
|
+
var totalToolCalls = eff.toolCalls || 0;
|
|
1076
|
+
var totalGotchas = eff.gotchas || 0;
|
|
1077
|
+
var totalArchFlows = eff.archFlows || 0;
|
|
1078
|
+
var totalDecisions = eff.decisions || 0;
|
|
1079
|
+
var totalEdits = eff.metrics ? (eff.metrics.filesEdited || 0) : 0;
|
|
1080
|
+
|
|
1081
|
+
var html = '';
|
|
1082
|
+
|
|
1083
|
+
// === COPY REPORT BUTTON ===
|
|
1084
|
+
html += '<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:10px">';
|
|
1085
|
+
html += '<div style="font-size:11px;font-weight:600;color:var(--muted);text-transform:uppercase;letter-spacing:0.5px">🤖 Agent Usage Intensity</div>';
|
|
1086
|
+
html += '<button onclick="copyActivityReport()" id="copy-report-btn" style="background:rgba(34,211,238,0.12);border:1px solid rgba(34,211,238,0.25);border-radius:6px;color:var(--accent2);padding:4px 10px;font-size:10px;cursor:pointer;transition:all 0.2s;display:flex;align-items:center;gap:4px"><i data-lucide="copy" style="width:11px;height:11px"></i> Copy Report</button>';
|
|
1087
|
+
html += '</div>';
|
|
1088
|
+
|
|
1089
|
+
// Intensity score: tool_calls per session
|
|
1090
|
+
var avgCallsPerSession = totalSessions > 0 ? (totalToolCalls / totalSessions).toFixed(1) : '0';
|
|
1091
|
+
var intensityLevel = totalToolCalls > 100 ? 'High' : totalToolCalls > 30 ? 'Medium' : totalToolCalls > 0 ? 'Low' : 'None';
|
|
1092
|
+
var intensityColor = totalToolCalls > 100 ? 'var(--green)' : totalToolCalls > 30 ? 'var(--yellow)' : totalToolCalls > 0 ? 'var(--orange)' : 'var(--muted)';
|
|
1093
|
+
var intensityIcon = totalToolCalls > 100 ? '🔥' : totalToolCalls > 30 ? '⚡' : totalToolCalls > 0 ? '💤' : '⚫';
|
|
1094
|
+
|
|
1095
|
+
html += '<div class="health-item" style="margin-bottom:12px;border-left:3px solid ' + intensityColor + '">';
|
|
1096
|
+
html += '<div style="display:flex;align-items:center;gap:8px;margin-bottom:6px">';
|
|
1097
|
+
html += '<span style="font-size:24px">' + intensityIcon + '</span>';
|
|
1098
|
+
html += '<div><div style="font-size:16px;font-weight:700;color:' + intensityColor + '">' + intensityLevel + '</div>';
|
|
1099
|
+
html += '<div style="font-size:11px;color:var(--muted)">' + avgCallsPerSession + ' calls/session average</div></div>';
|
|
1100
|
+
html += '</div>';
|
|
1101
|
+
html += '<div style="display:flex;flex-wrap:wrap;gap:6px;margin-top:6px">';
|
|
1102
|
+
html += '<span style="font-size:10px;padding:2px 8px;border-radius:4px;background:rgba(34,211,238,0.15);color:var(--accent2)">' + totalToolCalls + ' total calls</span>';
|
|
1103
|
+
html += '<span style="font-size:10px;padding:2px 8px;border-radius:4px;background:rgba(52,211,153,0.15);color:var(--green)">' + totalSessions + ' sessions</span>';
|
|
1104
|
+
html += '<span style="font-size:10px;padding:2px 8px;border-radius:4px;background:rgba(251,191,36,0.15);color:var(--yellow)">' + totalEdits + ' edits</span>';
|
|
1105
|
+
html += '</div>';
|
|
1106
|
+
html += '</div>';
|
|
1107
|
+
|
|
1108
|
+
// === HEALTH SCORE TREND ===
|
|
1109
|
+
html += '<div style="font-size:11px;font-weight:600;color:var(--muted);text-transform:uppercase;letter-spacing:0.5px;margin:10px 0 6px">📈 Activity Summary</div>';
|
|
1110
|
+
html += '<div style="display:flex;flex-wrap:wrap;gap:8px;margin-bottom:12px">';
|
|
1111
|
+
|
|
1112
|
+
var summaryCard = function(val, label, color) {
|
|
1113
|
+
return '<div style="flex:1;min-width:90px;padding:10px;background:var(--bg);border-radius:8px;border:1px solid var(--border);text-align:center">' +
|
|
1114
|
+
'<div style="font-size:20px;font-weight:700;color:' + (color || 'var(--accent2)') + '">' + val + '</div>' +
|
|
1115
|
+
'<div style="font-size:10px;color:var(--muted);margin-top:2px">' + label + '</div></div>';
|
|
1116
|
+
};
|
|
1117
|
+
|
|
1118
|
+
html += summaryCard(totalSessions, 'Sessions', 'var(--accent2)');
|
|
1119
|
+
html += summaryCard(totalToolCalls, 'Tool Calls', 'var(--accent)');
|
|
1120
|
+
html += summaryCard(totalGotchas, 'Gotchas', 'var(--orange)');
|
|
1121
|
+
html += summaryCard(totalArchFlows, 'Arch Flows', 'var(--accent)');
|
|
1122
|
+
html += summaryCard(totalDecisions, 'Decisions', 'var(--accent2)');
|
|
1123
|
+
html += '</div>';
|
|
1124
|
+
|
|
1125
|
+
// === RECENT SESSION ACTIVITY (last 8) ===
|
|
1126
|
+
if (sess.length) {
|
|
1127
|
+
html += '<div style="font-size:11px;font-weight:600;color:var(--muted);text-transform:uppercase;letter-spacing:0.5px;margin:10px 0 6px">🕐 Recent Activity</div>';
|
|
1128
|
+
html += '<div style="display:flex;flex-direction:column;gap:4px;margin-bottom:12px">';
|
|
1129
|
+
|
|
1130
|
+
var maxCalls = Math.max.apply(null, sess.map(function(s) { return s.toolCalls || 0; }).concat([1]));
|
|
1131
|
+
|
|
1132
|
+
sess.forEach(function(s) {
|
|
1133
|
+
var calls = s.toolCalls || 0;
|
|
1134
|
+
var edits = s.edits || 0;
|
|
1135
|
+
var rollbacks = s.rollbacks || 0;
|
|
1136
|
+
var failures = s.failures || 0;
|
|
1137
|
+
var barWidth = Math.round((calls / maxCalls) * 100);
|
|
1138
|
+
var barColor = failures > 0 ? 'var(--red)' : rollbacks > 0 ? 'var(--yellow)' : 'var(--accent)';
|
|
1139
|
+
var goal = s.goal || 'No goal';
|
|
1140
|
+
var date = s.startedAt ? new Date(s.startedAt * 1000) : null;
|
|
1141
|
+
var dateStr = date ? date.toLocaleDateString() + ' ' + date.toLocaleTimeString([], {hour: '2-digit', minute: '2-digit'}) : '-';
|
|
1142
|
+
|
|
1143
|
+
html += '<div style="background:var(--bg);border-radius:6px;padding:8px 10px;border:1px solid var(--border)">';
|
|
1144
|
+
html += '<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:4px">';
|
|
1145
|
+
html += '<span style="font-size:11px;color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:180px" title="' + escapeHtml(goal) + '">' + escapeHtml(goal) + '</span>';
|
|
1146
|
+
html += '<span style="font-size:10px;color:var(--muted);flex-shrink:0;margin-left:8px">' + dateStr + '</span>';
|
|
1147
|
+
html += '</div>';
|
|
1148
|
+
// Activity bar
|
|
1149
|
+
html += '<div style="height:6px;background:var(--border);border-radius:3px;overflow:hidden;margin-bottom:4px">';
|
|
1150
|
+
html += '<div style="height:100%;width:' + barWidth + '%;background:' + barColor + ';border-radius:3px;transition:width 0.3s"></div>';
|
|
1151
|
+
html += '</div>';
|
|
1152
|
+
// Metrics row
|
|
1153
|
+
html += '<div style="display:flex;gap:8px;font-size:10px;color:var(--muted)">';
|
|
1154
|
+
html += '<span>🔧 ' + calls + ' calls</span>';
|
|
1155
|
+
if (edits > 0) html += '<span>📝 ' + edits + ' edits</span>';
|
|
1156
|
+
if (rollbacks > 0) html += '<span style="color:var(--yellow)">↩ ' + rollbacks + '</span>';
|
|
1157
|
+
if (failures > 0) html += '<span style="color:var(--red)">❌ ' + failures + '</span>';
|
|
1158
|
+
if (s.safetyScore != null) html += '<span style="color:var(--green)">🛡 ' + s.safetyScore + '</span>';
|
|
1159
|
+
html += '</div>';
|
|
1160
|
+
html += '</div>';
|
|
1161
|
+
});
|
|
1162
|
+
html += '</div>';
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
// === EFFICIENCY TRENDS ===
|
|
1166
|
+
var successRate = eff.verificationPassRate || 100;
|
|
1167
|
+
var passColor = successRate >= 90 ? 'var(--green)' : successRate >= 70 ? 'var(--yellow)' : 'var(--red)';
|
|
1168
|
+
var recordingRate = totalToolCalls > 0 ? Math.round(((totalGotchas + totalArchFlows + totalDecisions) / totalToolCalls) * 100) : 0;
|
|
1169
|
+
// Rollback rate from recent sessions (rollbacks / calls, %)
|
|
1170
|
+
var totalRollbacks = sess.reduce(function(a, s) { return a + (s.rollbacks || 0); }, 0);
|
|
1171
|
+
var totalCalls = sess.reduce(function(a, s) { return a + (s.toolCalls || 0); }, 0);
|
|
1172
|
+
var rollbackRate = totalCalls > 0 ? Math.round((totalRollbacks / totalCalls) * 100) : 0;
|
|
1173
|
+
|
|
1174
|
+
html += '<div style="font-size:11px;font-weight:600;color:var(--muted);text-transform:uppercase;letter-spacing:0.5px;margin:10px 0 6px">📊 Efficiency Trends</div>';
|
|
1175
|
+
html += '<div class="health-item" style="margin-bottom:8px">';
|
|
1176
|
+
html += '<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:6px">';
|
|
1177
|
+
html += '<span style="font-size:12px;color:var(--text)">Verification Pass Rate</span>';
|
|
1178
|
+
html += '<span style="font-size:14px;font-weight:700;color:' + passColor + '">' + (successRate || '—') + '%</span>';
|
|
1179
|
+
html += '</div>';
|
|
1180
|
+
html += '<div style="height:8px;background:var(--border);border-radius:4px;overflow:hidden">';
|
|
1181
|
+
html += '<div style="height:100%;width:' + (successRate || 0) + '%;background:' + passColor + ';border-radius:4px"></div>';
|
|
1182
|
+
html += '</div>';
|
|
1183
|
+
html += '</div>';
|
|
1184
|
+
|
|
1185
|
+
html += '<div class="health-item" style="margin-bottom:8px">';
|
|
1186
|
+
html += '<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:6px">';
|
|
1187
|
+
html += '<span style="font-size:12px;color:var(--text)">Recording Rate</span>';
|
|
1188
|
+
html += '<span style="font-size:14px;font-weight:700;color:' + (recordingRate >= 10 ? 'var(--green)' : recordingRate >= 5 ? 'var(--yellow)' : 'var(--orange)') + '">' + recordingRate + '%</span>';
|
|
1189
|
+
html += '</div>';
|
|
1190
|
+
html += '<div style="height:8px;background:var(--border);border-radius:4px;overflow:hidden">';
|
|
1191
|
+
html += '<div style="height:100%;width:' + Math.min(recordingRate, 100) + '%;background:' + (recordingRate >= 10 ? 'var(--green)' : recordingRate >= 5 ? 'var(--yellow)' : 'var(--orange)') + ';border-radius:4px"></div>';
|
|
1192
|
+
html += '</div>';
|
|
1193
|
+
html += '</div>';
|
|
1194
|
+
|
|
1195
|
+
// === INSIGHT ===
|
|
1196
|
+
html += '<div style="margin-top:12px;padding:10px 12px;background:rgba(167,139,250,0.1);border:1px solid rgba(167,139,250,0.2);border-radius:8px;font-size:11px;color:var(--accent-bright)">';
|
|
1197
|
+
html += '💡 <b>Activity Insight:</b> ';
|
|
1198
|
+
if (totalSessions === 0) {
|
|
1199
|
+
html += 'No sessions recorded yet. Start using Kuma tools to track activity!';
|
|
1200
|
+
} else if (totalToolCalls < 10) {
|
|
1201
|
+
html += 'Getting started! Kuma learns more with each tool call.';
|
|
1202
|
+
} else if (successRate >= 90) {
|
|
1203
|
+
html += 'Excellent! High success rate shows Kuma is well-calibrated for your workflow.';
|
|
1204
|
+
} else if (rollbackRate > 10) {
|
|
1205
|
+
html += 'High rollback rate — Kuma gotchas may help avoid repeated mistakes.';
|
|
1206
|
+
} else {
|
|
1207
|
+
html += 'Healthy usage pattern. The more you use Kuma, the smarter it gets!';
|
|
1208
|
+
}
|
|
1209
|
+
html += '</div>';
|
|
1210
|
+
|
|
1211
|
+
el.innerHTML = html;
|
|
1212
|
+
lucide.createIcons();
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
// ============ COPY ACTIVITY REPORT ============
|
|
1216
|
+
function copyActivityReport() {
|
|
1217
|
+
var btn = document.getElementById('copy-report-btn');
|
|
1218
|
+
if (!btn) return;
|
|
1219
|
+
|
|
1220
|
+
// Find the data from the last render
|
|
1221
|
+
var panel = document.getElementById('panel-activity');
|
|
1222
|
+
if (!panel) return;
|
|
1223
|
+
|
|
1224
|
+
// Build a clean text report
|
|
1225
|
+
var report = [];
|
|
1226
|
+
report.push('=== KUMA STUDIO — ACTIVITY REPORT ===');
|
|
1227
|
+
report.push('Generated: ' + new Date().toLocaleString());
|
|
1228
|
+
report.push('');
|
|
1229
|
+
|
|
1230
|
+
// Get all stat cards
|
|
1231
|
+
var stats = panel.querySelectorAll('[style*="font-size:20px"]');
|
|
1232
|
+
var labels = panel.querySelectorAll('[style*="font-size:10px;color:var(--muted)"]');
|
|
1233
|
+
|
|
1234
|
+
// Extract intensity info
|
|
1235
|
+
var intensityEl = panel.querySelector('[style*="font-size:16px;font-weight:700"]');
|
|
1236
|
+
var intensity = intensityEl ? intensityEl.textContent : 'Unknown';
|
|
1237
|
+
var avgCallsEl = panel.querySelector('[style*="font-size:11px;color:var(--muted)"]');
|
|
1238
|
+
var avgCalls = avgCallsEl ? avgCallsEl.textContent : '';
|
|
1239
|
+
|
|
1240
|
+
report.push('INTENSITY: ' + intensity);
|
|
1241
|
+
if (avgCalls) report.push('Average: ' + avgCalls);
|
|
1242
|
+
report.push('');
|
|
1243
|
+
|
|
1244
|
+
// Get all section headers and content
|
|
1245
|
+
var sections = panel.querySelectorAll('[style*="text-transform:uppercase"]');
|
|
1246
|
+
sections.forEach(function(section) {
|
|
1247
|
+
var title = section.textContent.trim();
|
|
1248
|
+
report.push('--- ' + title + ' ---');
|
|
1249
|
+
|
|
1250
|
+
// Get next sibling content
|
|
1251
|
+
var next = section.nextElementSibling;
|
|
1252
|
+
if (next) {
|
|
1253
|
+
// Extract text content, cleaning up HTML
|
|
1254
|
+
var text = next.textContent.trim()
|
|
1255
|
+
.replace(/\s+/g, ' ')
|
|
1256
|
+
.replace(/([0-9]+) sessions/g, '$1 sessions')
|
|
1257
|
+
.replace(/([0-9]+) tool calls/g, '$1 tool calls')
|
|
1258
|
+
.replace(/([0-9]+) file edits/g, '$1 file edits')
|
|
1259
|
+
.replace(/([0-9]+) rollbacks/g, '$1 rollbacks')
|
|
1260
|
+
.replace(/([0-9]+) failures/g, '$1 failures')
|
|
1261
|
+
.replace(/([0-9]+) total calls/g, '$1 total calls');
|
|
1262
|
+
report.push(text);
|
|
1263
|
+
}
|
|
1264
|
+
report.push('');
|
|
1265
|
+
});
|
|
1266
|
+
|
|
1267
|
+
// Get recent sessions
|
|
1268
|
+
var sessionItems = panel.querySelectorAll('[style*="background:var(--bg);border-radius:6px"]');
|
|
1269
|
+
if (sessionItems.length > 0) {
|
|
1270
|
+
report.push('--- RECENT SESSIONS ---');
|
|
1271
|
+
sessionItems.forEach(function(item) {
|
|
1272
|
+
var goal = item.querySelector('[style*="font-size:11px"]');
|
|
1273
|
+
var metrics = item.querySelector('[style*="font-size:10px"]');
|
|
1274
|
+
if (goal) report.push('• ' + goal.textContent.trim());
|
|
1275
|
+
if (metrics) report.push(' ' + metrics.textContent.trim());
|
|
1276
|
+
});
|
|
1277
|
+
report.push('');
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
// Get efficiency trends
|
|
1281
|
+
var trends = panel.querySelectorAll('.health-item');
|
|
1282
|
+
if (trends.length > 0) {
|
|
1283
|
+
report.push('--- EFFICIENCY TRENDS ---');
|
|
1284
|
+
trends.forEach(function(trend) {
|
|
1285
|
+
var label = trend.querySelector('[style*="font-size:12px"]');
|
|
1286
|
+
var value = trend.querySelector('[style*="font-size:14px"]');
|
|
1287
|
+
if (label && value) {
|
|
1288
|
+
report.push(label.textContent.trim() + ': ' + value.textContent.trim());
|
|
1289
|
+
}
|
|
1290
|
+
});
|
|
1291
|
+
report.push('');
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
// Get insight
|
|
1295
|
+
var insight = panel.querySelector('[style*="rgba(167,139,250,0.1)"]');
|
|
1296
|
+
if (insight) {
|
|
1297
|
+
report.push('--- INSIGHT ---');
|
|
1298
|
+
report.push(insight.textContent.trim());
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
var reportText = report.join('\n');
|
|
1302
|
+
|
|
1303
|
+
// Copy to clipboard
|
|
1304
|
+
navigator.clipboard.writeText(reportText).then(function() {
|
|
1305
|
+
btn.innerHTML = '<i data-lucide="check" style="width:11px;height:11px"></i> Copied!';
|
|
1306
|
+
btn.style.background = 'rgba(52,211,153,0.15)';
|
|
1307
|
+
btn.style.borderColor = 'rgba(52,211,153,0.3)';
|
|
1308
|
+
btn.style.color = 'var(--green)';
|
|
1309
|
+
lucide.createIcons();
|
|
1310
|
+
setTimeout(function() {
|
|
1311
|
+
btn.innerHTML = '<i data-lucide="copy" style="width:11px;height:11px"></i> Copy Report';
|
|
1312
|
+
btn.style.background = 'rgba(34,211,238,0.12)';
|
|
1313
|
+
btn.style.borderColor = 'rgba(34,211,238,0.25)';
|
|
1314
|
+
btn.style.color = 'var(--accent2)';
|
|
1315
|
+
lucide.createIcons();
|
|
1316
|
+
}, 2000);
|
|
1317
|
+
}).catch(function() {
|
|
1318
|
+
// Fallback: select text for manual copy
|
|
1319
|
+
var textarea = document.createElement('textarea');
|
|
1320
|
+
textarea.value = reportText;
|
|
1321
|
+
textarea.style.position = 'fixed';
|
|
1322
|
+
textarea.style.left = '-9999px';
|
|
1323
|
+
document.body.appendChild(textarea);
|
|
1324
|
+
textarea.select();
|
|
1325
|
+
document.execCommand('copy');
|
|
1326
|
+
document.body.removeChild(textarea);
|
|
1327
|
+
btn.innerHTML = '<i data-lucide="check" style="width:11px;height:11px"></i> Copied!';
|
|
1328
|
+
lucide.createIcons();
|
|
1329
|
+
setTimeout(function() {
|
|
1330
|
+
btn.innerHTML = '<i data-lucide="copy" style="width:11px;height:11px"></i> Copy Report';
|
|
1331
|
+
lucide.createIcons();
|
|
1332
|
+
}, 2000);
|
|
1333
|
+
});
|
|
1334
|
+
}
|
|
923
1335
|
</script>
|
|
924
1336
|
</body>
|
|
925
1337
|
</html>
|