mirror of
https://gitcode.com/JianFeeeee/ModelRouter.git
synced 2026-09-20 00:48:00 +00:00
fix(ui): tokens card — fix 252px height overflow, show all models with usage in legend
- .kpi fixed height 206px was too short for the tokens card (hdr + big value + two-line in/out + chart + legend) -> text overflowed; bumped to 252px with legend capped at max-height/scroll - tokens chart: stack shows top-4 slices + lumped 'other', total uses the FULL model set (true shares, was relative to top-4); legend lists every model that has non-zero tokens (was hardcoded top-4, so small/zero slices were dropped -> 'only two models' visible) - add kOther zh/en - deployed (bak .bak.20260811x), server active
This commit is contained in:
@ -114,7 +114,7 @@ pre.configbox { background:var(--card2); border:1px solid var(--line); border-ra
|
||||
.tab-pane { animation:tabIn .25s ease; }
|
||||
.chat-log .chat-empty { animation:fadeIn .3s ease; }
|
||||
.kpis { display:grid; grid-template-columns:repeat(auto-fit,minmax(160px,1fr)); gap:12px; margin-bottom:18px; }
|
||||
.kpi { background:var(--card); border:1px solid var(--line); border-radius:14px; padding:14px 16px; box-shadow:var(--shadow); display:flex; flex-direction:column; height:206px; }
|
||||
.kpi { background:var(--card); border:1px solid var(--line); border-radius:14px; padding:14px 16px; box-shadow:var(--shadow); display:flex; flex-direction:column; height:252px; }
|
||||
.kpi .k-hdr { display:flex; align-items:center; justify-content:space-between; gap:8px; margin-bottom:6px; flex:0 0 auto; }
|
||||
.kpi .k-lab { font-size:12px; color:var(--muted); font-weight:600; white-space:nowrap; }
|
||||
.kpi .k-val { font-size:26px; font-weight:800; margin-top:4px; font-variant-numeric:tabular-nums; letter-spacing:.3px; flex:0 0 auto; }
|
||||
@ -123,7 +123,7 @@ pre.configbox { background:var(--card2); border:1px solid var(--line); border-ra
|
||||
.kpi .k-sub.k2 { display:flex; flex-direction:column; align-items:flex-start; line-height:1.5; }
|
||||
.kpi .k-chart { position:relative; flex:1 1 auto; min-height:40px; margin-top:8px; }
|
||||
.kpi canvas { position:absolute; inset:0; width:100%; height:100%; display:block; }
|
||||
.status-legend { display:flex; flex-wrap:wrap; gap:5px 9px; margin-top:6px; justify-content:center; }
|
||||
.status-legend { display:flex; flex-wrap:wrap; gap:5px 9px; margin-top:6px; justify-content:center; overflow-y:auto; max-height:66px; scrollbar-width:thin; }
|
||||
.status-legend .lg { display:inline-flex; align-items:center; gap:5px; font-size:11px; color:var(--muted); white-space:nowrap; }
|
||||
.status-legend .lg i { width:9px; height:9px; border-radius:2px; flex:0 0 auto; }
|
||||
.status-legend .lg b { color:var(--fg); font-variant-numeric:tabular-nums; }
|
||||
@ -522,7 +522,7 @@ const STR = {
|
||||
sortSource:'源', sortPrio:'优先级 %s', sortEmpty:'该源暂无模型',
|
||||
kpiActive:'活跃请求', kpiReqs:'总请求', kpiOk:'成功率', kpiTokens:'Tokens', kpiLat:'平均延迟', kpiMaxLat:'最大延迟',
|
||||
dashModel:'模型用量', dashSrc:'源用量与延迟', dashKey:'密钥用量', dashRecs:'请求记录', exportCsv:'导出 CSV', expWeek:'近一周', expMonth:'近一月', expYear:'近一年', expRange:'自定义范围', expStart:'开始日期', expEnd:'结束日期', expDownload:'下载', expKeysCsv:'导出密钥用量', exitKeyView:'退出该密钥视图',
|
||||
dashStatus:'状态码分布', thCode:'状态码', statusTag:'状态码分类统计(含 402 欠费 / 400 schema 错误;两者不计入上游退避但单独计数)',
|
||||
dashStatus:'状态码分布', thCode:'状态码', kOther:'其他', statusTag:'状态码分类统计(含 402 欠费 / 400 schema 错误;两者不计入上游退避但单独计数)',
|
||||
thModel:'模型', thSrc:'源', thKey:'密钥', thReqs:'请求', thOk:'成功', thErr:'失败',
|
||||
thPrompt:'输入 Tokens', thCompl:'输出 Tokens', thAvgLat:'平均延迟', thMaxLat:'最长延迟',
|
||||
thTime:'时间', thType:'类型', thStatus:'状态', thLatMs:'延迟',
|
||||
@ -578,7 +578,7 @@ kMeTitle:'My key', kMeRole:'Role', kMeModels:'Models I can use', kMeHint:'Keys c
|
||||
sortSource:'source', sortPrio:'priority %s', sortEmpty:'no models in this source',
|
||||
kpiActive:'Active requests', kpiReqs:'Requests', kpiOk:'Success rate', kpiTokens:'Tokens', kpiLat:'Avg latency', kpiMaxLat:'Max latency',
|
||||
dashModel:'Model usage', dashSrc:'Source usage & latency', dashKey:'Key usage', dashRecs:'Request records', exportCsv:'Export CSV', expWeek:'Last week', expMonth:'Last month', expYear:'Last year', expRange:'Custom range', expStart:'Start date', expEnd:'End date', expDownload:'Download', expKeysCsv:'Export key usage', exitKeyView:'Exit this key view',
|
||||
dashStatus:'Status codes', thCode:'Code', statusTag:'Per-status aggregates — 402 quota / 400 schema errors are counted here but never back off the provider',
|
||||
dashStatus:'Status codes', thCode:'Code', kOther:'other', statusTag:'Per-status aggregates — 402 quota / 400 schema errors are counted here but never back off the provider',
|
||||
thModel:'Model', thSrc:'Source', thKey:'Key', thReqs:'Requests', thOk:'OK', thErr:'Err',
|
||||
thPrompt:'Prompt Tokens', thCompl:'Completion Tokens', thAvgLat:'Avg latency', thMaxLat:'Max latency',
|
||||
thTime:'Time', thType:'Type', thStatus:'Status', thLatMs:'Latency',
|
||||
@ -962,18 +962,25 @@ function drawTokensChart(models) {
|
||||
const H = rect.height || 86;
|
||||
cv.width = rect.width * dpr; cv.height = H * dpr; ctx.scale(dpr, dpr);
|
||||
ctx.clearRect(0, 0, cv.width, cv.height);
|
||||
const rows = (models||[]).slice().sort((a,b)=>b.tokens-a.tokens).slice(0,4);
|
||||
const tot = rows.reduce((a,x)=>a+x.tokens,0) || 1;
|
||||
// all non-zero models sorted desc; total uses the full set so shares are true
|
||||
const all = (models||[]).filter(m => (m.tokens||0) > 0).sort((a,b)=>b.tokens-a.tokens);
|
||||
if (!all.length) return;
|
||||
const grand = all.reduce((a,x)=>a+x.tokens,0);
|
||||
// keep the top 4 as visible slices; lump the rest into "other"
|
||||
const top = all.slice(0,4);
|
||||
const rest = all.slice(4).reduce((a,x)=>a+x.tokens,0);
|
||||
const slices = rest > 0 ? top.concat([{ name: '… ' + t('kOther'), tokens: rest, _other: true }]) : top;
|
||||
const tot = grand;
|
||||
let x = 0;
|
||||
rows.forEach((m,i) => {
|
||||
slices.forEach((m,i) => {
|
||||
const w = Math.max(2, m.tokens / tot * rect.width);
|
||||
ctx.fillStyle = tokPalette[i%tokPalette.length];
|
||||
ctx.fillRect(x, 0, w, H);
|
||||
x += w;
|
||||
});
|
||||
// color-swatch legend instead of on-chart text
|
||||
// legend: list each individual model (not aggregated) that has tokens
|
||||
const lg = document.getElementById('tb-tokens-legend');
|
||||
if (lg) lg.innerHTML = rows.map((m,i) =>
|
||||
if (lg) lg.innerHTML = all.map((m,i) =>
|
||||
`<span class="lg" title="${esc(m.name)}: ${fmtTok(m.tokens)} (${(m.tokens/tot*100).toFixed(1)}%)">
|
||||
<i style="background:${tokPalette[i%tokPalette.length]}"></i>${esc(m.name)}<b>${fmtTok(m.tokens)}</b></span>`).join('');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user