Spaces:
Running
Running
| "use client"; | |
| import { useState, useEffect } from "react"; | |
| import { motion } from "framer-motion"; | |
| type NodeStatus = "ACTIVE" | "IDLE" | "OVERLOADED" | "FAILED"; | |
| interface GPUNode { | |
| id: string; | |
| utilization: number; | |
| memory: number; | |
| load: number; | |
| status: NodeStatus; | |
| } | |
| interface GPUClusterPanelProps { | |
| sessionId?: string; | |
| mode?: string; | |
| gpuPool?: any[]; // Live data from observation | |
| } | |
| export default function GPUClusterPanel({ sessionId, mode, gpuPool }: GPUClusterPanelProps) { | |
| const [mounted, setMounted] = useState(false); | |
| const [nodes, setNodes] = useState<GPUNode[]>([ | |
| { id: "GPU-1", utilization: 0, memory: 0, load: 0, status: "IDLE" }, | |
| { id: "GPU-2", utilization: 0, memory: 0, load: 0, status: "IDLE" }, | |
| { id: "GPU-3", utilization: 0, memory: 0, load: 0, status: "IDLE" }, | |
| { id: "GPU-4", utilization: 0, memory: 0, load: 0, status: "IDLE" }, | |
| ]); | |
| const [avgLoad, setAvgLoad] = useState(0); | |
| const [jitter, setJitter] = useState(0.45); | |
| useEffect(() => { setMounted(true); }, []); | |
| // ββ LIVE SYNC FROM OBSERVATION ββββββββββββββββββββββββββββ | |
| useEffect(() => { | |
| if (gpuPool && Array.isArray(gpuPool)) { | |
| setNodes(gpuPool.slice(0, 4).map((g: any) => { | |
| const util = (g.memory_used / g.memory_total) * 100; | |
| let status = g.state.toUpperCase(); | |
| if (status === "ALLOCATED") status = "ACTIVE"; | |
| return { | |
| id: g.id, | |
| utilization: util, | |
| memory: util, | |
| load: (util / 100) * 4.2, | |
| status: status as NodeStatus | |
| }; | |
| })); | |
| } else if (!sessionId || mode !== "cluster") { | |
| // Fallback to subtle idle simulation if no live data | |
| const timer = setInterval(() => { | |
| setJitter(Math.random() * 0.5); | |
| setNodes(prev => prev.map(n => ({ | |
| ...n, | |
| utilization: Math.max(0, n.utilization + (Math.random() - 0.5) * 2), | |
| load: n.utilization * 0.04 | |
| }))); | |
| }, 2000); | |
| return () => clearInterval(timer); | |
| } | |
| }, [gpuPool, sessionId, mode]); | |
| useEffect(() => { | |
| const total = nodes.reduce((acc, n) => acc + n.utilization, 0); | |
| setAvgLoad(total / nodes.length); | |
| }, [nodes]); | |
| if (!mounted) return null; | |
| return ( | |
| <section className="section-block" id="gpu-cluster"> | |
| <div className="section-label">03 // COMPUTE RESOURCES</div> | |
| <h2 className="section-title">GPU Compute Clusters</h2> | |
| <p className="section-desc"> | |
| Real-time telemetry from the underlying inference hardware. | |
| Note how cluster utilization spikes as the RL model allocates worker jobs. | |
| </p> | |
| <div className="cluster-grid"> | |
| {nodes.map((node) => ( | |
| <div key={node.id} className={`card node-card ${node.status.toLowerCase()}`}> | |
| <div className="card-id">{node.id} // CORE-AX-{node.id.split("-")[1] || "0X"}</div> | |
| <div className="node-status-badge"> | |
| <div className="status-dot" style={{ | |
| background: node.status === "ACTIVE" ? "var(--green)" : | |
| node.status === "OVERLOADED" ? "var(--red)" : | |
| node.status === "FAILED" ? "#555" : "var(--muted)" | |
| }} /> | |
| {node.status} | |
| </div> | |
| <div className="metric-bar-wrap" style={{ marginTop: 20 }}> | |
| <div className="metric-bar-label"> | |
| <span>UTILIZATION</span> | |
| <span style={{ color: "var(--cyan)" }}>{Math.round(node.utilization)}%</span> | |
| </div> | |
| <div className="metric-bar-bg"> | |
| <motion.div | |
| className="metric-bar-fill" | |
| animate={{ width: `${node.utilization}%` }} | |
| transition={{ type: "spring", stiffness: 100, damping: 20 }} | |
| style={{ background: node.utilization > 90 ? "var(--red)" : "var(--cyan)" } as any} | |
| /> | |
| </div> | |
| </div> | |
| <div className="metric-bar-wrap" style={{ marginTop: 12 }}> | |
| <div className="metric-bar-label"> | |
| <span>MEMORY USAGE</span> | |
| <span style={{ color: "var(--green)" }}>{Math.round(node.memory)}%</span> | |
| </div> | |
| <div className="metric-bar-bg"> | |
| <motion.div | |
| className="metric-bar-fill" | |
| animate={{ width: `${node.memory}%` }} | |
| transition={{ type: "spring", stiffness: 100, damping: 20 }} | |
| style={{ background: "var(--green)" } as any} | |
| /> | |
| </div> | |
| </div> | |
| <div className="node-footer-stats"> | |
| <div className="node-stat"> | |
| <span className="label">COMPUTE</span> | |
| <span className="val">{node.load.toFixed(1)} TFLOPS</span> | |
| </div> | |
| <div className="node-stat"> | |
| <span className="label">TEMP</span> | |
| <span className="val">{Math.round(40 + (node.utilization * 0.4))}Β°C</span> | |
| </div> | |
| </div> | |
| </div> | |
| ))} | |
| </div> | |
| <div className="cluster-footer"> | |
| <div className="cluster-total-load"> | |
| <span className="label">TOTAL CLUSTER LOAD</span> | |
| <div className="load-meter-bg"> | |
| <motion.div | |
| className="load-meter-fill" | |
| animate={{ width: `${avgLoad}%` }} | |
| style={{ background: avgLoad > 80 ? "var(--red)" : "var(--cyan)", color: avgLoad > 80 ? "var(--red)" : "var(--cyan)" } as any} | |
| /> | |
| </div> | |
| <span className="val">{Math.round(avgLoad)}%</span> | |
| </div> | |
| <div className="cluster-telemetry"> | |
| <span>THROUGHPUT: <b>{Math.round(140 - (avgLoad * 0.5))} FPS</b></span> | |
| <span>LATENCY: <b>{Math.round(12 + (avgLoad * 0.2))}ms</b></span> | |
| <span>JITTER: <b>{jitter.toFixed(2)}ms</b></span> | |
| </div> | |
| </div> | |
| </section> | |
| ); | |
| } |