Spaces:
Sleeping
Sleeping
| import React, { useEffect, useMemo, useRef, useState } from 'react'; | |
| import { createRoot } from 'react-dom/client'; | |
| import { SCENARIOS } from './fixtures.js'; | |
| import { | |
| ScenarioMap, OrdersPanel, AgentFeed, RewardPanel, | |
| ControlBar, TickScrubber, FinalCard, ActionBanner, | |
| } from './components.jsx'; | |
| import './styles.css'; | |
| const MODES = [ | |
| { id: 'trained', label: 'AI agent (trained LoRA)', shortLabel: 'Trained policy', tone: 'trained' }, | |
| { id: 'heuristic', label: 'Heuristic dispatcher', shortLabel: 'Heuristic baseline', tone: 'heuristic' }, | |
| { id: 'prompt_only', label: 'Prompt-only agent', shortLabel: 'Untrained baseline', tone: 'baseline' }, | |
| ]; | |
| const THINKING_MS = 1800; | |
| const ACTION_MS = 1200; | |
| function getTrajectory(scenario, modeId) { | |
| return scenario.trajectories[modeId] || scenario.trajectories.trained; | |
| } | |
| function summarizeScenario(scenario, modeId) { | |
| const trajectory = getTrajectory(scenario, modeId); | |
| const first = trajectory[0]; | |
| const last = trajectory[trajectory.length - 1]; | |
| const delivered = last.orders.filter((o) => o.status === 'delivered').length; | |
| const expired = last.orders.filter((o) => o.status === 'expired').length; | |
| const onTime = last.orders.filter( | |
| (o) => o.status === 'delivered' && (o.delivered_tick ?? 999) <= o.deadline_tick, | |
| ).length; | |
| const successRate = last.orders.length ? (delivered / last.orders.length) * 100 : 0; | |
| return { | |
| trajectory, | |
| previewTick: Math.min(2, trajectory.length - 1), | |
| delivered, | |
| expired, | |
| onTime, | |
| successRate, | |
| totalReward: last.cumulative_reward ?? 0, | |
| activeOrdersAtStart: first.orders.length, | |
| couriersAtStart: first.couriers.length, | |
| maxTicks: scenario.metadata.max_ticks, | |
| }; | |
| } | |
| function ScenarioSummary({ scenario }) { | |
| const m = scenario.metadata; | |
| return ( | |
| <div className="scenario-summary"> | |
| <div className="scenario-row"> | |
| <span className={`difficulty-badge ${m.difficulty}`}>{m.difficulty}</span> | |
| <span className="scenario-meta">{m.num_couriers} couriers · {m.num_orders} orders · {m.max_ticks} ticks</span> | |
| </div> | |
| <div className="scenario-skills"> | |
| {m.skill_focus.map((s) => <span key={s} className="skill-tag">{s.replace(/_/g, ' ')}</span>)} | |
| </div> | |
| <div className="scenario-desc">{m.description}</div> | |
| </div> | |
| ); | |
| } | |
| function PlaybackPanel({ scenario, modeId, currentTick, onJumpToTick, showRewards }) { | |
| const trajectory = getTrajectory(scenario, modeId); | |
| const snapshot = trajectory[Math.min(currentTick, trajectory.length - 1)]; | |
| const isFinished = currentTick >= trajectory.length - 1; | |
| return ( | |
| <div className="playback-col"> | |
| <div className="map-area"> | |
| <ScenarioMap nodes={scenario.nodes} snapshot={snapshot} /> | |
| {isFinished && <div className="map-final-overlay"><FinalCard trajectory={trajectory} scenario={scenario} /></div>} | |
| </div> | |
| <OrdersPanel snapshot={snapshot} maxTicks={scenario.metadata.max_ticks} /> | |
| <RewardPanel | |
| snapshot={snapshot} | |
| cumulative={snapshot.cumulative_reward ?? 0} | |
| showRewards={showRewards} | |
| /> | |
| <AgentFeed trajectory={trajectory} currentTick={currentTick} onJumpToTick={onJumpToTick} /> | |
| </div> | |
| ); | |
| } | |
| function OverviewSidebar({ | |
| scenarios, | |
| scenarioId, | |
| onScenarioChange, | |
| modeId, | |
| onModeChange, | |
| onOpenPlayground, | |
| onAutoplayDemo, | |
| }) { | |
| return ( | |
| <aside className="overview-sidebar"> | |
| <div className="overview-sidebar-brand"> | |
| <div className="overview-brand-title">Dispatch Control Room</div> | |
| <div className="overview-brand-subtitle">delivery intelligence unit</div> | |
| </div> | |
| <div className="overview-sidebar-section"> | |
| <div className="overview-label">Simulation difficulty</div> | |
| <select className="overview-select" value={scenarioId} onChange={(e) => onScenarioChange(e.target.value)}> | |
| {Object.entries(scenarios).map(([id, s]) => ( | |
| <option key={id} value={id}> | |
| {s.metadata.theme} | |
| </option> | |
| ))} | |
| </select> | |
| </div> | |
| <div className="overview-sidebar-section"> | |
| <div className="overview-label">Active intelligence</div> | |
| <div className="mode-stack"> | |
| {MODES.map((mode) => ( | |
| <button | |
| key={mode.id} | |
| className={`mode-card ${modeId === mode.id ? 'selected' : ''}`} | |
| onClick={() => onModeChange(mode.id)} | |
| > | |
| <span className={`mode-dot ${mode.tone}`} /> | |
| <span>{mode.shortLabel}</span> | |
| </button> | |
| ))} | |
| </div> | |
| </div> | |
| <div className="overview-actions"> | |
| <button className="launch-btn primary" onClick={() => onOpenPlayground(false)}>Run playground</button> | |
| <button className="launch-btn" onClick={onAutoplayDemo}>Autoplay demo</button> | |
| </div> | |
| <div className="overview-note"> | |
| Choose a scenario, pick the policy to inspect, and jump into the replay playground for step-by-step dispatch simulation. | |
| </div> | |
| </aside> | |
| ); | |
| } | |
| function HeroPanel({ scenario, modeId }) { | |
| const mode = MODES.find((item) => item.id === modeId) || MODES[0]; | |
| return ( | |
| <section className="hero-panel"> | |
| <div className="hero-badge">dispatch arena // live replay interface</div> | |
| <h1>Dispatch intelligence for food and grocery delivery</h1> | |
| <p> | |
| Explore how different dispatch policies behave under hidden prep times, deadline pressure, | |
| rolling arrivals, and traffic noise. The environment is packaged for OpenEnv, GRPO training, | |
| and public interactive demos on Hugging Face Spaces. | |
| </p> | |
| <div className="hero-pills"> | |
| <span className={`hero-pill ${scenario.metadata.difficulty}`}>{scenario.metadata.difficulty}</span> | |
| <span className="hero-pill neutral">{scenario.metadata.theme}</span> | |
| <span className="hero-pill accent">{mode.shortLabel}</span> | |
| </div> | |
| </section> | |
| ); | |
| } | |
| function MetricCard({ title, eyebrow, children, tall = false }) { | |
| return ( | |
| <section className={`overview-card${tall ? ' tall' : ''}`}> | |
| <div className="overview-card-title">{title}</div> | |
| <div className="overview-card-eyebrow">{eyebrow}</div> | |
| <div className="overview-card-body">{children}</div> | |
| </section> | |
| ); | |
| } | |
| function MetricBar({ label, value, max = 100, tone = 'accent' }) { | |
| const width = Math.max(6, Math.min(100, (value / max) * 100)); | |
| return ( | |
| <div className="metric-bar-row"> | |
| <div className="metric-bar-header"> | |
| <span>{label}</span> | |
| <span>{typeof value === 'number' ? value.toFixed(0) : value}</span> | |
| </div> | |
| <div className="metric-bar-track"> | |
| <div className={`metric-bar-fill ${tone}`} style={{ width: `${width}%` }} /> | |
| </div> | |
| </div> | |
| ); | |
| } | |
| function OverviewPage({ scenario, modeId, onOpenPlayground }) { | |
| const summary = summarizeScenario(scenario, modeId); | |
| const previewSnapshot = summary.trajectory[summary.previewTick]; | |
| return ( | |
| <main className="overview-main"> | |
| <HeroPanel scenario={scenario} modeId={modeId} /> | |
| <div className="overview-grid"> | |
| <MetricCard title="Live Shift State" eyebrow="replay preview of the selected scenario" tall> | |
| <div className="overview-map-frame"> | |
| <ScenarioMap nodes={scenario.nodes} snapshot={previewSnapshot} /> | |
| </div> | |
| <div className="overview-inline-stats"> | |
| <div className="overview-inline-stat"> | |
| <span className="overview-inline-label">Tick preview</span> | |
| <strong>t{previewSnapshot.tick}</strong> | |
| </div> | |
| <div className="overview-inline-stat"> | |
| <span className="overview-inline-label">Orders visible</span> | |
| <strong>{summary.activeOrdersAtStart}</strong> | |
| </div> | |
| <div className="overview-inline-stat"> | |
| <span className="overview-inline-label">Couriers online</span> | |
| <strong>{summary.couriersAtStart}</strong> | |
| </div> | |
| </div> | |
| </MetricCard> | |
| <MetricCard title="Performance Analytics" eyebrow="policy-level replay outcome"> | |
| <MetricBar label="Deliveries completed" value={summary.delivered} max={scenario.metadata.num_orders} tone="good" /> | |
| <MetricBar label="On-time deliveries" value={summary.onTime} max={Math.max(summary.delivered, 1)} tone="accent" /> | |
| <MetricBar label="Success rate" value={summary.successRate} max={100} tone="accent" /> | |
| <MetricBar label="Expired orders" value={summary.expired} max={scenario.metadata.num_orders} tone="bad" /> | |
| <div className="analytics-reward"> | |
| <span>Total replay reward</span> | |
| <strong>{summary.totalReward >= 0 ? '+' : ''}{summary.totalReward.toFixed(2)}</strong> | |
| </div> | |
| </MetricCard> | |
| <MetricCard title="Dispatch Constraints" eyebrow="what makes the environment hard"> | |
| <ul className="constraint-list"> | |
| <li>Hidden restaurant prep times and delayed readiness.</li> | |
| <li>Overlapping orders with shared courier capacity.</li> | |
| <li>Deadline pressure and lateness penalties.</li> | |
| <li>Traffic-sensitive travel times and route churn cost.</li> | |
| <li>Fairness pressure across the active courier fleet.</li> | |
| </ul> | |
| </MetricCard> | |
| <MetricCard title="Environment Contract" eyebrow="OpenEnv + replay-friendly surface"> | |
| <div className="endpoint-grid"> | |
| {['/reset', '/step', '/state', '/summary', '/api/sessions', '/ready'].map((endpoint) => ( | |
| <span key={endpoint} className="endpoint-pill">{endpoint}</span> | |
| ))} | |
| </div> | |
| <div className="contract-copy"> | |
| The same environment powers the public frontend, the replay interface, and GRPO training loops through the OpenEnv contract. | |
| </div> | |
| <button className="launch-btn primary full" onClick={() => onOpenPlayground(false)}>Open interactive playground</button> | |
| </MetricCard> | |
| </div> | |
| </main> | |
| ); | |
| } | |
| function PlaygroundHeader({ onBack, scenario, modeId }) { | |
| const mode = MODES.find((item) => item.id === modeId) || MODES[0]; | |
| return ( | |
| <div className="playground-header"> | |
| <button className="back-btn" onClick={onBack}>← Back to overview</button> | |
| <div className="playground-header-copy"> | |
| <div className="playground-title">Simulation playground</div> | |
| <div className="playground-subtitle"> | |
| {scenario.metadata.theme} · {mode.shortLabel} | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| } | |
| function App() { | |
| const scenarioIds = Object.keys(SCENARIOS); | |
| const [scenarioId, setScenarioId] = useState(scenarioIds[0]); | |
| const [modeId, setModeId] = useState('trained'); | |
| const [currentTick, setCurrentTick] = useState(0); | |
| const [hasReplayInteraction, setHasReplayInteraction] = useState(false); | |
| const [isPlaying, setIsPlaying] = useState(false); | |
| const [playbackPhase, setPlaybackPhase] = useState('idle'); | |
| const [view, setView] = useState('overview'); | |
| const scenario = SCENARIOS[scenarioId]; | |
| const trajectory = getTrajectory(scenario, modeId); | |
| const trajectoryLen = useMemo(() => trajectory.length, [trajectory]); | |
| useEffect(() => { | |
| setCurrentTick(0); | |
| setHasReplayInteraction(false); | |
| setIsPlaying(false); | |
| setPlaybackPhase('idle'); | |
| }, [scenarioId, modeId]); | |
| useEffect(() => { | |
| if (currentTick > 0) { | |
| setHasReplayInteraction(true); | |
| } | |
| }, [currentTick]); | |
| const timeoutRef = useRef(null); | |
| useEffect(() => { | |
| if (timeoutRef.current) { | |
| clearTimeout(timeoutRef.current); | |
| timeoutRef.current = null; | |
| } | |
| if (!isPlaying) { | |
| setPlaybackPhase('idle'); | |
| return; | |
| } | |
| if (currentTick >= trajectoryLen - 1) { | |
| setIsPlaying(false); | |
| setPlaybackPhase('idle'); | |
| return; | |
| } | |
| setPlaybackPhase('thinking'); | |
| timeoutRef.current = setTimeout(() => { | |
| setPlaybackPhase('acting'); | |
| timeoutRef.current = setTimeout(() => { | |
| setCurrentTick((prev) => { | |
| if (prev >= trajectoryLen - 1) { | |
| setIsPlaying(false); | |
| return prev; | |
| } | |
| return prev + 1; | |
| }); | |
| }, ACTION_MS); | |
| }, THINKING_MS); | |
| return () => { | |
| if (timeoutRef.current) { | |
| clearTimeout(timeoutRef.current); | |
| timeoutRef.current = null; | |
| } | |
| }; | |
| }, [isPlaying, currentTick, trajectoryLen]); | |
| const jumpToTick = (tick) => { | |
| setIsPlaying(false); | |
| setPlaybackPhase('idle'); | |
| setHasReplayInteraction(true); | |
| setCurrentTick(tick); | |
| }; | |
| const stepBack = () => { | |
| setIsPlaying(false); | |
| setPlaybackPhase('idle'); | |
| setHasReplayInteraction(true); | |
| setCurrentTick((t) => Math.max(0, t - 1)); | |
| }; | |
| const stepForward = () => { | |
| setIsPlaying(false); | |
| setPlaybackPhase('idle'); | |
| setHasReplayInteraction(true); | |
| setCurrentTick((t) => Math.min(trajectoryLen - 1, t + 1)); | |
| }; | |
| const resetPlayback = () => { | |
| setCurrentTick(0); | |
| setHasReplayInteraction(true); | |
| setIsPlaying(false); | |
| setPlaybackPhase('idle'); | |
| }; | |
| const openPlayground = (autoplay = false) => { | |
| setView('playground'); | |
| setCurrentTick(0); | |
| setHasReplayInteraction(false); | |
| setPlaybackPhase('idle'); | |
| setIsPlaying(autoplay); | |
| }; | |
| return ( | |
| <div className={`app view-${view}`}> | |
| {view === 'overview' ? ( | |
| <div className="overview-shell"> | |
| <OverviewSidebar | |
| scenarios={SCENARIOS} | |
| scenarioId={scenarioId} | |
| onScenarioChange={setScenarioId} | |
| modeId={modeId} | |
| onModeChange={setModeId} | |
| onOpenPlayground={openPlayground} | |
| onAutoplayDemo={() => openPlayground(true)} | |
| /> | |
| <OverviewPage scenario={scenario} modeId={modeId} onOpenPlayground={openPlayground} /> | |
| </div> | |
| ) : ( | |
| <> | |
| <PlaygroundHeader onBack={() => { setView('overview'); setIsPlaying(false); }} scenario={scenario} modeId={modeId} /> | |
| <ControlBar | |
| scenarios={SCENARIOS} | |
| scenarioId={scenarioId} | |
| onScenarioChange={setScenarioId} | |
| modes={MODES} | |
| modeId={modeId} | |
| onModeChange={setModeId} | |
| isPlaying={isPlaying} | |
| onPlayToggle={() => setIsPlaying((p) => !p)} | |
| onStepBack={stepBack} | |
| onStepForward={stepForward} | |
| onReset={resetPlayback} | |
| /> | |
| <ScenarioSummary scenario={scenario} /> | |
| <ActionBanner | |
| trajectory={trajectory} | |
| currentTick={currentTick} | |
| playbackPhase={playbackPhase} | |
| isPlaying={isPlaying} | |
| /> | |
| <div className="workspace"> | |
| <PlaybackPanel | |
| scenario={scenario} | |
| modeId={modeId} | |
| currentTick={currentTick} | |
| onJumpToTick={jumpToTick} | |
| showRewards={hasReplayInteraction} | |
| /> | |
| </div> | |
| <TickScrubber | |
| trajectory={trajectory} | |
| currentTick={currentTick} | |
| onTickChange={jumpToTick} | |
| maxTicks={scenario.metadata.max_ticks} | |
| /> | |
| </> | |
| )} | |
| </div> | |
| ); | |
| } | |
| createRoot(document.getElementById('root')).render(<App />); | |