import { useState } from "react" import { HeartPulse, HardDrive, Wrench, Check, AlertTriangle, Loader2, ShieldQuestion } from "lucide-react" import { api } from "@/lib/api" import { useDialog } from "@/lib/useDialog" import { useQueryClient, qk } from "@/lib/queries" import { useLucyHealth, type LucyCheck, type Repair } from "@/lib/useLucyHealth" import { cn } from "@/lib/utils" // Farb-/Text-Ton pro Check-Status. const DOT: Record = { ok: "bg-emerald-500", warn: "bg-amber-500", down: "bg-red-500", loading: "bg-muted-foreground/40", } // `frame` = Erzählrahmen: "lucy" (MC2-Dashboard) oder "box" (MC3-Basisstation). export function LucyHealthCard({ frame = "lucy" }: { frame?: "lucy" | "box" } = {}) { const { verdict, checks, memory, problems } = useLucyHealth() const { showAlert, dialogElement } = useDialog() const qc = useQueryClient() const [busy, setBusy] = useState>({}) async function runRepair(id: string, repair: Repair) { setBusy((b) => ({ ...b, [id]: true })) try { if (repair.kind === "loadModel") { await api(`/api/models/${encodeURIComponent(repair.model)}/load`, { method: "POST" }) } else { const r = await api<{ ok: boolean; err?: string }>("/api/maintenance/restart", { method: "POST", body: JSON.stringify({ service: repair.service }), }) if (!r.ok) { showAlert( "Reparatur nicht ganz geklappt", (r.err || "Unbekannter Fehler") + (repair.needsSudo ? "\n\nTipp: Dafür wird evtl. das Box-Passwort gebraucht (oben rechts hinterlegen)." : ""), ) } } // Status-Queries neu ziehen, damit die Ampel sich sofort aktualisiert. qc.invalidateQueries({ queryKey: qk.health }) qc.invalidateQueries({ queryKey: qk.services }) qc.invalidateQueries({ queryKey: qk.models }) } catch (e: any) { showAlert("Reparatur fehlgeschlagen", String(e?.message || e)) } finally { setBusy((b) => ({ ...b, [id]: false })) } } // Titel je Erzählrahmen (Box-Kontrollpanel vs. Lucy-Dashboard). const T = frame === "box" ? { loading: "Box wird geprüft …", gut: "Alles okay", warn: "Kleinigkeit an der Box", problem: "Box braucht Hilfe" } : { loading: "Lucy wird geprüft …", gut: "Lucy geht's gut 💚", warn: "Kleinigkeit bei Lucy", problem: "Lucy braucht Hilfe" } // Banner-Optik je Gesamt-Verdikt. const banner = { loading: { ring: "border-border/60 bg-card/45", icon: Loader2, iconCls: "text-muted-foreground animate-spin", title: T.loading, sub: "Einen Moment, ich schaue nach dem Rechten." }, gut: { ring: "border-emerald-500/40 bg-emerald-500/5", icon: HeartPulse, iconCls: "text-emerald-400", title: T.gut, sub: "Alle wichtigen Fähigkeiten laufen." }, warn: { ring: "border-amber-500/40 bg-amber-500/5", icon: AlertTriangle, iconCls: "text-amber-400", title: T.warn, sub: "Nichts Schlimmes — nur ein Hinweis." }, problem: { ring: "border-red-500/50 bg-red-500/5", icon: AlertTriangle, iconCls: "text-red-400", title: T.problem, sub: `${problems.length} wichtige${problems.length === 1 ? "s" : ""} Problem${problems.length === 1 ? "" : "e"} gefunden.` }, }[verdict] const BannerIcon = banner.icon const memTone = { ok: "bg-emerald-500", warn: "bg-amber-500", full: "bg-red-500", unknown: "bg-muted-foreground/40", }[memory.status] const memText = { ok: "text-emerald-400", warn: "text-amber-400", full: "text-red-400", unknown: "text-muted-foreground", }[memory.status] return (
{/* Gesamt-Verdikt */}

{banner.title}

{banner.sub}

{/* Speicher-Ampel */}
Speicher {memory.status === "unknown" ? "—" : `${memory.usedGb.toFixed(0)} / ${memory.totalGb.toFixed(0)} GB`}

{memory.text}

{/* Fähigkeiten-Checkliste */}
{checks.map((c) => ( c.repair && runRepair(c.id, c.repair)} /> ))}
{dialogElement}
) } function CheckRow({ c, busy, onRepair }: { c: LucyCheck; busy: boolean; onRepair: () => void }) { const Icon = c.icon const bad = c.status === "down" || c.status === "warn" return (
{c.label} {c.status === "ok" && } {!c.critical && optional}
{c.detail}
{bad && c.repair && ( )} {bad && !c.repair && ( manuell )}
) }