Feat: Pool-Auslastungs-Mini-Bar in der Dashboard-Modelle-Karte

Schlanke Glance-Anzeige der echten Speicher-Pool-Belegung (gtt_used/gtt_total
inkl. KV aus sysStatus.gpu) oben in der Modelle-Karte — teal/amber/rot ab 70/88 %.
Keine Karten-Doppelung: die volle interaktive VRAM-Bar + „Alle entladen" bleibt
im Modell-Manager.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-06-29 19:33:36 +02:00
parent c1fac1e688
commit a894a4e952
6 changed files with 113 additions and 92 deletions
@@ -1,7 +1,7 @@
import { useEffect, useRef, useState } from "react"
import { Boxes, Zap, Moon } from "lucide-react"
import { useModels, useTokenStats } from "@/lib/queries"
import { fmtSize } from "@/lib/format"
import { useModels, useTokenStats, useSystemStatus } from "@/lib/queries"
import { fmtSize, gb } from "@/lib/format"
import { cn } from "@/lib/utils"
import { ROLES, roleTone } from "@/components/models/ModelBadges"
@@ -10,9 +10,18 @@ import { ROLES, roleTone } from "@/components/models/ModelBadges"
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<number | null>(null)
const [generating, setGenerating] = useState(false)
@@ -46,6 +55,18 @@ export function ModelsCard() {
</span>
</div>
{gttTotal > 0 && (
<div className="mb-4">
<div className="flex items-center justify-between text-[10px] font-mono text-muted-foreground/80 mb-1">
<span className="uppercase tracking-wider font-semibold">Speicher-Pool</span>
<span><span className={cn("font-bold", poolText)}>{gb(gttUsed)}</span> / {gb(gttTotal)} GB belegt</span>
</div>
<div className="h-1.5 w-full rounded-full bg-background/50 border border-border/30 overflow-hidden">
<div className={cn("h-full rounded-full transition-all duration-500", poolBar)} style={{ width: `${poolPct}%` }} />
</div>
</div>
)}
<div className="space-y-2 flex-1">
{ROLES.map((role) => {
const m = models.find((x) => x.role === role)