103 lines
4.0 KiB
JavaScript
103 lines
4.0 KiB
JavaScript
// models.js — "Modelle"-Ansicht: Download + Einpflegen, Schnelltest-Chat, Modell-Tabelle.
|
||
|
||
import { api } from "../core/api.js";
|
||
import { $, badge, esc, toast } 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 & Ports</h3><span class="meta" id="m-count"></span></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">T</span>`;
|
||
if (c === "Code") return `<strong style="color:var(--blue)" title="Code">{ }</strong>`;
|
||
if (c === "Bild") return `<strong style="color:var(--purple)" title="Vision">👁</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>` : "";
|
||
|
||
tr.innerHTML = `<td class="mid" style="font-weight:500">${esc(m.name)}</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)}">Entladen</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 };
|