diff --git a/web/js/i18n/de.js b/web/js/i18n/de.js index 3bec4b8..0112cd4 100644 --- a/web/js/i18n/de.js +++ b/web/js/i18n/de.js @@ -616,4 +616,6 @@ export default { "keys.h.st.installed": "Auf der Gateway-Maschine installiert", "keys.h.st.configured": "Bereits für dieses Gateway konfiguriert", "keys.h.st.notFound": "Auf der Gateway-Maschine nicht gefunden", + "bm.degraded": "Router degradiert", + "bm.routerDegraded": "Der Router hat nicht rechtzeitig geantwortet — die Anfrage nutzte die Fallback-Kette; die Klassifikation wird nicht gewertet.", }; diff --git a/web/js/i18n/en.js b/web/js/i18n/en.js index 4883f18..0ef2998 100644 --- a/web/js/i18n/en.js +++ b/web/js/i18n/en.js @@ -609,4 +609,6 @@ export default { "keys.h.st.installed": "Installed on the gateway machine", "keys.h.st.configured": "Already configured for this gateway", "keys.h.st.notFound": "Not found on the gateway machine", + "bm.degraded": "router degraded", + "bm.routerDegraded": "The router did not answer in time — the request used the fallback chain; classification is not judged.", }; diff --git a/web/js/i18n/es.js b/web/js/i18n/es.js index d9e5395..969a1bd 100644 --- a/web/js/i18n/es.js +++ b/web/js/i18n/es.js @@ -616,4 +616,6 @@ export default { "keys.h.st.installed": "Instalado en la máquina de la pasarela", "keys.h.st.configured": "Ya configurado para esta pasarela", "keys.h.st.notFound": "No encontrado en la máquina de la pasarela", + "bm.degraded": "router degradado", + "bm.routerDegraded": "El router no respondió a tiempo — la petición usó la cadena de reserva; la clasificación no se cuenta.", }; diff --git a/web/js/i18n/fr.js b/web/js/i18n/fr.js index f15444f..ca5b61f 100644 --- a/web/js/i18n/fr.js +++ b/web/js/i18n/fr.js @@ -616,4 +616,6 @@ export default { "keys.h.st.installed": "Installé sur la machine de la passerelle", "keys.h.st.configured": "Déjà configuré pour cette passerelle", "keys.h.st.notFound": "Introuvable sur la machine de la passerelle", + "bm.degraded": "routeur dégradé", + "bm.routerDegraded": "Le routeur n'a pas répondu à temps — la requête a suivi la chaîne de secours ; la classification n'est pas comptée.", }; diff --git a/web/js/i18n/ru.js b/web/js/i18n/ru.js index e9326f9..2930096 100644 --- a/web/js/i18n/ru.js +++ b/web/js/i18n/ru.js @@ -608,4 +608,6 @@ export default { "keys.h.st.installed": "Установлен на машине шлюза", "keys.h.st.configured": "Уже настроен на этот шлюз", "keys.h.st.notFound": "Не найден на машине шлюза", + "bm.degraded": "роутер деградировал", + "bm.routerDegraded": "Роутер не успел ответить — запрос ушёл по резервной цепочке; классификация не засчитывается.", }; diff --git a/web/js/i18n/tr.js b/web/js/i18n/tr.js index 146f161..4aeedfc 100644 --- a/web/js/i18n/tr.js +++ b/web/js/i18n/tr.js @@ -616,4 +616,6 @@ export default { "keys.h.st.installed": "Ağ geçidi makinesinde kurulu", "keys.h.st.configured": "Bu ağ geçidi için zaten yapılandırılmış", "keys.h.st.notFound": "Ağ geçidi makinesinde bulunamadı", + "bm.degraded": "yönlendirici yedeğe düştü", + "bm.routerDegraded": "Yönlendirici zamanında yanıt vermedi — istek yedek zincire gitti; sınıflandırma sayılmaz.", }; diff --git a/web/js/i18n/zh.js b/web/js/i18n/zh.js index dbbd70c..1a90a9e 100644 --- a/web/js/i18n/zh.js +++ b/web/js/i18n/zh.js @@ -616,4 +616,6 @@ export default { "keys.h.st.installed": "已安装在网关所在机器上", "keys.h.st.configured": "已配置为使用此网关", "keys.h.st.notFound": "网关所在机器上未找到", + "bm.degraded": "路由器降级", + "bm.routerDegraded": "路由器未及时响应 — 请求走了备用链路;分类不计入统计。", }; diff --git a/web/js/ui.js b/web/js/ui.js index 7113d4c..eae147b 100644 --- a/web/js/ui.js +++ b/web/js/ui.js @@ -100,13 +100,16 @@ export function confirmDialog(message) { export function moneyEl(n) { if (n == null) return document.createTextNode("—"); if (n === 0) return document.createTextNode("$0"); - const s = n.toFixed(4); + // sub-0.0001 costs (cheap flash models) would collapse to $0.0000 — grow + // precision until two significant digits survive + const s = Math.abs(n) >= 0.0001 ? n.toFixed(4) : n.toFixed(Math.min(8, 1 - Math.floor(Math.log10(Math.abs(n))))); return h("span", { class: "money" }, "$" + s.slice(0, -2), h("small", { class: "money-tail" }, s.slice(-2))); } export function money(n) { if (n == null) return "—"; if (n === 0) return "$0"; + if (Math.abs(n) > 0 && Math.abs(n) < 0.0001) return "$" + n.toFixed(Math.min(8, 1 - Math.floor(Math.log10(Math.abs(n))))); // up to 4 decimals, trailing zeros trimmed (but keep money-like 2 minimum) let s = n.toFixed(4).replace(/0+$/, "").replace(/\.$/, ""); if (/\.\d$/.test(s)) s += "0"; diff --git a/web/js/views/benchmark.js b/web/js/views/benchmark.js index aa53427..13cce3f 100644 --- a/web/js/views/benchmark.js +++ b/web/js/views/benchmark.js @@ -170,7 +170,9 @@ export async function renderBenchmark() { let text = ""; try { const info = await testStream( - { model, messages: [{ role: "user", content: test.prompt }], temperature: 0, max_tokens: test.max || 128 }, + // +256: headroom for reasoning models — a tight cap cuts them + // mid- and turns a correct model into a false ❌ + { model, messages: [{ role: "user", content: test.prompt }], temperature: 0, max_tokens: (test.max || 128) + 256 }, (d) => (text += d) ); const latency = performance.now() - started; @@ -183,12 +185,17 @@ export async function renderBenchmark() { if (info.ttft_ms != null) rec.ttft.push(info.ttft_ms); const row = { id: test.id, cat: test.cat, ...ev, latency: Math.round(latency), cost: info.cost_usd || 0, answer: stripThink(text).slice(0, 300) }; if (mode === "routing") { + // router timed out / fell back — there was no classification to + // judge, so it must not count against accuracy + row.degraded = !info.task_label + || info.task_label === "degraded" + || String(info.route_source || "").includes("degraded"); row.task = info.task_label || "—"; row.tier = info.tier || "—"; row.chosen = info.selected_model || "—"; row.source = info.route_source || "—"; row.expected = test.label; // null = classification not judged - row.taskOk = test.label ? test.label.includes(info.task_label) : null; + row.taskOk = test.label && !row.degraded ? test.label.includes(info.task_label) : null; } rec.results.push(row); } catch (e) { @@ -311,11 +318,13 @@ export async function renderBenchmark() { h("tr", {}, h("td", {}, h("div", {}, r.id), h("div", { class: "muted small" }, r.cat)), h("td", {}, - r.taskOk == null - ? h("span", { class: "muted" }, r.task || "—") - : h("span", { class: r.taskOk ? "" : "err" }, - (r.taskOk ? "✓ " : "✗ ") + r.task, - r.taskOk ? "" : h("div", { class: "muted small" }, t("bm.expectedLbl") + " " + (r.expected || []).join("/")))), + r.degraded + ? h("span", { class: "muted", title: t("bm.routerDegraded") }, "⚠ " + t("bm.degraded")) + : r.taskOk == null + ? h("span", { class: "muted" }, r.task || "—") + : h("span", { class: r.taskOk ? "" : "err" }, + (r.taskOk ? "✓ " : "✗ ") + r.task, + r.taskOk ? "" : h("div", { class: "muted small" }, t("bm.expectedLbl") + " " + (r.expected || []).join("/")))), h("td", {}, r.tier ? h("span", { class: "badge tier-" + r.tier }, r.tier) : "—"), h("td", {}, h("span", { class: "mono small" }, r.chosen || "—"),