// overview.js — Dashboard-Kopf: Hero + Mini-Stats, KPI-Reihe, Stack-Gesundheit, // kompakte Modell-Liste ("Session Router"). Speist sich aus /api/status + /api/jobs. import { $, icon, esc } from "../core/ui.js"; let S = null; // letzter Status let J = []; // letzte Job-Liste let SYS = null; // letzte System-Auslastung const RUNNING = new Set(["running", "ready", "loading", "starting"]); function counts() { const models = S?.models || []; return { total: models.length, running: models.filter(m => RUNNING.has(m.state)).length, jobsRun: J.filter(j => j.state === "running" || j.state === "queued").length, jobsErr: J.filter(j => j.state === "failed").length, swap: !!S?.swap_ok, }; } function mini(label, val, tone = "") { const v = tone ? `${val}` : val; return `
${label}
${v}
`; } function renderHero() { const c = counts(); $("#hero").innerHTML = `
Übersicht

Mission Control

Steuerzentrale für deinen lokalen llama-swap-Stack — Modelle, Downloads, Wartung und Schnelltest an einem Ort.

${mini("Modelle", c.total)} ${mini("Aktiv", c.running, "on")} ${mini("Jobs", c.jobsRun)} ${mini("Fehler", c.jobsErr, c.jobsErr ? "bad" : "")}
`; } function kpi(cls, title, ic, value, sub) { return `
${title}${icon(ic)}
${value}
${sub}
`; } function renderKpis() { const c = counts(); const sysV = SYS ? `${SYS.cpu.percent.toFixed(0)}% CPU` : "n/a"; const sysS = SYS ? `${SYS.ram.percent.toFixed(0)}% RAM, ${SYS.gpu_temp ? SYS.gpu_temp.toFixed(0)+'°C' : SYS.cpu.temp ? SYS.cpu.temp.toFixed(0)+'°C' : '–'}` : "bald · Live-Auslastung"; $("#kpis").innerHTML = kpi(c.swap ? "green" : "red", "llama-swap", "swap", c.swap ? "Online" : "Offline", "Transport-Status") + kpi("blue", "Modelle", "monitor", `${c.running}/${c.total}`, "aktiv / gesamt") + kpi("purple", "Jobs", "layers", c.jobsRun, "laufend") + kpi(c.jobsErr ? "red" : "muted", "Fehler", "alert", c.jobsErr, "in der Aktivität") + kpi(SYS ? "blue" : "muted", "System-Last", "gauge", sysV, sysS); } function kvRow(k, v, cls = "") { return `
${k}${v}
`; } function renderHealth() { const c = counts(); let sysRow = kvRow("Auslastung (RAM/GPU/Disk)", "folgt", "na"); if (SYS) { const gb = b => (b / 1024 / 1024 / 1024).toFixed(1); const ram = `${gb(SYS.ram.used)}GB / ${gb(SYS.ram.total)}GB`; const gpu = (SYS.gpu && SYS.gpu.vram.total) ? `${gb(SYS.gpu.vram.used + SYS.gpu.gtt.used)}GB / ${gb(SYS.gpu.vram.total + SYS.gpu.gtt.total)}GB` : "–"; const disk = `${SYS.disk.percent.toFixed(0)}%`; sysRow = kvRow("Auslastung (RAM/GPU/Disk)", `${ram} | ${gpu} | ${disk}`); } $("#health").innerHTML = `

Stack-Gesundheit

${c.swap ? "Connected" : "Offline"}
${kvRow("llama-swap", c.swap ? "Connected" : "Offline", c.swap ? "ok" : "bad")} ${kvRow("Modelle (gesamt)", c.total)} ${kvRow("Aktiv", c.running, c.running ? "ok" : "")} ${kvRow("Jobs (laufend)", c.jobsRun)} ${kvRow("Fehler", c.jobsErr, c.jobsErr ? "bad" : "")} ${sysRow}
`; } function modelRow(m) { const on = RUNNING.has(m.state); const dot = m.state === "loading" || m.state === "starting" ? "load" : on ? "on" : ""; const state = on ? (m.state === "loading" ? "lädt…" : "geladen") : "bereit"; let caps = ""; if (m.meta && m.meta.caps) { caps = m.meta.caps.map(c => { if (c === "Code") return `{ }`; if (c === "Bild") return `👁`; return ""; }).join(""); } return `
${esc(m.name)}${caps}
TTL ${m.ttl ?? "—"}${typeof m.ttl === "number" ? "s" : ""}
${m.port ?? "auto"}
${state}
`; } function renderModels() { const models = S?.models || []; $("#ov-models").innerHTML = `

Modelle

${models.length || ""}
${models.length ? `
${models.map(modelRow).join("")}
` : `
Keine Modelle konfiguriert
Hol dir unter „Modelle" eins von HuggingFace.
`}`; } function renderAll() { renderHero(); renderKpis(); renderHealth(); renderModels(); } function mount() { renderAll(); } function onStatus(s) { S = s; renderAll(); } function onJobs(jobs) { J = jobs || []; renderHero(); renderKpis(); renderHealth(); } function onSystem(sys) { SYS = sys; renderKpis(); renderHealth(); } export default { id: "overview", mount, onStatus, onJobs, onSystem };