feat(server): llama-swap transparent machen
Neue Karte oben im Server-Tab beantwortet "funktioniert es / wie steuern / wie pruefen": Status (erreichbar/offline), aktuell geladenes Modell, Adresse fuer die Tools, Anzahl bereiter Modelle + Klartext, wie man ansteuert. Knopf "Verbindung pruefen" testet die Engine live (/api/integration/test). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -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() {
|
||||
<div class="sub">Dienste steuern, Updates einspielen und live mitlesen — ganz ohne SSH oder Terminal.</div>
|
||||
</div></div>
|
||||
|
||||
<div class="card" id="w-swap-card">
|
||||
<div class="card-h"><h3>LLM-Engine (llama-swap)</h3>
|
||||
<span class="chip" id="w-swap-state" style="margin-left:auto">prüfe…</span></div>
|
||||
<div class="card-sub">Das Herzstück: llama-swap lädt bei jeder Anfrage automatisch das passende Modell und entlädt die anderen — es läuft immer nur <b>eines</b> gleichzeitig.</div>
|
||||
<div class="kv" style="margin-bottom:6px">
|
||||
<div class="kv-row"><span class="kv-k">Adresse (für deine Tools)</span><span class="kv-v mono-sm" id="w-swap-url">—</span></div>
|
||||
<div class="kv-row"><span class="kv-k">Aktuell geladen</span><span class="kv-v" id="w-swap-loaded">—</span></div>
|
||||
<div class="kv-row"><span class="kv-k">Konfigurierte Modelle</span><span class="kv-v" id="w-swap-count">—</span></div>
|
||||
</div>
|
||||
<div class="hint" style="margin:8px 0 12px"><b>Wie ansteuern?</b> In deinem Tool (Zed/OpenCode) die obige Adresse als OpenAI-Basis-URL eintragen und einen <b>Modellnamen oder eine Rolle</b> angeben (siehe „Verbinden"). Eine einzige Anfrage genügt — das Modell wird automatisch geweckt.</div>
|
||||
<div class="btn-row" style="display:flex;align-items:center;gap:10px"><button class="ghost" id="w-swap-check">Verbindung prüfen</button><span id="w-swap-check-res" class="mono-sm"></span></div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-h"><h3>Dienste & Applikation</h3></div>
|
||||
<div class="card-sub">Diese Aktionen sind harmlos und passwortlos — es geht nichts dabei verloren.</div>
|
||||
@@ -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
|
||||
? `<b>${esc(loaded.name)}</b> <span class="text-mut">(${esc(loaded.state)})</span>`
|
||||
: `<span class="text-mut">nichts geladen — lädt bei der nächsten Anfrage</span>`;
|
||||
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 };
|
||||
|
||||
Reference in New Issue
Block a user