@automagik/genie-brain 0.260404.8
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/LICENSE +21 -0
- package/dist/bench/cli.js +146 -0
- package/dist/bench-ui/css/brain-lab.css +623 -0
- package/dist/bench-ui/index.html +147 -0
- package/dist/bench-ui/js/charts.js +409 -0
- package/dist/bench-ui/js/common.js +264 -0
- package/dist/bench-ui/js/compare.js +239 -0
- package/dist/bench-ui/js/detail.js +219 -0
- package/dist/bench-ui/js/leaderboard.js +222 -0
- package/dist/bench-ui/js/progress.js +158 -0
- package/dist/bench-ui/ui/css/brain-lab.css +623 -0
- package/dist/bench-ui/ui/index.html +147 -0
- package/dist/bench-ui/ui/js/charts.js +409 -0
- package/dist/bench-ui/ui/js/common.js +264 -0
- package/dist/bench-ui/ui/js/compare.js +239 -0
- package/dist/bench-ui/ui/js/detail.js +219 -0
- package/dist/bench-ui/ui/js/leaderboard.js +222 -0
- package/dist/bench-ui/ui/js/progress.js +158 -0
- package/dist/cli.js +1047 -0
- package/dist/index.js +1069 -0
- package/package.json +61 -0
- package/skills/brain/SKILL.md +115 -0
- package/skills/brain-analyze/SKILL.md +86 -0
- package/skills/brain-build/SKILL.md +100 -0
- package/skills/brain-health/SKILL.md +101 -0
- package/skills/brain-ingest/SKILL.md +107 -0
- package/skills/brain-init/SKILL.md +81 -0
- package/skills/brain-learn/SKILL.md +91 -0
- package/skills/brain-observe/SKILL.md +77 -0
- package/skills/brain-search/SKILL.md +82 -0
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Brain Lab — Benchmark Dashboard</title>
|
|
7
|
+
<link rel="stylesheet" href="/css/brain-lab.css">
|
|
8
|
+
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
|
|
9
|
+
</head>
|
|
10
|
+
<body>
|
|
11
|
+
<div class="container">
|
|
12
|
+
<!-- Header -->
|
|
13
|
+
<div class="header">
|
|
14
|
+
<h1>
|
|
15
|
+
<span class="logo">🧠</span>
|
|
16
|
+
Brain Lab
|
|
17
|
+
</h1>
|
|
18
|
+
<div class="header-right">
|
|
19
|
+
<span id="api-status" class="status-badge offline">
|
|
20
|
+
<span class="status-dot"></span> Connecting...
|
|
21
|
+
</span>
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
<!-- SSE Progress -->
|
|
26
|
+
<div class="progress-container" id="progress-container">
|
|
27
|
+
<div class="progress-bar-bg">
|
|
28
|
+
<div class="progress-bar-fill" id="progress-fill"></div>
|
|
29
|
+
</div>
|
|
30
|
+
<div class="progress-label">
|
|
31
|
+
<span id="progress-label-left">—</span>
|
|
32
|
+
<span id="progress-label-right">0%</span>
|
|
33
|
+
</div>
|
|
34
|
+
<div id="progress-log" style="max-height:120px;overflow-y:auto;margin-top:8px;padding:8px;background:var(--bg-tertiary);border-radius:var(--radius-sm)"></div>
|
|
35
|
+
</div>
|
|
36
|
+
|
|
37
|
+
<!-- Tabs -->
|
|
38
|
+
<div class="tabs">
|
|
39
|
+
<button class="tab-btn active" data-tab="leaderboard">Leaderboard</button>
|
|
40
|
+
<button class="tab-btn" data-tab="charts">Charts</button>
|
|
41
|
+
<button class="tab-btn" data-tab="compare">Compare</button>
|
|
42
|
+
</div>
|
|
43
|
+
|
|
44
|
+
<!-- Tab: Leaderboard -->
|
|
45
|
+
<div class="tab-content active" id="tab-leaderboard">
|
|
46
|
+
<div id="leaderboard-content">
|
|
47
|
+
<div style="text-align:center;padding:40px">
|
|
48
|
+
<span class="spinner"></span> Loading...
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
<!-- Detail view (overlays leaderboard) -->
|
|
54
|
+
<div id="detail-view" style="display:none"></div>
|
|
55
|
+
|
|
56
|
+
<!-- Tab: Charts -->
|
|
57
|
+
<div class="tab-content" id="tab-charts">
|
|
58
|
+
<div id="charts-content">
|
|
59
|
+
<div style="text-align:center;padding:40px">
|
|
60
|
+
<span class="spinner"></span> Loading charts...
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
|
|
65
|
+
<!-- Tab: Compare -->
|
|
66
|
+
<div class="tab-content" id="tab-compare">
|
|
67
|
+
<div id="compare-content">
|
|
68
|
+
<div style="text-align:center;padding:40px">
|
|
69
|
+
<span class="spinner"></span> Loading...
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
|
|
75
|
+
<!-- Scripts -->
|
|
76
|
+
<script src="/js/common.js"></script>
|
|
77
|
+
<script src="/js/leaderboard.js"></script>
|
|
78
|
+
<script src="/js/charts.js"></script>
|
|
79
|
+
<script src="/js/detail.js"></script>
|
|
80
|
+
<script src="/js/compare.js"></script>
|
|
81
|
+
<script src="/js/progress.js"></script>
|
|
82
|
+
|
|
83
|
+
<script>
|
|
84
|
+
// ─── Tab Navigation ─────────────────────────────────
|
|
85
|
+
(function () {
|
|
86
|
+
const { $, $$ } = window.BrainLab;
|
|
87
|
+
|
|
88
|
+
const tabBtns = $$('.tab-btn');
|
|
89
|
+
const tabContents = $$('.tab-content');
|
|
90
|
+
const loaded = new Set();
|
|
91
|
+
|
|
92
|
+
function switchTab(tabId) {
|
|
93
|
+
// Activate tab button
|
|
94
|
+
tabBtns.forEach(btn => {
|
|
95
|
+
btn.classList.toggle('active', btn.dataset.tab === tabId);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// Show tab content
|
|
99
|
+
tabContents.forEach(content => {
|
|
100
|
+
content.classList.toggle('active', content.id === `tab-${tabId}`);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// Hide detail view when switching tabs
|
|
104
|
+
if (tabId !== 'leaderboard' && window.BrainLabDetail) {
|
|
105
|
+
window.BrainLabDetail.hideDetail();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Lazy-load tab data
|
|
109
|
+
if (!loaded.has(tabId)) {
|
|
110
|
+
loaded.add(tabId);
|
|
111
|
+
if (tabId === 'leaderboard' && window.BrainLabLeaderboard) {
|
|
112
|
+
window.BrainLabLeaderboard.load();
|
|
113
|
+
} else if (tabId === 'charts' && window.BrainLabCharts) {
|
|
114
|
+
window.BrainLabCharts.load();
|
|
115
|
+
} else if (tabId === 'compare' && window.BrainLabCompare) {
|
|
116
|
+
window.BrainLabCompare.load();
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Bind tab clicks
|
|
122
|
+
tabBtns.forEach(btn => {
|
|
123
|
+
btn.addEventListener('click', () => switchTab(btn.dataset.tab));
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// ─── Init ─────────────────────────────────────────
|
|
127
|
+
|
|
128
|
+
// Load leaderboard on start
|
|
129
|
+
loaded.add('leaderboard');
|
|
130
|
+
if (window.BrainLabLeaderboard) {
|
|
131
|
+
window.BrainLabLeaderboard.load();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Connect SSE
|
|
135
|
+
if (window.BrainLabProgress) {
|
|
136
|
+
window.BrainLabProgress.connect();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Check URL params for initial tab
|
|
140
|
+
const urlTab = window.BrainLab.getUrlParam('tab');
|
|
141
|
+
if (urlTab && ['leaderboard', 'charts', 'compare'].includes(urlTab)) {
|
|
142
|
+
switchTab(urlTab);
|
|
143
|
+
}
|
|
144
|
+
})();
|
|
145
|
+
</script>
|
|
146
|
+
</body>
|
|
147
|
+
</html>
|
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Brain Lab — Charts
|
|
3
|
+
*
|
|
4
|
+
* Chart.js wrappers for benchmark visualizations.
|
|
5
|
+
* Adapted from cag-kb-agent charts.js.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
(() => {
|
|
9
|
+
const { api, getChartColor, hexToRgba, formatNumber } = window.BrainLab;
|
|
10
|
+
|
|
11
|
+
const charts = {};
|
|
12
|
+
|
|
13
|
+
// ─── Chart Defaults ───────────────────────────────────
|
|
14
|
+
|
|
15
|
+
const CHART_DEFAULTS = {
|
|
16
|
+
responsive: true,
|
|
17
|
+
maintainAspectRatio: false,
|
|
18
|
+
plugins: {
|
|
19
|
+
legend: {
|
|
20
|
+
labels: { color: "#8b949e", font: { size: 11 } },
|
|
21
|
+
},
|
|
22
|
+
tooltip: {
|
|
23
|
+
backgroundColor: "#1e2536",
|
|
24
|
+
titleColor: "#e6edf3",
|
|
25
|
+
bodyColor: "#8b949e",
|
|
26
|
+
borderColor: "#30363d",
|
|
27
|
+
borderWidth: 1,
|
|
28
|
+
padding: 10,
|
|
29
|
+
titleFont: { size: 12, weight: 600 },
|
|
30
|
+
bodyFont: { size: 11 },
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
scales: {
|
|
34
|
+
x: {
|
|
35
|
+
ticks: { color: "#656d76", font: { size: 10 } },
|
|
36
|
+
grid: { color: "rgba(48, 54, 61, 0.5)" },
|
|
37
|
+
},
|
|
38
|
+
y: {
|
|
39
|
+
ticks: { color: "#656d76", font: { size: 10 } },
|
|
40
|
+
grid: { color: "rgba(48, 54, 61, 0.5)" },
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// ─── Destroy Helper ───────────────────────────────────
|
|
46
|
+
|
|
47
|
+
function destroyChart(id) {
|
|
48
|
+
if (charts[id]) {
|
|
49
|
+
charts[id].destroy();
|
|
50
|
+
delete charts[id];
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ─── Quality Scatter (BrainScore vs Latency) ──────────
|
|
55
|
+
|
|
56
|
+
function createQualityScatter(canvasId, data) {
|
|
57
|
+
destroyChart(canvasId);
|
|
58
|
+
const canvas = document.getElementById(canvasId);
|
|
59
|
+
if (!canvas || !data || data.length === 0) return;
|
|
60
|
+
|
|
61
|
+
const paretoPoints = data
|
|
62
|
+
.filter((d) => d.isParetoOptimal)
|
|
63
|
+
.sort((a, b) => a.avgLatencyMs - b.avgLatencyMs);
|
|
64
|
+
|
|
65
|
+
const datasets = [
|
|
66
|
+
{
|
|
67
|
+
label: "Strategies",
|
|
68
|
+
data: data.map((d) => ({
|
|
69
|
+
x: d.avgLatencyMs,
|
|
70
|
+
y: d.brainScore,
|
|
71
|
+
r: Math.max(4, Math.min(16, (d.totalEvaluations || 10) / 5)),
|
|
72
|
+
label: d.strategy,
|
|
73
|
+
})),
|
|
74
|
+
backgroundColor: data.map((d, i) =>
|
|
75
|
+
d.isParetoOptimal
|
|
76
|
+
? hexToRgba("#a371f7", 0.7)
|
|
77
|
+
: hexToRgba(getChartColor(i), 0.6),
|
|
78
|
+
),
|
|
79
|
+
borderColor: data.map((d, i) =>
|
|
80
|
+
d.isParetoOptimal ? "#a371f7" : getChartColor(i),
|
|
81
|
+
),
|
|
82
|
+
borderWidth: 1,
|
|
83
|
+
},
|
|
84
|
+
];
|
|
85
|
+
|
|
86
|
+
// Pareto frontier line
|
|
87
|
+
if (paretoPoints.length > 1) {
|
|
88
|
+
datasets.push({
|
|
89
|
+
label: "Pareto Frontier",
|
|
90
|
+
data: paretoPoints.map((d) => ({ x: d.avgLatencyMs, y: d.brainScore })),
|
|
91
|
+
type: "line",
|
|
92
|
+
borderColor: "#ffd700",
|
|
93
|
+
borderWidth: 2,
|
|
94
|
+
borderDash: [6, 3],
|
|
95
|
+
pointRadius: 0,
|
|
96
|
+
fill: false,
|
|
97
|
+
order: 1,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
charts[canvasId] = new Chart(canvas, {
|
|
102
|
+
type: "bubble",
|
|
103
|
+
data: { datasets },
|
|
104
|
+
options: {
|
|
105
|
+
...CHART_DEFAULTS,
|
|
106
|
+
plugins: {
|
|
107
|
+
...CHART_DEFAULTS.plugins,
|
|
108
|
+
tooltip: {
|
|
109
|
+
...CHART_DEFAULTS.plugins.tooltip,
|
|
110
|
+
callbacks: {
|
|
111
|
+
label: (ctx) => {
|
|
112
|
+
const d = ctx.raw;
|
|
113
|
+
return `${d.label}: Score ${formatNumber(d.y, 1)}, Latency ${formatNumber(d.x, 0)}ms`;
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
scales: {
|
|
119
|
+
x: {
|
|
120
|
+
...CHART_DEFAULTS.scales.x,
|
|
121
|
+
title: {
|
|
122
|
+
display: true,
|
|
123
|
+
text: "Avg Latency (ms)",
|
|
124
|
+
color: "#8b949e",
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
y: {
|
|
128
|
+
...CHART_DEFAULTS.scales.y,
|
|
129
|
+
title: { display: true, text: "BrainScore", color: "#8b949e" },
|
|
130
|
+
min: 0,
|
|
131
|
+
max: 100,
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// ─── Radar Comparison (Top 5) ─────────────────────────
|
|
139
|
+
|
|
140
|
+
function createRadarChart(canvasId, data) {
|
|
141
|
+
destroyChart(canvasId);
|
|
142
|
+
const canvas = document.getElementById(canvasId);
|
|
143
|
+
if (!canvas || !data || data.length === 0) return;
|
|
144
|
+
|
|
145
|
+
const top5 = data.slice(0, 5);
|
|
146
|
+
const labels = ["Quality", "MRR", "NDCG", "F1@K", "Hit Rate", "Speed"];
|
|
147
|
+
|
|
148
|
+
const maxLatency = Math.max(...data.map((d) => d.avgLatencyMs || 1));
|
|
149
|
+
|
|
150
|
+
const datasets = top5.map((d, i) => ({
|
|
151
|
+
label: d.strategy,
|
|
152
|
+
data: [
|
|
153
|
+
(d.avgScore || 0) * 100,
|
|
154
|
+
(d.mrr || 0) * 100,
|
|
155
|
+
(d.ndcg || 0) * 100,
|
|
156
|
+
(d.f1AtK || 0) * 100,
|
|
157
|
+
d.hitRatePct || 0,
|
|
158
|
+
(1 - (d.avgLatencyMs || 0) / maxLatency) * 100,
|
|
159
|
+
],
|
|
160
|
+
backgroundColor: hexToRgba(getChartColor(i), 0.15),
|
|
161
|
+
borderColor: getChartColor(i),
|
|
162
|
+
borderWidth: 2,
|
|
163
|
+
pointRadius: 3,
|
|
164
|
+
pointBackgroundColor: getChartColor(i),
|
|
165
|
+
}));
|
|
166
|
+
|
|
167
|
+
charts[canvasId] = new Chart(canvas, {
|
|
168
|
+
type: "radar",
|
|
169
|
+
data: { labels, datasets },
|
|
170
|
+
options: {
|
|
171
|
+
responsive: true,
|
|
172
|
+
maintainAspectRatio: false,
|
|
173
|
+
scales: {
|
|
174
|
+
r: {
|
|
175
|
+
min: 0,
|
|
176
|
+
max: 100,
|
|
177
|
+
ticks: {
|
|
178
|
+
color: "#656d76",
|
|
179
|
+
backdropColor: "transparent",
|
|
180
|
+
font: { size: 9 },
|
|
181
|
+
},
|
|
182
|
+
grid: { color: "rgba(48, 54, 61, 0.5)" },
|
|
183
|
+
pointLabels: { color: "#8b949e", font: { size: 11 } },
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
plugins: {
|
|
187
|
+
legend: CHART_DEFAULTS.plugins.legend,
|
|
188
|
+
tooltip: CHART_DEFAULTS.plugins.tooltip,
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// ─── Score Breakdown (Stacked Bar) ────────────────────
|
|
195
|
+
|
|
196
|
+
function createScoreBreakdown(canvasId, data) {
|
|
197
|
+
destroyChart(canvasId);
|
|
198
|
+
const canvas = document.getElementById(canvasId);
|
|
199
|
+
if (!canvas || !data || data.length === 0) return;
|
|
200
|
+
|
|
201
|
+
const top10 = data.slice(0, 10);
|
|
202
|
+
const labels = top10.map((d) => d.strategy);
|
|
203
|
+
|
|
204
|
+
// BrainScore components
|
|
205
|
+
const components = [
|
|
206
|
+
{ key: "quality", label: "Quality (25)", weight: 25, color: "#3fb950" },
|
|
207
|
+
{ key: "mrr", label: "MRR (20)", weight: 20, color: "#58a6ff" },
|
|
208
|
+
{ key: "f1AtK", label: "F1@K (15)", weight: 15, color: "#d29922" },
|
|
209
|
+
{ key: "hitRate", label: "Hit Rate (15)", weight: 15, color: "#a371f7" },
|
|
210
|
+
{ key: "speed", label: "Speed (15)", weight: 15, color: "#39d2c0" },
|
|
211
|
+
{ key: "cost", label: "Cost (10)", weight: 10, color: "#ff6eb4" },
|
|
212
|
+
];
|
|
213
|
+
|
|
214
|
+
const maxLatency = 10000;
|
|
215
|
+
const maxCostPerQ = 5.0;
|
|
216
|
+
|
|
217
|
+
const datasets = components.map((comp) => ({
|
|
218
|
+
label: comp.label,
|
|
219
|
+
data: top10.map((d) => {
|
|
220
|
+
let val = 0;
|
|
221
|
+
if (comp.key === "quality") val = Math.min(1, d.avgScore || 0);
|
|
222
|
+
else if (comp.key === "mrr") val = Math.min(1, d.mrr || 0);
|
|
223
|
+
else if (comp.key === "f1AtK") val = Math.min(1, d.f1AtK || 0);
|
|
224
|
+
else if (comp.key === "hitRate")
|
|
225
|
+
val = Math.min(1, (d.hitRatePct || 0) / 100);
|
|
226
|
+
else if (comp.key === "speed")
|
|
227
|
+
val = Math.max(0, 1 - (d.avgLatencyMs || 0) / maxLatency);
|
|
228
|
+
else if (comp.key === "cost") {
|
|
229
|
+
const perQ =
|
|
230
|
+
d.totalEvaluations > 0
|
|
231
|
+
? (d.totalCostCents || 0) / d.totalEvaluations
|
|
232
|
+
: 0;
|
|
233
|
+
val = Math.max(0, 1 - perQ / maxCostPerQ);
|
|
234
|
+
}
|
|
235
|
+
return val * comp.weight;
|
|
236
|
+
}),
|
|
237
|
+
backgroundColor: comp.color,
|
|
238
|
+
}));
|
|
239
|
+
|
|
240
|
+
charts[canvasId] = new Chart(canvas, {
|
|
241
|
+
type: "bar",
|
|
242
|
+
data: { labels, datasets },
|
|
243
|
+
options: {
|
|
244
|
+
...CHART_DEFAULTS,
|
|
245
|
+
plugins: {
|
|
246
|
+
...CHART_DEFAULTS.plugins,
|
|
247
|
+
tooltip: {
|
|
248
|
+
...CHART_DEFAULTS.plugins.tooltip,
|
|
249
|
+
callbacks: {
|
|
250
|
+
footer: (items) => {
|
|
251
|
+
const total = items.reduce((s, i) => s + i.raw, 0);
|
|
252
|
+
return `Total: ${formatNumber(total, 1)}`;
|
|
253
|
+
},
|
|
254
|
+
},
|
|
255
|
+
},
|
|
256
|
+
},
|
|
257
|
+
scales: {
|
|
258
|
+
x: {
|
|
259
|
+
...CHART_DEFAULTS.scales.x,
|
|
260
|
+
stacked: true,
|
|
261
|
+
ticks: { ...CHART_DEFAULTS.scales.x.ticks, maxRotation: 45 },
|
|
262
|
+
},
|
|
263
|
+
y: {
|
|
264
|
+
...CHART_DEFAULTS.scales.y,
|
|
265
|
+
stacked: true,
|
|
266
|
+
max: 100,
|
|
267
|
+
title: { display: true, text: "BrainScore", color: "#8b949e" },
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
},
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// ─── Latency Histogram ────────────────────────────────
|
|
275
|
+
|
|
276
|
+
function createLatencyHistogram(canvasId, data) {
|
|
277
|
+
destroyChart(canvasId);
|
|
278
|
+
const canvas = document.getElementById(canvasId);
|
|
279
|
+
if (!canvas || !data || data.length === 0) return;
|
|
280
|
+
|
|
281
|
+
const bins = [
|
|
282
|
+
{ label: "< 500ms", min: 0, max: 500, count: 0 },
|
|
283
|
+
{ label: "500ms-1s", min: 500, max: 1000, count: 0 },
|
|
284
|
+
{ label: "1-2s", min: 1000, max: 2000, count: 0 },
|
|
285
|
+
{ label: "2-5s", min: 2000, max: 5000, count: 0 },
|
|
286
|
+
{ label: "> 5s", min: 5000, max: Number.POSITIVE_INFINITY, count: 0 },
|
|
287
|
+
];
|
|
288
|
+
|
|
289
|
+
for (const d of data) {
|
|
290
|
+
const lat = d.avgLatencyMs || 0;
|
|
291
|
+
for (const bin of bins) {
|
|
292
|
+
if (lat >= bin.min && lat < bin.max) {
|
|
293
|
+
bin.count++;
|
|
294
|
+
break;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
charts[canvasId] = new Chart(canvas, {
|
|
300
|
+
type: "bar",
|
|
301
|
+
data: {
|
|
302
|
+
labels: bins.map((b) => b.label),
|
|
303
|
+
datasets: [
|
|
304
|
+
{
|
|
305
|
+
label: "Strategies",
|
|
306
|
+
data: bins.map((b) => b.count),
|
|
307
|
+
backgroundColor: "#58a6ff",
|
|
308
|
+
borderRadius: 4,
|
|
309
|
+
},
|
|
310
|
+
],
|
|
311
|
+
},
|
|
312
|
+
options: {
|
|
313
|
+
...CHART_DEFAULTS,
|
|
314
|
+
scales: {
|
|
315
|
+
x: { ...CHART_DEFAULTS.scales.x },
|
|
316
|
+
y: {
|
|
317
|
+
...CHART_DEFAULTS.scales.y,
|
|
318
|
+
beginAtZero: true,
|
|
319
|
+
ticks: { ...CHART_DEFAULTS.scales.y.ticks, stepSize: 1 },
|
|
320
|
+
title: { display: true, text: "Count", color: "#8b949e" },
|
|
321
|
+
},
|
|
322
|
+
},
|
|
323
|
+
},
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// ─── Load & Render All Charts ─────────────────────────
|
|
328
|
+
|
|
329
|
+
async function loadCharts() {
|
|
330
|
+
const container = document.getElementById("charts-content");
|
|
331
|
+
if (!container) return;
|
|
332
|
+
|
|
333
|
+
try {
|
|
334
|
+
// Try to use cached leaderboard data first
|
|
335
|
+
let data = window.BrainLabLeaderboard
|
|
336
|
+
? window.BrainLabLeaderboard.getData()
|
|
337
|
+
: null;
|
|
338
|
+
if (!data || data.length === 0) {
|
|
339
|
+
data = await api("GET", "/api/leaderboard");
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
if (!data || data.length === 0) {
|
|
343
|
+
container.innerHTML = `
|
|
344
|
+
<div class="empty-state">
|
|
345
|
+
<div class="empty-state-icon">📈</div>
|
|
346
|
+
<div class="empty-state-title">No chart data</div>
|
|
347
|
+
<div class="empty-state-text">Run a benchmark to see charts.</div>
|
|
348
|
+
</div>`;
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Sort by brainScore for consistent display
|
|
353
|
+
data.sort((a, b) => (b.brainScore || 0) - (a.brainScore || 0));
|
|
354
|
+
|
|
355
|
+
container.innerHTML = `
|
|
356
|
+
<div class="charts-grid">
|
|
357
|
+
<div class="chart-card">
|
|
358
|
+
<div class="chart-header">
|
|
359
|
+
<div class="chart-title">Quality vs Latency</div>
|
|
360
|
+
<div class="chart-subtitle">BrainScore vs average latency. Bubble size = evaluation count. Gold line = Pareto frontier.</div>
|
|
361
|
+
</div>
|
|
362
|
+
<canvas id="chart-scatter" height="280"></canvas>
|
|
363
|
+
</div>
|
|
364
|
+
<div class="chart-card">
|
|
365
|
+
<div class="chart-header">
|
|
366
|
+
<div class="chart-title">Top 5 Radar</div>
|
|
367
|
+
<div class="chart-subtitle">Multi-dimensional comparison of top strategies.</div>
|
|
368
|
+
</div>
|
|
369
|
+
<canvas id="chart-radar" height="280"></canvas>
|
|
370
|
+
</div>
|
|
371
|
+
<div class="chart-card full-width">
|
|
372
|
+
<div class="chart-header">
|
|
373
|
+
<div class="chart-title">Score Breakdown</div>
|
|
374
|
+
<div class="chart-subtitle">BrainScore component contributions per strategy.</div>
|
|
375
|
+
</div>
|
|
376
|
+
<canvas id="chart-breakdown" height="280"></canvas>
|
|
377
|
+
</div>
|
|
378
|
+
<div class="chart-card">
|
|
379
|
+
<div class="chart-header">
|
|
380
|
+
<div class="chart-title">Latency Distribution</div>
|
|
381
|
+
<div class="chart-subtitle">How many strategies fall into each latency bucket.</div>
|
|
382
|
+
</div>
|
|
383
|
+
<canvas id="chart-latency" height="280"></canvas>
|
|
384
|
+
</div>
|
|
385
|
+
</div>`;
|
|
386
|
+
|
|
387
|
+
// Wait for DOM update
|
|
388
|
+
requestAnimationFrame(() => {
|
|
389
|
+
createQualityScatter("chart-scatter", data);
|
|
390
|
+
createRadarChart("chart-radar", data);
|
|
391
|
+
createScoreBreakdown("chart-breakdown", data);
|
|
392
|
+
createLatencyHistogram("chart-latency", data);
|
|
393
|
+
});
|
|
394
|
+
} catch (err) {
|
|
395
|
+
container.innerHTML = `<div class="empty-state"><div class="empty-state-icon">⚠</div><div class="empty-state-title">Chart error</div><div class="empty-state-text">${window.BrainLab.escapeHtml(err.message)}</div></div>`;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// ─── Public API ───────────────────────────────────────
|
|
400
|
+
|
|
401
|
+
window.BrainLabCharts = {
|
|
402
|
+
load: loadCharts,
|
|
403
|
+
createQualityScatter,
|
|
404
|
+
createRadarChart,
|
|
405
|
+
createScoreBreakdown,
|
|
406
|
+
createLatencyHistogram,
|
|
407
|
+
destroyChart,
|
|
408
|
+
};
|
|
409
|
+
})();
|