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:
@@ -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 }
|
||||
}
|
||||
Reference in New Issue
Block a user