Spaces:
Sleeping
Sleeping
| import { useState, useEffect } from 'react'; | |
| import { Play, Pause, RotateCcw, FastForward, Activity, MapPin } from 'lucide-react'; | |
| import './App.css'; | |
| const API_BASE = window.location.origin === 'http://localhost:5173' || window.location.origin === 'http://127.0.0.1:5173' | |
| ? 'http://127.0.0.1:8000/api' | |
| : '/api'; | |
| function App() { | |
| const [state, setState] = useState({ | |
| queues: [0, 0, 0, 0, 0, 0, 0, 0], // N_SR, N_L, E_SR, E_L, S_SR, S_L, W_SR, W_L | |
| phase: 0, | |
| reward: 0, | |
| vehicles_passed: 0, | |
| step: 0, | |
| total_reward: 0, | |
| is_done: false | |
| }); | |
| const [isPlaying, setIsPlaying] = useState(false); | |
| const [speedMs, setSpeedMs] = useState(250); | |
| const fetchState = async (endpoint) => { | |
| try { | |
| const res = await fetch(`${API_BASE}/${endpoint}`, { method: 'POST' }); | |
| const data = await res.json(); | |
| setState(data); | |
| if (data.is_done) { | |
| setIsPlaying(false); | |
| } | |
| } catch (err) { | |
| console.error(err); | |
| setIsPlaying(false); | |
| } | |
| }; | |
| useEffect(() => { | |
| fetchState('reset'); | |
| }, []); | |
| useEffect(() => { | |
| let interval; | |
| if (isPlaying) { | |
| interval = setInterval(() => { | |
| fetchState('step'); | |
| }, speedMs); | |
| } | |
| return () => clearInterval(interval); | |
| }, [isPlaying, speedMs]); | |
| // Phase logic: 0=N, 1=E, 2=S, 3=W | |
| const isNGreen = state.phase === 0; | |
| const isEGreen = state.phase === 1; | |
| const isSGreen = state.phase === 2; | |
| const isWGreen = state.phase === 3; | |
| const renderCars = (count, direction, isLeftTurn) => { | |
| const displayCount = Math.min(count, 5); // Cap visuals for ultra-premium look | |
| return Array.from({ length: displayCount }).map((_, i) => ( | |
| <div | |
| key={i} | |
| className={`car car-${direction} ${isLeftTurn ? 'left-turn' : 'straight'} ${i === 0 && ((direction==='n'&&isNGreen)||(direction==='e'&&isEGreen)||(direction==='s'&&isSGreen)||(direction==='w'&&isWGreen)) && isPlaying ? 'animating' : ''}`} | |
| style={{ '--idx': i }} | |
| > | |
| <div className="car-glow"></div> | |
| </div> | |
| )); | |
| }; | |
| const getPhaseName = () => { | |
| if (isNGreen) return "NORTH GREEN"; | |
| if (isEGreen) return "EAST GREEN"; | |
| if (isSGreen) return "SOUTH GREEN"; | |
| if (isWGreen) return "WEST GREEN"; | |
| }; | |
| return ( | |
| <> | |
| <div className="bg-blob blob-1"></div> | |
| <div className="bg-blob blob-2"></div> | |
| <div className="bg-blob blob-3"></div> | |
| <div className="app-container"> | |
| <header className="premium-header"> | |
| <div className="header-brand"> | |
| <div className="icon-wrapper"> | |
| <Activity size={28} /> | |
| </div> | |
| <div> | |
| <h1>Nexus Control</h1> | |
| <p>Deep Q-Network Traffic Simulation</p> | |
| </div> | |
| </div> | |
| <div className="header-status"> | |
| <div className="pulse-dot"></div> | |
| <span>System Active • {getPhaseName()}</span> | |
| </div> | |
| </header> | |
| <main className="main-layout"> | |
| {/* INTERSECTION CENTERPIECE */} | |
| <div className="intersection-wrapper glass-panel"> | |
| <div className="intersection-container"> | |
| {/* Roads */} | |
| <div className="road road-vertical"> | |
| <div className="lane-divider vertical-left"></div> | |
| <div className="lane-divider vertical-right"></div> | |
| <div className="stop-line sl-top"></div> | |
| <div className="stop-line sl-bottom"></div> | |
| </div> | |
| <div className="road road-horizontal"> | |
| <div className="lane-divider horizontal-top"></div> | |
| <div className="lane-divider horizontal-bottom"></div> | |
| <div className="stop-line sl-left"></div> | |
| <div className="stop-line sl-right"></div> | |
| </div> | |
| <div className="intersection-center"> | |
| <div className="center-logo"><MapPin size={24} opacity={0.2} /></div> | |
| </div> | |
| {/* Traffic Light Fixtures */} | |
| <div className="traffic-fixture tf-north"> | |
| <div className={`t-light red ${!isNGreen ? 'active' : ''}`}></div> | |
| <div className={`t-light green ${isNGreen ? 'active' : ''}`}></div> | |
| </div> | |
| <div className="traffic-fixture tf-east"> | |
| <div className={`t-light red ${!isEGreen ? 'active' : ''}`}></div> | |
| <div className={`t-light green ${isEGreen ? 'active' : ''}`}></div> | |
| </div> | |
| <div className="traffic-fixture tf-south"> | |
| <div className={`t-light red ${!isSGreen ? 'active' : ''}`}></div> | |
| <div className={`t-light green ${isSGreen ? 'active' : ''}`}></div> | |
| </div> | |
| <div className="traffic-fixture tf-west"> | |
| <div className={`t-light red ${!isWGreen ? 'active' : ''}`}></div> | |
| <div className={`t-light green ${isWGreen ? 'active' : ''}`}></div> | |
| </div> | |
| {/* Vehicles */} | |
| <div className="queue q-north"> | |
| {renderCars(state.queues[0], 'n', false)} | |
| {renderCars(state.queues[1], 'n', true)} | |
| </div> | |
| <div className="queue q-east"> | |
| {renderCars(state.queues[2], 'e', false)} | |
| {renderCars(state.queues[3], 'e', true)} | |
| </div> | |
| <div className="queue q-south"> | |
| {renderCars(state.queues[4], 's', false)} | |
| {renderCars(state.queues[5], 's', true)} | |
| </div> | |
| <div className="queue q-west"> | |
| {renderCars(state.queues[6], 'w', false)} | |
| {renderCars(state.queues[7], 'w', true)} | |
| </div> | |
| </div> | |
| {/* Control Bar Overlay */} | |
| <div className="premium-controls"> | |
| <button onClick={() => setIsPlaying(!isPlaying)} className={`p-btn ${isPlaying ? 'btn-pause' : 'btn-play'}`}> | |
| {isPlaying ? <><Pause size={20}/> PAUSE</> : <><Play size={20}/> START</>} | |
| </button> | |
| <div className="control-divider"></div> | |
| <button onClick={() => fetchState('step')} disabled={isPlaying} className="p-btn btn-ghost"> | |
| <FastForward size={18}/> STEP | |
| </button> | |
| <button onClick={() => fetchState('reset')} className="p-btn btn-ghost"> | |
| <RotateCcw size={18}/> RESET | |
| </button> | |
| <div className="control-divider"></div> | |
| <select value={speedMs} onChange={(e) => setSpeedMs(Number(e.target.value))} className="p-select"> | |
| <option value={500}>0.5x Speed</option> | |
| <option value={250}>1.0x Speed</option> | |
| <option value={100}>2.0x Speed</option> | |
| </select> | |
| </div> | |
| </div> | |
| {/* METRICS SIDEBAR */} | |
| <aside className="metrics-sidebar"> | |
| <div className="glass-card primary-card"> | |
| <h3>AI Performance</h3> | |
| <div className="big-metric"> | |
| <span className="value">{state.vehicles_passed}</span> | |
| <span className="label">Total Vehicles Cleared</span> | |
| </div> | |
| <div className="mini-progress"> | |
| <div className="fill" style={{ width: `${(state.step/3600)*100}%` }}></div> | |
| </div> | |
| <div className="step-count">Time: {state.step} / 3600</div> | |
| </div> | |
| <div className="glass-card"> | |
| <h3>Reward Signal</h3> | |
| <div className={`reward-value ${state.total_reward < -50 ? 'negative' : 'positive'}`}> | |
| {state.total_reward.toFixed(2)} | |
| </div> | |
| <p className="reward-desc">Optimizing to keep queues at absolute minimums.</p> | |
| </div> | |
| <div className="glass-card queues-card"> | |
| <h3>Live Lane Telemetry</h3> | |
| <div className="telemetry-grid"> | |
| <div className={`t-item ${isNGreen ? 'active-lane' : ''}`}> | |
| <div className="t-head">NORTH</div> | |
| <div className="t-data">SR: {state.queues[0]} <span>|</span> L: {state.queues[1]}</div> | |
| </div> | |
| <div className={`t-item ${isEGreen ? 'active-lane' : ''}`}> | |
| <div className="t-head">EAST</div> | |
| <div className="t-data">SR: {state.queues[2]} <span>|</span> L: {state.queues[3]}</div> | |
| </div> | |
| <div className={`t-item ${isSGreen ? 'active-lane' : ''}`}> | |
| <div className="t-head">SOUTH</div> | |
| <div className="t-data">SR: {state.queues[4]} <span>|</span> L: {state.queues[5]}</div> | |
| </div> | |
| <div className={`t-item ${isWGreen ? 'active-lane' : ''}`}> | |
| <div className="t-head">WEST</div> | |
| <div className="t-data">SR: {state.queues[6]} <span>|</span> L: {state.queues[7]}</div> | |
| </div> | |
| </div> | |
| </div> | |
| </aside> | |
| </main> | |
| </div> | |
| </> | |
| ); | |
| } | |
| export default App; | |