Feinschliff Phase 2: Dashboard Redesign, RAM Check, Accordions
This commit is contained in:
+66
-13
@@ -1,8 +1,10 @@
|
||||
// models.js — "Modelle"-Ansicht: Download + Einpflegen, Schnelltest-Chat, Modell-Tabelle.
|
||||
// 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() {
|
||||
@@ -13,43 +15,63 @@ function mount() {
|
||||
<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>
|
||||
<button class="primary" id="chat-btn" title="Sendet eine Chat-Anfrage an das Modell (weckt es auf, falls es schläft)">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>
|
||||
<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.
|
||||
💡 <b>Modelle werden automatisch geladen</b>, sobald eine Chat-Anfrage an sie gestellt wird. Du musst sie 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>
|
||||
<thead><tr><th>Modell</th><th>Fähigkeiten</th><th>Details</th><th>Status</th><th>Port</th><th style="text-align:right">Aktionen</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>`;
|
||||
<div id="models-empty" class="empty" style="display:none">Noch keine Modelle konfiguriert — zieh dir oben eins rein. 👇</div>
|
||||
|
||||
<!-- Config Modal -->
|
||||
<div id="cfg-modal" style="display:none; position:fixed; top:0; left:0; right:0; bottom:0; background:rgba(0,0,0,0.8); z-index:100; align-items:center; justify-content:center;">
|
||||
<div class="card" style="width:100%; max-width:400px; position:relative">
|
||||
<button id="cfg-close" class="ghost" style="position:absolute; top:12px; right:12px;">Schließen</button>
|
||||
<h2 style="margin-top:0">Modell konfigurieren</h2>
|
||||
<p class="meta" id="cfg-model-name"></p>
|
||||
|
||||
<div style="margin-top:24px">
|
||||
<label>Context-Size (Tokens)</label>
|
||||
<input id="cfg-ctx" type="number" class="tokin" value="8192">
|
||||
<div class="meta" style="font-size:12px; margin-top:4px;">Höhere Werte erlauben längere Dokumente, brauchen aber mehr VRAM.</div>
|
||||
</div>
|
||||
|
||||
<button class="primary" id="cfg-save" style="width:100%; margin-top:24px; padding:12px">Speichern</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
$("#chat-btn").addEventListener("click", sendChat);
|
||||
$("#cfg-close").addEventListener("click", () => $("#cfg-modal").style.display = "none");
|
||||
$("#cfg-save").addEventListener("click", saveConfig);
|
||||
}
|
||||
|
||||
function onStatus(s) {
|
||||
const models = s?.models || [];
|
||||
ALL_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" : "";
|
||||
$("#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 models) {
|
||||
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 `<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>`;
|
||||
if (c === "Text") return `<span class="meta" title="Text" style="display:inline-block;width:18px">${icon("compass")}</span>`;
|
||||
if (c === "Code") return `<strong style="color:var(--blue);display:inline-block;width:18px" title="Code">{ }</strong>`;
|
||||
if (c === "Bild") return `<strong style="color:var(--purple);display:inline-block;width:18px" title="Vision">👁</strong>`;
|
||||
return "";
|
||||
}).join(" ");
|
||||
}
|
||||
@@ -71,14 +93,45 @@ function onStatus(s) {
|
||||
<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>`;
|
||||
<td style="text-align:right">
|
||||
<button class="ghost" data-cfg="${esc(m.name)}" title="Context-Size anpassen">Konfigurieren</button>
|
||||
<button class="ghost" data-unload="${esc(m.name)}" title="Beendet das Modell sofort und gibt den VRAM wieder frei">Aus 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")))
|
||||
);
|
||||
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) {
|
||||
|
||||
Reference in New Issue
Block a user