const BASE = "/admin/api";
const TOKEN_KEY = "allaigate_token";
let onUnauthorized = null;
export function setUnauthorizedHandler(fn) {
onUnauthorized = fn;
}
export function getToken() {
return localStorage.getItem(TOKEN_KEY) || "";
}
export function setToken(tok) {
if (tok) localStorage.setItem(TOKEN_KEY, tok);
else localStorage.removeItem(TOKEN_KEY);
}
async function j(method, path, body) {
const tok = getToken();
const headers = {};
if (tok) headers["Authorization"] = "Bearer " + tok;
const opt = { method, headers };
if (body !== undefined) {
opt.headers["content-type"] = "application/json";
opt.body = JSON.stringify(body);
}
const r = await fetch(BASE + path, opt);
if (r.status === 401) {
if (onUnauthorized) onUnauthorized();
throw new Error("unauthorized");
}
const text = await r.text();
let data = null;
try {
data = text ? JSON.parse(text) : null;
} catch {
data = text;
}
if (!r.ok) {
const msg =
(data && data.error && (data.error.message || data.error)) ||
(data && (data.detail || data.message)) ||
r.statusText;
throw new Error(typeof msg === "string" ? msg : JSON.stringify(msg));
}
return data;
}
export const api = {
meta: () => j("GET", "/meta"),
health: () => j("GET", "/health"),
probeRouter: () => j("POST", "/router/probe"),
getConfig: () => j("GET", "/config"),
putConfig: (b) => j("PUT", "/config", b),
listModels: () => j("GET", "/models"),
createModel: (b) => j("POST", "/models", b),
updateModel: (id, b) => j("PUT", "/models/" + encodeURIComponent(id), b),
deleteModel: (id) => j("DELETE", "/models/" + encodeURIComponent(id)),
probeModel: (id) => j("POST", "/models/" + encodeURIComponent(id) + "/probe"),
getRouting: () => j("GET", "/routing"),
routingLabels: () => j("GET", "/routing/labels"),
putRouting: (b) => j("PUT", "/routing", b),
getProtocols: () => j("GET", "/protocols"),
harnesses: () => j("GET", "/harnesses"),
putProtocols: (b) => j("PUT", "/protocols", b),
getSettings: () => j("GET", "/settings"),
putSettings: (b) => j("PUT", "/settings", b),
listKeys: () => j("GET", "/keys"),
createKey: (b) => j("POST", "/keys", b),
deleteKey: (k) => j("DELETE", "/keys/" + encodeURIComponent(k)),
listSecrets: () => j("GET", "/secrets"),
setSecret: (name, value) => j("PUT", "/secrets", { name, value }),
clearSecret: (name) => j("DELETE", "/secrets?name=" + encodeURIComponent(name)),
stats: (range, groupby) => j("GET", `/stats?range=${range}&groupby=${groupby}`),
clearStats: () => j("DELETE", "/stats"),
requests: (limit = 50, offset = 0) => j("GET", `/requests?limit=${limit}&offset=${offset}`),
test: (b) => j("POST", "/test", b),
shadow: () => j("GET", "/shadow"),
cmfStatus: () => j("GET", "/cmf"),
cmfInstall: () => j("POST", "/cmf/install"),
cmfPortCheck: (port) => j("GET", "/cmf/port?port=" + encodeURIComponent(port)),
cmfFiles: () => j("GET", "/cmf/files"),
providerModels: (b) => j("POST", "/provider/models", b),
hfSearch: (q, limit = 24) =>
j("GET", `/hf/search?q=${encodeURIComponent(q || "")}&limit=${limit}`),
startImport: (b) => j("POST", "/import", b),
listImports: () => j("GET", "/import"),
importStatus: (job) => j("GET", "/import/" + encodeURIComponent(job)),
cancelImport: (job) =>
j("POST", "/import/" + encodeURIComponent(job) + "/cancel"),
deleteImport: (job) => j("DELETE", "/import/" + encodeURIComponent(job)),
registerImport: (job) =>
j("POST", "/import/" + encodeURIComponent(job) + "/register"),
mediaModels: () => j("GET", "/media/models"),
mediaGenerate: (b) => j("POST", "/media", b),
mediaJobs: () => j("GET", "/media"),
mediaJob: (id) => j("GET", "/media/" + encodeURIComponent(id)),
mediaCancel: (id) => j("POST", "/media/" + encodeURIComponent(id) + "/cancel"),
mediaDelete: (id) => j("DELETE", "/media/" + encodeURIComponent(id)),
system: () => j("GET", "/system"),
logs: (limit = 300) => j("GET", "/logs?limit=" + limit),
clearLogs: () => j("DELETE", "/logs"),
restart: () => j("POST", "/restart"),
benchStart: (model) => j("POST", "/bench", { model }),
benchList: () => j("GET", "/bench"),
setupStatus: () => j("GET", "/setup/status"),
setupComplete: (lang, register) => j("POST", "/setup/complete", { lang, register }),
setupFeatured: () => j("GET", "/setup/featured"),
deleteSetupFile: (repo, variant, name) =>
j(
"DELETE",
`/setup/file?repo=${encodeURIComponent(repo)}` +
(variant ? `&variant=${encodeURIComponent(variant)}` : "") +
(name ? `&name=${encodeURIComponent(name)}` : "")
),
};
export async function postRaw(path, text) {
const tok = getToken();
const headers = { "content-type": "application/toml" };
if (tok) headers["Authorization"] = "Bearer " + tok;
const r = await fetch(BASE + path, { method: "POST", headers, body: text });
const body = await r.text();
if (!r.ok) {
let msg = body;
try { msg = JSON.parse(body).error?.message || body; } catch { }
throw new Error(msg || r.statusText);
}
return body ? JSON.parse(body) : null;
}
export async function testStream(body, onDelta) {
const tok = getToken();
const headers = { "content-type": "application/json" };
if (tok) headers["Authorization"] = "Bearer " + tok;
const r = await fetch(BASE + "/test/stream", {
method: "POST",
headers,
body: JSON.stringify(body),
});
if (r.status === 401) {
if (onUnauthorized) onUnauthorized();
throw new Error("unauthorized");
}
if (!r.ok || !r.body) {
throw new Error((await r.text()) || r.statusText);
}
const reader = r.body.getReader();
const dec = new TextDecoder();
let buf = "";
const t0 = performance.now();
let ttftMs = null; let usage = null; let usageEstimated = false; let costUsd = null; for (;;) {
const { done, value } = await reader.read();
if (done) break;
buf += dec.decode(value, { stream: true });
let idx;
while ((idx = buf.indexOf("\n\n")) >= 0) {
const line = buf.slice(0, idx).split("\n").find((l) => l.startsWith("data:"));
buf = buf.slice(idx + 2);
if (!line) continue;
const data = line.slice(5).trim();
if (!data || data === "[DONE]") continue;
try {
const chunk = JSON.parse(data);
if (chunk.usage) {
usage = chunk.usage;
usageEstimated = !!chunk.cortiq_estimated;
}
if (chunk.cortiq_cost_usd != null) costUsd = chunk.cortiq_cost_usd;
const d = chunk.choices?.[0]?.delta?.content;
if (d) {
if (ttftMs == null) ttftMs = Math.round(performance.now() - t0);
onDelta(d);
}
} catch {
}
}
}
const h = r.headers;
return {
task_label: h.get("x-cortiq-task-label") || "",
tier: h.get("x-cortiq-complexity-tier") || "",
score: parseFloat(h.get("x-cortiq-complexity-score") || "0"),
selected_model: h.get("x-cortiq-selected-model") || "",
route_source: h.get("x-cortiq-route-source") || "",
cost_usd: costUsd != null ? costUsd : parseFloat(h.get("x-cortiq-cost-usd") || "0"),
usage,
usage_estimated: usageEstimated,
ttft_ms: ttftMs,
};
}
export async function mediaFileUrl(job, kind) {
const tok = getToken();
const headers = {};
if (tok) headers["Authorization"] = "Bearer " + tok;
const r = await fetch(`${BASE}/media/${encodeURIComponent(job)}/file/${encodeURIComponent(kind)}`, { headers });
if (!r.ok) throw new Error((await r.text()) || r.statusText);
return { url: URL.createObjectURL(await r.blob()), size: +(r.headers.get("content-length") || 0) };
}
export async function mediaFileBytes(job, kind) {
const tok = getToken();
const headers = {};
if (tok) headers["Authorization"] = "Bearer " + tok;
const r = await fetch(`${BASE}/media/${encodeURIComponent(job)}/file/${encodeURIComponent(kind)}`, { headers });
if (!r.ok) throw new Error((await r.text()) || r.statusText);
return r.arrayBuffer();
}