Dashboard: 'Popular in the co-op' model leaderboard (ranked bars, aggregate requests)
This commit is contained in:
1 parent
112e9ca1fc
commit
d09bc8a9c6
1 file changed
+52
+52
@@ -122,6 +122,12 @@ async def api_usage(request: Request):
|
||||
data = await broker_get(identity, "/broker/usage")
|
||||
keys = await broker_get(identity, "/broker/keys")
|
||||
data["keys"] = keys.get("keys", [])
|
||||
# Co-op-wide model popularity (aggregate, anonymized) for the leaderboard.
|
||||
try:
|
||||
board = await broker_get(identity, "/broker/model-leaderboard")
|
||||
data["coop_models"] = board.get("models", [])
|
||||
except HTTPException:
|
||||
data["coop_models"] = [] # best-effort; never block the main view
|
||||
return data
|
||||
except HTTPException as e:
|
||||
return JSONResponse({"error": e.detail}, status_code=e.status_code)
|
||||
@@ -318,6 +324,24 @@ PAGE_HTML = """<!DOCTYPE html>
|
||||
.model-name { font-weight: 600; font-size: 13px; }
|
||||
.model-sub { color: var(--muted); font-size: 12px; }
|
||||
.model-spend { font-size: 13px; font-weight: 600; color: var(--ink); text-align: right; }
|
||||
|
||||
/* Leaderboard */
|
||||
.board-row {
|
||||
display: flex; align-items: flex-start; gap: 14px;
|
||||
padding: 12px 0; border-top: 1px solid #f0ede6;
|
||||
}
|
||||
.board-rank {
|
||||
width: 26px; height: 26px; border-radius: 50%;
|
||||
background: #eaf3ea; color: #2f5c30; font-weight: 700; font-size: 13px;
|
||||
display: flex; align-items: center; justify-content: center; flex-shrink: 0;
|
||||
margin-top: 2px;
|
||||
}
|
||||
.board-row:first-child .board-rank { background: var(--green-deep); color: #fff; }
|
||||
.board-main { flex: 1; min-width: 0; }
|
||||
.board-name { font-weight: 600; font-size: 13px; margin-bottom: 5px; }
|
||||
.board-bar { height: 6px; background: #eef0ea; border-radius: 4px; overflow: hidden; }
|
||||
.board-fill { height: 100%; background: var(--green-mid); border-radius: 4px; }
|
||||
.board-sub { color: var(--muted); font-size: 11px; margin-top: 4px; }
|
||||
.keys { margin-top: 8px; }
|
||||
.key-row {
|
||||
display: flex; align-items: center; justify-content: space-between; gap: 12px;
|
||||
@@ -461,6 +485,12 @@ PAGE_HTML = """<!DOCTYPE html>
|
||||
<div class="models" id="models"></div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>Popular in the co-op</h2>
|
||||
<p class="hint">Which models members are using most, across the whole co-op. A starting point when choosing one.</p>
|
||||
<div class="board" id="coop-board"></div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>API keys</h2>
|
||||
<p class="hint">Keys let you use the co-op's AI from your own tools. They draw from the same balance as chat.</p>
|
||||
@@ -538,6 +568,7 @@ async function load() {
|
||||
$('calls').textContent = calls.toLocaleString();
|
||||
|
||||
renderModels(d.models || []);
|
||||
renderBoard(d.coop_models || []);
|
||||
renderKeys(d.keys || []);
|
||||
} catch (e) {
|
||||
flash('Could not load usage: ' + e.message, false);
|
||||
@@ -557,6 +588,27 @@ function renderModels(models) {
|
||||
}).join('');
|
||||
}
|
||||
|
||||
function renderBoard(models) {
|
||||
const el = $('coop-board');
|
||||
if (!models.length) {
|
||||
el.innerHTML = '<div class="empty">Not enough usage data yet — check back soon.</div>';
|
||||
return;
|
||||
}
|
||||
// Rank bar widths are relative to the top model's request count.
|
||||
const top = Math.max(...models.map(m => m.requests || 0), 1);
|
||||
el.innerHTML = models.map((m, i) => {
|
||||
const pct = Math.round(((m.requests || 0) / top) * 100);
|
||||
return '<div class="board-row">' +
|
||||
'<div class="board-rank">' + (i + 1) + '</div>' +
|
||||
'<div class="board-main">' +
|
||||
'<div class="board-name">' + escapeHtml(m.model) + '</div>' +
|
||||
'<div class="board-bar"><div class="board-fill" style="width:' + pct + '%"></div></div>' +
|
||||
'<div class="board-sub">' + (m.requests || 0).toLocaleString() + ' requests by members</div>' +
|
||||
'</div>' +
|
||||
'</div>';
|
||||
}).join('');
|
||||
}
|
||||
|
||||
function renderKeys(keys) {
|
||||
const el = $('keys');
|
||||
if (!keys.length) {
|
||||
|
||||
Reference in new issue
Block a user