This repository has been archived on 2026-07-22. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
mission-control/static/js/main.js
T
Hitonabi 3a4de152b0 v6: Toolbar zeigt AI-Engine-Update-Status + immer-sichtbare Badges
- /api/updates liefert zusaetzlich "engine" (llama.cpp-ROCm: neueste
  GitHub-Release vs. /opt/llamacpp-mtime, 1h gecacht)
- Toolbar-Badges fuer OS/Engine/Modelle jetzt auch im "aktuell"-Zustand
  sichtbar (gruener .ok-Style) statt nur bei ausstehenden Updates
- Guide: neuer Tutorial-Eintrag "Was passiert, wenn der Kontext volllaeuft?"

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 17:40:56 +02:00

165 lines
5.2 KiB
JavaScript
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.
// 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, getHfToken, setHfToken } 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 connect from "./panels/connect.js";
import news from "./panels/news.js";
import guides from "./panels/guides.js";
const panels = [overview, models, server, jobs, cookbook, connect, news, 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: <b style="color:var(--teal)">${active.name}</b>`;
} else {
$("#top-active-text").innerHTML = "Kein Modell im VRAM";
}
if (s.swap_ok) hideAlert();
else showAlert(`LLM-Engine nicht erreichbar unter <b>${host}</b> läuft der llama-swap Dienst?`, true);
}
setSecChip(s);
for (const p of panels) p.onStatus?.(s);
}
// Security-Chip: ehrlich anzeigen, ob ein Zugangs-Token aktiv ist.
function setSecChip(s) {
const chip = $("#sec-chip"), tx = $("#sec-chip-tx");
if (!chip || !tx) return;
const secured = !!s?.secured;
chip.classList.toggle("ok", secured);
tx.textContent = secured ? "Geschützt · Heimnetz" : "Nur im Heimnetz (offen)";
}
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 = `<span class="a-dot"></span><span>${html}</span>`;
a.style.display = "flex";
}
function hideAlert() { $("#alert").style.display = "none"; }
// ---- Toolbar: ausstehende Updates anzeigen ----
const goView = v => document.querySelector(`.nav-item[data-view="${v}"]`)?.click();
function badge(text, cls, view) {
const s = document.createElement("span");
s.className = "upd-badge" + (cls ? " " + cls : "");
s.textContent = text; s.onclick = () => goView(view);
return s;
}
async function pollUpdates() {
try {
const u = await api("/api/updates");
const el = $("#update-badges"); if (!el) return;
el.innerHTML = "";
el.appendChild(badge(u.os > 0 ? `OS: ${u.os} Updates` : "OS: aktuell", u.os > 0 ? "warn" : "ok", "server"));
el.appendChild(badge(u.engine > 0 ? "Engine: Update" : "Engine: aktuell", u.engine > 0 ? "warn" : "ok", "server"));
el.appendChild(badge(u.models > 0 ? `Modelle: ${u.models}` : "Modelle: aktuell", u.models > 0 ? "warn" : "ok", "news"));
} catch { /* still */ }
}
// ---- Polling ----
async function pollStatus() {
try { applyStatus(await api("/api/status")); }
catch { applyStatus(null); }
}
async function pollJobs() {
try { applyJobs(await api("/api/jobs")); }
catch { /* still */ }
}
function connectSystemStream() {
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
const wsUrl = `${protocol}//${window.location.host}/api/system/stream`;
const ws = new WebSocket(wsUrl);
ws.onmessage = (e) => {
try {
const data = JSON.parse(e.data);
applySystem(data);
} catch (err) {}
};
ws.onclose = () => {
// Bei Verbindungsabbruch nach 3 Sekunden erneut versuchen
setTimeout(connectSystemStream, 3000);
};
ws.onerror = () => ws.close();
}
// ---- Boot ----
function bootToken() {
const i = $("#token");
if (i) {
i.value = getToken();
i.addEventListener("change", e => { setToken(e.target.value); pollStatus(); });
}
const hf = $("#hf-token");
if (hf) { hf.value = getHfToken(); hf.addEventListener("change", e => setHfToken(e.target.value)); }
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();
connectSystemStream();
setInterval(tickClock, 1000);
setInterval(pollStatus, 3000);
setInterval(pollJobs, 1500);
pollUpdates();
setInterval(pollUpdates, 60000);