Files
cortiq-gateway/web/js/app.js
T
infosave2007 1120d8f11a feat(benchmark): the built-in benchmark, redesigned into the console
The old standalone benchmark.html (Russian-only, Tailwind+Chart.js from CDNs,
never shipped in the crate) becomes a proper sidebar view. Same scoring ideas,
reimplemented: keyword checks with |-synonyms, think-tag stripping, tail-loop
detection, pass/partial/fail — plus what the old page never had: runs go
through the LIVE pipeline (pinned per model, streamed), so every test also
measures tok/s and time-to-first-token with the gateway's real token counts.

A built-in suite of 12 language-neutral probes (math, logic, code, QA,
instruction-following, extraction, translation, JSON formatting); the ⚡ quick
mode runs the 6 short ones — about a minute on a local model. Results: score
bars per model sorted best-first, a per-test grid (click a cell for the full
answer), previous runs and custom tests kept in the browser.

22 new strings in all 7 languages.
2026-08-07 18:56:47 +03:00

336 lines
11 KiB
JavaScript
Vendored
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// App bootstrap: login gate, sidebar, topbar, hash router, language + theme.
import { h, mount, toast } from "./ui.js";
import { t, getLang, setLang, onLangChange, LANGS } from "./i18n.js";
import { api, getToken, setToken, setUnauthorizedHandler } from "./api.js";
import { renderLogin } from "./views/login.js";
import { renderDashboard } from "./views/dashboard.js";
import { renderModels } from "./views/models.js?v=3";
import { renderImport } from "./views/import.js";
import { renderRouting } from "./views/routing.js";
import { renderProtocols } from "./views/protocols.js";
import { renderKeys } from "./views/keys.js";
import { renderPlayground } from "./views/playground.js";
import { renderSettings } from "./views/settings.js";
import { renderLogs } from "./views/logs.js";
import { renderBenchmark } from "./views/benchmark.js";
import { renderOnboarding } from "./views/onboarding.js";
const ROUTES = [
{ id: "dashboard", icon: "▤", view: renderDashboard },
{ id: "models", icon: "▦", view: renderModels },
{ id: "import", icon: "⬇", view: renderImport },
{ id: "routing", icon: "⇄", view: renderRouting },
{ id: "protocols", icon: "⇆", view: renderProtocols },
{ id: "keys", icon: "🔑", view: renderKeys },
{ id: "playground", icon: "✦", view: renderPlayground },
{ id: "benchmark", icon: "⏱", view: renderBenchmark },
{ id: "logs", icon: "📜", view: renderLogs },
{ id: "settings", icon: "⚙", view: renderSettings },
{ id: "onboarding", icon: "✦", view: renderOnboarding, hidden: true },
];
export const appState = { meta: null, health: null, version: "" };
// allaigate site — where router keys are issued and topped up
export const SITE_URL = "https://api.allaigate.com/";
let mobileNavOpen = false;
function setMobileNav(open) {
mobileNavOpen = !!open;
const app = document.querySelector(".app");
if (app) app.classList.toggle("nav-open", mobileNavOpen);
document.body.classList.toggle("mobile-nav-open", mobileNavOpen);
const toggle = document.querySelector(".menu-toggle");
if (toggle) toggle.setAttribute("aria-expanded", String(mobileNavOpen));
}
function currentRoute() {
const id = location.hash.replace(/^#\/?/, "") || "dashboard";
return ROUTES.find((r) => r.id === id) || ROUTES[0];
}
const FAVICON =
"data:image/svg+xml;utf8," +
encodeURIComponent(
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><defs><linearGradient id="g" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="%233b82f6"/><stop offset=".5" stop-color="%238b5cf6"/><stop offset="1" stop-color="%2306b6d4"/></linearGradient></defs><rect width="32" height="32" rx="8" fill="url(%23g)"/><path d="M9 16h14M17 10l6 6-6 6" stroke="white" stroke-width="2.4" fill="none" stroke-linecap="round" stroke-linejoin="round"/></svg>'
);
// One-line banners under the topbar: open-admin warning, update available.
function shellNotices() {
const host = h("div", {});
const m = appState.meta || {};
if (m.admin_open_public && !sessionStorage.getItem("hide_open_admin")) {
const close = h("button", { class: "icon-btn", title: "×", onClick: (e) => { sessionStorage.setItem("hide_open_admin", "1"); e.target.closest(".notice").remove(); } }, "×");
host.appendChild(
h("div", { class: "notice warn" }, "⚠ " + t("notice.openAdmin"), " ",
h("a", { href: "#/settings" }, t("nav.settings") + " →"), close)
);
}
if (m.update_available) {
host.appendChild(
h("div", { class: "notice info" },
"⬆ " + t("notice.update", { v: m.update_available }),
" ", h("code", { class: "mono" }, "cargo install cortiq-gateway"))
);
}
return host;
}
function sidebar(activeId) {
const visibleRoutes = ROUTES.filter((r) => !r.hidden);
return h(
"aside",
{ class: "sidebar", id: "sidebar", "aria-label": "Main navigation" },
h(
"div",
{ class: "brand" },
h("img", { src: FAVICON, alt: "" }),
h("div", { class: "brand-copy" }, h("b", {}, "allaigate"), h("div", { class: "sub" }, t("brand.sub"))),
h("button", { class: "icon-btn nav-close", type: "button", title: "Close menu", "aria-label": "Close menu", onClick: () => setMobileNav(false) }, "×")
),
...visibleRoutes.map((r) =>
h(
"a",
{ class: "nav-item" + (r.id === activeId ? " active" : ""), href: "#/" + r.id, onClick: () => setMobileNav(false) },
h("span", { class: "ico" }, r.icon),
t("nav." + r.id)
)
),
h("div", { class: "nav-spacer" }),
h(
"a",
{ class: "nav-item", href: SITE_URL, target: "_blank", rel: "noopener" },
h("span", { class: "ico" }, "◈"),
t("nav.billing"),
h("span", { style: "margin-left:auto;opacity:.55" }, "↗")
),
h("div", { class: "nav-foot" }, t("nav.foot", { v: appState.version || "—" }))
);
}
function statusPills() {
const h_ = appState.health;
const gwOk = !!appState.meta;
const routerOk = h_?.router?.reachable;
// reachable but no key resolves → warn: routing silently degrades to default
const keyMissing = h_?.router?.key_source === "missing";
const routerDot = h_ ? (routerOk ? (keyMissing ? "warn" : "ok") : "bad") : "warn";
const routerText = h_
? routerOk
? keyMissing
? t("status.noKey")
: t("status.online")
: t("status.offline")
: t("status.checking");
return h(
"div",
{ class: "flex status-pills" },
h(
"span",
{ class: "pill" },
h("span", { class: "dot " + (gwOk ? "ok" : "bad") }),
t("status.gateway") + ": " + (gwOk ? t("status.online") : t("status.offline"))
),
h(
"span",
{ class: "pill", title: h_?.router?.url || "" },
h("span", { class: "dot " + routerDot }),
t("status.router") + ": " + routerText
)
);
}
function langSelect() {
const sel = h(
"select",
{
class: "lang-select",
title: "Language",
onChange: (e) => setLang(e.target.value),
},
...LANGS.map((l) => h("option", { value: l.code, selected: l.code === getLang() }, `${l.flag} ${l.label}`))
);
return sel;
}
function themeToggle() {
const cur = document.documentElement.getAttribute("data-theme") || "dark";
return h(
"button",
{
class: "icon-btn",
title: t("theme.toggle"),
onClick: () => {
const next = (document.documentElement.getAttribute("data-theme") || "dark") === "dark" ? "light" : "dark";
document.documentElement.setAttribute("data-theme", next);
localStorage.setItem("allaigate_theme", next);
renderShell();
},
},
cur === "dark" ? "☀" : "☾"
);
}
function topbar(activeId) {
return h(
"header",
{ class: "topbar" },
h(
"button",
{
class: "icon-btn menu-toggle",
type: "button",
title: "Menu",
"aria-label": "Open menu",
"aria-controls": "sidebar",
"aria-expanded": String(mobileNavOpen),
onClick: () => setMobileNav(!mobileNavOpen),
},
h("span", { class: "hamburger", "aria-hidden": "true" }, h("i"), h("i"), h("i"))
),
h("div", { class: "title" }, t("nav." + activeId)),
h(
"div",
{ class: "right" },
statusPills(),
themeToggle(),
langSelect(),
h(
"button",
{
class: "icon-btn",
title: t("logout"),
onClick: () => {
setToken("");
boot();
},
},
"⎋"
)
)
);
}
async function renderView() {
const route = currentRoute();
const view = document.getElementById("view");
if (!view) return;
mount(view, h("div", { class: "empty" }, h("span", { class: "spinner" }), " ", t("common.loading")));
try {
const node = await route.view();
mount(view, node);
} catch (e) {
if (String(e.message) === "unauthorized") return;
mount(view, h("div", { class: "callout" }, String(e.message || e)));
}
}
function renderShell() {
const route = currentRoute();
mobileNavOpen = false;
document.body.classList.remove("mobile-nav-open");
// onboarding — full screen without sidebar/topbar
if (route.id === "onboarding") {
mount(
document.getElementById("app"),
h("div", { class: "onb-shell" }, h("div", { class: "onb-content", id: "view" }))
);
renderView();
return;
}
mount(
document.getElementById("app"),
h(
"div",
{ class: "app" },
sidebar(route.id),
h("button", { class: "mobile-nav-backdrop", type: "button", tabindex: "-1", "aria-label": "Close menu", onClick: () => setMobileNav(false) }),
h("main", { class: "main" }, topbar(route.id), shellNotices(), h("div", { class: "content", id: "view" }))
)
);
renderView();
}
function showLogin() {
mount(document.getElementById("app"), renderLogin(boot));
}
async function refreshHealth() {
try {
appState.health = await api.health();
} catch {
appState.health = null;
}
// re-render topbar only if shell present
const route = currentRoute();
const main = document.querySelector(".main");
if (main) {
const old = main.querySelector(".topbar");
if (old) old.replaceWith(topbar(route.id));
}
}
export async function boot() {
// convenience: /admin?token=… logs in once, then is stripped from the URL
const urlTok = new URLSearchParams(location.search).get("token");
if (urlTok) {
setToken(urlTok);
history.replaceState({}, "", location.pathname + location.hash);
}
// try without token first — open mode (no CORTIQ_ADMIN_TOKEN) succeeds,
// protected mode returns 401 and we show login
try {
appState.meta = await api.meta();
appState.version = appState.meta.version || "";
} catch (e) {
if (String(e.message) === "unauthorized") {
// if we have no token stored, show login; with a stale token also show login
showLogin();
return;
}
// gateway reachable but error — still show shell
// if meta failed for other reason but token was present, fall through
if (!getToken()) {
// open mode but gateway unreachable — still try shell; health will show
try {
appState.meta = await api.meta();
appState.version = appState.meta.version || "";
} catch {}
}
}
// first-run check — redirect to onboarding if needed (unless already there)
try {
const st = await api.setupStatus();
if (st.needs_setup && location.hash !== "#/onboarding") {
location.hash = "#/onboarding";
}
} catch {}
if (!location.hash) location.hash = "#/dashboard";
renderShell();
refreshHealth();
}
setUnauthorizedHandler(() => {
toast(t("login.failed"), "bad");
setToken("");
showLogin();
});
onLangChange(() => {
if (document.querySelector(".app")) renderShell();
});
window.addEventListener("hashchange", () => {
if (document.querySelector(".app")) renderShell();
});
window.addEventListener("keydown", (e) => {
if (e.key === "Escape" && mobileNavOpen) setMobileNav(false);
});
window.addEventListener("resize", () => {
if (window.innerWidth > 980 && mobileNavOpen) setMobileNav(false);
});
setInterval(() => {
if (document.querySelector(".app")) refreshHealth();
}, 20000);
boot();