fix(phase-a): Kontext-Cap, Download-State, Recipe-Edit, OS-Badge, Swap-Flash
A1: 32768-Kontext-Cap entfernt (install-recipe, install-model, register) →
max_ctx_for() liefert nun bis zu 128k auf Strix Halo; behebt "context
size exceeded" bei externen Tools.
A2: Download-State jetzt im Status-Endpoint sichtbar: Modelle zeigen
"↓ Download X%" statt "bereit" während Job läuft (Backend + Frontend).
A3: PUT /api/cookbook/user-recipe/{id} + Edit-Button (✎) für eigene Setups.
Download-Modal setzt Kontext-Input automatisch auf optimal.
A4: /api/updates liefert apt_cache_age_h; Badge zeigt Tooltip + ⚠ wenn >24h.
A5: Swap-Flash: Topbar-Text pulst kurz teal wenn Modell den State wechselt.
A6: LLM-Engine-Update fragt jetzt per confirmModal nach (Konsistenz).
A7: Event-Delegation statt per-render addEventListener in models.js.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+35
-4
@@ -18,6 +18,7 @@ const panels = [overview, models, server, jobs, cookbook, connect, news, guides]
|
||||
|
||||
let lastJobs = [];
|
||||
let lastSystem = null;
|
||||
let prevModelStates = {};
|
||||
|
||||
// ---- Topbar / Alert aus dem Status ableiten ----
|
||||
function applyStatus(s) {
|
||||
@@ -76,26 +77,56 @@ function hideAlert() { $("#alert").style.display = "none"; }
|
||||
|
||||
// ---- Toolbar: ausstehende Updates anzeigen ----
|
||||
const goView = v => document.querySelector(`.nav-item[data-view="${v}"]`)?.click();
|
||||
function badge(text, cls, view) {
|
||||
function badge(text, cls, view, title) {
|
||||
const s = document.createElement("span");
|
||||
s.className = "upd-badge" + (cls ? " " + cls : "");
|
||||
s.textContent = text; s.onclick = () => goView(view);
|
||||
s.textContent = text;
|
||||
if (title) s.title = title;
|
||||
s.onclick = () => goView(view);
|
||||
return s;
|
||||
}
|
||||
async function pollUpdates() {
|
||||
try {
|
||||
const u = await api("/api/updates");
|
||||
const el = $("#update-badges"); if (!el) return;
|
||||
const age = u.apt_cache_age_h;
|
||||
const stale = age != null && age > 24;
|
||||
const ageNote = age != null ? ` · apt-Cache: vor ${age}h` : "";
|
||||
el.innerHTML = "";
|
||||
el.appendChild(badge(u.os > 0 ? `OS: ${u.os} Updates` : "OS: aktuell", u.os > 0 ? "warn" : "ok", "server"));
|
||||
el.appendChild(badge(
|
||||
u.os > 0 ? `OS: ${u.os} Updates` : (stale ? "OS: ?" : "OS: aktuell"),
|
||||
u.os > 0 ? "warn" : stale ? "warn" : "ok",
|
||||
"server",
|
||||
u.os > 0 ? `${u.os} ausstehende Pakete${ageNote}` : (stale ? `apt-Cache veraltet (${age}h) — OS-Update prüfen` : `Keine ausstehenden Pakete${ageNote}`)
|
||||
));
|
||||
el.appendChild(badge(u.engine > 0 ? "Engine: Update" : "Engine: aktuell", u.engine > 0 ? "warn" : "ok", "server"));
|
||||
el.appendChild(badge(u.models > 0 ? `Modelle: ${u.models}` : "Modelle: aktuell", u.models > 0 ? "warn" : "ok", "news"));
|
||||
} catch { /* still */ }
|
||||
}
|
||||
|
||||
// ---- Swap-Erkennung: kurze visuelle Hervorhebung wenn Modell wechselt ----
|
||||
function flashSwap() {
|
||||
const el = $("#top-active-text");
|
||||
if (el) { el.classList.add("swap-flash"); setTimeout(() => el.classList.remove("swap-flash"), 900); }
|
||||
}
|
||||
|
||||
// ---- Polling ----
|
||||
async function pollStatus() {
|
||||
try { applyStatus(await api("/api/status")); }
|
||||
try {
|
||||
const s = await api("/api/status");
|
||||
// Zustandswechsel erkennen → Swap-Flash auslösen
|
||||
const newStates = {};
|
||||
for (const m of (s?.models || [])) newStates[m.name] = m.state;
|
||||
for (const [name, state] of Object.entries(newStates)) {
|
||||
const prev = prevModelStates[name];
|
||||
if (prev && prev !== state && (state === "running" || state === "loading")) {
|
||||
flashSwap();
|
||||
break;
|
||||
}
|
||||
}
|
||||
prevModelStates = newStates;
|
||||
applyStatus(s);
|
||||
}
|
||||
catch { applyStatus(null); }
|
||||
}
|
||||
async function pollJobs() {
|
||||
|
||||
Reference in New Issue
Block a user