This repository has been archived on 2026-07-22. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
mission-control/static/js/panels/models.js
T
Hitonabi 654720ed05 v4 Schritt 1: optimale Kontextfenster automatisch ermitteln
- hw_math: max_ctx_for() (Umkehrung der ctx-Heuristik) + extract_params_b + recommend_ctx.
- cookbook.py: optimal_ctx pro Datei in analyze + evaluate.
- models.py status: params_b + optimal_ctx pro Modell (psutil-RAM).
- models.js Konfig-Modal: Empfehlung + 'Optimal uebernehmen'.
- cookbook.js Modal: 'Empfohlener Kontext ... uebernehmen'.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 13:13:30 +02:00

141 lines
7.0 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 (v3): Schnelltest-Chat, Modell-Tabelle, Kontext-Konfiguration.
import { api } from "../core/api.js";
import { $, badge, esc, toast, confirmModal } from "../core/ui.js";
let ALL = [];
function refreshSoon() { document.dispatchEvent(new Event("mc:refresh")); }
function capTags(caps) {
if (!caps?.length) return "";
return caps.map(c =>
c === "Code" ? `<span class="tag code">Code</span>`
: c === "Bild" ? `<span class="tag img">Bild</span>`
: `<span class="tag text">Text</span>`).join(" ");
}
function details(meta) {
if (!meta) return "";
const q = meta.quant || "?";
const c = meta.ctx ? Math.round(meta.ctx / 1024) + "K" : "?";
const s = meta.size_bytes ? (meta.size_bytes / 1024 ** 3).toFixed(1) + " GB" : "?";
return `<span class="mono-sm">${esc(q)} · ${c} · ${s}</span>`;
}
function mount() {
$("#m-head").innerHTML = `<div class="pagehead"><div>
<h1>Modelle</h1>
<div class="sub">Deine konfigurierten Modelle — testen, Kontext anpassen oder aus dem Speicher werfen.</div></div></div>`;
$("#m-chat").innerHTML = `
<div class="card-h"><h3>Schnelltest</h3></div>
<div class="card-sub">Schreib eine Nachricht — das gewählte Modell wird automatisch geweckt.</div>
<label>Modell</label>
<select id="chat-model"></select>
<label>Nachricht</label>
<textarea id="chat-msg" placeholder="z.B. „Erklär mir kurz, was du kannst."></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="card-sub">Modelle laden automatisch, sobald eine Anfrage kommt — du musst nichts manuell starten.</div>
<table>
<thead><tr><th>Modell</th><th>Kann</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-c" style="display:none">
<div class="e-t">Noch keine Modelle konfiguriert</div>
<div class="e-s">Hol dir unter „Cookbook" ein passendes Modell.</div>
</div>
<div id="cfg-modal" class="modal-overlay" style="display:none">
<div class="modal-card">
<button id="cfg-close" class="ghost" style="position:absolute;top:14px;right:14px">Schließen</button>
<h3>Modell konfigurieren</h3>
<p class="mono-sm" id="cfg-model-name" style="margin:-4px 0 16px"></p>
<label>Kontext-Größe (Tokens)</label>
<input id="cfg-ctx" type="number" value="8192">
<div class="hint">Höhere Werte erlauben längere Texte, brauchen aber mehr Grafikspeicher.</div>
<div class="tile" id="cfg-rec" style="display:none;margin:0 0 14px;justify-content:space-between;align-items:center" >
<div style="flex:1"><div style="font-size:13px">Empfohlen für deine Hardware</div>
<div class="hint" id="cfg-rec-note" style="margin:4px 0 0"></div></div>
<button class="ghost" id="cfg-rec-apply">Optimal übernehmen</button>
</div>
<button class="primary" id="cfg-save" style="width:100%;margin-top:6px">Speichern</button>
</div>
</div>`;
$("#chat-btn").addEventListener("click", sendChat);
$("#cfg-close").addEventListener("click", () => $("#cfg-modal").style.display = "none");
$("#cfg-modal").addEventListener("click", e => { if (e.target.id === "cfg-modal") $("#cfg-modal").style.display = "none"; });
$("#cfg-save").addEventListener("click", saveConfig);
}
function onStatus(s) {
ALL = s?.models || [];
const tb = $("#models"); if (!tb) return;
$("#models-empty").style.display = ALL.length ? "none" : "flex";
$("#m-count").textContent = ALL.length ? ALL.length + " konfiguriert" : "";
const sel = $("#chat-model"), cur = sel.value; sel.innerHTML = "";
tb.innerHTML = ALL.map(m => {
const fn = m.meta?.filename ? `<div class="li-sub mono-sm">${esc(m.meta.filename)}</div>` : "";
return `<tr>
<td class="mid" style="font-weight:500">${esc(m.name)}${fn}</td>
<td>${capTags(m.meta?.caps)}</td>
<td>${details(m.meta)}</td>
<td>${badge(m.state)}</td>
<td class="port">${m.port ?? "auto"}</td>
<td style="text-align:right;white-space:nowrap">
<button class="ghost" data-cfg="${esc(m.name)}">Konfigurieren</button>
<button class="ghost" data-unload="${esc(m.name)}">Entladen</button>
</td></tr>`;
}).join("");
for (const m of ALL) 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.find(x => x.name === alias); if (!m) return;
$("#cfg-model-name").textContent = m.name;
$("#cfg-ctx").value = m.meta?.ctx || 8192;
const opt = m.meta?.optimal_ctx, rec = $("#cfg-rec");
if (opt) {
const cur = m.meta?.ctx || 0;
$("#cfg-rec-note").textContent =
`Bis ~${Math.round(opt / 1024)}k passt komfortabel. Aktuell: ~${Math.round(cur / 1024)}k.`;
$("#cfg-rec-apply").onclick = () => { $("#cfg-ctx").value = opt; };
rec.style.display = "flex";
} else { rec.style.display = "none"; }
$("#cfg-modal").style.display = "flex";
}
async function saveConfig() {
const alias = $("#cfg-model-name").textContent, 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 — aktiv beim nächsten Modell-Start."); $("#cfg-modal").style.display = "none"; refreshSoon(); }
catch (e) { toast(e.message, true); }
$("#cfg-save").disabled = false;
}
async function unloadOne(m) {
if (!await confirmModal({ title: `${m}" entladen?`, body: "Das Modell wird aus dem Grafikspeicher geworfen und lädt beim nächsten Aufruf automatisch neu.", confirmLabel: "Entladen" })) return;
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 Laden 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 };