XcodeAddy commited on
Commit
e2f4da8
·
1 Parent(s): 1c2514b

Merge remote main with local project

Browse files
ui/app/components/AgentTrustMonitor.tsx CHANGED
@@ -30,7 +30,7 @@ function trustColor(t: number) {
30
  export default function AgentTrustMonitor({
31
  observation, trustDeltas, activeSpec, events, running, totalReward,
32
  }: Props) {
33
- const agents = observation?.available_specialists ?? ["S0", "S1", "S2", "S3", "S4"];
34
  const trust = observation?.trust_snapshot ?? {};
35
  const lastReward = observation?.last_reward ?? 0;
36
 
@@ -108,4 +108,4 @@ export default function AgentTrustMonitor({
108
  </div>
109
  </div>
110
  );
111
- }
 
30
  export default function AgentTrustMonitor({
31
  observation, trustDeltas, activeSpec, events, running, totalReward,
32
  }: Props) {
33
+ const agents = observation?.available_specialists || observation?.available_workers || ["S0", "S1", "S2", "S3", "S4"];
34
  const trust = observation?.trust_snapshot ?? {};
35
  const lastReward = observation?.last_reward ?? 0;
36
 
 
108
  </div>
109
  </div>
110
  );
111
+ }
ui/app/components/GPUClusterPanel.tsx CHANGED
@@ -1,7 +1,7 @@
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
 
@@ -13,79 +13,82 @@ interface GPUNode {
13
  status: NodeStatus;
14
  }
15
 
16
- export default function GPUClusterPanel() {
 
 
 
 
 
 
17
  const [mounted, setMounted] = useState(false);
18
  const [nodes, setNodes] = useState<GPUNode[]>([
19
- { id: "GPU-1", utilization: 45, memory: 32, load: 1.2, status: "ACTIVE" },
20
- { id: "GPU-2", utilization: 12, memory: 8, load: 0.4, status: "IDLE" },
21
- { id: "GPU-3", utilization: 88, memory: 64, load: 2.8, status: "ACTIVE" },
22
  { id: "GPU-4", utilization: 0, memory: 0, load: 0, status: "IDLE" },
23
  ]);
24
 
25
  const [avgLoad, setAvgLoad] = useState(0);
26
  const [jitter, setJitter] = useState(0.45);
27
 
 
 
 
28
  useEffect(() => {
29
- setMounted(true);
30
- const interval = setInterval(() => {
31
- setJitter(Math.random() * 2);
32
- setNodes((prev) =>
33
- prev.map((node) => {
34
- if (node.status === "FAILED") {
35
- if (Math.random() > 0.95) return { ...node, status: "IDLE", utilization: 0, load: 0 };
36
- return node;
37
- }
38
- if (Math.random() > 0.995) {
39
- return { ...node, status: "FAILED", utilization: 0, memory: 0, load: 0 };
40
- }
41
- let util = node.utilization + (Math.random() - 0.5) * 15;
42
- if (Math.random() > 0.9) util += 30;
43
- util = Math.max(0, Math.min(100, util));
44
- const mem = Math.max(0, Math.min(100, node.memory + (Math.random() - 0.5) * 5));
45
- const load = (util / 100) * 4;
46
- let status: NodeStatus = "ACTIVE";
47
- if (util > 90) status = "OVERLOADED";
48
- else if (util < 5) status = "IDLE";
49
- return { ...node, utilization: util, memory: mem, load, status };
50
- })
51
- );
52
- }, 1500);
53
- return () => clearInterval(interval);
54
- }, []);
 
55
 
56
  useEffect(() => {
57
  const total = nodes.reduce((acc, n) => acc + n.utilization, 0);
58
  setAvgLoad(total / nodes.length);
59
  }, [nodes]);
60
 
61
- if (!mounted) {
62
- return (
63
- <section className="section-block" id="gpu-cluster" style={{ opacity: 0 }}>
64
- <div className="section-label">03 // COMPUTE RESOURCES</div>
65
- <h2 className="section-title">GPU Compute Clusters</h2>
66
- </section>
67
- );
68
- }
69
 
70
  return (
71
  <section className="section-block" id="gpu-cluster">
72
  <div className="section-label">03 // COMPUTE RESOURCES</div>
73
  <h2 className="section-title">GPU Compute Clusters</h2>
74
  <p className="section-desc">
75
- Real-time telemetry from the underlying inference hardware.
76
- High cluster utilization may introduce latency in the trust calibration loop.
77
  </p>
78
 
79
  <div className="cluster-grid">
80
  {nodes.map((node) => (
81
  <div key={node.id} className={`card node-card ${node.status.toLowerCase()}`}>
82
- <div className="card-id">{node.id} // NODE-0{node.id.split("-")[1]}</div>
83
-
84
  <div className="node-status-badge">
85
- <div className="status-dot" style={{
86
- background: node.status === "OVERLOADED" ? "var(--red)" :
87
- node.status === "FAILED" ? "#555" :
88
- node.status === "IDLE" ? "var(--muted)" : "var(--green)"
89
  }} />
90
  {node.status}
91
  </div>
@@ -96,9 +99,10 @@ export default function GPUClusterPanel() {
96
  <span style={{ color: "var(--cyan)" }}>{Math.round(node.utilization)}%</span>
97
  </div>
98
  <div className="metric-bar-bg">
99
- <motion.div
100
- className="metric-bar-fill"
101
  animate={{ width: `${node.utilization}%` }}
 
102
  style={{ background: node.utilization > 90 ? "var(--red)" : "var(--cyan)" } as any}
103
  />
104
  </div>
@@ -110,9 +114,10 @@ export default function GPUClusterPanel() {
110
  <span style={{ color: "var(--green)" }}>{Math.round(node.memory)}%</span>
111
  </div>
112
  <div className="metric-bar-bg">
113
- <motion.div
114
- className="metric-bar-fill"
115
  animate={{ width: `${node.memory}%` }}
 
116
  style={{ background: "var(--green)" } as any}
117
  />
118
  </div>
@@ -136,7 +141,7 @@ export default function GPUClusterPanel() {
136
  <div className="cluster-total-load">
137
  <span className="label">TOTAL CLUSTER LOAD</span>
138
  <div className="load-meter-bg">
139
- <motion.div
140
  className="load-meter-fill"
141
  animate={{ width: `${avgLoad}%` }}
142
  style={{ background: avgLoad > 80 ? "var(--red)" : "var(--cyan)", color: avgLoad > 80 ? "var(--red)" : "var(--cyan)" } as any}
@@ -152,4 +157,4 @@ export default function GPUClusterPanel() {
152
  </div>
153
  </section>
154
  );
155
- }
 
1
  "use client";
2
 
3
+ import { useState, useEffect } from "react";
4
+ import { motion } from "framer-motion";
5
 
6
  type NodeStatus = "ACTIVE" | "IDLE" | "OVERLOADED" | "FAILED";
7
 
 
13
  status: NodeStatus;
14
  }
15
 
16
+ interface GPUClusterPanelProps {
17
+ sessionId?: string;
18
+ mode?: string;
19
+ gpuPool?: any[]; // Live data from observation
20
+ }
21
+
22
+ export default function GPUClusterPanel({ sessionId, mode, gpuPool }: GPUClusterPanelProps) {
23
  const [mounted, setMounted] = useState(false);
24
  const [nodes, setNodes] = useState<GPUNode[]>([
25
+ { id: "GPU-1", utilization: 0, memory: 0, load: 0, status: "IDLE" },
26
+ { id: "GPU-2", utilization: 0, memory: 0, load: 0, status: "IDLE" },
27
+ { id: "GPU-3", utilization: 0, memory: 0, load: 0, status: "IDLE" },
28
  { id: "GPU-4", utilization: 0, memory: 0, load: 0, status: "IDLE" },
29
  ]);
30
 
31
  const [avgLoad, setAvgLoad] = useState(0);
32
  const [jitter, setJitter] = useState(0.45);
33
 
34
+ useEffect(() => { setMounted(true); }, []);
35
+
36
+ // ── LIVE SYNC FROM OBSERVATION ────────────────────────────
37
  useEffect(() => {
38
+ if (gpuPool && Array.isArray(gpuPool)) {
39
+ setNodes(gpuPool.slice(0, 4).map((g: any) => {
40
+ const util = (g.memory_used / g.memory_total) * 100;
41
+ let status = g.state.toUpperCase();
42
+ if (status === "ALLOCATED") status = "ACTIVE";
43
+
44
+ return {
45
+ id: g.id,
46
+ utilization: util,
47
+ memory: util,
48
+ load: (util / 100) * 4.2,
49
+ status: status as NodeStatus
50
+ };
51
+ }));
52
+ } else if (!sessionId || mode !== "cluster") {
53
+ // Fallback to subtle idle simulation if no live data
54
+ const timer = setInterval(() => {
55
+ setJitter(Math.random() * 0.5);
56
+ setNodes(prev => prev.map(n => ({
57
+ ...n,
58
+ utilization: Math.max(0, n.utilization + (Math.random() - 0.5) * 2),
59
+ load: n.utilization * 0.04
60
+ })));
61
+ }, 2000);
62
+ return () => clearInterval(timer);
63
+ }
64
+ }, [gpuPool, sessionId, mode]);
65
 
66
  useEffect(() => {
67
  const total = nodes.reduce((acc, n) => acc + n.utilization, 0);
68
  setAvgLoad(total / nodes.length);
69
  }, [nodes]);
70
 
71
+ if (!mounted) return null;
 
 
 
 
 
 
 
72
 
73
  return (
74
  <section className="section-block" id="gpu-cluster">
75
  <div className="section-label">03 // COMPUTE RESOURCES</div>
76
  <h2 className="section-title">GPU Compute Clusters</h2>
77
  <p className="section-desc">
78
+ Real-time telemetry from the underlying inference hardware.
79
+ Note how cluster utilization spikes as the RL model allocates worker jobs.
80
  </p>
81
 
82
  <div className="cluster-grid">
83
  {nodes.map((node) => (
84
  <div key={node.id} className={`card node-card ${node.status.toLowerCase()}`}>
85
+ <div className="card-id">{node.id} // CORE-AX-{node.id.split("-")[1] || "0X"}</div>
86
+
87
  <div className="node-status-badge">
88
+ <div className="status-dot" style={{
89
+ background: node.status === "ACTIVE" ? "var(--green)" :
90
+ node.status === "OVERLOADED" ? "var(--red)" :
91
+ node.status === "FAILED" ? "#555" : "var(--muted)"
92
  }} />
93
  {node.status}
94
  </div>
 
99
  <span style={{ color: "var(--cyan)" }}>{Math.round(node.utilization)}%</span>
100
  </div>
101
  <div className="metric-bar-bg">
102
+ <motion.div
103
+ className="metric-bar-fill"
104
  animate={{ width: `${node.utilization}%` }}
105
+ transition={{ type: "spring", stiffness: 100, damping: 20 }}
106
  style={{ background: node.utilization > 90 ? "var(--red)" : "var(--cyan)" } as any}
107
  />
108
  </div>
 
114
  <span style={{ color: "var(--green)" }}>{Math.round(node.memory)}%</span>
115
  </div>
116
  <div className="metric-bar-bg">
117
+ <motion.div
118
+ className="metric-bar-fill"
119
  animate={{ width: `${node.memory}%` }}
120
+ transition={{ type: "spring", stiffness: 100, damping: 20 }}
121
  style={{ background: "var(--green)" } as any}
122
  />
123
  </div>
 
141
  <div className="cluster-total-load">
142
  <span className="label">TOTAL CLUSTER LOAD</span>
143
  <div className="load-meter-bg">
144
+ <motion.div
145
  className="load-meter-fill"
146
  animate={{ width: `${avgLoad}%` }}
147
  style={{ background: avgLoad > 80 ? "var(--red)" : "var(--cyan)", color: avgLoad > 80 ? "var(--red)" : "var(--cyan)" } as any}
 
157
  </div>
158
  </section>
159
  );
160
+ }
ui/app/components/SpecialistNetwork.tsx CHANGED
@@ -17,7 +17,7 @@ export default function SpecialistNetwork({
17
  trustDeltas: Record<string, number>;
18
  activeSpec: string | null;
19
  }) {
20
- const ids = observation?.available_specialists ?? ["S0", "S1", "S2", "S3", "S4"];
21
  return (
22
  <div className="net">
23
  <svg className="net-svg" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid meet">
@@ -79,4 +79,4 @@ export default function SpecialistNetwork({
79
  })}
80
  </div>
81
  );
82
- }
 
17
  trustDeltas: Record<string, number>;
18
  activeSpec: string | null;
19
  }) {
20
+ const ids = observation?.available_specialists || observation?.available_workers || ["S0", "S1", "S2", "S3", "S4"];
21
  return (
22
  <div className="net">
23
  <svg className="net-svg" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid meet">
 
79
  })}
80
  </div>
81
  );
82
+ }
ui/app/components/TrustTimeline.tsx CHANGED
@@ -10,7 +10,7 @@ export default function TrustTimeline({
10
  observation: Observation | null;
11
  trustDeltas: Record<string, number>;
12
  }) {
13
- const ids = observation?.available_specialists ?? ["S0", "S1", "S2", "S3", "S4"];
14
  return (
15
  <div className="tl">
16
  {ids.map((id) => {
@@ -38,4 +38,4 @@ export default function TrustTimeline({
38
  })}
39
  </div>
40
  );
41
- }
 
10
  observation: Observation | null;
11
  trustDeltas: Record<string, number>;
12
  }) {
13
+ const ids = observation?.available_specialists ?? observation?.available_workers ?? ["S0", "S1", "S2", "S3", "S4"];
14
  return (
15
  <div className="tl">
16
  {ids.map((id) => {
 
38
  })}
39
  </div>
40
  );
41
+ }
ui/app/hooks/useSentinel.ts CHANGED
@@ -9,13 +9,16 @@ import type {
9
 
10
  /* ── helpers ──────────────────────────────────────────── */
11
 
12
- const API_BASE = process.env.NEXT_PUBLIC_API_URL || "";
 
 
13
 
14
  function bestSpec(obs: Observation | null): string {
15
  if (!obs) return "S0";
16
- return [...obs.available_specialists].sort(
 
17
  (a, b) => (obs.trust_snapshot[b] ?? 0.5) - (obs.trust_snapshot[a] ?? 0.5),
18
- )[0];
19
  }
20
 
21
  function heuristicMove(obs: Observation | null) {
@@ -29,9 +32,8 @@ function heuristicMove(obs: Observation | null) {
29
 
30
  function randomMove(obs: Observation | null) {
31
  if (!obs) return { action: "delegate" as ActionType, specialist: "S0", trust: 0.5 };
32
- const sp = obs.available_specialists[
33
- Math.floor(Math.random() * obs.available_specialists.length)
34
- ] || "S0";
35
  return { action: "delegate" as ActionType, specialist: sp, trust: obs.trust_snapshot[sp] ?? 0.5 };
36
  }
37
 
@@ -122,7 +124,8 @@ export function useSentinel() {
122
  const trustDeltas = useMemo(() => {
123
  if (!observation) return {};
124
  const d: Record<string, number> = {};
125
- for (const id of observation.available_specialists) {
 
126
  d[id] = (observation.trust_snapshot[id] ?? 0.5) - (prevTrust[id] ?? 0.5);
127
  }
128
  return d;
@@ -150,10 +153,10 @@ export function useSentinel() {
150
  const s = nextSeed ?? seed;
151
  setRunning(true);
152
  abortRef.current = false;
153
- const payload = { task_type: t, seed: s };
154
  setLastReq({ method: "POST", path: "/reset", body: payload });
155
  try {
156
- const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/reset`, {
157
  method: "POST",
158
  headers: { "Content-Type": "application/json" },
159
  body: JSON.stringify(payload),
@@ -195,17 +198,20 @@ export function useSentinel() {
195
 
196
  setActiveSpec(specialist);
197
 
 
 
 
198
  const payload = {
199
  session_id: sid,
200
  task_type: obs.task_type,
201
- action_type: action,
202
  specialist_id: specialist,
203
  subtask_response: action === "solve_independently" ? "SELF_SOLVED" : null,
204
  reasoning: `ui-${action}${specialist ? `-${specialist}` : ""}`,
205
  };
206
  setLastReq({ method: "POST", path: `/step?session_id=${sid}`, body: payload });
207
  try {
208
- const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/step?session_id=${encodeURIComponent(sid)}`, {
209
  method: "POST",
210
  headers: { "Content-Type": "application/json" },
211
  body: JSON.stringify(payload),
@@ -276,4 +282,4 @@ export function useSentinel() {
276
  prevTrust, trustDeltas, recommended, activeSpec,
277
  resetEpisode, stepEpisode, autoRun, stopAutoRun, swapProfiles,
278
  };
279
- }
 
9
 
10
  /* ── helpers ──────────────────────────────────────────── */
11
 
12
+ const API_BASE = typeof window !== "undefined"
13
+ ? (process.env.NEXT_PUBLIC_API_URL || (window.location.port === "3000" || window.location.port === "3458" ? "http://127.0.0.1:7860" : ""))
14
+ : "";
15
 
16
  function bestSpec(obs: Observation | null): string {
17
  if (!obs) return "S0";
18
+ const ids = obs.available_specialists || obs.available_workers || [];
19
+ return [...ids].sort(
20
  (a, b) => (obs.trust_snapshot[b] ?? 0.5) - (obs.trust_snapshot[a] ?? 0.5),
21
+ )[0] || "S0";
22
  }
23
 
24
  function heuristicMove(obs: Observation | null) {
 
32
 
33
  function randomMove(obs: Observation | null) {
34
  if (!obs) return { action: "delegate" as ActionType, specialist: "S0", trust: 0.5 };
35
+ const ids = obs.available_specialists || obs.available_workers || [];
36
+ const sp = ids[Math.floor(Math.random() * ids.length)] || "S0";
 
37
  return { action: "delegate" as ActionType, specialist: sp, trust: obs.trust_snapshot[sp] ?? 0.5 };
38
  }
39
 
 
124
  const trustDeltas = useMemo(() => {
125
  if (!observation) return {};
126
  const d: Record<string, number> = {};
127
+ const ids = observation.available_specialists || observation.available_workers || [];
128
+ for (const id of ids) {
129
  d[id] = (observation.trust_snapshot[id] ?? 0.5) - (prevTrust[id] ?? 0.5);
130
  }
131
  return d;
 
153
  const s = nextSeed ?? seed;
154
  setRunning(true);
155
  abortRef.current = false;
156
+ const payload = { task_type: t, seed: s, mode: "cluster" };
157
  setLastReq({ method: "POST", path: "/reset", body: payload });
158
  try {
159
+ const res = await fetch(`${API_BASE}/reset`, {
160
  method: "POST",
161
  headers: { "Content-Type": "application/json" },
162
  body: JSON.stringify(payload),
 
198
 
199
  setActiveSpec(specialist);
200
 
201
+ const isCluster = active?.info?.environment_mode === "cluster" || sessionId === "cluster";
202
+ const mappedAction = (isCluster && action === "delegate") ? "allocate" : action;
203
+
204
  const payload = {
205
  session_id: sid,
206
  task_type: obs.task_type,
207
+ action_type: mappedAction,
208
  specialist_id: specialist,
209
  subtask_response: action === "solve_independently" ? "SELF_SOLVED" : null,
210
  reasoning: `ui-${action}${specialist ? `-${specialist}` : ""}`,
211
  };
212
  setLastReq({ method: "POST", path: `/step?session_id=${sid}`, body: payload });
213
  try {
214
+ const res = await fetch(`${API_BASE}/step?session_id=${encodeURIComponent(sid)}`, {
215
  method: "POST",
216
  headers: { "Content-Type": "application/json" },
217
  body: JSON.stringify(payload),
 
282
  prevTrust, trustDeltas, recommended, activeSpec,
283
  resetEpisode, stepEpisode, autoRun, stopAutoRun, swapProfiles,
284
  };
285
+ }
ui/app/lib/types.ts CHANGED
@@ -14,6 +14,7 @@ export type Observation = {
14
  subtasks_total: number;
15
  subtasks_remaining: number;
16
  available_specialists: string[];
 
17
  trust_snapshot: Record<string, number>;
18
  stakes_level: number;
19
  step_count: number;
@@ -21,6 +22,7 @@ export type Observation = {
21
  last_action_summary: string | null;
22
  last_reward: number;
23
  episode_status: string;
 
24
  };
25
 
26
  export type Reward = {
@@ -41,6 +43,7 @@ export type StepResult = {
41
  score: number;
42
  adversarial_detections?: number;
43
  adversarial_poisonings?: number;
 
44
  };
45
  };
46
 
@@ -73,4 +76,4 @@ export type EventItem = {
73
  summary: string;
74
  reward: number;
75
  outcome: "success" | "blocked" | "poisoned" | "skipped" | "reset";
76
- };
 
14
  subtasks_total: number;
15
  subtasks_remaining: number;
16
  available_specialists: string[];
17
+ available_workers?: string[];
18
  trust_snapshot: Record<string, number>;
19
  stakes_level: number;
20
  step_count: number;
 
22
  last_action_summary: string | null;
23
  last_reward: number;
24
  episode_status: string;
25
+ gpu_pool?: any[];
26
  };
27
 
28
  export type Reward = {
 
43
  score: number;
44
  adversarial_detections?: number;
45
  adversarial_poisonings?: number;
46
+ environment_mode?: string;
47
  };
48
  };
49
 
 
76
  summary: string;
77
  reward: number;
78
  outcome: "success" | "blocked" | "poisoned" | "skipped" | "reset";
79
+ };
ui/app/page.tsx CHANGED
@@ -78,7 +78,7 @@ export default function Page() {
78
  }}
79
  disabled={s.running}
80
  >
81
- ▶ Launch Simulation
82
  </button>
83
  <button
84
  className="btn-secondary"
@@ -191,7 +191,7 @@ export default function Page() {
191
  <div className="sim-controls-row">
192
  <span className="ctrl-label">POLICY:</span>
193
  <button className="btn-sm-ctrl" onClick={() => void s.autoRun("heuristic" as AutoPolicy)} disabled={s.running || s.done}>
194
- ▶ HEURISTIC
195
  </button>
196
  <button className="btn-sm-ctrl" onClick={() => void s.autoRun("random" as AutoPolicy)} disabled={s.running || s.done}>
197
  ⚄ RANDOM
@@ -256,7 +256,11 @@ export default function Page() {
256
  <div className="divider" />
257
 
258
  {/* GPU CLUSTER */}
259
- <GPUClusterPanel />
 
 
 
 
260
 
261
  <div className="divider" />
262
 
@@ -293,9 +297,9 @@ export default function Page() {
293
  </div>
294
  <div className="footer-right">
295
  BUILD 2.4.1 // MARL-FRAMEWORK // MIT LICENSE<br />
296
- © 2025 THE_BOYS. ALL RIGHTS RESERVED.
297
  </div>
298
  </footer>
299
  </>
300
  );
301
- }
 
78
  }}
79
  disabled={s.running}
80
  >
81
+ Launch Simulation
82
  </button>
83
  <button
84
  className="btn-secondary"
 
191
  <div className="sim-controls-row">
192
  <span className="ctrl-label">POLICY:</span>
193
  <button className="btn-sm-ctrl" onClick={() => void s.autoRun("heuristic" as AutoPolicy)} disabled={s.running || s.done}>
194
+ HEURISTIC
195
  </button>
196
  <button className="btn-sm-ctrl" onClick={() => void s.autoRun("random" as AutoPolicy)} disabled={s.running || s.done}>
197
  ⚄ RANDOM
 
256
  <div className="divider" />
257
 
258
  {/* GPU CLUSTER */}
259
+ <GPUClusterPanel
260
+ sessionId={s.info?.session_id}
261
+ mode={s.info?.environment_mode}
262
+ gpuPool={s.observation?.gpu_pool}
263
+ />
264
 
265
  <div className="divider" />
266
 
 
297
  </div>
298
  <div className="footer-right">
299
  BUILD 2.4.1 // MARL-FRAMEWORK // MIT LICENSE<br />
300
+ © 2025 THE_BOYS. ALL RIGHTS RESERVED.
301
  </div>
302
  </footer>
303
  </>
304
  );
305
+ }