import { useEffect, useRef, useState } from "react" import { Boxes, Zap, Moon } from "lucide-react" import { useModels, useTokenStats, useSystemStatus } from "@/lib/queries" import { fmtSize, gb } from "@/lib/format" import { cn } from "@/lib/utils" import { ROLES, RoleLabel } from "@/components/models/ModelBadges" // Vereint die frühere RolesCard (Rolle→Modell) + ActiveModelsCard (warm/Inferenz/Größe) // zu EINER Karte — dieselbe Info stand vorher doppelt auf der Zentrale. export function ModelsCard() { const { data } = useModels(2_000) const { data: stats } = useTokenStats(2_000) const { data: sysStatus } = useSystemStatus() const models = data?.models ?? [] const running = data?.running ?? [] // Echte Pool-Belegung (Unified-RAM inkl. KV) — schlanke Glance-Bar. Die volle // Segment-Bar + „Alle entladen" bleibt im Modell-Manager. const gttTotal = sysStatus?.gpu?.gtt_total || sysStatus?.gpu?.vram_total || 0 const gttUsed = sysStatus?.gpu?.gtt_used || 0 const poolPct = gttTotal > 0 ? Math.min(100, (gttUsed / gttTotal) * 100) : 0 const poolBar = poolPct >= 88 ? "bg-red-500" : poolPct >= 70 ? "bg-amber-500" : "bg-teal-500" const poolText = poolPct >= 88 ? "text-red-400" : poolPct >= 70 ? "text-amber-400" : "text-foreground" // „Inferenz aktiv": total_tokens seit letztem Poll gestiegen → es wird generiert. const prev = useRef(null) const [generating, setGenerating] = useState(false) useEffect(() => { if (!stats) return const total = stats.total_tokens if (prev.current !== null && total > prev.current) { setGenerating(true) const t = setTimeout(() => setGenerating(false), 4_000) prev.current = total return () => clearTimeout(t) } prev.current = total }, [stats?.total_tokens]) return (

Modelle

{generating ? : } {generating ? "Inferenz aktiv" : "Idle"}
{gttTotal > 0 && (
Speicher-Pool {gb(gttUsed)} / {gb(gttTotal)} GB belegt
)}
{ROLES.map((role) => { const m = models.find((x) => x.role === role) const isRunning = m ? running.includes(m.name) : false return (
{m ? m.name.split("/").pop()?.replace(/\.gguf$/i, "") : "nicht zugewiesen"} {m && (
{fmtSize(m.size_bytes)} {m.prompt_cache && ( PC )} {m.spec_active && ( SPEC )} {m.parallel_slots > 1 && ( SLOTS: {m.parallel_slots} )} {m.incomplete && ( ⚠ fehlt )}
)}
{m ? ( isRunning ? ( warm ) : ( bereit ) ) : ( )}
) })}
Laden erfolgt automatisch per Auto-Swap. Details & Routing im Modell-Manager.
) }