Kontext in Connect-Snippet: - ConnectPanel: max_tokens nutzt jetzt den echten konfigurierten Kontext des Modells (m.meta.ctx) statt hartkodiertem 32768 -> Zed sieht 128k Phase D - 'Immer aktiv halten' Toggle: - models.py: UpdateReq bekommt optionales ttl-Feld - update_model: setzt TTL wenn angegeben (ctx und ttl unabhaengig) - ModelsPanel: Checkbox 'Immer aktiv halten' im Konfig-Modal (TTL=99999 = nie entladen, TTL=300 = Standard 5min) MoE tps-Schaetzung: - hw_math.py: extract_active_params_b() erkennt A3B-Suffix - estimate_speed(): moe_active_ratio-Parameter, sqrt-Boost fuer MoE - evaluate_fit(): name-Parameter (optional) fuer automatische MoE-Erkennung - cookbook.py: alle evaluate_fit-Aufrufe mit name= versehen Cleanup: - static/js/panels/*.js + static/js/main.js geloescht (Dead Code) - wird-Datei (versehentlich committet) geloescht - CLAUDE.md: KISS-Statement auf Vite+Svelte-Stand aktualisiert Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
11 KiB
Mission Control
Web-Dashboard zur Verwaltung eines lokalen LLM-Stacks (llama-swap) auf dem Bosgame M5.
FastAPI-Backend + Vanilla-JS-Dashboard. Leitprinzip: KISS — kein Build-Schritt, kein Frontend-Framework, keine Datenbank. Concerns sind getrennt (SoC) — aber ohne Build: native ES-Module im Frontend, FastAPI-APIRouter im Backend.
Architektur
Backend (Top-Level-Helfer + ein Router je Bereich):
app.py— dünner Einstieg:FastAPI, hängt die Router ein, liefert das statische UI aus, Exception-Handler + eineCache-Control: no-cache-Middleware für/und/static(UI wirkt sofort nach rsync, kein Stale-JS im Browser).config.py— alle Env-Vars + die gemeinsameruamel.yaml-Instanz. AuchSOURCE_DIR/PROD_DIRfürs Self-Update.auth.py— optionale Token-Auth: HeaderX-MC-Tokenoder Query?token=(Query ist Pflicht für WebSockets, da Browser dort keine Header senden).jobengine.py— In-Memory-Job-System (Threads + Subprocess) mit Live-Log.start_job(..., stdin_data=, log_cmd=): Secrets (sudo-PW) gehen über stdin, die Log-Zeile wird sanitisiert (kein Passwort im Log/ps).llamaswap.py— sprichtllama-swapan (/running,/v1/models, unload) und liest/schreibt dessenconfig.yamlperruamel.yaml(Kommentare bleiben erhalten).hw_math.py— Odysseus-Fit-Mathe: VRAM/RAM-Bedarf, tps-Schätzung,max_ctx_for(optimaler Kontext aus Hardware),extract_params_b. Genutzt von cookbook + models.recipes.py— kuratierte Use-Case-Stacks (Daten, kein Code) fürs Cookbook 2.0.routers/*.py— ein Router je Bereich:models.py(statusmit Meta/Caps/optimal_ctx,download,register,update_model,unload,chat),jobs.py(jobs),maintenance.py(update= llama.cpp,self-update= MC selbst,os-update,service/{name}/restart,reboot,logs-WebSocket),system.py(status+stream-WebSocket, Live-Metriken via psutil/sysfs),cookbook.py(analyze/evaluate/recipes/install-recipe),integration.py(test— Engine-Verbindung),news.py(news— RSS-Aggregation via stdlib). Alle unter/api/*.
Frontend (static/, dünne Hülle + ES-Module, kein Build):
-
index.html— nur Gerüst: Sidebar-Nav, Topbar, Alert-Banner, ein.view-Container je Bereich (Hash-Routing). Lädtcss/*undjs/main.jsals Modul. -
Design-System (v3): EINE Akzentfarbe (Teal
#2dd4bf) für alles Klickbare; Grün/Gelb/Rot nur für Status. Dichtes Control-Plane-Layout, Monospace-Zahlen, Bento. Keine Inline-Styles in Panels — alles über Klassen auscomponents.css(.card,.tile,.qa,.fit-badge,.modal-*,.badge,.bar/.meter,.chip…). Tokens incss/base.css(:root). -
js/core/*—api.js(Fetch + Token),ui.js(DOM-Helfer, Toast, Inline-Icon-Set,confirmModal/promptModalfür Beginner-UX,fmtBytes/fmtPct),nav.js(beschrifteter View-Switch). -
js/panels/*— ein Panel je Bereich (overview,models,server,cookbook,connect=Verbinden,news,jobs=Aktivität,guides). Panel-Vertrag:{ id, mount?(), onStatus?(s), onJobs?(jobs), onSystem?(sys) }. -
js/main.js— bootet Panels, pflegt Topbar/Alert + ehrlichen Security-Chip (status.secured), WebSocket für Live-Metriken (/api/system/stream, 2 Hz), Polling (/api/status3 s,/api/jobs1.5 s). -
Leitprinzip UX: verständlich/idiotensicher — Klartext-Microcopy, Fachbegriffe übersetzt (CPU→Prozessor, VRAM→Grafikspeicher), geführte Aktionen, heikle Aktionen mit
confirmModal(Klartext-Konsequenz). -
mission-control.service— systemd-Unit (uvicorn auf Port 9000). -
Konfiguration rein über Env-Vars:
MC_LLAMA_SWAP_URL,MC_CONFIG_PATH,MC_MODELS_DIR,MC_CMD_TEMPLATE,MC_UPDATE_CMD,MC_DEFAULT_TTL,MC_TOKEN,MC_SOURCE_DIR(Self-Update-Quelle, Default~/mission-control).
Der Stack drumherum (Kontext)
- Bosgame M5: AMD Strix Halo (gfx1151), Ubuntu 26.04, Kernel 7.0, ~124 GB GTT-Speicher. LAN-IP
192.168.178.151. - Inferenz: vorgebaute llama.cpp-ROCm-Binaries →
llama-swapauf*:8080(LAN-offen, mit-watch-config) → die Guides-Integrationen funktionieren von anderen Geräten. Engine-Update viaMC_UPDATE_CMD=/usr/local/bin/update-llamacpp(lädt llama.cpp-ROCm für gfx1151). - 3 Modelle / 5 Rollen:
coder,scout,vision(Qwen3-Familie). Modelle werden geswappt, nicht parallel geladen. - Mission Control: Produktiv unter
/opt/mission-control(als Dienst), Source-Repo unter~/mission-control.
Entwickeln & Deployen
Wo entwickelt wird: primär auf einem Windows-PC (Repo z. B. F:\Coding Stuff\mission-control),
Zielsystem ist der Linux-Bosgame. Lokal nur Smoke-Test (rendert/bootet sauber?), echter
Funktionstest nur auf dem Bosgame (dort läuft llama-swap).
Lokaler Smoke-Test (Windows):
python -m venv .venv && .venv/Scripts/python -m pip install -r requirements.txt
.venv/Scripts/python -m uvicorn app:app --port 9001
Ohne llama-swap ist alles im Offline-Zustand (rote Pill, Warn-Banner) — das ist erwartet.
Bosgame-Zugang (SSH, key-basiert, passwortlos):
ssh -i ~/.ssh/id_ed25519_hermes -o IdentitiesOnly=yes hitonabi@192.168.178.151
User ist hitonabi (klein!). sudo braucht generell ein Passwort — außer einer engen NOPASSWD-Whitelist für genau systemctl restart mission-control|llama-swap und journalctl (dadurch laufen Self-Update, Dienst-Restarts und Log-Stream passwortlos). OS-Update/Reboot fragen das Passwort weiterhin ab.
Self-Update statt manuellem Deploy: Der Button „Mission Control aktualisieren" (POST /api/self-update) macht git pull (Source) → rsync → systemctl restart — der dokumentierte manuelle Weg unten ist nur noch Fallback/Erstinstallation.
Frontend-Build (Vite + Svelte 5, ab Phase C):
cd frontend && npm install # einmalig
npm run build # vor jedem git push — Output: static/dist/main.js
Das Build-Ergebnis (static/dist/) wird committet → kein Build-Schritt auf dem Server.
Beim Entwickeln: npm run dev in frontend/ startet den Vite Dev-Server (Port 5173) für HMR.
Neue Svelte-Panels kommen in frontend/src/panels/. Sobald ein Panel migriert ist,
wird es in frontend/src/main.ts importiert und das alte static/js/panels/<panel>.js nicht mehr gebraucht.
Deploy-Kette (Windows → Gitea → Bosgame):
- Frontend bauen:
cd frontend && npm run build→static/dist/main.jsaktualisiert. - Lokal Smoke-Test →
git push(origin = Giteahttp://192.168.178.153:3000/Hitonabi/mission-control.git). - Bosgame:
cd ~/mission-control && git pull --ff-only(Source-Repo). - Nach
/optausrollen — ohne sudo,node_modulesausnehmen:rsync -a --exclude='.git' --exclude='.venv' --exclude='__pycache__' --exclude='*.pyc' --exclude='frontend/node_modules' ~/mission-control/ /opt/mission-control/ sudo systemctl restart mission-control(Passwort nötig). Prod =:9000, Dev-Spielwiese =:9001.- Niemals direkt in
/optarbeiten. Logs:journalctl -u mission-control -f.
Statische Dateien werden je Request frisch von Platte gelesen → UI-Änderungen wirken schon nach rsync; Python-Code-Änderungen brauchen den Restart.
Konventionen
- Frontend: Vite + Svelte 5 (Phase C). Neuer Bereich =
frontend/src/panels/<Bereich>Panel.svelte+routers/<bereich>.py+initNav-Eintrag inindex.html. Danachnpm run buildinfrontend/→static/dist/main.jswird committet. - Stores:
frontend/src/stores/status.svelte.ts,jobs.svelte.ts,system.svelte.ts— Svelte 5 Runes ($state). - Statische Hilfsfunktionen in
static/js/core/(api.js, ui.js, nav.js) bleiben als gemeinsame Basis — werden von Vite gebündelt, nicht direkt geladen. - Backend SoC: ein
routers/<bereich>.pyje Bereich, gemeinsame Logik inconfig/auth/jobengine/llamaswap/hw_math. - Endpoint-URLs bleiben unter
/api/*; neue Bereiche degradieren sauber, wenn ihre Quelle (sysfs,amd-smi,systemctl) fehlt (z. B. beim Entwickeln auf Windows). - Funktion darf nicht von
localStorageabhängen (nur das Token-Feld nutzt es, das ist ok). - Sicherheit: Das Backend führt Shell-Befehle aus → ausschließlich im vertrauenswürdigen LAN betreiben, niemals offen ins Internet.
Gotchas (wichtig!)
${PORT}in der generiertenllama-swap-Config muss literal stehen bleiben → beim Bauen des cmd-Stringsstr.replacebenutzen, NICHT.format(sonst KeyError aufPORT).llama-swapmuss mit-watch-configlaufen, sonst greift das Auto-Einpflegen neuer Modelle nicht.- HuggingFace-Downloads mit
HF_HUB_DISABLE_XET=1(sonst reproduzierbarer Hänger bei ~6 MB). - Vision-Modelle in llama.cpp brauchen zusätzlich
--mmproj <projektor>und--jinja.
Gotchas v3 (zusätzlich)
.qaist ein<button>→ die globalebutton.danger-Regel (Vollrot) schlägt durch. Für gefährliche Aktionszeilen.qa-dangernutzen (rotes Icon), NICHT.danger.- CSS rückwärtskompatibel halten: Panels werden schrittweise migriert; beim Token-/Klassen-Umbau alte Klassennamen + Vars (
--hi,--red,--red-dim) bedienen, sonst brechen noch nicht migrierte Panels. - Browser-ESM-Cache: Beim lokalen Testen bustet ein Soft-Reload den Modul-Cache nicht zuverlässig → Preview-Server neu starten oder cache-bustend dynamisch importieren. In Prod erledigt das die
no-cache-Middleware (einmal Strg+Shift+R nach dem ersten Deploy). - Gitea-Push kann transient mit „Failed to authenticate user" fehlschlagen (Server kurz weg) → einfach erneut versuchen.
Projektstatus & Roadmap
v3 ist umgesetzt & live (siehe ROADMAP.md): einheitliches Design-System, Beginner-UX
(Klartext/Führung), Security-Härtung (Passwort-Leak dicht, ehrlicher Chip), Self-Update-Button.
v4 ist umgesetzt & live („Der Lotse" — siehe ROADMAP.md): (1) optimale Kontextfenster
auto-ermittelt (hw_math.max_ctx_for, in Cookbook + Modelle-Konfig) → (2) Cookbook 2.0
use-case-getrieben mit Stack-Empfehlungen (recipes.py, „Komplettes Setup installieren") →
(3) „Verbinden"-Tab mit Connection-Test + Bild-/Vision-Flow → (4) News-Board (RSS via stdlib).
Alles automatisch aus Modellen + Hardware; KISS/SoC blieb (keine DB, keine neue Lib).
v5 ist umgesetzt & live („Anfänger-Lotse"): Klartext-Erklärungen (Swapping/Spitzenbedarf, Kontext-ⓘ,
Guides-Neubau), aktuelle Juni-2026-Modelle (Rezepte + dynamische GGUF-Auflösung _pick_gguf),
GGUF/MCP erklärt, empfohlene Tools + HF-Token (Einstellungen), News aus Qualitätsquellen +
Upgrade-Vorschläge (/api/cookbook/upgrades + /install-model, UPGRADES in recipes.py).
v6 ist umgesetzt & live: Stack zeigt echtes Modell; Top-News + Toolbar-Update-Badges
(/api/updates); News-Magazin-Layout; Guide neu mit Tutorials/Workflows + Konzept-Karten;
Cookbook-Modelle diversifiziert best-in-class (Qwen3/Gemma/Mistral/DeepSeek, neue Kategorie
„Nachdenken & Logik") + „Beste Wahl für dein System" (recommended_id).
Wir sind im Feinschliff- und Wartungsmodus.
Nordstern: den Server nie wieder via SSH/Putty anfassen müssen — 100 % Automatisierung / Klicki-Bunti.