UI idiotensicher: Klartext-Rollen, Lucy-Statuspanel, Einfach/Experte, Warm-Set

Vier-Etappen-Umbau der MC2-Oberfläche für Endnutzer (kein Fachjargon):

1. Klartext-Schicht (lib/roleMeta.ts): Rollen-Kürzel → Zweck-Namen mit Icons
   (hermes=Lucys Hirn, embed=Lucys Gedächtnis, vision=Lucys Augen, …), zentral
   in ModelBadges/ModelsCard/Cockpit. Veraltete Rollenliste korrigiert
   (Phantom-„fast" raus; embed/coder-lite ergänzt).
2. „Geht's Lucy gut?"-Panel (lib/useLucyHealth.ts + LucyHealthCard): 5 Fähigkeiten
   in Klartext, Ampel-Verdikt, Speicher-Ampel, 1-Klick-Reparatur (echte Unit-Namen).
3. Einfach/Experte-Schalter (lib/useExpertMode.ts + ExpertToggle) + Schutzgeländer:
   Hirn/Gedächtnis (protected) nicht löschbar/entladbar; Ctx/Spec/Slots/Lane-Details
   im Einfach-Modus versteckt.
4. „Immer-bereit-Set"-Manager (WarmSetManager): brains-Gruppe in Klartext + ehrliches
   Speicher-Budget (inkl. KV) mit Verdrängungs-Hinweis.

Live gegen die Box verifiziert. frontend/dist mitgebaut.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-07-01 17:04:56 +02:00
parent 6db1224d95
commit a341d254e1
17 changed files with 6436 additions and 5588 deletions
+173
View File
@@ -0,0 +1,173 @@
// „Geht's Lucy gut?" — bündelt die vielen technischen Status-Signale zu wenigen
// Lucy-Fähigkeiten in Klartext, inkl. Gesamt-Verdikt, Speicher-Ampel und einer
// konkreten 1-Klick-Reparatur pro Problem. EINE Quelle für das Status-Panel.
import { Brain, BookMarked, Plug, Bot, Mic, type LucideIcon } from "lucide-react"
import { useHealth, useServices, useModels, useSystemStatus } from "./queries"
export type CheckStatus = "ok" | "warn" | "down" | "loading"
// Reparatur-Rezept: entweder einen systemd-Dienst neu starten oder ein Modell laden.
// WICHTIG: `service` sind die ECHTEN Unit-Namen (nicht die Anzeige-Namen der
// Dienste-Karte), damit /api/maintenance/restart sie akzeptiert.
export type Repair =
| { kind: "restart"; service: string; label: string; needsSudo?: boolean }
| { kind: "loadModel"; model: string; label: string }
export interface LucyCheck {
id: string
label: string // Klartext-Name der Fähigkeit
icon: LucideIcon
status: CheckStatus
detail: string // Ein Satz: was ist gerade los
critical: boolean // zählt fürs Gesamt-Verdikt „Problem"
repair?: Repair
}
export type Verdict = "loading" | "gut" | "warn" | "problem"
export interface MemoryAmpel {
status: "ok" | "warn" | "full" | "unknown"
usedGb: number
totalGb: number
pct: number
text: string
}
export interface LucyHealth {
verdict: Verdict
checks: LucyCheck[]
memory: MemoryAmpel
problems: LucyCheck[] // kritische Down-Checks (für den Schnellzugriff im Banner)
}
export function useLucyHealth(): LucyHealth {
const { data: health } = useHealth()
const { data: svc } = useServices()
const { data: modelsResp } = useModels()
const { data: sys } = useSystemStatus()
const loaded = !!health && !!svc
const svcOk = (frag: string): boolean | undefined =>
svc?.services.find((s) => s.name.toLowerCase().includes(frag))?.ok
const engineOk = health?.engine_reachable
const brainReady = health?.brain?.ready
const brainName =
health?.brain?.model ||
modelsResp?.models.find((m) => m.role === "hermes")?.name ||
""
const checks: LucyCheck[] = []
// 1) Lucys Hirn — die wichtigste Fähigkeit. Engine läuft UND das Hirn-Modell ist geladen?
checks.push(((): LucyCheck => {
const base = { id: "brain", label: "Lucys Hirn", icon: Brain, critical: true }
if (!loaded) return { ...base, status: "loading", detail: "Wird geprüft …" }
if (engineOk === false)
return {
...base,
status: "down",
detail: "Die Motor-Maschine (Engine) läuft nicht — Lucy kann gerade gar nicht denken.",
repair: { kind: "restart", service: "llama-swap", label: "Motor neu starten", needsSudo: true },
}
if (brainReady === false)
return {
...base,
status: "down",
detail: "Lucys Hirn ist gerade eingeschlafen — einmal aufwecken.",
repair: brainName
? { kind: "loadModel", model: brainName, label: "Hirn aufwecken" }
: undefined,
}
return { ...base, status: "ok", detail: "Wach und ansprechbar." }
})())
// 2) Lucys Gedächtnis — Mem0-Dienst.
checks.push(((): LucyCheck => {
const base = { id: "memory", label: "Lucys Gedächtnis", icon: BookMarked, critical: true }
const ok = svcOk("mem0")
if (!loaded || ok === undefined) return { ...base, status: "loading", detail: "Wird geprüft …" }
return ok
? { ...base, status: "ok", detail: "Merkt sich Neues und findet Altes wieder." }
: {
...base,
status: "down",
detail: "Lucy kann sich gerade nichts merken — das Gedächtnis antwortet nicht.",
repair: { kind: "restart", service: "mem0-service", label: "Gedächtnis neu starten" },
}
})())
// 3) Verbindung (Gateway) — der integrierte OpenAI-Gateway, über den Apps/IDEs Lucy erreichen.
checks.push(((): LucyCheck => {
const base = { id: "gateway", label: "Verbindung (Gateway)", icon: Plug, critical: true }
const ok = health ? health.gateway_reachable : svcOk("integriert")
if (!loaded || ok === undefined) return { ...base, status: "loading", detail: "Wird geprüft …" }
return ok
? { ...base, status: "ok", detail: "Apps und IDEs können Lucy erreichen." }
: {
...base,
status: "down",
detail: "Die Verbindungs-Zentrale antwortet nicht — Apps erreichen Lucy nicht.",
repair: { kind: "restart", service: "mission-control-2", label: "Zentrale neu starten" },
}
})())
// 4) Lucys Agent (Hermes) — der Dienst, der als Lucy antwortet (Terminal/Telegram/Sprechen).
checks.push(((): LucyCheck => {
const base = { id: "agent", label: "Lucys Agent (Hermes)", icon: Bot, critical: true }
const ok = svcOk("hermes-gateway")
if (!loaded || ok === undefined) return { ...base, status: "loading", detail: "Wird geprüft …" }
return ok
? { ...base, status: "ok", detail: "Lucy ist bereit zu reden und zu handeln." }
: {
...base,
status: "down",
detail: "Lucys Agent ist offline — sie reagiert gerade nicht.",
repair: { kind: "restart", service: "hermes-gateway", label: "Agent neu starten" },
}
})())
// 5) Lucys Stimme — Voice-Dienst (STT/TTS). Nicht kritisch: Chat geht auch ohne.
checks.push(((): LucyCheck => {
const base = { id: "voice", label: "Lucys Stimme", icon: Mic, critical: false }
const ok = svcOk("voice")
if (!loaded || ok === undefined) return { ...base, status: "loading", detail: "Wird geprüft …" }
return ok
? { ...base, status: "ok", detail: "Lucy kann hören und sprechen." }
: {
...base,
status: "warn",
detail: "Sprechen ist gerade aus — Tippen geht weiter normal.",
repair: { kind: "restart", service: "voice-service", label: "Stimme neu starten" },
}
})())
// Speicher-Ampel aus dem echten GTT-Pool (unified RAM inkl. KV-Cache).
const total = sys?.gpu?.gtt_total || sys?.gpu?.vram_total || 0
const used = sys?.gpu?.gtt_used || 0
const pct = total > 0 ? Math.min(100, (used / total) * 100) : 0
const B = 1024 ** 3
const memory: MemoryAmpel = !total
? { status: "unknown", usedGb: 0, totalGb: 0, pct: 0, text: "Speicher-Auslastung unbekannt." }
: pct >= 90
? { status: "full", usedGb: used / B, totalGb: total / B, pct, text: "Speicher fast voll — Lucy könnte langsamer werden." }
: pct >= 72
? { status: "warn", usedGb: used / B, totalGb: total / B, pct, text: "Speicher gut gefüllt — noch okay." }
: { status: "ok", usedGb: used / B, totalGb: total / B, pct, text: "Genug Speicher frei." }
const anyLoading = checks.some((c) => c.status === "loading")
const problems = checks.filter((c) => c.critical && c.status === "down")
const warns = checks.filter((c) => c.status === "warn" || (!c.critical && c.status === "down"))
const memBad = memory.status === "full"
const verdict: Verdict = anyLoading && !problems.length
? "loading"
: problems.length
? "problem"
: warns.length || memBad
? "warn"
: "gut"
return { verdict, checks, memory, problems }
}