diff --git a/routers/models.py b/routers/models.py index 8bbd632..d198ebf 100644 --- a/routers/models.py +++ b/routers/models.py @@ -16,6 +16,8 @@ from auth import auth from config import CMD_TEMPLATE, CONFIG_PATH, DEFAULT_TTL, LLAMA_SWAP_URL, MODELS_DIR from jobengine import JOBS, start_job from llamaswap import _swap_get, read_config, write_config +import re +import os router = APIRouter(prefix="/api", dependencies=[Depends(auth)]) @@ -50,12 +52,42 @@ def status(): configured = {} for name, spec in (cfg.get("models") or {}).items(): spec = spec or {} + cmd = str(spec.get("cmd", "")).strip() + + # Parse Meta + ctx = 8192 + m_ctx = re.search(r'-(?:c|-ctx-size)\s+(\d+)', cmd) + if m_ctx: ctx = int(m_ctx.group(1)) + + size_bytes = None + quant = "" + m_path = re.search(r'-(?:m|-model)\s+([^\s]+)', cmd) + if m_path: + path = m_path.group(1).replace("'", "").replace('"', '') + if os.path.exists(path): + size_bytes = os.path.getsize(path) + q_match = re.search(r'(Q\d_[A-Z0-9_]+|IQ\d_[A-Z0-9_]+|fp16|bf16)\.gguf', path, flags=re.IGNORECASE) + if q_match: + quant = q_match.group(1).upper() + + caps = ["Text"] + if "coder" in name.lower() or (m_path and "code" in m_path.group(1).lower()): + caps = ["Code"] + if "--mmproj" in cmd: + caps.append("Bild") + configured[name] = { "name": name, "ttl": spec.get("ttl", cfg.get("globalTTL", 0)), - "cmd": str(spec.get("cmd", "")).strip(), + "cmd": cmd, "state": "idle", "port": None, + "meta": { + "ctx": ctx, + "size_bytes": size_bytes, + "quant": quant, + "caps": caps + } } swap_ok = True try: diff --git a/static/js/panels/models.js b/static/js/panels/models.js index 09c55e8..7cb276e 100644 --- a/static/js/panels/models.js +++ b/static/js/panels/models.js @@ -36,7 +36,7 @@ function mount() { $("#m-table").innerHTML = `

Modelle & Ports

- +
ModellStatusPortAktion
ModellFähigkeitenDetailsStatusPortAktion
`; @@ -59,7 +59,31 @@ function onStatus(s) { sel.innerHTML = ""; for (const m of models) { const tr = document.createElement("tr"); - tr.innerHTML = `${esc(m.name)}${badge(m.state)} + + let capsHtml = "–"; + if (m.meta && m.meta.caps) { + capsHtml = m.meta.caps.map(c => { + if (c === "Text") return `T`; + 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` : ""; + + tr.innerHTML = `${esc(m.name)} + ${capsHtml} + ${detailsHtml} + ${badge(m.state)}${perfHtml} ${m.port ?? "auto"} `; tb.appendChild(tr); diff --git a/static/js/panels/overview.js b/static/js/panels/overview.js index 5fdf88c..25c0040 100644 --- a/static/js/panels/overview.js +++ b/static/js/panels/overview.js @@ -99,10 +99,20 @@ function modelRow(m) { const on = RUNNING.has(m.state); const dot = m.state === "loading" || m.state === "starting" ? "load" : on ? "on" : ""; const state = on ? (m.state === "loading" ? "lädt…" : "geladen") : "bereit"; + + let caps = ""; + if (m.meta && m.meta.caps) { + caps = m.meta.caps.map(c => { + if (c === "Code") return `{ }`; + if (c === "Bild") return `👁`; + return ""; + }).join(""); + } + return `
-
${esc(m.name)}
+
${esc(m.name)}${caps}
TTL ${m.ttl ?? "—"}${typeof m.ttl === "number" ? "s" : ""}