feat: universal LLM gateway with embedded multilingual admin console
- OpenAI-compatible gateway with intelligent routing via cortiq-router (allaigate) - Embedded build-free admin SPA at /admin: 7 languages (en/ru/de/fr/es/zh/tr), light/dark themes, hot config reload (no restart) - Admin API, per-request statistics + Prometheus /metrics, secret store, playground - English documentation (primary) with Russian translations (*.ru.md)
This commit is contained in:
Vendored
+158
@@ -0,0 +1,158 @@
|
||||
// Routing — tier editor (ordered model chips), default model, and policy.
|
||||
import { h, mount, toast } from "../ui.js";
|
||||
import { t } from "../i18n.js";
|
||||
import { api } from "../api.js";
|
||||
import { appState } from "../app.js";
|
||||
|
||||
export async function renderRouting() {
|
||||
const meta = appState.meta || (await api.meta());
|
||||
const [routing, modelsData] = await Promise.all([api.getRouting(), api.listModels()]);
|
||||
const ids = (modelsData.models || []).map((m) => m.id);
|
||||
|
||||
// local editable state — ensure standard tiers exist for a friendly editor
|
||||
const tiers = Object.assign({ low: [], medium: [], high: [] }, routing.tiers || {});
|
||||
const st = {
|
||||
tiers,
|
||||
default: routing.default || ids[0] || "",
|
||||
policy: routing.policy || { mode: "fixed_table", min_class: {} },
|
||||
};
|
||||
|
||||
const root = h("div");
|
||||
|
||||
function moveChip(tier, i, dir) {
|
||||
const arr = st.tiers[tier];
|
||||
const ni = i + dir;
|
||||
if (ni < 0 || ni >= arr.length) return;
|
||||
[arr[i], arr[ni]] = [arr[ni], arr[i]];
|
||||
render();
|
||||
}
|
||||
function removeChip(tier, i) {
|
||||
st.tiers[tier].splice(i, 1);
|
||||
render();
|
||||
}
|
||||
function addChip(tier, id) {
|
||||
if (id && !st.tiers[tier].includes(id)) st.tiers[tier].push(id);
|
||||
render();
|
||||
}
|
||||
|
||||
function tierRow(tier) {
|
||||
const arr = st.tiers[tier];
|
||||
const avail = ids.filter((id) => !arr.includes(id));
|
||||
const addSel = h(
|
||||
"select",
|
||||
{
|
||||
style: "width:auto",
|
||||
onChange: (e) => {
|
||||
addChip(tier, e.target.value);
|
||||
},
|
||||
},
|
||||
h("option", { value: "" }, t("routing.addModel")),
|
||||
...avail.map((id) => h("option", { value: id }, id))
|
||||
);
|
||||
return h(
|
||||
"div",
|
||||
{ class: "tierrow" },
|
||||
h("div", { class: "tname" }, h("span", { class: "badge tier-" + tier }, tier)),
|
||||
h(
|
||||
"div",
|
||||
{ class: "chips" },
|
||||
...arr.map((id, i) =>
|
||||
h(
|
||||
"div",
|
||||
{ class: "chip" },
|
||||
h("span", { class: "ord" }, i + 1),
|
||||
id,
|
||||
h("span", { class: "arrow", title: "←", onClick: () => moveChip(tier, i, -1) }, "◄"),
|
||||
h("span", { class: "arrow", title: "→", onClick: () => moveChip(tier, i, 1) }, "►"),
|
||||
h("span", { class: "x", onClick: () => removeChip(tier, i) }, "×")
|
||||
)
|
||||
),
|
||||
avail.length ? addSel : null
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function policyCard() {
|
||||
const modeSel = h(
|
||||
"select",
|
||||
{ onChange: (e) => { st.policy.mode = e.target.value; render(); } },
|
||||
h("option", { value: "fixed_table", selected: st.policy.mode === "fixed_table" }, t("routing.policy.fixed")),
|
||||
h("option", { value: "cost_aware", selected: st.policy.mode === "cost_aware" }, t("routing.policy.costAware"))
|
||||
);
|
||||
const children = [
|
||||
h("label", { class: "field" }, h("span", {}, t("routing.policy.mode")), modeSel),
|
||||
];
|
||||
if (st.policy.mode === "cost_aware") {
|
||||
const maxIn = h("input", {
|
||||
type: "number",
|
||||
step: "0.01",
|
||||
value: st.policy.max_cost_usd_per_request ?? "",
|
||||
placeholder: "0.50",
|
||||
onChange: (e) => { st.policy.max_cost_usd_per_request = e.target.value === "" ? null : parseFloat(e.target.value); },
|
||||
});
|
||||
children.push(h("label", { class: "field" }, h("span", {}, t("routing.policy.maxCost")), maxIn));
|
||||
st.policy.min_class = st.policy.min_class || {};
|
||||
const rows = Object.keys(st.tiers).map((tier) => {
|
||||
const sel = h(
|
||||
"select",
|
||||
{ onChange: (e) => { st.policy.min_class[tier] = e.target.value; } },
|
||||
h("option", { value: "" }, "—"),
|
||||
...(meta.cost_tiers || []).map((c) => h("option", { value: c, selected: st.policy.min_class[tier] === c }, c))
|
||||
);
|
||||
return h("label", { class: "field" }, h("span", {}, t("routing.policy.minClass") + " · " + tier), sel);
|
||||
});
|
||||
children.push(h("div", { class: "row" }, ...rows));
|
||||
}
|
||||
return h("div", { class: "card" }, h("div", { class: "card-head" }, h("h3", {}, t("routing.policy"))), ...children);
|
||||
}
|
||||
|
||||
function render() {
|
||||
const defSel = h(
|
||||
"select",
|
||||
{ onChange: (e) => { st.default = e.target.value; } },
|
||||
...ids.map((id) => h("option", { value: id, selected: id === st.default }, id))
|
||||
);
|
||||
const saveBtn = h("button", { class: "btn primary" }, t("common.save"));
|
||||
saveBtn.addEventListener("click", async () => {
|
||||
saveBtn.disabled = true;
|
||||
try {
|
||||
await api.putRouting({ tiers: st.tiers, default: st.default, policy: st.policy });
|
||||
toast(t("toast.saved"), "good");
|
||||
} catch (e) {
|
||||
toast(String(e.message || e), "bad");
|
||||
}
|
||||
saveBtn.disabled = false;
|
||||
});
|
||||
|
||||
mount(
|
||||
root,
|
||||
h(
|
||||
"div",
|
||||
{ class: "page-head" },
|
||||
h(
|
||||
"div",
|
||||
{ class: "flex" },
|
||||
h("div", { class: "grow" }, h("h2", {}, t("routing.title")), h("p", {}, t("routing.subtitle"))),
|
||||
saveBtn
|
||||
)
|
||||
),
|
||||
ids.length === 0 ? h("div", { class: "callout" }, t("routing.empty")) : null,
|
||||
h(
|
||||
"div",
|
||||
{ class: "card" },
|
||||
h("div", { class: "card-head" }, h("h3", {}, t("routing.tiers")), h("span", { class: "card-head sub" }, t("routing.tierHint"))),
|
||||
...Object.keys(st.tiers).map(tierRow)
|
||||
),
|
||||
h(
|
||||
"div",
|
||||
{ class: "card" },
|
||||
h("div", { class: "card-head" }, h("h3", {}, t("routing.default"))),
|
||||
h("label", { class: "field" }, h("span", {}, t("routing.defaultHint")), defSel)
|
||||
),
|
||||
policyCard()
|
||||
);
|
||||
}
|
||||
|
||||
render();
|
||||
return root;
|
||||
}
|
||||
Reference in New Issue
Block a user