Spaces:
Running
Running
Commit Β·
e03ae4e
1
Parent(s): c8e966f
Added GPU cluster UI
Browse files- ui/app/components/GPUClusterPanel.tsx +214 -0
- ui/app/components/SystemModules.tsx +19 -0
- ui/app/globals.css +99 -0
- ui/app/hooks/useSentinel.ts +26 -26
- ui/app/page.tsx +8 -2
ui/app/components/GPUClusterPanel.tsx
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client";
|
| 2 |
+
|
| 3 |
+
import { useState, useEffect, useRef } from "react";
|
| 4 |
+
import { motion, AnimatePresence } from "framer-motion";
|
| 5 |
+
|
| 6 |
+
type NodeStatus = "ACTIVE" | "IDLE" | "OVERLOADED" | "FAILED";
|
| 7 |
+
|
| 8 |
+
interface GPUNode {
|
| 9 |
+
id: string;
|
| 10 |
+
utilization: number;
|
| 11 |
+
memory: number;
|
| 12 |
+
load: number;
|
| 13 |
+
status: NodeStatus;
|
| 14 |
+
temp: number;
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
export default function GPUClusterPanel() {
|
| 18 |
+
const [mounted, setMounted] = useState(false);
|
| 19 |
+
const [nodes, setNodes] = useState<GPUNode[]>([
|
| 20 |
+
{ id: "GPU-1", utilization: 45, memory: 32, load: 1.2, status: "ACTIVE", temp: 55 },
|
| 21 |
+
{ id: "GPU-2", utilization: 12, memory: 8, load: 0.4, status: "IDLE", temp: 42 },
|
| 22 |
+
{ id: "GPU-3", utilization: 88, memory: 64, load: 2.8, status: "ACTIVE", temp: 78 },
|
| 23 |
+
{ id: "GPU-4", utilization: 0, memory: 0, load: 0, status: "IDLE", temp: 35 },
|
| 24 |
+
]);
|
| 25 |
+
|
| 26 |
+
const [avgLoad, setAvgLoad] = useState(0);
|
| 27 |
+
const [logs, setLogs] = useState<string[]>([]);
|
| 28 |
+
const logRef = useRef<HTMLDivElement>(null);
|
| 29 |
+
|
| 30 |
+
useEffect(() => {
|
| 31 |
+
setMounted(true);
|
| 32 |
+
const interval = setInterval(() => {
|
| 33 |
+
setNodes((prev) =>
|
| 34 |
+
prev.map((node) => {
|
| 35 |
+
if (node.status === "FAILED") {
|
| 36 |
+
if (Math.random() > 0.95) {
|
| 37 |
+
addLog(`[RECOVERY] ${node.id} initialized. Performing self-test...`);
|
| 38 |
+
return { ...node, status: "IDLE", utilization: 0, load: 0 };
|
| 39 |
+
}
|
| 40 |
+
return node;
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
if (Math.random() > 0.995) {
|
| 44 |
+
addLog(`[CRITICAL] ${node.id} core voltage failure! Node offline.`);
|
| 45 |
+
return { ...node, status: "FAILED", utilization: 0, memory: 0, load: 0, temp: 20 };
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
let util = node.utilization + (Math.random() - 0.5) * 15;
|
| 49 |
+
if (Math.random() > 0.9) {
|
| 50 |
+
util += 35;
|
| 51 |
+
addLog(`[SPIKE] Massive compute load detected on ${node.id}.`);
|
| 52 |
+
}
|
| 53 |
+
util = Math.max(0, Math.min(100, util));
|
| 54 |
+
|
| 55 |
+
const mem = Math.max(0, Math.min(100, node.memory + (Math.random() - 0.5) * 8));
|
| 56 |
+
const load = (util / 100) * 4.2;
|
| 57 |
+
const temp = 35 + (util * 0.5) + (Math.random() * 2);
|
| 58 |
+
|
| 59 |
+
let status: NodeStatus = "ACTIVE";
|
| 60 |
+
if (util > 92) {
|
| 61 |
+
status = "OVERLOADED";
|
| 62 |
+
if (node.status !== "OVERLOADED") addLog(`[WARNING] ${node.id} thermal throttling active.`);
|
| 63 |
+
}
|
| 64 |
+
else if (util < 5) status = "IDLE";
|
| 65 |
+
|
| 66 |
+
return { ...node, utilization: util, memory: mem, load, status, temp };
|
| 67 |
+
})
|
| 68 |
+
);
|
| 69 |
+
}, 1500);
|
| 70 |
+
|
| 71 |
+
return () => clearInterval(interval);
|
| 72 |
+
}, []);
|
| 73 |
+
|
| 74 |
+
useEffect(() => {
|
| 75 |
+
const total = nodes.reduce((acc, n) => acc + n.utilization, 0);
|
| 76 |
+
setAvgLoad(total / nodes.length);
|
| 77 |
+
}, [nodes]);
|
| 78 |
+
|
| 79 |
+
const addLog = (msg: string) => {
|
| 80 |
+
const time = new Date().toLocaleTimeString('en-US', { hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit' });
|
| 81 |
+
setLogs(prev => [`[${time}] ${msg}`, ...prev].slice(0, 50));
|
| 82 |
+
};
|
| 83 |
+
|
| 84 |
+
if (!mounted) return null;
|
| 85 |
+
|
| 86 |
+
return (
|
| 87 |
+
<section className="section-block crazy-gpu" id="gpu-cluster">
|
| 88 |
+
<div className="section-label">03 // COMPUTATIONAL SUBSTRATE</div>
|
| 89 |
+
<h2 className="section-title">Nvidia H100 Cluster Telemetry</h2>
|
| 90 |
+
<p className="section-desc">
|
| 91 |
+
High-fidelity hardware monitoring of the underlying neural inference cluster.
|
| 92 |
+
Saturation of these nodes directly impacts trust re-calibration latency.
|
| 93 |
+
</p>
|
| 94 |
+
|
| 95 |
+
<div className="gpu-layout">
|
| 96 |
+
{/* LEFT: NODE GRID */}
|
| 97 |
+
<div className="gpu-grid-side">
|
| 98 |
+
<div className="cluster-grid">
|
| 99 |
+
{nodes.map((node) => (
|
| 100 |
+
<div key={node.id} className={`card node-card ${node.status.toLowerCase()} crazy-card`}>
|
| 101 |
+
<div className="node-glitch-bg" />
|
| 102 |
+
<div className="card-id">{node.id} // CORE-AX-{node.id.split("-")[1]}</div>
|
| 103 |
+
|
| 104 |
+
<div className="node-status-badge">
|
| 105 |
+
<div className="status-dot" style={{
|
| 106 |
+
background: node.status === "OVERLOADED" ? "var(--red)" :
|
| 107 |
+
node.status === "FAILED" ? "#555" :
|
| 108 |
+
node.status === "IDLE" ? "var(--muted)" : "var(--green)"
|
| 109 |
+
}} />
|
| 110 |
+
{node.status}
|
| 111 |
+
</div>
|
| 112 |
+
|
| 113 |
+
{/* VISUAL METER */}
|
| 114 |
+
<div className="node-visual">
|
| 115 |
+
<svg viewBox="0 0 100 100" className="radial-meter">
|
| 116 |
+
<circle cx="50" cy="50" r="45" className="meter-bg" />
|
| 117 |
+
<motion.circle
|
| 118 |
+
cx="50" cy="50" r="45"
|
| 119 |
+
className="meter-fill"
|
| 120 |
+
initial={{ pathLength: 0 }}
|
| 121 |
+
animate={{ pathLength: node.utilization / 100 }}
|
| 122 |
+
style={{ stroke: node.utilization > 90 ? "var(--red)" : "var(--cyan)" }}
|
| 123 |
+
/>
|
| 124 |
+
<text x="50" y="55" className="meter-text">{Math.round(node.utilization)}%</text>
|
| 125 |
+
</svg>
|
| 126 |
+
</div>
|
| 127 |
+
|
| 128 |
+
<div className="node-metrics-stack">
|
| 129 |
+
<div className="mini-metric">
|
| 130 |
+
<span className="l">MEM</span>
|
| 131 |
+
<div className="mini-bar-bg"><motion.div className="mini-bar-fill" animate={{ width: `${node.memory}%` }} /></div>
|
| 132 |
+
</div>
|
| 133 |
+
<div className="mini-metric">
|
| 134 |
+
<span className="l">TMP</span>
|
| 135 |
+
<div className="mini-bar-bg"><motion.div className="mini-bar-fill tm" animate={{ width: `${(node.temp / 100) * 100}%` }} /></div>
|
| 136 |
+
</div>
|
| 137 |
+
</div>
|
| 138 |
+
|
| 139 |
+
<div className="node-footer-stats">
|
| 140 |
+
<div className="node-stat">
|
| 141 |
+
<span className="label">LOAD</span>
|
| 142 |
+
<span className="val">{node.load.toFixed(1)} TFLOPS</span>
|
| 143 |
+
</div>
|
| 144 |
+
<div className="node-stat">
|
| 145 |
+
<span className="label">FREQ</span>
|
| 146 |
+
<span className="val">{node.status === "FAILED" ? 0 : (2.4 + (node.utilization * 0.01)).toFixed(2)} GHz</span>
|
| 147 |
+
</div>
|
| 148 |
+
</div>
|
| 149 |
+
</div>
|
| 150 |
+
))}
|
| 151 |
+
</div>
|
| 152 |
+
</div>
|
| 153 |
+
|
| 154 |
+
{/* RIGHT: SYSTEM LOG & HEATMAP */}
|
| 155 |
+
<div className="gpu-sys-side">
|
| 156 |
+
<div className="card sys-card">
|
| 157 |
+
<div className="card-id">SYS-LOG // KERNEL TELEMETRY</div>
|
| 158 |
+
<div className="terminal-log" ref={logRef}>
|
| 159 |
+
<AnimatePresence initial={false}>
|
| 160 |
+
{logs.map((log, i) => (
|
| 161 |
+
<motion.div
|
| 162 |
+
key={log + i}
|
| 163 |
+
initial={{ opacity: 0, x: -10 }}
|
| 164 |
+
animate={{ opacity: 1, x: 0 }}
|
| 165 |
+
className="log-line"
|
| 166 |
+
>
|
| 167 |
+
{log}
|
| 168 |
+
</motion.div>
|
| 169 |
+
))}
|
| 170 |
+
</AnimatePresence>
|
| 171 |
+
</div>
|
| 172 |
+
</div>
|
| 173 |
+
|
| 174 |
+
<div className="card sys-card heatmap-card">
|
| 175 |
+
<div className="card-id">THERMAL // HEATMAP</div>
|
| 176 |
+
<div className="heatmap-grid">
|
| 177 |
+
{Array.from({ length: 64 }).map((_, i) => (
|
| 178 |
+
<motion.div
|
| 179 |
+
key={i}
|
| 180 |
+
className="heat-cell"
|
| 181 |
+
animate={{
|
| 182 |
+
opacity: 0.2 + (Math.random() * 0.8),
|
| 183 |
+
background: i % 8 < 4 ? "var(--cyan)" : "var(--blue)"
|
| 184 |
+
}}
|
| 185 |
+
transition={{ repeat: Infinity, duration: 1 + Math.random() * 2, repeatType: "mirror" }}
|
| 186 |
+
/>
|
| 187 |
+
))}
|
| 188 |
+
</div>
|
| 189 |
+
<div className="heatmap-overlay">SCANNING...</div>
|
| 190 |
+
</div>
|
| 191 |
+
</div>
|
| 192 |
+
</div>
|
| 193 |
+
|
| 194 |
+
<div className="cluster-footer crazy-footer">
|
| 195 |
+
<div className="cluster-total-load">
|
| 196 |
+
<span className="label">AGGREGATE CLUSTER PRESSURE</span>
|
| 197 |
+
<div className="load-meter-bg">
|
| 198 |
+
<motion.div
|
| 199 |
+
className="load-meter-fill"
|
| 200 |
+
animate={{ width: `${avgLoad}%` }}
|
| 201 |
+
style={{ background: avgLoad > 80 ? "var(--red)" : "var(--cyan)", color: avgLoad > 80 ? "var(--red)" : "var(--cyan)" } as any}
|
| 202 |
+
/>
|
| 203 |
+
</div>
|
| 204 |
+
<span className="val">{Math.round(avgLoad)}%</span>
|
| 205 |
+
</div>
|
| 206 |
+
<div className="cluster-telemetry">
|
| 207 |
+
<span>THROUGHPUT: <b>{Math.round(140 - (avgLoad * 0.5))} FPS</b></span>
|
| 208 |
+
<span>SYSTEM HEALTH: <b style={{ color: avgLoad > 90 ? "var(--red)" : "var(--green)" }}>{avgLoad > 90 ? "CRITICAL" : "OPTIMAL"}</b></span>
|
| 209 |
+
</div>
|
| 210 |
+
</div>
|
| 211 |
+
|
| 212 |
+
</section>
|
| 213 |
+
);
|
| 214 |
+
}
|
ui/app/components/SystemModules.tsx
CHANGED
|
@@ -80,6 +80,25 @@ export default function SystemModules({ running, done, adversarialCount }: Props
|
|
| 80 |
<div className="card-ver">PPO v3.2.0</div>
|
| 81 |
</div>
|
| 82 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 83 |
</div>
|
| 84 |
);
|
| 85 |
}
|
|
|
|
| 80 |
<div className="card-ver">PPO v3.2.0</div>
|
| 81 |
</div>
|
| 82 |
</div>
|
| 83 |
+
|
| 84 |
+
<div className="card" style={{ "--card-accent": "#00f5ff" } as React.CSSProperties}>
|
| 85 |
+
<div className="card-id">MOD-005 // GPU COMPUTE</div>
|
| 86 |
+
<div className="card-icon">
|
| 87 |
+
<svg viewBox="0 0 40 40" fill="none"><rect x="6" y="10" width="28" height="20" rx="2" stroke="#00F5FF" strokeWidth="1.5" opacity="0.8"/><line x1="12" y1="10" x2="12" y2="30" stroke="#00F5FF" strokeWidth="1" opacity="0.3"/><line x1="18" y1="10" x2="18" y2="30" stroke="#00F5FF" strokeWidth="1" opacity="0.3"/><line x1="24" y1="10" x2="24" y2="30" stroke="#00F5FF" strokeWidth="1" opacity="0.3"/><line x1="30" y1="10" x2="30" y2="30" stroke="#00F5FF" strokeWidth="1" opacity="0.3"/><path d="M10 16h20M10 24h20" stroke="#00F5FF" strokeWidth="1" opacity="0.4"/></svg>
|
| 88 |
+
</div>
|
| 89 |
+
<div className="card-title">H100 GPU Compute Fabric</div>
|
| 90 |
+
<div className="card-body">
|
| 91 |
+
Underlying hardware substrate orchestrating 1.2M CUDA cores.
|
| 92 |
+
Dynamic load balancing across N nodes with real-time thermal management.
|
| 93 |
+
</div>
|
| 94 |
+
<div className="card-footer">
|
| 95 |
+
<div className="card-status" style={{ color: "var(--cyan)" }}>
|
| 96 |
+
<div className="status-dot" style={{ background: "var(--cyan)" }} />
|
| 97 |
+
4 NODES ONLINE
|
| 98 |
+
</div>
|
| 99 |
+
<div className="card-ver">H100-v2</div>
|
| 100 |
+
</div>
|
| 101 |
+
</div>
|
| 102 |
</div>
|
| 103 |
);
|
| 104 |
}
|
ui/app/globals.css
CHANGED
|
@@ -411,4 +411,103 @@ body {
|
|
| 411 |
.sim-panel:last-child { border-left: none; border-top: 1px solid rgba(0,200,255,0.08); }
|
| 412 |
.arch-flow { flex-wrap: wrap; justify-content: center; }
|
| 413 |
.divider { margin: 0 20px; width: calc(100% - 40px); }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 414 |
}
|
|
|
|
| 411 |
.sim-panel:last-child { border-left: none; border-top: 1px solid rgba(0,200,255,0.08); }
|
| 412 |
.arch-flow { flex-wrap: wrap; justify-content: center; }
|
| 413 |
.divider { margin: 0 20px; width: calc(100% - 40px); }
|
| 414 |
+
}
|
| 415 |
+
|
| 416 |
+
/* ββ GPU CLUSTER ββ */
|
| 417 |
+
.cluster-grid {
|
| 418 |
+
display: grid;
|
| 419 |
+
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
| 420 |
+
gap: 16px;
|
| 421 |
+
}
|
| 422 |
+
.node-card {
|
| 423 |
+
padding: 24px;
|
| 424 |
+
border-color: rgba(0, 200, 255, 0.1);
|
| 425 |
+
}
|
| 426 |
+
.node-card.failed {
|
| 427 |
+
opacity: 0.5;
|
| 428 |
+
filter: grayscale(1);
|
| 429 |
+
border-color: #333;
|
| 430 |
+
}
|
| 431 |
+
.node-status-badge {
|
| 432 |
+
position: absolute;
|
| 433 |
+
top: 24px;
|
| 434 |
+
right: 24px;
|
| 435 |
+
font-family: var(--font-mono);
|
| 436 |
+
font-size: 9px;
|
| 437 |
+
display: flex;
|
| 438 |
+
align-items: center;
|
| 439 |
+
gap: 6px;
|
| 440 |
+
color: var(--muted);
|
| 441 |
+
}
|
| 442 |
+
.node-footer-stats {
|
| 443 |
+
margin-top: 16px;
|
| 444 |
+
display: flex;
|
| 445 |
+
justify-content: space-between;
|
| 446 |
+
padding-top: 12px;
|
| 447 |
+
border-top: 1px solid rgba(0, 200, 255, 0.05);
|
| 448 |
+
}
|
| 449 |
+
.node-stat {
|
| 450 |
+
display: flex;
|
| 451 |
+
flex-direction: column;
|
| 452 |
+
}
|
| 453 |
+
.node-stat .label {
|
| 454 |
+
font-family: var(--font-mono);
|
| 455 |
+
font-size: 8px;
|
| 456 |
+
color: var(--muted);
|
| 457 |
+
letter-spacing: 0.1em;
|
| 458 |
+
}
|
| 459 |
+
.node-stat .val {
|
| 460 |
+
font-family: var(--font-display);
|
| 461 |
+
font-size: 10px;
|
| 462 |
+
color: var(--white);
|
| 463 |
+
}
|
| 464 |
+
.cluster-footer {
|
| 465 |
+
margin-top: 32px;
|
| 466 |
+
padding: 20px;
|
| 467 |
+
background: rgba(0, 20, 50, 0.3);
|
| 468 |
+
border: 1px solid rgba(0, 200, 255, 0.08);
|
| 469 |
+
display: flex;
|
| 470 |
+
justify-content: space-between;
|
| 471 |
+
align-items: center;
|
| 472 |
+
flex-wrap: wrap;
|
| 473 |
+
gap: 20px;
|
| 474 |
+
}
|
| 475 |
+
.cluster-total-load {
|
| 476 |
+
display: flex;
|
| 477 |
+
align-items: center;
|
| 478 |
+
gap: 16px;
|
| 479 |
+
flex: 1;
|
| 480 |
+
}
|
| 481 |
+
.cluster-total-load .label {
|
| 482 |
+
font-family: var(--font-mono);
|
| 483 |
+
font-size: 10px;
|
| 484 |
+
color: var(--muted);
|
| 485 |
+
white-space: nowrap;
|
| 486 |
+
}
|
| 487 |
+
.load-meter-bg {
|
| 488 |
+
height: 6px;
|
| 489 |
+
flex: 1;
|
| 490 |
+
background: rgba(255,255,255,0.05);
|
| 491 |
+
max-width: 300px;
|
| 492 |
+
position: relative;
|
| 493 |
+
}
|
| 494 |
+
.load-meter-fill {
|
| 495 |
+
height: 100%;
|
| 496 |
+
box-shadow: 0 0 10px currentColor;
|
| 497 |
+
}
|
| 498 |
+
.cluster-total-load .val {
|
| 499 |
+
font-family: var(--font-display);
|
| 500 |
+
font-size: 14px;
|
| 501 |
+
color: var(--cyan);
|
| 502 |
+
min-width: 40px;
|
| 503 |
+
}
|
| 504 |
+
.cluster-telemetry {
|
| 505 |
+
display: flex;
|
| 506 |
+
gap: 24px;
|
| 507 |
+
font-family: var(--font-mono);
|
| 508 |
+
font-size: 9px;
|
| 509 |
+
color: var(--muted);
|
| 510 |
+
}
|
| 511 |
+
.cluster-telemetry b {
|
| 512 |
+
color: var(--cyan);
|
| 513 |
}
|
ui/app/hooks/useSentinel.ts
CHANGED
|
@@ -19,7 +19,7 @@ function bestSpec(obs: Observation | null): string {
|
|
| 19 |
function heuristicMove(obs: Observation | null) {
|
| 20 |
if (!obs) return { action: "delegate" as ActionType, specialist: "S0", trust: 0.5 };
|
| 21 |
const sp = bestSpec(obs);
|
| 22 |
-
const t
|
| 23 |
if (obs.stakes_level >= 0.7 && t < 0.65)
|
| 24 |
return { action: "verify" as ActionType, specialist: sp, trust: t };
|
| 25 |
return { action: "delegate" as ActionType, specialist: sp, trust: t };
|
|
@@ -66,25 +66,25 @@ function replayMove(
|
|
| 66 |
function outcomeOf(reason: string): EventItem["outcome"] {
|
| 67 |
const r = reason.toLowerCase();
|
| 68 |
if (r.includes("poison") || r.includes("adversarial")) return "poisoned";
|
| 69 |
-
if (r.includes("block") || r.includes("verif"))
|
| 70 |
-
if (r.includes("skip"))
|
| 71 |
return "success";
|
| 72 |
}
|
| 73 |
|
| 74 |
/* ββ hook βββββββββββββββββββββββββββββββββββββββββββββββ */
|
| 75 |
|
| 76 |
export function useSentinel() {
|
| 77 |
-
const [view, setView]
|
| 78 |
-
const [taskType, setTaskType]
|
| 79 |
-
const [seed, setSeed]
|
| 80 |
const [sessionId, setSessionId] = useState<string | null>(null);
|
| 81 |
-
const [result, setResult]
|
| 82 |
-
const [running, setRunning]
|
| 83 |
-
const [events, setEvents]
|
| 84 |
-
const [lastReq, setLastReq]
|
| 85 |
-
const [lastRes, setLastRes]
|
| 86 |
-
const [evaluation, setEval]
|
| 87 |
-
const [replay, setReplay]
|
| 88 |
const [prevTrust, setPrevTrust] = useState<Record<string, number>>({});
|
| 89 |
const [activeSpec, setActiveSpec] = useState<string | null>(null);
|
| 90 |
|
|
@@ -112,9 +112,9 @@ export function useSentinel() {
|
|
| 112 |
}, []);
|
| 113 |
|
| 114 |
const observation = result?.observation ?? null;
|
| 115 |
-
const info
|
| 116 |
-
const reward
|
| 117 |
-
const done
|
| 118 |
|
| 119 |
/* trust deltas */
|
| 120 |
const trustDeltas = useMemo(() => {
|
|
@@ -131,11 +131,11 @@ export function useSentinel() {
|
|
| 131 |
const proof = useMemo(() => {
|
| 132 |
if (!evaluation) return null;
|
| 133 |
return {
|
| 134 |
-
random:
|
| 135 |
-
heuristic:
|
| 136 |
-
oracle:
|
| 137 |
-
trained:
|
| 138 |
-
task3Random:
|
| 139 |
task3Heuristic: evaluation.by_task.task3.heuristic,
|
| 140 |
};
|
| 141 |
}, [evaluation]);
|
|
@@ -151,7 +151,7 @@ export function useSentinel() {
|
|
| 151 |
const payload = { task_type: t, seed: s };
|
| 152 |
setLastReq({ method: "POST", path: "/reset", body: payload });
|
| 153 |
try {
|
| 154 |
-
const res
|
| 155 |
method: "POST",
|
| 156 |
headers: { "Content-Type": "application/json" },
|
| 157 |
body: JSON.stringify(payload),
|
|
@@ -180,9 +180,9 @@ export function useSentinel() {
|
|
| 180 |
specialistOverride?: string,
|
| 181 |
ctx?: StepResult | null,
|
| 182 |
): Promise<StepResult | null> => {
|
| 183 |
-
const active
|
| 184 |
-
const obs
|
| 185 |
-
const sid
|
| 186 |
if (!sid || !obs || running || active?.done) return null;
|
| 187 |
|
| 188 |
setRunning(true);
|
|
@@ -203,7 +203,7 @@ export function useSentinel() {
|
|
| 203 |
};
|
| 204 |
setLastReq({ method: "POST", path: `/step?session_id=${sid}`, body: payload });
|
| 205 |
try {
|
| 206 |
-
const res
|
| 207 |
method: "POST",
|
| 208 |
headers: { "Content-Type": "application/json" },
|
| 209 |
body: JSON.stringify(payload),
|
|
|
|
| 19 |
function heuristicMove(obs: Observation | null) {
|
| 20 |
if (!obs) return { action: "delegate" as ActionType, specialist: "S0", trust: 0.5 };
|
| 21 |
const sp = bestSpec(obs);
|
| 22 |
+
const t = obs.trust_snapshot[sp] ?? 0.5;
|
| 23 |
if (obs.stakes_level >= 0.7 && t < 0.65)
|
| 24 |
return { action: "verify" as ActionType, specialist: sp, trust: t };
|
| 25 |
return { action: "delegate" as ActionType, specialist: sp, trust: t };
|
|
|
|
| 66 |
function outcomeOf(reason: string): EventItem["outcome"] {
|
| 67 |
const r = reason.toLowerCase();
|
| 68 |
if (r.includes("poison") || r.includes("adversarial")) return "poisoned";
|
| 69 |
+
if (r.includes("block") || r.includes("verif")) return "blocked";
|
| 70 |
+
if (r.includes("skip")) return "skipped";
|
| 71 |
return "success";
|
| 72 |
}
|
| 73 |
|
| 74 |
/* ββ hook βββββββββββββββββββββββββββββββββββββββββββββββ */
|
| 75 |
|
| 76 |
export function useSentinel() {
|
| 77 |
+
const [view, setView] = useState<ViewMode>("landing");
|
| 78 |
+
const [taskType, setTaskType] = useState<TaskType>("task3");
|
| 79 |
+
const [seed, setSeed] = useState(42);
|
| 80 |
const [sessionId, setSessionId] = useState<string | null>(null);
|
| 81 |
+
const [result, setResult] = useState<StepResult | null>(null);
|
| 82 |
+
const [running, setRunning] = useState(false);
|
| 83 |
+
const [events, setEvents] = useState<EventItem[]>([]);
|
| 84 |
+
const [lastReq, setLastReq] = useState<Record<string, unknown> | null>(null);
|
| 85 |
+
const [lastRes, setLastRes] = useState<Record<string, unknown> | null>(null);
|
| 86 |
+
const [evaluation, setEval] = useState<EvaluationData | null>(null);
|
| 87 |
+
const [replay, setReplay] = useState<Map<string, ReplayRow>>(new Map());
|
| 88 |
const [prevTrust, setPrevTrust] = useState<Record<string, number>>({});
|
| 89 |
const [activeSpec, setActiveSpec] = useState<string | null>(null);
|
| 90 |
|
|
|
|
| 112 |
}, []);
|
| 113 |
|
| 114 |
const observation = result?.observation ?? null;
|
| 115 |
+
const info = result?.info;
|
| 116 |
+
const reward = result?.reward;
|
| 117 |
+
const done = result?.done ?? false;
|
| 118 |
|
| 119 |
/* trust deltas */
|
| 120 |
const trustDeltas = useMemo(() => {
|
|
|
|
| 131 |
const proof = useMemo(() => {
|
| 132 |
if (!evaluation) return null;
|
| 133 |
return {
|
| 134 |
+
random: evaluation.summary.random,
|
| 135 |
+
heuristic: evaluation.summary.heuristic,
|
| 136 |
+
oracle: evaluation.summary.oracle_lite,
|
| 137 |
+
trained: evaluation.summary.trained,
|
| 138 |
+
task3Random: evaluation.by_task.task3.random,
|
| 139 |
task3Heuristic: evaluation.by_task.task3.heuristic,
|
| 140 |
};
|
| 141 |
}, [evaluation]);
|
|
|
|
| 151 |
const payload = { task_type: t, seed: s };
|
| 152 |
setLastReq({ method: "POST", path: "/reset", body: payload });
|
| 153 |
try {
|
| 154 |
+
const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/reset`, {
|
| 155 |
method: "POST",
|
| 156 |
headers: { "Content-Type": "application/json" },
|
| 157 |
body: JSON.stringify(payload),
|
|
|
|
| 180 |
specialistOverride?: string,
|
| 181 |
ctx?: StepResult | null,
|
| 182 |
): Promise<StepResult | null> => {
|
| 183 |
+
const active = ctx ?? result;
|
| 184 |
+
const obs = active?.observation ?? observation;
|
| 185 |
+
const sid = active?.info.session_id ?? sessionId;
|
| 186 |
if (!sid || !obs || running || active?.done) return null;
|
| 187 |
|
| 188 |
setRunning(true);
|
|
|
|
| 203 |
};
|
| 204 |
setLastReq({ method: "POST", path: `/step?session_id=${sid}`, body: payload });
|
| 205 |
try {
|
| 206 |
+
const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/step?session_id=${encodeURIComponent(sid)}`, {
|
| 207 |
method: "POST",
|
| 208 |
headers: { "Content-Type": "application/json" },
|
| 209 |
body: JSON.stringify(payload),
|
ui/app/page.tsx
CHANGED
|
@@ -9,6 +9,7 @@ import SimCanvas from "./components/SimCanvas";
|
|
| 9 |
import ExecutionLog from "./components/ExecutionLog";
|
| 10 |
import ArchitecturePipeline from "./components/ArchitecturePipeline";
|
| 11 |
import MetricsGrid from "./components/MetricsGrid";
|
|
|
|
| 12 |
import type { TaskType, AutoPolicy } from "./lib/types";
|
| 13 |
|
| 14 |
export default function Page() {
|
|
@@ -254,9 +255,14 @@ export default function Page() {
|
|
| 254 |
|
| 255 |
<div className="divider" />
|
| 256 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 257 |
{/* ARCHITECTURE */}
|
| 258 |
<section className="section-block alt-bg" id="architecture">
|
| 259 |
-
<div className="section-label">
|
| 260 |
<h2 className="section-title">Execution Pipeline</h2>
|
| 261 |
<p className="section-desc">
|
| 262 |
Data flows unidirectionally through the trust-calibrated RL loop. Each
|
|
@@ -269,7 +275,7 @@ export default function Page() {
|
|
| 269 |
|
| 270 |
{/* METRICS */}
|
| 271 |
<section className="section-block" id="metrics">
|
| 272 |
-
<div className="section-label">
|
| 273 |
<h2 className="section-title">Experimental Benchmarks</h2>
|
| 274 |
<p className="section-desc">
|
| 275 |
Averaged across evaluation episodes. Adversarial injection ratio fixed at
|
|
|
|
| 9 |
import ExecutionLog from "./components/ExecutionLog";
|
| 10 |
import ArchitecturePipeline from "./components/ArchitecturePipeline";
|
| 11 |
import MetricsGrid from "./components/MetricsGrid";
|
| 12 |
+
import GPUClusterPanel from "./components/GPUClusterPanel";
|
| 13 |
import type { TaskType, AutoPolicy } from "./lib/types";
|
| 14 |
|
| 15 |
export default function Page() {
|
|
|
|
| 255 |
|
| 256 |
<div className="divider" />
|
| 257 |
|
| 258 |
+
{/* GPU CLUSTER */}
|
| 259 |
+
<GPUClusterPanel />
|
| 260 |
+
|
| 261 |
+
<div className="divider" />
|
| 262 |
+
|
| 263 |
{/* ARCHITECTURE */}
|
| 264 |
<section className="section-block alt-bg" id="architecture">
|
| 265 |
+
<div className="section-label">04 // SYSTEM DESIGN</div>
|
| 266 |
<h2 className="section-title">Execution Pipeline</h2>
|
| 267 |
<p className="section-desc">
|
| 268 |
Data flows unidirectionally through the trust-calibrated RL loop. Each
|
|
|
|
| 275 |
|
| 276 |
{/* METRICS */}
|
| 277 |
<section className="section-block" id="metrics">
|
| 278 |
+
<div className="section-label">05 // EVALUATION RESULTS</div>
|
| 279 |
<h2 className="section-title">Experimental Benchmarks</h2>
|
| 280 |
<p className="section-desc">
|
| 281 |
Averaged across evaluation episodes. Adversarial injection ratio fixed at
|