fix(ui): stop rebuilding the five KPI cards every 3s poll (was causing a visible flicker)

paintStats() rewrote #kpi-row innerHTML on every 3s refresh, destroying/recreating all five cards + canvases each time -> each poll flashed. Now the card structure is built once (dataset.built guard) and subsequent polls only update the value text (data-kpi attrs) and redraw the canvases. Preserves colored success rate and two-line in/out tokens. Deployed (bak .bak.20260811t), server active.
This commit is contained in:
root
2026-08-11 17:42:51 +08:00
parent c006c3988e
commit a83abee378

View File

@ -766,37 +766,52 @@ async function paintStats() {
const okr = tot.reqs ? Math.round(tot.ok * 100 / tot.reqs) : 0;
const avg = fmtLat(tot.latency_sum_ms, tot.reqs);
const kpi = $('#kpi-row');
if (kpi) kpi.innerHTML = `
if (kpi && !kpi.dataset.built) {
kpi.dataset.built = '1';
kpi.innerHTML = `
<div class="kpi${statsKeyF ? ' kpi-exit' : ''}" title="${statsKeyF ? t('exitKeyView') : ''}" onclick="${statsKeyF ? `renderKeyF('${escAttr(statsKeyF)}')` : ''}">
<div class="k-hdr"><span class="k-lab">${t('kpiActive')} <span class="dot"></span></span></div>
<div class="k-val">${st.active || 0}</div>
<div class="k-val" data-kpi="active"></div>
<div class="k-sub">reqs / 3s</div>
<div class="k-chart"><canvas id="cv-active"></canvas></div>
</div>
<div class="kpi">
<div class="k-hdr"><span class="k-lab">${t('kpiReqs')}</span></div>
<div class="k-val">${fmtN(tot.reqs)}</div>
<div class="k-sub">${t('kpiOk')} <b class="${okr >= 90 ? 'okc' : 'errc'}">${okr}%</b></div>
<div class="k-val" data-kpi="reqs"></div>
<div class="k-sub" data-kpi="reqs-sub"></div>
<div class="k-chart"><canvas id="cv-reqs"></canvas></div>
</div>
<div class="kpi">
<div class="k-hdr"><span class="k-lab">${t('kpiTokens')}</span></div>
<div class="k-val">${fmtTok(tot.tokens)}</div>
<div class="k-sub k2"><span>${t('thPrompt')} ${fmtTok(tot.prompt_tokens)}</span><span>${t('thCompl')} ${fmtTok(tot.completion_tokens)}</span></div>
<div class="k-val" data-kpi="tokens"></div>
<div class="k-sub k2" data-kpi="tokens-sub"></div>
<div class="k-chart"><canvas id="cv-tokens"></canvas></div>
<div class="status-legend" id="tb-tokens-legend"></div>
</div>
<div class="kpi">
<div class="k-hdr"><span class="k-lab">${t('kpiLat')}</span></div>
<div class="k-val">${fmtMs(avg)}</div>
<div class="k-sub">${t('kpiMaxLat')} ${fmtMs(tot.latency_max_ms)}</div>
<div class="k-val" data-kpi="lat"></div>
<div class="k-sub" data-kpi="lat-sub"></div>
<div class="k-chart"><canvas id="cv-lat"></canvas></div>
</div>
<div class="kpi kpi-status">
<div class="k-hdr"><span class="k-lab">${t('dashStatus')}</span><span class="k-sub" id="tb-status-total"></span></div>
<div class="k-chart k-chart-pie"><canvas id="cv-status"></canvas><div class="donut-note"></div></div>
<div class="k-chart k-chart-pie"><canvas id="cv-status"></canvas></div>
<div class="status-legend" id="tb-status"></div>
</div>`;
}
// update values without rebuilding the card DOM (prevents the 3s flicker)
const kset = (sel, txt) => { const e = $(sel); if (e) e.textContent = txt; };
kset('[data-kpi="active"]', st.active || 0);
kset('[data-kpi="reqs"]', fmtN(tot.reqs));
const rsub = $('[data-kpi="reqs-sub"]');
if (rsub) rsub.innerHTML = `${t('kpiOk')} <b class="${okr >= 90 ? 'okc' : 'errc'}">${okr}%</b>`;
kset('[data-kpi="tokens"]', fmtTok(tot.tokens));
const tsub = $('[data-kpi="tokens-sub"]');
if (tsub) tsub.innerHTML = `<span>${t('thPrompt')} ${fmtTok(tot.prompt_tokens)}</span><span>${t('thCompl')} ${fmtTok(tot.completion_tokens)}</span>`;
kset('[data-kpi="lat"]', fmtMs(avg));
kset('[data-kpi="lat-sub"]', `${t('kpiMaxLat')} ${fmtMs(tot.latency_max_ms)}`);
if (kpi) kpi.classList.toggle('kpi-exit', !!statsKeyF);
paintModelTable(st.by_model || []);
paintSrcTable(st.by_source || []);
paintStatusTable(st.by_status || []);