Zeitachse (24h-Historie), Verbinden-Autofill, Modelltausch abgesichert

Paket 1 - Metrik-Historie mit Zeitachse (User: 'WANN war Last?'):
- services/metrics_history.py: 10s-Sampler (CPU/RAM/GPU/Disk + Token-Totale) in
  24h-Ringpuffer (aelteres faellt automatisch raus, nichts waechst unbegrenzt),
  persistiert alle 5 min -> uebersteht Deploys; GET /api/system/history?minutes
  mit Downsampling auf ~300 Punkte; Lifespan-Task in app.py
- Cockpit-Karten System-Status + Token-Durchsatz: Bereichs-Schalter Live/1h/24h,
  sichtbare Zeitachse (HH:MM:SS bei kurzen, HH:MM bei langen Fenstern), Tooltip
  zeigt Uhrzeit; Token-Raten in 1h/24h aus Totale-Deltas

Paket 2 - Verbinden selbsterklaerend:
- Box-IP vorbefuellt aus window.location.hostname (Dev-Fallback bleibt)
- MCP-Scriptpfad-Feld aus der Kopfzeile in die 'Gedaechtnis anbinden'-Karte
  verschoben, mit Erklaerung WANN man es braucht

Paket 3 - Modelltausch-Loecher gestopft (Review 16.07.):
- install: Registrierung OHNE Alias-Umzug; Rolle wird erst NACH erfolgreichem
  Download uebernommen (on_done) - hermes dabei durch den warm-bewussten
  set_agent_brain-Flow statt rohem Alias-Move (Lucy waere sonst bis Download-
  Ende tot gewesen); Re-Install erhaelt bestehende Aliase
- set_role_alias: lebenswichtige Aliase (hermes/embed) des Ziel-Modells
  ueberleben jeden Rollen-Klick (Qwen3.6 haelt live hermes+fast!)
- Rollen-Endpoint verweigert Rollen-Wechsel am Halter geschuetzter Aliase
  mit klarer Anleitung; Werkbank sperrt die Rollen-Chips sichtbar

Verifiziert: py_compile + Sampler-Smoke (2 Punkte, Persist ok) + Alias-Logik-
Unittest (3 Faelle) + Browser (Zeitachse tickt, Schalter, Leerzustand, Verbinden).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-07-16 01:08:03 +02:00
parent 26224804ed
commit 71192b3db0
36 changed files with 562 additions and 213 deletions
+39 -8
View File
@@ -153,9 +153,26 @@ def model_id_from_path(model_path: str) -> str:
return name or "modell"
# Lebenswichtige Aliase: hängen direkt an Lucys Denk- und Gedächtnis-Pfad. Sie dürfen
# beim Rollen-Umhängen NIE stillschweigend verloren gehen (Review 16.07.: Qwen3.6 hält
# live `hermes` UND `fast` — ein Rollen-Klick hätte beide gelöscht → Lucy tot).
PROTECTED_ALIASES = {"hermes", "embed"}
def protected_alias_of(model_id: str) -> str | None:
"""Hält dieses Modell gerade einen lebenswichtigen Alias? (für Guards in der API)"""
spec = (read_config().get("models") or {}).get(model_id)
if isinstance(spec, dict):
for a in spec.get("aliases") or []:
if str(a).lower() in PROTECTED_ALIASES:
return str(a).lower()
return None
def set_role_alias(cfg: dict, model_id: str, role: str | None) -> None:
"""Rolle als eindeutigen llama-swap-`aliases`-Eintrag setzen (vorher bei allen
anderen Modellen entfernen). role=None/leer entfernt den Alias."""
"""Rolle als llama-swap-`aliases`-Eintrag setzen (vorher bei allen anderen Modellen
entfernen — deren übrige Aliase bleiben). role=None/leer entfernt die Rolle.
Lebenswichtige Aliase (PROTECTED_ALIASES) des Ziel-Modells bleiben IMMER erhalten."""
models = cfg.get("models") or {}
role = (role or "").strip().lower()
if role:
@@ -169,8 +186,11 @@ def set_role_alias(cfg: dict, model_id: str, role: str | None) -> None:
spec.pop("aliases", None)
spec = models.get(model_id)
if isinstance(spec, dict):
if role and role != model_id.lower():
spec["aliases"] = [role]
keep = [a for a in (spec.get("aliases") or [])
if str(a).lower() in PROTECTED_ALIASES and str(a).lower() != role]
new = keep + ([role] if role and role != model_id.lower() else [])
if new:
spec["aliases"] = new
else:
spec.pop("aliases", None)
@@ -214,9 +234,12 @@ def write_config(cfg: dict) -> None:
def register_model(model_path: str, role: str | None = None, ctx: int = 8192,
ttl: int | None = None, mmproj_path: str | None = None,
jinja: bool = False) -> str:
jinja: bool = False, set_alias: bool = True) -> str:
"""Ein GGUF als llama-swap-Modell eintragen (cmd + Rolle-Alias). Gibt die
Modell-ID zurück. jinja=True erzwingt --jinja (Tool-Calling, z.B. fürs Agent-Hirn)."""
Modell-ID zurück. jinja=True erzwingt --jinja (Tool-Calling, z.B. fürs Agent-Hirn).
set_alias=False: Rolle nur für die cmd-Flags nutzen, den Alias aber NICHT umhängen —
der Install-Flow setzt ihn erst NACH fertigem Download (sonst zeigt die Rolle
minutenlang auf eine Datei, die noch gar nicht existiert)."""
cfg = read_config()
model_id = model_id_from_path(model_path)
cmd = CMD_TEMPLATE.replace("{model}", model_path).replace("{ctx}", str(ctx))
@@ -242,11 +265,19 @@ def register_model(model_path: str, role: str | None = None, ctx: int = 8192,
if "--spec-draft-model" not in cmd and "--model-draft" not in cmd:
cmd += spec_draft_flags(model_path)
cfg.setdefault("models", {})[model_id] = {
# Bestehende Aliase des Eintrags erhalten (Re-Install/Upgrade desselben Repos):
# die Rolle soll während des Downloads beim ALTEN Stand bleiben.
old_aliases = (cfg.get("models") or {}).get(model_id, {})
old_aliases = old_aliases.get("aliases") if isinstance(old_aliases, dict) else None
entry: dict = {
"cmd": LiteralScalarString(cmd + "\n"),
"ttl": ttl if ttl is not None else DEFAULT_TTL,
}
set_role_alias(cfg, model_id, role)
if old_aliases:
entry["aliases"] = old_aliases
cfg.setdefault("models", {})[model_id] = entry
if set_alias:
set_role_alias(cfg, model_id, role)
write_config(cfg)
return model_id