Dhaerya's picture
Add files
b00d5d5
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;