feat: display LLM metrics and capabilities (Feature 6)
This commit is contained in:
+33
-1
@@ -16,6 +16,8 @@ from auth import auth
|
|||||||
from config import CMD_TEMPLATE, CONFIG_PATH, DEFAULT_TTL, LLAMA_SWAP_URL, MODELS_DIR
|
from config import CMD_TEMPLATE, CONFIG_PATH, DEFAULT_TTL, LLAMA_SWAP_URL, MODELS_DIR
|
||||||
from jobengine import JOBS, start_job
|
from jobengine import JOBS, start_job
|
||||||
from llamaswap import _swap_get, read_config, write_config
|
from llamaswap import _swap_get, read_config, write_config
|
||||||
|
import re
|
||||||
|
import os
|
||||||
|
|
||||||
router = APIRouter(prefix="/api", dependencies=[Depends(auth)])
|
router = APIRouter(prefix="/api", dependencies=[Depends(auth)])
|
||||||
|
|
||||||
@@ -50,12 +52,42 @@ def status():
|
|||||||
configured = {}
|
configured = {}
|
||||||
for name, spec in (cfg.get("models") or {}).items():
|
for name, spec in (cfg.get("models") or {}).items():
|
||||||
spec = spec or {}
|
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] = {
|
configured[name] = {
|
||||||
"name": name,
|
"name": name,
|
||||||
"ttl": spec.get("ttl", cfg.get("globalTTL", 0)),
|
"ttl": spec.get("ttl", cfg.get("globalTTL", 0)),
|
||||||
"cmd": str(spec.get("cmd", "")).strip(),
|
"cmd": cmd,
|
||||||
"state": "idle",
|
"state": "idle",
|
||||||
"port": None,
|
"port": None,
|
||||||
|
"meta": {
|
||||||
|
"ctx": ctx,
|
||||||
|
"size_bytes": size_bytes,
|
||||||
|
"quant": quant,
|
||||||
|
"caps": caps
|
||||||
|
}
|
||||||
}
|
}
|
||||||
swap_ok = True
|
swap_ok = True
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ function mount() {
|
|||||||
$("#m-table").innerHTML = `
|
$("#m-table").innerHTML = `
|
||||||
<div class="card-h"><h3>Modelle & Ports</h3><span class="meta" id="m-count"></span></div>
|
<div class="card-h"><h3>Modelle & Ports</h3><span class="meta" id="m-count"></span></div>
|
||||||
<table>
|
<table>
|
||||||
<thead><tr><th>Modell</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">Aktion</th></tr></thead>
|
||||||
<tbody id="models"></tbody>
|
<tbody id="models"></tbody>
|
||||||
</table>
|
</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>`;
|
||||||
@@ -59,7 +59,31 @@ function onStatus(s) {
|
|||||||
sel.innerHTML = "";
|
sel.innerHTML = "";
|
||||||
for (const m of models) {
|
for (const m of models) {
|
||||||
const tr = document.createElement("tr");
|
const tr = document.createElement("tr");
|
||||||
tr.innerHTML = `<td class="mid">${esc(m.name)}</td><td>${badge(m.state)}</td>
|
|
||||||
|
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 class="port">${m.port ?? "auto"}</td>
|
||||||
<td style="text-align:right"><button class="ghost" data-unload="${esc(m.name)}">Entladen</button></td>`;
|
<td style="text-align:right"><button class="ghost" data-unload="${esc(m.name)}">Entladen</button></td>`;
|
||||||
tb.appendChild(tr);
|
tb.appendChild(tr);
|
||||||
|
|||||||
@@ -99,10 +99,20 @@ function modelRow(m) {
|
|||||||
const on = RUNNING.has(m.state);
|
const on = RUNNING.has(m.state);
|
||||||
const dot = m.state === "loading" || m.state === "starting" ? "load" : on ? "on" : "";
|
const dot = m.state === "loading" || m.state === "starting" ? "load" : on ? "on" : "";
|
||||||
const state = on ? (m.state === "loading" ? "lädt…" : "geladen") : "bereit";
|
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 `<span title="Code" style="color:var(--blue);font-size:0.9em;margin-left:6px">{ }</span>`;
|
||||||
|
if (c === "Bild") return `<span title="Vision" style="color:var(--purple);font-size:0.9em;margin-left:6px">👁</span>`;
|
||||||
|
return "";
|
||||||
|
}).join("");
|
||||||
|
}
|
||||||
|
|
||||||
return `<div class="li">
|
return `<div class="li">
|
||||||
<span class="li-dot ${dot}"></span>
|
<span class="li-dot ${dot}"></span>
|
||||||
<div class="li-main">
|
<div class="li-main">
|
||||||
<div class="li-id">${esc(m.name)}</div>
|
<div class="li-id" style="font-weight:500">${esc(m.name)}${caps}</div>
|
||||||
<div class="li-sub">TTL ${m.ttl ?? "—"}${typeof m.ttl === "number" ? "s" : ""}</div>
|
<div class="li-sub">TTL ${m.ttl ?? "—"}${typeof m.ttl === "number" ? "s" : ""}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="li-right">
|
<div class="li-right">
|
||||||
|
|||||||
Reference in New Issue
Block a user