Files
mission-control/static/js/panels/models.js
T

108 lines
4.6 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.
// models.js — "Modelle"-Ansicht: Download + Einpflegen, Schnelltest-Chat, Modell-Tabelle.
import { api } from "../core/api.js";
import { $, badge, esc, toast, icon } from "../core/ui.js";
function refreshSoon() { document.dispatchEvent(new Event("mc:refresh")); }
function mount() {
$("#m-chat").innerHTML = `
<div class="card-h"><h3>Schnelltest</h3></div>
<label>Modell</label>
<select id="chat-model"></select>
<label>Nachricht</label>
<textarea id="chat-msg" placeholder="Schreib was, um ein Modell zu wecken…"></textarea>
<button class="primary" id="chat-btn">Senden</button>
<div id="chat-reply" class="reply" style="display:none"></div>`;
$("#m-table").innerHTML = `
<div class="card-h"><h3>Modelle &amp; Ports</h3><span class="meta" id="m-count"></span></div>
<div class="hint" style="margin-bottom: 16px;">
💡 <b>Modelle werden automatisch geladen</b>, sobald eine Chat-Anfrage (z.B. über OpenWebUI oder Cursor) an sie gestellt wird. Du musst sie hier nicht manuell starten.
</div>
<table>
<thead><tr><th>Modell</th><th>Fähigkeiten</th><th>Details</th><th>Status</th><th>Port</th><th style="text-align:right">Aktion</th></tr></thead>
<tbody id="models"></tbody>
</table>
<div id="models-empty" class="empty" style="display:none">Noch keine Modelle konfiguriert — zieh dir oben eins rein. 👇</div>`;
$("#chat-btn").addEventListener("click", sendChat);
}
function onStatus(s) {
const models = s?.models || [];
const tb = $("#models");
if (!tb) return;
tb.innerHTML = "";
$("#models-empty").style.display = models.length ? "none" : "block";
$("#m-count").textContent = models.length ? models.length + " konfiguriert" : "";
const sel = $("#chat-model");
const cur = sel.value;
sel.innerHTML = "";
for (const m of models) {
const tr = document.createElement("tr");
let capsHtml = "";
if (m.meta && m.meta.caps) {
capsHtml = m.meta.caps.map(c => {
if (c === "Text") return `<span class="meta" title="Text" style="display:inline-block;width:18px">${icon("search")}</span>`;
if (c === "Code") return `<strong style="color:var(--blue);display:inline-block;width:18px" title="Code">${icon("code")}</strong>`;
if (c === "Bild") return `<strong style="color:var(--purple);display:inline-block;width:18px" title="Vision">${icon("eye")}</strong>`;
return "";
}).join(" ");
}
let detailsHtml = "";
if (m.meta) {
const q = m.meta.quant || "?";
const c = m.meta.ctx ? (m.meta.ctx / 1024).toFixed(0) + "K" : "?";
const s = m.meta.size_bytes ? (m.meta.size_bytes / 1024 / 1024 / 1024).toFixed(1) + " GB" : "?";
detailsHtml = `<span class="meta" style="font-size:0.9em">${q} · ${c} · ${s}</span>`;
}
const perfHtml = m.state === "running" ? `<br><small class="meta" style="font-size:0.75em">n/a t/s</small>` : "";
const filenameHtml = m.meta?.filename ? `<br><small class="meta" style="font-size:0.8em">${esc(m.meta.filename)}</small>` : "";
tr.innerHTML = `<td class="mid" style="font-weight:500">${esc(m.name)}${filenameHtml}</td>
<td>${capsHtml}</td>
<td>${detailsHtml}</td>
<td>${badge(m.state)}${perfHtml}</td>
<td class="port">${m.port ?? "auto"}</td>
<td style="text-align:right"><button class="ghost" data-unload="${esc(m.name)}">Aus dem VRAM entfernen</button></td>`;
tb.appendChild(tr);
sel.insertAdjacentHTML("beforeend", `<option>${esc(m.name)}</option>`);
}
if (cur) sel.value = cur;
tb.querySelectorAll("[data-unload]").forEach(b =>
b.addEventListener("click", () => unloadOne(b.getAttribute("data-unload")))
);
}
async function unloadOne(m) {
try {
await api("/api/unload?model=" + encodeURIComponent(m), { method: "POST" });
toast("Entladen: " + m);
setTimeout(refreshSoon, 600);
} catch (e) { toast(e.message, true); }
}
async function sendChat() {
const model = $("#chat-model").value, message = $("#chat-msg").value.trim();
if (!model) return toast("Kein Modell vorhanden.", true);
if (!message) return;
const btn = $("#chat-btn"); btn.disabled = true; btn.textContent = "…";
const box = $("#chat-reply"); box.style.display = "block";
box.textContent = "(wecke Modell, kann beim Swap kurz dauern…)";
try {
const r = await api("/api/chat", { method: "POST", body: JSON.stringify({ model, message }) });
box.textContent = r.reply;
} catch (e) { box.textContent = "Fehler: " + e.message; }
btn.disabled = false; btn.textContent = "Senden";
refreshSoon();
}
export default { id: "models", mount, onStatus };