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
co-authored by Claude Fable 5
parent 26224804ed
commit 71192b3db0
36 changed files with 562 additions and 213 deletions
@@ -259,14 +259,18 @@ function Detail({
{lockUnload ? "🔒 bleibt geladen" : warm ? "Entladen" : "Ins Warm-Set laden"}
</button>
{/* Rolle wählen */}
{/* Rolle wählen — bei lebenswichtigen Rollen (Hirn/Gedächtnis) gesperrt: die Rolle
wandert nur, indem man sie einem ANDEREN Modell zuweist (Backend erzwingt das auch). */}
<div className="rounded-xl border border-border/30 bg-background/20 p-3">
<div className="mb-2 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground">Rolle</div>
<div className="mb-2 flex items-center justify-between text-[11px] font-semibold uppercase tracking-wider text-muted-foreground">
<span>Rolle</span>
{protectedRole && <span className="normal-case font-medium tracking-normal text-muted-foreground/70">🔒 lebenswichtig zum Tausch die Rolle woanders zuweisen</span>}
</div>
<div className="flex flex-wrap gap-1.5">
<RoleChip active={!m.role} onClick={() => onRole("")} dot="bg-slate-400" label="keine" />
<RoleChip active={!m.role} onClick={() => onRole("")} dot="bg-slate-400" label="keine" disabled={protectedRole} />
{ROLE_META.map((r) => (
<RoleChip key={r.role} active={m.role === r.role} onClick={() => onRole(r.role)}
dot={roleBarColor(r.role).dot} label={r.short} />
dot={roleBarColor(r.role).dot} label={r.short} disabled={protectedRole && m.role !== r.role} />
))}
</div>
</div>
@@ -322,11 +326,16 @@ function Detail({
)
}
function RoleChip({ active, onClick, dot, label }: { active: boolean; onClick: () => void; dot: string; label: string }) {
function RoleChip({ active, onClick, dot, label, disabled }: {
active: boolean; onClick: () => void; dot: string; label: string; disabled?: boolean
}) {
return (
<button onClick={onClick}
className={cn("flex items-center gap-1.5 rounded-lg border px-2 py-1 text-[10px] font-semibold transition-all cursor-pointer",
active ? "border-primary/60 bg-primary/15 text-foreground" : "border-border/40 bg-background/20 text-muted-foreground hover:border-primary/30")}>
<button onClick={onClick} disabled={disabled}
title={disabled ? "Gesperrt: dieses Modell hält eine lebenswichtige Rolle." : undefined}
className={cn("flex items-center gap-1.5 rounded-lg border px-2 py-1 text-[10px] font-semibold transition-all",
disabled ? "cursor-not-allowed opacity-40 border-border/40 bg-background/20 text-muted-foreground"
: active ? "cursor-pointer border-primary/60 bg-primary/15 text-foreground"
: "cursor-pointer border-border/40 bg-background/20 text-muted-foreground hover:border-primary/30")}>
<span className={cn("h-2 w-2 rounded-sm", dot)} />
{label}
{active && <Check className="h-3 w-3 text-primary" />}