0c69c491e5
Backend (Wahrheit reparieren): - gateway_reachable() prueft jetzt den ECHTEN mc2-gateway (:9010 via V1_UPSTREAM) statt nur die Engine - toter Denkpfad war in Health/Cockpit unsichtbar - /api/system/services vollstaendig: + LLM-Gateway, + Steuerpult, + Waechter (mc2-steward via is-active); scope-Feld (user/system); mc2-gateway/mc2-steward in die Restart-Allowlist - Connect-Health Leitung 1 prueft den Client-Pfad (V1_UPSTREAM statt llama-swap) - agent_status liefert pc_executor_url (UI zeigte hartcodierte IP/Ports) - SSE-Endlosschleife gefixt: /api/auftragsbuch schrieb announce-branches bei JEDEM Aufruf neu -> mtime-Bump -> invalidate -> Refetch im 3-s-Takt je Client; jetzt nur noch bei echter Aenderung Frontend (ein Datenweg): - SystemDrawer komplett auf React-Query-Hooks (vorher eigenes 3-s-setInterval auf dieselben Endpunkte inkl. teurem Updates-Check); Dienste-Liste = Backend-Antwort (SERVICES-UI-Kopie geloescht); useDialog statt Eigenbau; fmtBytes statt Duplikat - useLucyHealth: Dienst-Matching per systemd-unit statt Namensfragment; Gateway- Reparatur zielt auf mc2-gateway; neuer Waechter-Check; Backend-tot => ehrliches rotes Verdikt statt eingefrorener letzter Stand (auch Sidebar) - Toter Code raus: views/models/Cockpit.tsx (908 Z.) + RoleAssignModal + Placeholder; LaneEditor (Routing-Policy) + WarmSetManager damit ZURUECK im UI als Tab "Routing & Warm-Set" im Modell-Manager - Cockpit: blockierte Ideen-Karten erscheinen in "Braucht dich" + Kachel-Hint; Verbinden-Kachel zeigt 3 Leitungen live statt hartem "Zed" - Maschinenraum: ehrliches "frei" (reale Belegung) + eigenes KV-Cache-Segment - AgentView: Ports/PC-Adresse aus der API; Guide lehrt Lanes chat/coding - queries.ts: useModels nutzt SSE-relax; Chronik-Limit im Query-Key; useJobs/useZeitmaschine mit enabled-Schalter Verifiziert: tsc+vite gruen, Backend-Smoke beide Gateway-Modi, UI live gegen die Box (Cockpit/Werkbank/Routing-Tab/Drawer rendern mit Echtdaten). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
236 lines
11 KiB
TypeScript
236 lines
11 KiB
TypeScript
// „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, Eye, Plug, Bot, Mic, ShieldAlert, Server, 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)
|
|
warns: LucyCheck[] // nicht-kritische Hinweise — damit das Banner SAGT, was los ist
|
|
unreachable: boolean // Zentrale (MC2) antwortet nicht → alle anderen Anzeigen sind veraltet
|
|
}
|
|
|
|
export function useLucyHealth(): LucyHealth {
|
|
const { data: health, isError: healthErr } = useHealth()
|
|
const { data: svc, isError: svcErr } = useServices()
|
|
const { data: modelsResp } = useModels()
|
|
const { data: sys } = useSystemStatus()
|
|
|
|
// React Query behält bei Fehlern die letzten Daten — ohne diesen Riegel zeigte die UI
|
|
// bei totem Backend dauerhaft den letzten grünen Stand („eingefrorene Ampel", Review 15.07.).
|
|
const unreachable = healthErr || svcErr
|
|
|
|
const loaded = !!health && !!svc
|
|
// Matching über den ECHTEN systemd-Unit-Namen (Backend liefert `unit`) — vorher wurde
|
|
// fragil im deutschen Anzeige-Namen gesucht ("gateway (integr" …).
|
|
const svcOk = (unit: string): boolean | undefined =>
|
|
svc?.services.find((s) => s.unit === unit)?.ok
|
|
const svcHas = (unit: string): boolean => !!svc?.services.some((s) => s.unit === unit)
|
|
|
|
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[] = []
|
|
|
|
// 0) Die Zentrale selbst. Antwortet MC2 nicht, ist ALLES andere Spekulation —
|
|
// dieser Check übertrumpft die übrigen (die zeigen sonst eingefrorene alte Werte).
|
|
if (unreachable) {
|
|
checks.push({
|
|
id: "mc2", label: "Steuerpult (MC2)", icon: Server, critical: true, status: "down",
|
|
detail: "Die Zentrale antwortet nicht — alle Anzeigen hier können veraltet sein. Läuft die Box? Deploy aktiv?",
|
|
})
|
|
}
|
|
|
|
// 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 Augen — das Vision-Modell (Rolle `vision`). Seit der Warm-Set-Diät 07.07. ist
|
|
// ON-DEMAND der NORMALZUSTAND (User-Entscheid: Augen nur, wenn Lucy an ist / IDE sie ruft;
|
|
// die Lucy-App wärmt sie beim Start selbst an). Ruhende Augen sind darum OK, keine Warnung.
|
|
checks.push(((): LucyCheck => {
|
|
const base = { id: "vision", label: "Lucys Augen", icon: Eye, critical: false }
|
|
if (!loaded || !modelsResp) return { ...base, status: "loading", detail: "Wird geprüft …" }
|
|
const vm = modelsResp.models.find((m) => m.role === "vision")
|
|
if (!vm)
|
|
return { ...base, status: "warn", detail: "Kein Augen-Modell eingerichtet — Lucy kann keine Bilder ansehen." }
|
|
const warm = (modelsResp.running ?? []).includes(vm.name)
|
|
return warm
|
|
? { ...base, status: "ok", detail: "Sieht gerade zu (geladen, solange Lucy sie nutzt)." }
|
|
: {
|
|
...base,
|
|
status: "ok",
|
|
detail: "Augen ruhen — sie laden von selbst, sobald Lucy startet oder ein Bild kommt.",
|
|
repair: { kind: "loadModel", model: vm.name, label: "Augen wecken" },
|
|
}
|
|
})())
|
|
|
|
// 3) Lucys Gedächtnis — Mem0-Dienst.
|
|
checks.push(((): LucyCheck => {
|
|
const base = { id: "memory", label: "Lucys Gedächtnis", icon: BookMarked, critical: true }
|
|
const ok = svcOk("mem0-service")
|
|
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" },
|
|
}
|
|
})())
|
|
|
|
// 4) Verbindung (LLM-Gateway) — der Denkpfad, über den Apps/IDEs/Lucy die Modelle erreichen.
|
|
// Seit UMBAU v3 ist das der EIGENE mc2-gateway-Prozess; health.gateway_reachable prüft
|
|
// ihn jetzt wirklich (statt nur die Engine). Repariert wird die richtige Unit.
|
|
checks.push(((): LucyCheck => {
|
|
const base = { id: "gateway", label: "Verbindung (Gateway)", icon: Plug, critical: true }
|
|
const ok = health ? health.gateway_reachable : svcOk("mc2-gateway")
|
|
if (!loaded || ok === undefined) return { ...base, status: "loading", detail: "Wird geprüft …" }
|
|
const unit = svcHas("mc2-gateway") ? "mc2-gateway" : "mission-control-2"
|
|
return ok
|
|
? { ...base, status: "ok", detail: "Apps und IDEs können Lucy erreichen." }
|
|
: {
|
|
...base,
|
|
status: "down",
|
|
detail: "Der Modell-Gateway antwortet nicht — Lucy und die IDEs erreichen kein Modell.",
|
|
repair: { kind: "restart", service: unit, label: "Gateway neu starten" },
|
|
}
|
|
})())
|
|
|
|
// 4b) Der Wächter (Steward) — überwacht Warm-Set, Health-Flanken und Gedächtnis-Dubletten.
|
|
// Nicht kritisch fürs Antworten, aber ohne ihn merkt niemand, wenn etwas kippt.
|
|
checks.push(((): LucyCheck => {
|
|
const base = { id: "steward", label: "Wächter (Steward)", icon: ShieldAlert, critical: false }
|
|
if (!loaded) return { ...base, status: "loading", detail: "Wird geprüft …" }
|
|
if (!svcHas("mc2-steward"))
|
|
return { ...base, status: "ok", detail: "Auf dieser Installation nicht eingerichtet." }
|
|
return svcOk("mc2-steward")
|
|
? { ...base, status: "ok", detail: "Wacht über Warm-Set, Dienste und Gedächtnis." }
|
|
: {
|
|
...base,
|
|
status: "warn",
|
|
detail: "Der Wächter schläft — niemand meldet gerade Ausfälle oder wärmt Modelle nach.",
|
|
repair: { kind: "restart", service: "mc2-steward", label: "Wächter neu starten" },
|
|
}
|
|
})())
|
|
|
|
// 5) 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") // unit-Name (identisch zum alten Namens-Match, jetzt exakt)
|
|
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" },
|
|
}
|
|
})())
|
|
|
|
// 6) 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-service")
|
|
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 = System-RAM, DIESELBE Quelle wie die RAM-Zeile in der
|
|
// System-Status-Kachel. Vorher zeigte die Pille den GTT-Pool (GPU-Sicht auf den
|
|
// Unified-Memory) — der weicht auf der APU vom System-RAM ab, wodurch „Box gesund"
|
|
// und „System-Status" zwei verschiedene Speicher-Zahlen zeigten. Eine Box, eine Zahl.
|
|
// Schwellen am RAM-Charakter geeicht: 4 Modelle warm ≈ 78 % gilt als gesund → grün.
|
|
const total = sys?.ram?.total ?? 0
|
|
const used = sys?.ram?.used ?? 0
|
|
const pct = sys?.ram?.percent ?? (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 >= 93
|
|
? { status: "full", usedGb: used / B, totalGb: total / B, pct, text: "Speicher fast voll — Lucy könnte langsamer werden." }
|
|
: pct >= 85
|
|
? { 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, warns, unreachable }
|
|
}
|