Harshit200431 commited on
Commit
e03ae4e
Β·
1 Parent(s): c8e966f

Added GPU cluster UI

Browse files
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 = 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,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")) 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,9 +112,9 @@ export function useSentinel() {
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,11 +131,11 @@ export function useSentinel() {
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,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 = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/reset`, {
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 = 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,7 +203,7 @@ export function useSentinel() {
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),
 
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">03 // SYSTEM DESIGN</div>
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">04 // EVALUATION RESULTS</div>
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