// 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.
| Modell | Fähigkeiten | Details | Status | Port | Aktionen |
Noch keine Modelle konfiguriert — zieh dir oben eins rein. 👇
Modell konfigurieren
`;
$("#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 };