// main.js — App-Boot: Panels mounten, Nav starten, Topbar/Alert pflegen, Polling fahren. // Panel-Vertrag: { id, mount?(), onStatus?(s), onJobs?(jobs) }. import { api, getToken, setToken } from "./core/api.js"; import { $ } from "./core/ui.js"; import { initNav } from "./core/nav.js"; import overview from "./panels/overview.js"; import models from "./panels/models.js"; import server from "./panels/server.js"; import jobs from "./panels/jobs.js"; import cookbook from "./panels/cookbook.js"; import guides from "./panels/guides.js"; const panels = [overview, models, server, jobs, cookbook, guides]; let lastJobs = []; let lastSystem = null; // ---- Topbar / Alert aus dem Status ableiten ---- function applyStatus(s) { const dot = $("#swdot"), label = $("#swlabel"), alert = $("#alert"); if (!s) { dot.className = "dot off"; label.textContent = "Backend nicht erreichbar"; $("#top-active-text").textContent = "Backend offline"; showAlert("Backend nicht erreichbar – läuft uvicorn?", false); } else { const host = s.swap_url.replace(/^https?:\/\//, ""); dot.className = "dot " + (s.swap_ok ? "on" : "off"); label.textContent = (s.swap_ok ? "LLM-Engine: Online – " : "LLM-Engine: Offline – ") + host; const active = (s.models || []).find(m => m.state === "running"); if (active) { $("#top-active-text").innerHTML = `Geladen: ${active.name}`; } else { $("#top-active-text").innerHTML = "Kein Modell im VRAM"; } if (s.swap_ok) hideAlert(); else showAlert(`LLM-Engine nicht erreichbar unter ${host} – läuft der llama-swap Dienst?`, true); } for (const p of panels) p.onStatus?.(s); } function applyJobs(jobs) { lastJobs = jobs || []; for (const p of panels) p.onJobs?.(lastJobs); } function applySystem(sys) { lastSystem = sys; for (const p of panels) p.onSystem?.(sys); } function showAlert(html, warn) { const a = $("#alert"); a.className = "alert" + (warn ? " warn" : ""); a.innerHTML = `${html}`; a.style.display = "flex"; } function hideAlert() { $("#alert").style.display = "none"; } // ---- Polling ---- async function pollStatus() { try { applyStatus(await api("/api/status")); } catch { applyStatus(null); } } async function pollJobs() { try { applyJobs(await api("/api/jobs")); } catch { /* still */ } } async function pollSystem() { try { applySystem(await api("/api/system/status")); } catch { /* still */ } } // ---- Boot ---- function bootToken() { const i = $("#token"); if (i) { i.value = getToken(); i.addEventListener("change", e => { setToken(e.target.value); pollStatus(); }); } const sbtn = $("#nav-settings"); const smod = $("#settings-modal"); const scls = $("#sm-close"); if (sbtn && smod && scls) { sbtn.addEventListener("click", () => smod.style.display = "flex"); scls.addEventListener("click", () => smod.style.display = "none"); } } function tickClock() { const c = $("#clock"); if (c) c.textContent = new Date().toTimeString().slice(0, 5); } for (const p of panels) p.mount?.(); initNav("overview"); bootToken(); tickClock(); document.addEventListener("mc:refresh", pollStatus); pollStatus(); pollJobs(); pollSystem(); setInterval(tickClock, 1000); setInterval(pollStatus, 3000); setInterval(pollJobs, 1500); setInterval(pollSystem, 2000);