Spaces:
Running
Running
Upload folder using huggingface_hub
Browse files- app.py +14 -0
- static/index.html +99 -1
app.py
CHANGED
|
@@ -256,6 +256,20 @@ async def wiki_merges() -> Any:
|
|
| 256 |
return await _proxy_backend_get("/v1/wiki/merges")
|
| 257 |
|
| 258 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 259 |
# ──────────────────────────────────────────────────────────────
|
| 260 |
# OAuth (HF Spaces auto-injects OAUTH_CLIENT_ID/SECRET when
|
| 261 |
# `hf_oauth: true` is set in README.md).
|
|
|
|
| 256 |
return await _proxy_backend_get("/v1/wiki/merges")
|
| 257 |
|
| 258 |
|
| 259 |
+
# Trace & stats sharing (proxied same-origin like the wiki reads). The backend
|
| 260 |
+
# computes the project token aggregate and the trace listing; the SPA can't call
|
| 261 |
+
# it cross-origin (no CORS), so the dashboard relays it here.
|
| 262 |
+
@app.get("/api/stats")
|
| 263 |
+
async def stats_proxy() -> Any:
|
| 264 |
+
return await _proxy_backend_get("/v1/stats")
|
| 265 |
+
|
| 266 |
+
|
| 267 |
+
@app.get("/api/traces")
|
| 268 |
+
async def traces_proxy(request: Request) -> Any:
|
| 269 |
+
qs = request.url.query
|
| 270 |
+
return await _proxy_backend_get(f"/v1/traces?{qs}" if qs else "/v1/traces")
|
| 271 |
+
|
| 272 |
+
|
| 273 |
# ──────────────────────────────────────────────────────────────
|
| 274 |
# OAuth (HF Spaces auto-injects OAUTH_CLIENT_ID/SECRET when
|
| 275 |
# `hf_oauth: true` is set in README.md).
|
static/index.html
CHANGED
|
@@ -258,6 +258,22 @@
|
|
| 258 |
.lb-table.wiki-table th, .lb-table.wiki-table td { padding: 8px 8px; }
|
| 259 |
.lb-table.wiki-table td { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
| 260 |
.lb-table.wiki-table a.lb-link { margin: 0; } /* pill fits its narrow column */
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 261 |
.lb-table tr { border-bottom: 1px solid var(--border-soft); }
|
| 262 |
.lb-table tbody tr:last-child { border-bottom: none; }
|
| 263 |
.lb-table tbody tr:hover td { background: #fafafa; }
|
|
@@ -3186,7 +3202,16 @@ function initWiki() {
|
|
| 3186 |
<div><table class="lb-table wiki-table"><thead><tr>
|
| 3187 |
<th style="width:40px">PR</th><th>Title</th><th style="width:108px">Agent</th>
|
| 3188 |
<th style="width:70px">Kind</th><th style="width:100px">Date</th><th style="width:60px">Link</th>
|
| 3189 |
-
</tr></thead><tbody id="mpBody"></tbody></table></div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3190 |
refreshWiki();
|
| 3191 |
}
|
| 3192 |
|
|
@@ -3197,6 +3222,7 @@ async function refreshWiki() {
|
|
| 3197 |
try { const r = await fetchWithTimeout('/api/wiki/leaderboard'); if (r.ok) renderWikiLeaderboard(await r.json()); } catch {}
|
| 3198 |
try { const r = await fetchWithTimeout('/api/wiki/activity'); if (r.ok) renderActivity(await r.json()); } catch {}
|
| 3199 |
try { const r = await fetchWithTimeout('/api/wiki/merges'); if (r.ok) renderMergedPRs(await r.json(), ds); } catch {}
|
|
|
|
| 3200 |
}
|
| 3201 |
|
| 3202 |
function fmtMergeDate(ts) {
|
|
@@ -3217,6 +3243,78 @@ function renderMergedPRs(data, ds) {
|
|
| 3217 |
}).join('') || '<tr><td colspan="6" style="color:#999">No merged PRs yet.</td></tr>';
|
| 3218 |
}
|
| 3219 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3220 |
let wikiCharts = {};
|
| 3221 |
// epoch ms -> "MM-DD HH:MM" (UTC) for the time axis ticks
|
| 3222 |
function fmtTick(v) {
|
|
|
|
| 258 |
.lb-table.wiki-table th, .lb-table.wiki-table td { padding: 8px 8px; }
|
| 259 |
.lb-table.wiki-table td { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
| 260 |
.lb-table.wiki-table a.lb-link { margin: 0; } /* pill fits its narrow column */
|
| 261 |
+
/* Traces — project token estimate + shared session library */
|
| 262 |
+
.stats-tile {
|
| 263 |
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
| 264 |
+
border: 1px solid var(--border); background: #fff;
|
| 265 |
+
padding: 12px 14px; margin-bottom: 12px;
|
| 266 |
+
font-size: 11px; color: var(--muted); font-variant-numeric: tabular-nums;
|
| 267 |
+
}
|
| 268 |
+
.stats-tile .big { font-size: 20px; font-weight: 600; color: var(--accent); }
|
| 269 |
+
.stats-tile .muted2 { color: var(--muted-3); }
|
| 270 |
+
.stats-tile .row { display: flex; gap: 16px; flex-wrap: wrap; margin-top: 8px; }
|
| 271 |
+
.stats-tile .row .k {
|
| 272 |
+
color: var(--muted-3); text-transform: uppercase;
|
| 273 |
+
letter-spacing: 0.5px; margin-right: 2px;
|
| 274 |
+
}
|
| 275 |
+
.stats-tile .coverage { color: var(--muted-3); font-size: 10px; margin-top: 8px; }
|
| 276 |
+
.traces-table .muted2 { color: var(--muted-4); }
|
| 277 |
.lb-table tr { border-bottom: 1px solid var(--border-soft); }
|
| 278 |
.lb-table tbody tr:last-child { border-bottom: none; }
|
| 279 |
.lb-table tbody tr:hover td { background: #fafafa; }
|
|
|
|
| 3202 |
<div><table class="lb-table wiki-table"><thead><tr>
|
| 3203 |
<th style="width:40px">PR</th><th>Title</th><th style="width:108px">Agent</th>
|
| 3204 |
<th style="width:70px">Kind</th><th style="width:100px">Date</th><th style="width:60px">Link</th>
|
| 3205 |
+
</tr></thead><tbody id="mpBody"></tbody></table></div>
|
| 3206 |
+
<div class="section-title" id="tracesSectionTitle" hidden>Traces<span class="hint" id="tracesHint"></span></div>
|
| 3207 |
+
<div class="stats-tile" id="tracesStatsTile" hidden></div>
|
| 3208 |
+
<div id="tracesListWrap" style="overflow-x:auto" hidden>
|
| 3209 |
+
<table class="lb-table traces-table" style="min-width:760px"><thead><tr>
|
| 3210 |
+
<th style="width:130px">Agent</th><th style="width:90px">Harness</th><th style="width:120px">Model</th>
|
| 3211 |
+
<th class="num" style="width:80px">Tokens</th><th class="num" style="width:56px">Tools</th>
|
| 3212 |
+
<th>Summary</th><th style="width:72px">Trace</th>
|
| 3213 |
+
</tr></thead><tbody id="tracesBody"></tbody></table>
|
| 3214 |
+
</div>`;
|
| 3215 |
refreshWiki();
|
| 3216 |
}
|
| 3217 |
|
|
|
|
| 3222 |
try { const r = await fetchWithTimeout('/api/wiki/leaderboard'); if (r.ok) renderWikiLeaderboard(await r.json()); } catch {}
|
| 3223 |
try { const r = await fetchWithTimeout('/api/wiki/activity'); if (r.ok) renderActivity(await r.json()); } catch {}
|
| 3224 |
try { const r = await fetchWithTimeout('/api/wiki/merges'); if (r.ok) renderMergedPRs(await r.json(), ds); } catch {}
|
| 3225 |
+
refreshTraces();
|
| 3226 |
}
|
| 3227 |
|
| 3228 |
function fmtMergeDate(ts) {
|
|
|
|
| 3243 |
}).join('') || '<tr><td colspan="6" style="color:#999">No merged PRs yet.</td></tr>';
|
| 3244 |
}
|
| 3245 |
|
| 3246 |
+
// ── Traces: project token estimate + shared-session library (best-effort;
|
| 3247 |
+
// the section stays hidden until ≥1 trace exists) ──
|
| 3248 |
+
const STATS_URL = '/api/stats';
|
| 3249 |
+
const TRACES_URL = '/api/traces?expand=true&limit=50';
|
| 3250 |
+
|
| 3251 |
+
function fmtTokens(n) {
|
| 3252 |
+
if (n == null) return '—';
|
| 3253 |
+
if (n >= 1e6) return (n / 1e6).toFixed(n >= 1e7 ? 0 : 1) + 'M';
|
| 3254 |
+
if (n >= 1e3) return (n / 1e3).toFixed(n >= 1e4 ? 0 : 1) + 'k';
|
| 3255 |
+
return String(n);
|
| 3256 |
+
}
|
| 3257 |
+
|
| 3258 |
+
function renderStatsTile(stats) {
|
| 3259 |
+
const el = document.getElementById('tracesStatsTile');
|
| 3260 |
+
const t = stats.tokens || {};
|
| 3261 |
+
const cost = stats.cost_usd != null ? `<span><span class="k">cost</span> $${stats.cost_usd.toFixed(2)}</span>` : '';
|
| 3262 |
+
const cov = stats.sessions_missing_tokens
|
| 3263 |
+
? `<div class="coverage">reported floor — ${stats.sessions_missing_tokens} session(s) without token data excluded</div>` : '';
|
| 3264 |
+
el.innerHTML =
|
| 3265 |
+
`<span class="big">${fmtTokens(t.total || 0)}</span> tokens reported`
|
| 3266 |
+
+ ` <span class="muted2">· ${stats.sessions_counted} sessions · ${stats.agents_reporting} agents</span>`
|
| 3267 |
+
+ `<div class="row"><span><span class="k">in</span> ${fmtTokens(t.input)}</span>`
|
| 3268 |
+
+ `<span><span class="k">out</span> ${fmtTokens(t.output)}</span>`
|
| 3269 |
+
+ `<span><span class="k">cache</span> ${fmtTokens(t.cache_read)}</span>${cost}</div>${cov}`;
|
| 3270 |
+
}
|
| 3271 |
+
|
| 3272 |
+
function renderTracesList(items) {
|
| 3273 |
+
const body = document.getElementById('tracesBody');
|
| 3274 |
+
body.innerHTML = items.map(it => {
|
| 3275 |
+
const traceFile = it.primary_log_file || '';
|
| 3276 |
+
const full = it.share === 'full' && CFG.bucket_web_url && traceFile;
|
| 3277 |
+
const link = full
|
| 3278 |
+
? `<a class="lb-link" href="${escapeHtml(CFG.bucket_web_url + '/' + traceFile)}" target="_blank" rel="noopener noreferrer" title="Open the native session log in Hugging Face's trace viewer">view ↗</a>`
|
| 3279 |
+
: '<span class="muted2">stats</span>';
|
| 3280 |
+
return `<tr>
|
| 3281 |
+
<td>${renderAgentName(it.agent)}</td>
|
| 3282 |
+
<td>${escapeHtml(it.harness || '—')}</td>
|
| 3283 |
+
<td>${escapeHtml(it.model || '—')}</td>
|
| 3284 |
+
<td class="num">${fmtTokens(it.total_tokens)}</td>
|
| 3285 |
+
<td class="num">${it.tool_calls == null ? '—' : it.tool_calls}</td>
|
| 3286 |
+
<td class="desc" title="${escapeHtml(it.summary_excerpt || '')}">${escapeHtml(it.summary_excerpt || '')}</td>
|
| 3287 |
+
<td class="links">${link}</td>
|
| 3288 |
+
</tr>`;
|
| 3289 |
+
}).join('');
|
| 3290 |
+
}
|
| 3291 |
+
|
| 3292 |
+
async function refreshTraces() {
|
| 3293 |
+
const titleEl = document.getElementById('tracesSectionTitle');
|
| 3294 |
+
const tileEl = document.getElementById('tracesStatsTile');
|
| 3295 |
+
const wrapEl = document.getElementById('tracesListWrap');
|
| 3296 |
+
const hintEl = document.getElementById('tracesHint');
|
| 3297 |
+
if (!titleEl) return;
|
| 3298 |
+
try {
|
| 3299 |
+
const [sr, tr] = await Promise.allSettled([
|
| 3300 |
+
fetchWithTimeout(STATS_URL), fetchWithTimeout(TRACES_URL),
|
| 3301 |
+
]);
|
| 3302 |
+
let stats = null, items = [], count = 0;
|
| 3303 |
+
if (sr.status === 'fulfilled' && sr.value.ok) stats = await sr.value.json();
|
| 3304 |
+
if (tr.status === 'fulfilled' && tr.value.ok) {
|
| 3305 |
+
const j = await tr.value.json();
|
| 3306 |
+
items = j.items || []; count = j.count ?? items.length;
|
| 3307 |
+
}
|
| 3308 |
+
const has = items.length > 0 || (stats && (stats.sessions_counted || stats.sessions_missing_tokens));
|
| 3309 |
+
titleEl.hidden = tileEl.hidden = wrapEl.hidden = !has;
|
| 3310 |
+
if (!has) return;
|
| 3311 |
+
if (stats) renderStatsTile(stats);
|
| 3312 |
+
renderTracesList(items);
|
| 3313 |
+
hintEl.textContent = count > items.length
|
| 3314 |
+
? `${items.length} of ${count}` : `${count} session${count === 1 ? '' : 's'}`;
|
| 3315 |
+
} catch { /* traces are best-effort; never disrupt the dashboard */ }
|
| 3316 |
+
}
|
| 3317 |
+
|
| 3318 |
let wikiCharts = {};
|
| 3319 |
// epoch ms -> "MM-DD HH:MM" (UTC) for the time axis ticks
|
| 3320 |
function fmtTick(v) {
|