Sahil Tailor
Final Commit - HP
6dbfea6
Raw
History Blame Contribute Delete
27.2 kB
import { useEffect, useMemo, useRef, useState } from "react";
import Globe from "react-globe.gl";
import { motion, AnimatePresence } from "framer-motion";
import {
Activity,
CheckCircle2,
Circle,
Factory,
Gauge,
Package,
RefreshCcw,
Sun,
Truck,
Wrench,
Zap,
} from "lucide-react";
const TASKS = ["easy", "medium", "hard"];
const STEP_INTERVAL_MS = 2200;
const explicitApiBase = import.meta.env.VITE_API_BASE?.replace(/\/$/, "");
const isLocalPreview =
typeof window !== "undefined" &&
["localhost", "127.0.0.1"].includes(window.location.hostname) &&
window.location.port === "4173";
const API_BASE = explicitApiBase || (isLocalPreview ? "http://127.0.0.1:8000" : "");
const EARTH_TEXTURE = "https://unpkg.com/three-globe/example/img/earth-blue-marble.jpg";
const EARTH_BUMP = "https://unpkg.com/three-globe/example/img/earth-topology.png";
const STARFIELD = "https://unpkg.com/three-globe/example/img/night-sky.png";
function apiUrl(path) {
return `${API_BASE}${path}`;
}
// ─── data hook ───────────────────────────────────────────────────────────────
function useDemoData() {
const [snapshot, setSnapshot] = useState(null);
const [loading, setLoading] = useState(true);
const [playing, setPlaying] = useState(true);
const [taskName, setTaskName] = useState("medium");
const [error, setError] = useState("");
const steppingRef = useRef(false);
async function loadSnapshot() {
try {
const res = await fetch(apiUrl("/api/ui/demo"));
if (!res.ok) throw new Error(`Snapshot ${res.status}`);
const data = await res.json();
setSnapshot(data);
setTaskName(data.task_name ?? "medium");
setError("");
} catch (err) {
setError(err instanceof Error ? err.message : "Unable to load snapshot");
} finally {
setLoading(false);
}
}
async function resetDemo(nextTask) {
setLoading(true);
try {
const res = await fetch(
apiUrl(`/api/ui/demo/reset?task_name=${encodeURIComponent(nextTask)}`),
{ method: "POST" }
);
if (!res.ok) throw new Error(`Reset ${res.status}`);
const data = await res.json();
setSnapshot(data);
setTaskName(data.task_name ?? nextTask);
setPlaying(true);
setError("");
} catch (err) {
setError(err instanceof Error ? err.message : "Unable to reset");
} finally {
setLoading(false);
}
}
async function stepDemo() {
if (steppingRef.current || loading) return;
if (snapshot?.done || (snapshot?.step_count ?? 0) >= (snapshot?.max_steps ?? Infinity)) {
setPlaying(false);
return;
}
steppingRef.current = true;
try {
const res = await fetch(apiUrl("/api/ui/demo/step"), { method: "POST" });
if (!res.ok) throw new Error(`Step ${res.status}`);
const data = await res.json();
setSnapshot(data);
if (data.done || (data.step_count ?? 0) >= (data.max_steps ?? Infinity))
setPlaying(false);
setError("");
} catch (err) {
setPlaying(false);
setError(err instanceof Error ? err.message : "Unable to step");
} finally {
steppingRef.current = false;
}
}
useEffect(() => { loadSnapshot(); }, []);
useEffect(() => {
if (!playing || loading || snapshot?.done ||
(snapshot?.step_count ?? 0) >= (snapshot?.max_steps ?? Infinity)) return;
const timer = window.setInterval(stepDemo, STEP_INTERVAL_MS);
return () => window.clearInterval(timer);
}, [playing, loading, snapshot]);
return { snapshot, loading, playing, taskName, error, setPlaying, resetDemo, stepDemo };
}
// ─── colour helpers ──────────────────────────────────────────────────────────
function platformColor(action) {
switch (action) {
case "produce": return "#ffb86b";
case "assemble": return "#9caaff";
case "deliver": return "#7cf7c9";
case "recharge": return "#59b8ff";
default: return "#95a2bb";
}
}
function energyColor(v) {
if (v < 20) return "#ff7171";
if (v < 40) return "#ffb86b";
return "#7cf7c9";
}
// ─── reward sparkline ────────────────────────────────────────────────────────
function RewardSparkline({ history }) {
if (!history || history.length < 2) return null;
const W = 160, H = 36;
const min = Math.min(...history);
const max = Math.max(...history);
const range = max - min || 1;
const pts = history.map((v, i) => {
const x = (i / (history.length - 1)) * W;
const y = H - ((v - min) / range) * (H - 4) - 2;
return `${x.toFixed(1)},${y.toFixed(1)}`;
}).join(" ");
const zeroY = H - ((0 - min) / range) * (H - 4) - 2;
return (
<svg width={W} height={H} className="sparkline">
<line x1="0" y1={zeroY.toFixed(1)} x2={W} y2={zeroY.toFixed(1)} className="spark-zero" />
<polyline points={pts} className="spark-line" />
<circle cx={W} cy={H - ((history[history.length - 1] - min) / range) * (H - 4) - 2}
r="2.5" className="spark-dot" />
</svg>
);
}
// ─── Globe view ───────────────────────────────────────────────────────────────
function GlobeView({ snapshot, globeSize }) {
const globeRef = useRef(null);
const pathStoreRef = useRef([]);
const snapAnimRef = useRef(null);
const [pathData, setPathData] = useState([]);
// Lock camera controls + snap back to equator after user interaction
useEffect(() => {
if (!globeRef.current) return;
const controls = globeRef.current.controls();
controls.autoRotate = true;
controls.autoRotateSpeed = 0.6;
controls.enablePan = false;
controls.enableZoom = false;
controls.minDistance = 210;
controls.maxDistance = 310;
controls.minPolarAngle = Math.PI * 0.25;
controls.maxPolarAngle = Math.PI * 0.75;
const snapToEquator = () => {
if (snapAnimRef.current) cancelAnimationFrame(snapAnimRef.current);
const camera = globeRef.current.camera();
const tick = () => {
const tx = controls.target.x, ty = controls.target.y, tz = controls.target.z;
const dx = camera.position.x - tx;
const dy = camera.position.y - ty;
const dz = camera.position.z - tz;
const r = Math.sqrt(dx * dx + dy * dy + dz * dz);
const phi = Math.atan2(Math.sqrt(dx * dx + dz * dz), dy); // current polar angle
const diff = Math.PI / 2 - phi; // distance from equator
if (Math.abs(diff) < 0.002) { controls.update(); return; }
const newPhi = phi + diff * 0.08; // ease toward equator
const theta = Math.atan2(dz, dx);
const sinPhi = Math.sin(newPhi);
const cosPhi = Math.cos(newPhi);
camera.position.set(
tx + r * sinPhi * Math.cos(theta),
ty + r * cosPhi,
tz + r * sinPhi * Math.sin(theta),
);
controls.update();
snapAnimRef.current = requestAnimationFrame(tick);
};
snapAnimRef.current = requestAnimationFrame(tick);
};
// Cancel snap if user grabs the globe again
const cancelSnap = () => {
if (snapAnimRef.current) cancelAnimationFrame(snapAnimRef.current);
};
controls.addEventListener('end', snapToEquator);
controls.addEventListener('start', cancelSnap);
return () => {
controls.removeEventListener('end', snapToEquator);
controls.removeEventListener('start', cancelSnap);
if (snapAnimRef.current) cancelAnimationFrame(snapAnimRef.current);
};
}, []);
// Build/update path data from snapshot
useEffect(() => {
const platforms = snapshot?.platforms ?? [];
const stepCount = snapshot?.step_count ?? 0;
if (!platforms.length) { pathStoreRef.current = []; setPathData([]); return; }
if (stepCount === 0) {
// Fresh reset β€” build all paths from scratch
const next = [];
for (const p of platforms) {
const alt = 0.11 + Math.min(p.altitude_km / 8000, 0.1);
const color = platformColor(p.last_action);
// Full orbit ring
const orbitPts = (p.route ?? []).map(n => ({ lat: n.latitude, lng: n.longitude, alt }));
if (orbitPts.length > 1)
next.push({ id: `orbit-${p.id}`, color, points: orbitPts });
// Moving trail
next.push({ id: `trail-${p.id}`, color,
points: [{ lat: p.latitude, lng: p.longitude, alt }] });
// Collapsed delivery arc (pre-create so it never pops in)
next.push({ id: `deliver-${p.id}`, color: "#7cf7c9",
points: [{ lat: p.latitude, lng: p.longitude, alt },
{ lat: p.latitude, lng: p.longitude, alt: 0.01 }],
active: false });
}
pathStoreRef.current = next;
} else {
const pathMap = new Map(pathStoreRef.current.map(p => [p.id, p]));
for (const p of platforms) {
const alt = 0.11 + Math.min(p.altitude_km / 8000, 0.1);
const color = platformColor(p.last_action);
const pt = { lat: p.latitude, lng: p.longitude, alt };
// Update orbit ring colour
const orbit = pathMap.get(`orbit-${p.id}`);
if (orbit) orbit.color = color;
// Extend trail
const trail = pathMap.get(`trail-${p.id}`);
if (trail) {
trail.color = color;
const last = trail.points[trail.points.length - 1];
const moved = !last ||
Math.abs(last.lat - pt.lat) > 0.0001 ||
Math.abs(last.lng - pt.lng) > 0.0001;
if (moved) {
trail.points.push(pt);
if (trail.points.length > 120) trail.points.shift();
}
}
// Delivery arc β€” show when action is "deliver"
const arc = pathMap.get(`deliver-${p.id}`);
if (arc) {
if (p.last_action === "deliver") {
// Point to a notional ground station below
arc.points = [{ lat: p.latitude, lng: p.longitude, alt },
{ lat: 0, lng: p.longitude, alt: 0.01 }];
arc.color = "#7cf7c9";
arc.active = true;
} else if (arc.active) {
const start = arc.points[0] ?? pt;
arc.points = [start, { ...start, alt: start.alt - 0.001 }];
arc.active = false;
}
}
}
pathStoreRef.current = [...pathMap.values()];
}
setPathData([...pathStoreRef.current]);
}, [snapshot]);
// HTML marker nodes for platforms
const markerNodes = useMemo(() => {
return (snapshot?.platforms ?? []).map(p => ({
...p,
lat: p.latitude,
lng: p.longitude,
altitude: 0.1 + Math.min(p.altitude_km / 8000, 0.1),
color: platformColor(p.last_action),
size: 0.44,
}));
}, [snapshot]);
return (
<div className="globe-shell">
<div className="globe-backdrop" />
<div className="globe-frame">
<Globe
ref={globeRef}
width={globeSize}
height={globeSize}
backgroundColor="rgba(4,11,20,0)"
backgroundImageUrl={STARFIELD}
globeImageUrl={EARTH_TEXTURE}
bumpImageUrl={EARTH_BUMP}
showAtmosphere
atmosphereColor="#7ec8ff"
atmosphereAltitude={0.18}
animateIn={false}
waitForGlobeReady={false}
pathsData={pathData}
pathPoints="points"
pathPointLat="lat"
pathPointLng="lng"
pathPointAlt="alt"
pathColor="color"
pathTransitionDuration={STEP_INTERVAL_MS * 0.8}
pathStroke={path => {
const id = String(path.id);
if (id.startsWith("orbit-")) return 0.6;
if (id.startsWith("trail-")) return 1.5;
return 1.2;
}}
pathDashLength={path => {
const id = String(path.id);
return (id.startsWith("orbit-") || id.startsWith("trail-")) ? 0 : 0.05;
}}
pathDashGap={path => {
const id = String(path.id);
return (id.startsWith("orbit-") || id.startsWith("trail-")) ? 0 : 0.1;
}}
pathDashAnimateTime={path => {
const id = String(path.id);
return (id.startsWith("deliver-") && path.active) ? 1600 : 0;
}}
htmlElementsData={markerNodes}
htmlLat="lat"
htmlLng="lng"
htmlAltitude="altitude"
htmlElement={p => {
const el = document.createElement("div");
el.className = "globe-marker platform";
el.style.setProperty("--marker-color", p.color);
el.style.setProperty("--marker-size", `${p.size}rem`);
el.innerHTML = `
<span></span>
<div class="marker-tip">
<div class="tip-title">Platform ${p.id}</div>
<div class="tip-row"><span>Action</span>
<strong class="tip-action ${p.last_action ?? 'idle'}">${p.last_action ?? "idle"}</strong></div>
<div class="tip-row"><span>Energy</span><strong>${p.energy?.toFixed(0) ?? "?"}%</strong></div>
<div class="tip-row"><span>Materials</span><strong>${p.material_stock?.toFixed(0) ?? "?"}</strong></div>
<div class="tip-row"><span>Components</span><strong>${p.component_stock?.toFixed(0) ?? "?"}</strong></div>
<div class="tip-row"><span>Products</span><strong>${p.product_stock ?? 0}</strong></div>
<div class="tip-row"><span>Alt</span><strong>${p.altitude_km?.toFixed(0) ?? "?"}km</strong></div>
</div>`;
return el;
}}
/>
</div>
<div className="globe-overlay">
<div>
<p className="eyebrow">Orbital Manufacturing Console</p>
<h1>Live platform tracking</h1>
</div>
<div className="overlay-metrics">
<div>
<span>Platforms</span>
<strong>{snapshot?.platforms?.length ?? 0}</strong>
</div>
<div>
<span>Delivering</span>
<strong>
{snapshot?.platforms?.filter(p => p.last_action === "deliver").length ?? 0}
</strong>
</div>
</div>
</div>
</div>
);
}
// ─── agent stats ─────────────────────────────────────────────────────────────
function AgentStats({ snapshot }) {
const metrics = snapshot?.metrics ?? {};
const history = snapshot?.reward_history ?? [];
const score = snapshot?.mission_score ?? 0;
const step = snapshot?.step_count ?? 0;
const maxStep = snapshot?.max_steps ?? 1;
const pct = Math.min(100, Math.round((step / maxStep) * 100));
const lastReward = history.length ? history[history.length - 1] : 0;
const avgReward = history.length
? (history.reduce((a, b) => a + b, 0) / history.length).toFixed(2)
: "0.00";
const posSteps = history.filter(r => r > 0).length;
const efficiency = history.length ? Math.round((posSteps / history.length) * 100) : 0;
return (
<div className="card agent-card">
<div className="card-head">
<div><p className="eyebrow">RL Agent</p><h3>Performance</h3></div>
<Gauge size={15} className="icon-muted" />
</div>
<div className="score-row">
<div className="score-arc">
<svg viewBox="0 0 60 38" width="90" height="56">
<path d="M 5 35 A 25 25 0 0 1 55 35" fill="none"
stroke="rgba(100,140,200,0.15)" strokeWidth="5" strokeLinecap="round" />
<path d="M 5 35 A 25 25 0 0 1 55 35" fill="none"
stroke={score > 0.7 ? "#7cf7c9" : score > 0.4 ? "#ffb86b" : "#ff8f8f"}
strokeWidth="5" strokeLinecap="round"
strokeDasharray={`${score * 78.5} 78.5`} />
<text x="30" y="34" textAnchor="middle" fill="#e8f4ff" fontSize="10" fontWeight="700">
{Math.round(score * 100)}%
</text>
</svg>
<span className="score-label">Mission Score</span>
</div>
<div className="agent-kpis">
<div className="kpi">
<span>Avg reward/step</span>
<strong className={Number(avgReward) >= 0 ? "pos" : "neg"}>
{Number(avgReward) >= 0 ? "+" : ""}{avgReward}
</strong>
</div>
<div className="kpi"><span>Positive steps</span><strong>{efficiency}%</strong></div>
<div className="kpi">
<span>Last step</span>
<strong className={lastReward >= 0 ? "pos" : "neg"}>
{lastReward >= 0 ? "+" : ""}{lastReward.toFixed(2)}
</strong>
</div>
</div>
</div>
<div className="spark-wrap">
<span className="spark-label">Step rewards</span>
<RewardSparkline history={history} />
</div>
<div className="ep-progress">
<div className="ep-head">
<span>Episode {step}/{maxStep}</span>
<span>{pct}% complete</span>
</div>
<div className="ep-bar"><div className="ep-fill" style={{ width: `${pct}%` }} /></div>
</div>
<div className="agent-pills">
<span className="ap">Produced: {metrics.production_runs ?? 0}</span>
<span className="ap">Assembled: {metrics.assemblies_completed ?? 0}</span>
<span className="ap">Delivered: {metrics.deliveries_completed ?? 0}</span>
<span className="ap">On-time: {metrics.on_time_deliveries ?? 0}</span>
<span className="ap warn">Invalid: {metrics.invalid_actions ?? 0}</span>
</div>
</div>
);
}
// ─── platform fleet ──────────────────────────────────────────────────────────
function PlatformFleet({ snapshot }) {
const platforms = snapshot?.platforms ?? [];
return (
<div className="card fleet-card">
<div className="card-head">
<div><p className="eyebrow">Fleet</p><h3>Platforms</h3></div>
<Factory size={15} className="icon-muted" />
</div>
<div className="sat-list">
<AnimatePresence>
{platforms.map(p => (
<motion.div key={p.id} className="sat-row" layout>
<div className="sat-head">
<span className="sat-name">Platform {p.id}</span>
<span className={`act-badge ${p.last_action ?? "idle"}`}>
{p.last_action ?? "idle"}
</span>
</div>
<div className="bar-row">
<Zap size={10} className="bar-icon" />
<div className="bar-track">
<div className="bar-fill battery"
style={{ width: `${p.energy}%`, "--bar-color": energyColor(p.energy) }} />
</div>
<span className="bar-val">{p.energy.toFixed(0)}%</span>
</div>
<div className="bar-row">
<Wrench size={10} className="bar-icon" />
<div className="bar-track">
<div className="bar-fill mat" style={{ width: `${p.material_stock}%` }} />
</div>
<span className="bar-val">{p.material_stock.toFixed(0)}</span>
</div>
<div className="bar-row">
<Activity size={10} className="bar-icon" />
<div className="bar-track">
<div className="bar-fill comp" style={{ width: `${p.component_stock}%` }} />
</div>
<span className="bar-val">{p.component_stock.toFixed(0)}</span>
</div>
<div className="bar-row">
<Package size={10} className="bar-icon" />
<div className="bar-track">
<div className="bar-fill prod"
style={{ width: `${(p.product_stock / 10) * 100}%` }} />
</div>
<span className="bar-val">{p.product_stock}</span>
</div>
</motion.div>
))}
</AnimatePresence>
</div>
</div>
);
}
// ─── delivery orders ─────────────────────────────────────────────────────────
function DeliveryOrders({ snapshot }) {
const windows = snapshot?.delivery_windows ?? [];
const orders = snapshot?.pending_orders ?? [];
const step = snapshot?.step_count ?? 0;
return (
<div className="card mission-card">
<div className="card-head">
<div><p className="eyebrow">Logistics</p><h3>Delivery Orders</h3></div>
<Truck size={16} className="icon-muted" />
</div>
<div className="task-list">
{windows.slice(0, 8).map(w => {
const urgency = w.deadline - step;
const isUrgent = urgency <= 10;
return (
<div key={w.order_id} className="task-row">
<div className="task-icon">
<Circle size={13} className="icon-pending" />
</div>
<div className="task-body">
<span className="task-id">Order #{w.order_id} β€” {w.product_type}</span>
<span className="task-desc">
Deadline: step {w.deadline} ({urgency > 0 ? `${urgency} left` : "overdue"})
</span>
</div>
<span className={`prio-badge ${isUrgent ? "prio-3" : urgency <= 25 ? "prio-2" : "prio-1"}`}>
{isUrgent ? "URGENT" : `${urgency}s`}
</span>
</div>
);
})}
{windows.length === 0 && (
<p className="empty-text">
<CheckCircle2 size={14} style={{ display: "inline", marginRight: 5 }} />
All deliveries complete
</p>
)}
</div>
<div className="task-summary">
<span><CheckCircle2 size={11} /> {(snapshot?.metrics?.deliveries_completed ?? 0)} delivered</span>
<span><Package size={11} /> {orders.length} pending orders</span>
</div>
</div>
);
}
// ─── solar conditions ─────────────────────────────────────────────────────────
function SolarConditions({ snapshot }) {
const solar = snapshot?.solar_conditions ?? {};
return (
<div className="card">
<div className="card-head">
<div><p className="eyebrow">Power</p><h3>Solar Conditions</h3></div>
<Sun size={15} className="icon-muted" />
</div>
<div className="weather-row">
{Object.entries(solar).map(([zone, irr]) => (
<div key={zone} className="weather-pill"
style={{ "--cloud-pct": `${Math.round(Number(irr) * 100)}%` }}>
<span className="wregion">{zone.replace(/_/g, " ")}</span>
<span className="wval">{Math.round(Number(irr) * 100)}%</span>
<div className="wbar"><div className="wfill solar" /></div>
</div>
))}
</div>
</div>
);
}
// ─── main app ─────────────────────────────────────────────────────────────────
export default function App() {
const { snapshot, loading, playing, taskName, error, setPlaying, resetDemo, stepDemo } =
useDemoData();
const [globeSize, setGlobeSize] = useState(540);
const panelRef = useRef(null);
useEffect(() => {
function resize() {
if (!panelRef.current) return;
const { width, height } = panelRef.current.getBoundingClientRect();
setGlobeSize(Math.floor(Math.min(width, height, 620)));
}
resize();
window.addEventListener("resize", resize);
return () => window.removeEventListener("resize", resize);
}, []);
const score = snapshot?.mission_score ?? 0;
const scoreStr = `${Math.round(score * 100)}%`;
const scoreClass = score > 0.7 ? "good" : score > 0.4 ? "warn" : "bad";
return (
<div className="app-shell">
<div className="ambient ambient-a" />
<div className="ambient ambient-b" />
{/* ── header ── */}
<header className="topbar">
<div className="brand">
<div className="brand-mark"><Factory size={16} /></div>
<div>
<p className="eyebrow">OpenEnv Β· RL Benchmark</p>
<h2>Space Manufacturing Control</h2>
</div>
</div>
<div className="task-switcher">
{TASKS.map(t => (
<button key={t} type="button"
className={t === taskName ? "task-pill active" : "task-pill"}
onClick={() => resetDemo(t)}>
{t}
</button>
))}
</div>
<div className="header-right">
<div className={`score-badge ${scoreClass}`}>
<span>Mission</span>
<strong>{scoreStr}</strong>
</div>
<div className="header-controls">
<button type="button" className="action-button primary sm"
onClick={() => setPlaying(!playing)}>
{snapshot?.done ? "Done" : playing ? "Pause" : "Play"}
</button>
<button type="button" className="action-button sm" onClick={stepDemo}
disabled={loading || snapshot?.done ||
(snapshot?.step_count ?? 0) >= (snapshot?.max_steps ?? 0)}>
Step
</button>
<button type="button" className="ghost-button sm" onClick={() => resetDemo(taskName)}>
<RefreshCcw size={12} /> Reset
</button>
</div>
</div>
</header>
{error && <div className="error-banner">{error}</div>}
{/* ── main grid ── */}
<main className="main-grid">
{/* Globe panel */}
<section className="col-globe" ref={panelRef}>
{loading
? <div className="loading-state">Loading orbital telemetry…</div>
: <GlobeView snapshot={snapshot} globeSize={globeSize} />}
</section>
{/* Middle column */}
<section className="col-mid">
<AgentStats snapshot={snapshot} />
<DeliveryOrders snapshot={snapshot} />
<SolarConditions snapshot={snapshot} />
</section>
{/* Right column */}
<section className="col-right">
<PlatformFleet snapshot={snapshot} />
</section>
</main>
</div>
);
}