Files
atc-agents/src/components/GpuPanel.tsx
T
mo a11621b21f Add Command Center v2: DQ/RAG integration, S3 browser, Jupyter, GPU matrix.
Mirror mo/atc-GPU layout with config/, docs/, scripts/ for Gitea deploy.
2026-06-25 00:28:23 +00:00

138 lines
5.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { GpuStatus } from '../types'
type Props = {
gpu: GpuStatus | null
compact?: boolean
}
function memPct(used: number, total: number) {
if (!total) return 0
return Math.round((used / total) * 100)
}
export function GpuPanel({ gpu, compact }: Props) {
if (!gpu) {
return (
<div className="panel rounded-2xl p-5 animate-pulse">
<div className="h-4 w-32 rounded bg-[var(--surface-elevated)] mb-4" />
<div className="h-24 rounded-xl bg-[var(--surface-elevated)]" />
</div>
)
}
const online = gpu.ok
const gpus = gpu.gpus || []
const avgUtil = gpus.length ? gpus.reduce((s, g) => s + g.util_gpu, 0) / gpus.length : 0
const avgVram = gpus.length ? gpus.reduce((s, g) => s + memPct(g.memory_used_mib, g.memory_total_mib), 0) / gpus.length : 0
if (compact) {
return (
<div className="panel rounded-2xl p-4 relative overflow-hidden">
<div className="absolute -top-8 -right-8 w-24 h-24 rounded-full bg-[var(--accent-gpu)] opacity-[0.1] blur-2xl pointer-events-none" />
<div className="flex items-center justify-between gap-2 mb-3 relative">
<div className="flex items-center gap-2">
<span></span>
<h2 className="font-bold text-sm text-[var(--accent-gpu)]">GPU Lab</h2>
</div>
<a href={gpu.ui_url} target="_blank" rel="noopener noreferrer" className="text-[10px] font-mono text-[var(--accent-gpu)] hover:underline">
Open
</a>
</div>
{gpu.active_model && (
<div className="text-sm font-semibold text-[var(--text)] truncate">{gpu.active_model}</div>
)}
<div className="flex gap-3 mt-2 text-[10px] font-mono text-[var(--text-muted)]">
<span>{gpu.gpu_count ?? gpus.length}× V100</span>
<span>{Math.round(avgUtil)}% util</span>
<span>{Math.round(avgVram)}% VRAM</span>
<span className={online && gpu.inference_active ? 'text-[var(--status-ok)]' : 'text-[var(--status-warn)]'}>
{online ? (gpu.inference_active ? 'ON' : 'STBY') : 'OFF'}
</span>
</div>
<div className="h-1.5 rounded-full bg-[var(--surface-muted)] mt-3 overflow-hidden">
<div
className="h-full rounded-full transition-all duration-500"
style={{
width: `${Math.max(avgUtil, avgVram * 0.5)}%`,
background: 'linear-gradient(90deg, var(--accent-gpu), var(--accent))',
}}
/>
</div>
</div>
)
}
return (
<div className="panel rounded-2xl p-5 relative overflow-hidden">
<div className="absolute -top-12 -right-12 w-40 h-40 rounded-full bg-[var(--accent-gpu)] opacity-[0.08] blur-2xl pointer-events-none" />
<div className="flex flex-wrap items-start justify-between gap-3 mb-4 relative">
<div>
<p className="section-eyebrow">Inference cluster</p>
<div className="flex items-center gap-2">
<span className="text-lg" aria-hidden></span>
<h2 className="font-display font-bold text-[var(--accent-gpu)] tracking-tight">GPU Lab</h2>
<span
className={`text-[10px] font-mono px-2 py-0.5 rounded-full border ${
online && gpu.inference_active
? 'border-[var(--status-ok)] text-[var(--status-ok)] bg-[var(--status-ok-bg)]'
: 'border-[var(--status-warn)] text-[var(--status-warn)] bg-[var(--status-warn-bg)]'
}`}
>
{online ? (gpu.inference_active ? 'INFERENCE ON' : 'STANDBY') : 'OFFLINE'}
</span>
</div>
<p className="text-xs font-mono text-[var(--text-muted)] mt-1">
atc-gpu-dev · {gpu.host} · {gpu.gpu_count ?? gpus.length}× V100
</p>
</div>
<a href={gpu.ui_url} target="_blank" rel="noopener noreferrer" className="btn-secondary text-xs shrink-0">
Open GPU Manager
</a>
</div>
{gpu.active_model && (
<div className="rounded-xl border border-[var(--border)] bg-[var(--surface-elevated)] px-4 py-3 mb-4">
<div className="text-[10px] font-mono uppercase tracking-widest text-[var(--text-faint)]">Active model</div>
<div className="font-semibold text-[var(--text)] mt-0.5">{gpu.active_model}</div>
{gpu.vllm_url && (
<div className="text-[11px] font-mono text-[var(--text-muted)] mt-1 truncate">{gpu.vllm_url}</div>
)}
</div>
)}
{!online && (
<p className="text-sm text-[var(--status-down)]">{gpu.error || 'GPU manager unreachable'}</p>
)}
<div className="grid gap-2 sm:grid-cols-2">
{gpus.map((g) => {
const pct = memPct(g.memory_used_mib, g.memory_total_mib)
return (
<div key={g.index} className="gpu-card rounded-xl p-3 border border-[var(--border)]">
<div className="flex justify-between items-center mb-2">
<span className="text-xs font-mono font-semibold text-[var(--accent-gpu)]">GPU {g.index}</span>
<span className="text-[10px] font-mono text-[var(--text-faint)]">{g.temperature_c}°C · {g.power_w}W</span>
</div>
<div className="text-[11px] text-[var(--text-muted)] truncate mb-2">{g.name}</div>
<div className="flex gap-3 text-[10px] font-mono mb-1.5">
<span>Util {Math.round(g.util_gpu)}%</span>
<span>VRAM {pct}%</span>
</div>
<div className="h-1.5 rounded-full bg-[var(--surface-muted)] overflow-hidden">
<div
className="h-full rounded-full transition-all duration-500"
style={{
width: `${Math.max(g.util_gpu, pct * 0.3)}%`,
background: 'linear-gradient(90deg, var(--accent-gpu), var(--accent))',
}}
/>
</div>
</div>
)
})}
</div>
</div>
)
}