From a83abee378c24920c370614e292f89305cb2ef14 Mon Sep 17 00:00:00 2001 From: root Date: Tue, 11 Aug 2026 17:42:51 +0800 Subject: [PATCH] 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. --- internal/gateway/ui/index.html | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/internal/gateway/ui/index.html b/internal/gateway/ui/index.html index b5db78d..16f8522 100644 --- a/internal/gateway/ui/index.html +++ b/internal/gateway/ui/index.html @@ -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 = `
${t('kpiActive')}
-
${st.active || 0}
+
reqs / 3s
${t('kpiReqs')}
-
${fmtN(tot.reqs)}
-
${t('kpiOk')} ${okr}%
+
+
${t('kpiTokens')}
-
${fmtTok(tot.tokens)}
-
${t('thPrompt')} ${fmtTok(tot.prompt_tokens)}${t('thCompl')} ${fmtTok(tot.completion_tokens)}
+
+
${t('kpiLat')}
-
${fmtMs(avg)}
-
${t('kpiMaxLat')} ${fmtMs(tot.latency_max_ms)}
+
+
${t('dashStatus')}
-
+
`; + } + // 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')} ${okr}%`; + kset('[data-kpi="tokens"]', fmtTok(tot.tokens)); + const tsub = $('[data-kpi="tokens-sub"]'); + if (tsub) tsub.innerHTML = `${t('thPrompt')} ${fmtTok(tot.prompt_tokens)}${t('thCompl')} ${fmtTok(tot.completion_tokens)}`; + 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 || []);