94 lines
4.7 KiB
TypeScript
94 lines
4.7 KiB
TypeScript
import { ArrowUpCircle, ChevronRight, Check, Cpu, Code } from "lucide-react"
|
|
import { LucyHealthCard } from "@/components/dashboard/LucyHealthCard"
|
|
import { SystemStatusCard } from "@/components/dashboard/SystemStatusCard"
|
|
import { TokenPerformanceCard } from "@/components/dashboard/TokenPerformanceCard"
|
|
import { useUpdates, useModels } from "@/lib/queries"
|
|
import { cn } from "@/lib/utils"
|
|
|
|
// MC3-Startseite = der eine „Basisstation"-Blick: Ampel (wiederverwendet) + Updates
|
|
// (klickbar) + was gerade läuft + die geschätzte Telemetrie (System/Token/Sprach-Latenz).
|
|
// Bewusst KEINE Wärme/Platte/Speicher-Kacheln — die stehen schon im System-Status.
|
|
export function Mc3Start() {
|
|
const { data: updates } = useUpdates(30_000)
|
|
const { data: modelsResp } = useModels()
|
|
|
|
const upCount = (updates?.os || 0) + (updates?.engine || 0) + (updates?.swap || 0) + (updates?.models || 0)
|
|
const openUpdates = () => window.dispatchEvent(new CustomEvent("open-system-drawer", { detail: { tab: "maintenance" } }))
|
|
|
|
const models = modelsResp?.models ?? []
|
|
const running = modelsResp?.running ?? []
|
|
const brain = models.find((m) => m.role === "hermes")
|
|
const coder = models.find((m) => m.role === "coder-lite") || models.find((m) => m.role === "coder")
|
|
const isWarm = (name?: string) => (name ? running.includes(name) : false)
|
|
const shortName = (n?: string) => n?.split("/").pop()?.replace(/\.gguf$/i, "") || "—"
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
<div>
|
|
<h1 className="text-2xl font-space font-bold tracking-tight bg-gradient-to-r from-foreground via-foreground to-primary bg-clip-text text-transparent">
|
|
Basisstation
|
|
</h1>
|
|
<p className="text-sm text-muted-foreground">Geht's der Box gut, gibt's Updates, was läuft gerade.</p>
|
|
</div>
|
|
|
|
{/* Gesamt-Ampel + Fähigkeiten + Speicher (voll wiederverwendet, Box-Rahmen) */}
|
|
<LucyHealthCard frame="box" />
|
|
|
|
{/* Updates — klickbar, führt direkt zur Liste, was aktualisiert wird */}
|
|
<button
|
|
onClick={openUpdates}
|
|
className={cn(
|
|
"w-full flex items-center gap-3 rounded-2xl border p-4 text-left transition-all cursor-pointer",
|
|
upCount > 0
|
|
? "border-amber-500/30 bg-amber-500/5 hover:bg-amber-500/10"
|
|
: "border-border/60 bg-card/45 hover:border-primary/40",
|
|
)}
|
|
>
|
|
{upCount > 0
|
|
? <ArrowUpCircle className="h-6 w-6 shrink-0 text-amber-400" />
|
|
: <Check className="h-6 w-6 shrink-0 text-emerald-400" />}
|
|
<div className="flex-1 min-w-0">
|
|
<div className={cn("text-sm font-semibold", upCount > 0 ? "text-amber-400" : "text-foreground")}>
|
|
{upCount > 0 ? `${upCount} Update${upCount === 1 ? "" : "s"} verfügbar` : "Alles aktuell"}
|
|
</div>
|
|
<div className="text-xs text-muted-foreground">
|
|
{upCount > 0 ? "Antippen, um zu sehen, was genau aktualisiert wird." : "Keine ausstehenden Updates."}
|
|
</div>
|
|
</div>
|
|
{upCount > 0 && (
|
|
<span className="shrink-0 flex items-center gap-1 text-xs font-semibold text-amber-400">
|
|
Ansehen <ChevronRight className="h-4 w-4" />
|
|
</span>
|
|
)}
|
|
</button>
|
|
|
|
{/* Was gerade läuft — beide Lanes */}
|
|
<div>
|
|
<div className="text-xs text-muted-foreground mb-2">Was gerade läuft</div>
|
|
<div className="rounded-2xl border border-border/60 overflow-hidden">
|
|
{[
|
|
{ icon: Cpu, label: "Lucys Hirn", model: brain?.name, warm: isWarm(brain?.name) },
|
|
{ icon: Code, label: "Coder (Vibe-Coding)", model: coder?.name, warm: isWarm(coder?.name) },
|
|
].map((r) => (
|
|
<div key={r.label} className="flex items-center gap-3 px-4 py-3 border-b border-border/40 last:border-0">
|
|
<span className={cn("h-2 w-2 shrink-0 rounded-full", r.warm ? "bg-emerald-500 animate-pulse" : "bg-muted-foreground/40")} />
|
|
<r.icon className="h-4 w-4 text-muted-foreground shrink-0" />
|
|
<span className="text-sm text-foreground w-40 shrink-0">{r.label}</span>
|
|
<span className="text-xs text-muted-foreground flex-1 truncate font-mono">{shortName(r.model)}</span>
|
|
<span className={cn("text-xs shrink-0", r.warm ? "text-emerald-400" : "text-muted-foreground")}>
|
|
{r.model ? (r.warm ? "warm" : "bereit") : "nicht gesetzt"}
|
|
</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Geschätzte Telemetrie — 1:1 aus MC2 übernommen */}
|
|
<div className="grid gap-6 lg:grid-cols-2">
|
|
<SystemStatusCard />
|
|
<TokenPerformanceCard />
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|