diff --git a/static/js/panels/server.js b/static/js/panels/server.js index c0ee8c0..0cabf6b 100644 --- a/static/js/panels/server.js +++ b/static/js/panels/server.js @@ -2,7 +2,7 @@ // Selbsterklärende Aktionszeilen + heikle Aktionen mit Klartext-Bestätigung. import { api, getToken } from "../core/api.js"; -import { $, toast, icon, confirmModal, promptModal } from "../core/ui.js"; +import { $, esc, toast, icon, confirmModal, promptModal } from "../core/ui.js"; import { track } from "./jobs.js"; function refreshSoon() { document.dispatchEvent(new Event("mc:refresh")); } @@ -21,6 +21,19 @@ function mount() {
Dienste steuern, Updates einspielen und live mitlesen — ganz ohne SSH oder Terminal.
+
+

LLM-Engine (llama-swap)

+ prüfe…
+
Das Herzstück: llama-swap lädt bei jeder Anfrage automatisch das passende Modell und entlädt die anderen — es läuft immer nur eines gleichzeitig.
+
+
Adresse (für deine Tools)
+
Aktuell geladen
+
Konfigurierte Modelle
+
+
Wie ansteuern? In deinem Tool (Zed/OpenCode) die obige Adresse als OpenAI-Basis-URL eintragen und einen Modellnamen oder eine Rolle angeben (siehe „Verbinden"). Eine einzige Anfrage genügt — das Modell wird automatisch geweckt.
+
+
+

Dienste & Applikation

Diese Aktionen sind harmlos und passwortlos — es geht nichts dabei verloren.
@@ -71,9 +84,41 @@ function mount() { $("#w-reboot").addEventListener("click", rebootServer); $("#w-console-sel").addEventListener("change", connectConsole); $("#w-update-cancel").addEventListener("click", cancelActive); + $("#w-swap-check").addEventListener("click", checkSwap); + if (lastStatus) renderSwap(lastStatus); connectConsole(); } +// ---- llama-swap-Transparenz ---- +let lastStatus = null; +function renderSwap(s) { + const chip = $("#w-swap-state"); if (!chip) return; + const ok = !!s?.swap_ok; + chip.textContent = ok ? "● erreichbar" : "● offline"; + chip.style.cssText = "margin-left:auto;" + (ok + ? "background:rgba(63,185,80,.12);border-color:rgba(63,185,80,.25);color:#7ee29a" + : "background:rgba(248,81,73,.12);border-color:rgba(248,81,73,.3);color:#ff9b95"); + $("#w-swap-url").textContent = (s?.swap_url || "—") + "/v1"; + const models = (s?.models || []); + const loaded = models.find(m => m.state === "running" || m.state === "loading"); + $("#w-swap-loaded").innerHTML = loaded + ? `${esc(loaded.name)} (${esc(loaded.state)})` + : `nichts geladen — lädt bei der nächsten Anfrage`; + const real = models.filter(m => !m.incomplete).length; + $("#w-swap-count").textContent = real ? `${real} bereit` : "keine"; +} + +async function checkSwap() { + const res = $("#w-swap-check-res"); res.textContent = "prüfe…"; res.style.color = "var(--mut)"; + try { + const r = await api("/api/integration/test"); + if (r.ok) { res.style.color = "#7ee29a"; res.textContent = `✓ Engine antwortet — ${r.models.length} Modell(e): ${r.models.join(", ")}`; } + else { res.style.color = "#ff9b95"; res.textContent = "✗ keine Verbindung: " + r.error; } + } catch (e) { res.style.color = "#ff9b95"; res.textContent = "✗ " + e.message; } +} + +function onStatus(s) { lastStatus = s; renderSwap(s); } + async function cancelActive() { if (!activeJob) return; if (!await confirmModal({ title: activeJob.label + " abbrechen?", body: "Der laufende Vorgang wird gestoppt. Bei einem Download bleibt eine unvollständige Datei zurück (kannst du später neu starten).", confirmLabel: "Abbrechen", danger: true })) return; @@ -193,4 +238,4 @@ async function rebootServer() { catch (e) { toast(e.message, true); } } -export default { id: "server", mount, onJobs }; +export default { id: "server", mount, onJobs, onStatus };