// models.js — "Modelle"-Ansicht: Schnelltest-Chat, Modell-Tabelle & Konfiguration. import { api } from "../core/api.js"; import { $, badge, esc, toast, icon } from "../core/ui.js"; let ALL_MODELS = []; function refreshSoon() { document.dispatchEvent(new Event("mc:refresh")); } function mount() { $("#m-chat").innerHTML = `

Schnelltest

`; $("#m-table").innerHTML = `

Modelle & Ports

💡 Modelle werden automatisch geladen, sobald eine Chat-Anfrage an sie gestellt wird. Du musst sie nicht manuell starten.
ModellFähigkeitenDetailsStatusPortAktionen
`; $("#chat-btn").addEventListener("click", sendChat); $("#cfg-close").addEventListener("click", () => $("#cfg-modal").style.display = "none"); $("#cfg-save").addEventListener("click", saveConfig); } function onStatus(s) { ALL_MODELS = s?.models || []; const tb = $("#models"); if (!tb) return; tb.innerHTML = ""; $("#models-empty").style.display = ALL_MODELS.length ? "none" : "block"; $("#m-count").textContent = ALL_MODELS.length ? ALL_MODELS.length + " konfiguriert" : ""; const sel = $("#chat-model"); const cur = sel.value; sel.innerHTML = ""; for (const m of ALL_MODELS) { const tr = document.createElement("tr"); let capsHtml = "–"; if (m.meta && m.meta.caps) { capsHtml = m.meta.caps.map(c => { if (c === "Text") return `${icon("compass")}`; if (c === "Code") return `{ }`; if (c === "Bild") return `👁`; 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 = `${q} · ${c} · ${s}`; } const perfHtml = m.state === "running" ? `
n/a t/s` : ""; const filenameHtml = m.meta?.filename ? `
${esc(m.meta.filename)}` : ""; tr.innerHTML = `${esc(m.name)}${filenameHtml} ${capsHtml} ${detailsHtml} ${badge(m.state)}${perfHtml} ${m.port ?? "auto"} `; tb.appendChild(tr); sel.insertAdjacentHTML("beforeend", ``); } if (cur) sel.value = cur; tb.querySelectorAll("[data-unload]").forEach(b => b.addEventListener("click", () => unloadOne(b.getAttribute("data-unload"))) ); tb.querySelectorAll("[data-cfg]").forEach(b => b.addEventListener("click", () => openConfig(b.getAttribute("data-cfg"))) ); } function openConfig(alias) { const m = ALL_MODELS.find(x => x.name === alias); if (!m) return; $("#cfg-model-name").textContent = m.name; $("#cfg-ctx").value = m.meta?.ctx || 8192; $("#cfg-modal").style.display = "flex"; } async function saveConfig() { const alias = $("#cfg-model-name").textContent; const ctx = parseInt($("#cfg-ctx").value) || 8192; $("#cfg-save").disabled = true; try { await api("/api/update_model", { method: "POST", body: JSON.stringify({ alias, ctx }) }); toast("Gespeichert! Änderungen werden beim nächsten Modell-Start aktiv."); $("#cfg-modal").style.display = "none"; refreshSoon(); } catch (e) { toast(e.message, true); } $("#cfg-save").disabled = false; } 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 };