meta_env / index.html
arrow072's picture
Upload 19 files
94ed1c9 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>OpenEnv Traffic Signal Optimization</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;600;800&family=JetBrains+Mono:wght@400;700&display=swap');
:root {
--bg-color: #0d1117;
--panel-bg: rgba(22, 27, 34, 0.6);
--panel-border: rgba(48, 54, 61, 0.8);
--text-main: #c9d1d9;
--text-muted: #8b949e;
--accent-glow: rgba(56, 139, 253, 0.4);
--accent-color: #58a6ff;
--green-light: #3fb950;
--red-light: #f85149;
--ev-color: #ff7b72;
}
body {
margin: 0;
padding: 20px;
background-color: var(--bg-color);
color: var(--text-main);
font-family: 'Outfit', sans-serif;
display: grid;
grid-template-columns: 300px 1fr 300px;
gap: 20px;
height: 100vh;
overflow: hidden;
background: radial-gradient(circle at 50% -20%, #1a2332, #0d1117 70%);
}
.panel {
background: var(--panel-bg);
border: 1px solid var(--panel-border);
border-radius: 16px;
padding: 20px;
backdrop-filter: blur(12px);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
display: flex;
flex-direction: column;
}
.header {
grid-column: 1 / -1;
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 20px;
background: var(--panel-bg);
border: 1px solid var(--panel-border);
border-radius: 16px;
margin-bottom: -10px;
z-index: 10;
}
.header h1 {
font-size: 1.4rem;
margin: 0;
font-weight: 800;
background: linear-gradient(90deg, #58a6ff, #a371f7);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.badge {
background: rgba(88, 166, 255, 0.1);
color: var(--accent-color);
padding: 4px 12px;
border-radius: 20px;
font-size: 0.8rem;
font-weight: 600;
border: 1px solid rgba(88, 166, 255, 0.2);
}
/* Metrics */
.metric-group {
margin-bottom: 20px;
}
.metric-label {
font-size: 0.8rem;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 1px;
margin-bottom: 5px;
}
.metric-value {
font-family: 'JetBrains Mono', monospace;
font-size: 1.8rem;
font-weight: 700;
color: white;
text-shadow: 0 0 10px rgba(255, 255, 255, 0.2);
}
.metric-value.good { color: var(--green-light); text-shadow: 0 0 10px rgba(63, 185, 80, 0.4); }
.metric-value.warn { color: #d29922; }
.metric-value.bad { color: var(--red-light); }
/* Controls */
.controls {
margin-top: auto;
display: flex;
flex-direction: column;
gap: 10px;
}
button {
background: rgba(255, 255, 255, 0.05);
border: 1px solid var(--panel-border);
color: white;
padding: 12px;
border-radius: 8px;
font-family: 'Outfit', sans-serif;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
position: relative;
overflow: hidden;
}
button:hover {
background: rgba(255, 255, 255, 0.1);
transform: translateY(-2px);
}
button:active {
transform: translateY(1px);
}
button.primary {
background: var(--accent-color);
color: #0d1117;
border: none;
box-shadow: 0 0 15px var(--accent-glow);
}
button.primary:hover {
background: #79c0ff;
box-shadow: 0 0 20px var(--accent-glow);
}
button.danger {
background: rgba(248, 81, 73, 0.1);
color: var(--red-light);
border-color: rgba(248, 81, 73, 0.3);
}
button.danger:hover {
background: rgba(248, 81, 73, 0.2);
}
/* Visualizer */
.visualizer {
position: relative;
background: #11161d;
border-radius: 16px;
border: 1px solid var(--panel-border);
overflow: hidden;
display: flex;
justify-content: center;
align-items: center;
box-shadow: inset 0 0 50px rgba(0,0,0,0.5);
}
.road {
position: absolute;
background: #1e242c;
}
.road-v {
width: 120px;
height: 100%;
border-left: 2px dashed #4b5363;
border-right: 2px dashed #4b5363;
}
.road-h {
width: 100%;
height: 120px;
border-top: 2px dashed #4b5363;
border-bottom: 2px dashed #4b5363;
}
.intersection {
width: 120px;
height: 120px;
background: #232933;
position: absolute;
z-index: 2;
}
/* Traffic Lights */
.light {
width: 12px;
height: 12px;
border-radius: 50%;
position: absolute;
z-index: 5;
background: #30363d;
box-shadow: 0 0 0 2px #0d1117;
transition: all 0.3s ease;
}
.light.green {
background: var(--green-light);
box-shadow: 0 0 15px var(--green-light), 0 0 0 2px #0d1117;
}
.light.red {
background: var(--red-light);
box-shadow: 0 0 15px var(--red-light), 0 0 0 2px #0d1117;
}
.light-n { top: -20px; left: 20px; }
.light-s { bottom: -20px; right: 20px; }
.light-e { right: -20px; top: 20px; }
.light-w { left: -20px; bottom: 20px; }
/* Queues */
.queue-container {
position: absolute;
display: flex;
gap: 4px;
z-index: 3;
}
.queue-n { top: 10px; right: 50%; margin-right: 5px; flex-direction: column-reverse; height: calc(50% - 70px); align-items: center; }
.queue-s { bottom: 10px; left: 50%; margin-left: 5px; flex-direction: column; height: calc(50% - 70px); align-items: center; }
.queue-e { right: 10px; bottom: 50%; margin-bottom: 5px; flex-direction: row-reverse; width: calc(50% - 70px); align-items: center; justify-content: flex-start; }
.queue-w { left: 10px; top: 50%; margin-top: 5px; flex-direction: row; width: calc(50% - 70px); align-items: center; justify-content: flex-start; }
.car {
width: 14px;
height: 14px;
background: #8b949e;
border-radius: 3px;
transition: all 0.2s;
}
.queue-n .car, .queue-s .car { width: 14px; height: 18px; }
.queue-e .car, .queue-w .car { width: 18px; height: 14px; }
.car.emergency {
background: var(--ev-color);
box-shadow: 0 0 10px var(--ev-color);
animation: pulse 1s infinite alternate;
}
@keyframes pulse {
0% { box-shadow: 0 0 5px var(--ev-color); }
100% { box-shadow: 0 0 20px var(--ev-color); background: #ff9999; }
}
/* Toasts */
#toast-container {
position: fixed;
bottom: 20px;
right: 20px;
display: flex;
flex-direction: column;
gap: 10px;
z-index: 100;
}
.toast {
background: var(--panel-bg);
border: 1px solid var(--panel-border);
padding: 12px 20px;
border-radius: 8px;
backdrop-filter: blur(10px);
opacity: 0;
transform: translateY(20px);
animation: slideIn 0.3s forwards;
font-size: 0.9rem;
}
@keyframes slideIn {
to { opacity: 1; transform: translateY(0); }
}
.toggle-container {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 20px;
background: rgba(0,0,0,0.2);
padding: 12px;
border-radius: 8px;
}
/* Queue Numbers */
.q-num {
position: absolute;
font-family: 'JetBrains Mono', monospace;
font-size: 14px;
font-weight: bold;
color: white;
background: rgba(0,0,0,0.6);
padding: 2px 6px;
border-radius: 4px;
z-index: 10;
}
.qn-n { top: 20px; right: 20px; }
.qn-s { bottom: 20px; left: 20px; }
.qn-e { bottom: 20px; right: 20px; }
.qn-w { top: 20px; left: 20px; }
</style>
</head>
<body>
<div class="header">
<h1>Traffic Signal Optimization</h1>
<div class="badge">OpenEnv Elite Submission</div>
</div>
<!-- Left Panel: State -->
<div class="panel">
<h2 style="font-size: 1.1rem; margin-top: 0; border-bottom: 1px solid var(--panel-border); padding-bottom: 10px;">Simulation State</h2>
<div class="metric-group" style="margin-top: 15px;">
<div class="metric-label">Step Count</div>
<div class="metric-value" id="val-step">0</div>
</div>
<div class="metric-group">
<div class="metric-label">Signal Phase</div>
<div class="metric-value" id="val-phase" style="color: #58a6ff;">NS GREEN</div>
</div>
<div style="flex: 1;"></div>
<h3 style="font-size: 0.9rem; color: var(--text-muted); margin-bottom: 10px;">Waiting Time Pressure</h3>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px;">
<div>
<div style="font-size: 0.7rem; color: var(--text-muted);">NORTH</div>
<div id="wait-n" style="font-family: monospace; font-size: 1.2rem;">0.0</div>
</div>
<div>
<div style="font-size: 0.7rem; color: var(--text-muted);">SOUTH</div>
<div id="wait-s" style="font-family: monospace; font-size: 1.2rem;">0.0</div>
</div>
<div>
<div style="font-size: 0.7rem; color: var(--text-muted);">EAST</div>
<div id="wait-e" style="font-family: monospace; font-size: 1.2rem;">0.0</div>
</div>
<div>
<div style="font-size: 0.7rem; color: var(--text-muted);">WEST</div>
<div id="wait-w" style="font-family: monospace; font-size: 1.2rem;">0.0</div>
</div>
</div>
</div>
<!-- Center: Visualizer -->
<div class="visualizer">
<div class="road road-v"></div>
<div class="road road-h"></div>
<div class="intersection">
<div class="light light-n" id="light-n"></div>
<div class="light light-s" id="light-s"></div>
<div class="light light-e" id="light-e"></div>
<div class="light light-w" id="light-w"></div>
</div>
<div class="q-num qn-n" id="qn-n">N: 0</div>
<div class="q-num qn-s" id="qn-s">S: 0</div>
<div class="q-num qn-e" id="qn-e">E: 0</div>
<div class="q-num qn-w" id="qn-w">W: 0</div>
<div class="queue-container queue-n" id="q-n"></div>
<div class="queue-container queue-s" id="q-s"></div>
<div class="queue-container queue-e" id="q-e"></div>
<div class="queue-container queue-w" id="q-w"></div>
</div>
<!-- Right Panel: Metrics & Controls -->
<div class="panel">
<h2 style="font-size: 1.1rem; margin-top: 0; border-bottom: 1px solid var(--panel-border); padding-bottom: 10px;">Metrics</h2>
<div class="metric-group" style="margin-top: 15px;">
<div class="metric-label">Total Cleared</div>
<div class="metric-value good" id="val-cleared">0</div>
</div>
<div class="metric-group">
<div class="metric-label">Fairness Score</div>
<div class="metric-value" id="val-fairness">1.00</div>
</div>
<div class="metric-group">
<div class="metric-label">Congestion Base</div>
<div class="metric-value warn" id="val-congestion">0.00</div>
</div>
<div class="controls">
<div class="toggle-container">
<span style="font-weight: 600;">Agent Auto-Mode</span>
<label style="position: relative; display: inline-block; width: 40px; height: 20px;">
<input type="checkbox" id="auto-play" style="opacity: 0; width: 0; height: 0;">
<span style="position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: rgba(255,255,255,0.1); transition: .4s; border-radius: 20px; border: 1px solid var(--panel-border);" id="toggle-slider"></span>
</label>
</div>
<button onclick="doStep(0)">Keep Phase (0)</button>
<button class="primary" onclick="doStep(1)">Switch Phase (1)</button>
<button class="danger" onclick="doReset()" style="margin-top: 10px;">Reset Env</button>
</div>
</div>
<div id="toast-container"></div>
<script>
let autoPlayInterval = null;
document.getElementById('auto-play').addEventListener('change', function(e) {
const slider = document.getElementById('toggle-slider');
if (e.target.checked) {
slider.style.backgroundColor = 'var(--accent-color)';
autoPlayInterval = setInterval(() => {
doAutoStep();
}, 300);
showToast('Agent Auto-Mode Enabled');
} else {
slider.style.backgroundColor = 'rgba(255,255,255,0.1)';
if (autoPlayInterval) {
clearInterval(autoPlayInterval);
autoPlayInterval = null;
}
showToast('Manual Control Restored');
}
});
function showToast(msg) {
const container = document.getElementById('toast-container');
const toast = document.createElement('div');
toast.className = 'toast';
toast.innerText = msg;
container.appendChild(toast);
setTimeout(() => {
toast.style.opacity = '0';
setTimeout(() => toast.remove(), 300);
}, 2000);
}
function updateUI(data) {
const state = data.state;
const info = data.info || {};
// Update State Top
document.getElementById('val-step').innerText = state.step_count;
const pText = state.phase === 0 ? "NS GREEN" : "EW GREEN";
const pColor = state.phase === 0 ? "var(--green-light)" : "var(--accent-color)";
const pEl = document.getElementById('val-phase');
pEl.innerText = pText;
pEl.style.color = pColor;
// Lights
if (state.phase === 0) {
document.getElementById('light-n').className = 'light light-n green';
document.getElementById('light-s').className = 'light light-s green';
document.getElementById('light-e').className = 'light light-e red';
document.getElementById('light-w').className = 'light light-w red';
} else {
document.getElementById('light-n').className = 'light light-n red';
document.getElementById('light-s').className = 'light light-s red';
document.getElementById('light-e').className = 'light light-e green';
document.getElementById('light-w').className = 'light light-w green';
}
// Waiting
document.getElementById('wait-n').innerText = (state.waiting_times.north || 0).toFixed(1);
document.getElementById('wait-s').innerText = (state.waiting_times.south || 0).toFixed(1);
document.getElementById('wait-e').innerText = (state.waiting_times.east || 0).toFixed(1);
document.getElementById('wait-w').innerText = (state.waiting_times.west || 0).toFixed(1);
// Queues numbers
document.getElementById('qn-n').innerText = `N: ${state.north_cars}`;
document.getElementById('qn-s').innerText = `S: ${state.south_cars}`;
document.getElementById('qn-e').innerText = `E: ${state.east_cars}`;
document.getElementById('qn-w').innerText = `W: ${state.west_cars}`;
// Draw Cars
const drawQueue = (id, count, hasEV) => {
const q = document.getElementById(id);
q.innerHTML = '';
const displayCount = Math.min(count, 10);
for(let i=0; i<displayCount; i++) {
const car = document.createElement('div');
car.className = 'car';
// Make the first car emergency if flag is true
if (i === 0 && hasEV) car.classList.add('emergency');
q.appendChild(car);
}
};
const ev = state.emergency_flags;
drawQueue('q-n', state.north_cars, ev.north);
drawQueue('q-s', state.south_cars, ev.south);
drawQueue('q-e', state.east_cars, ev.east);
drawQueue('q-w', state.west_cars, ev.west);
// Audio Visuals (Metrics)
if (info.total_cleared !== undefined) {
document.getElementById('val-cleared').innerText = info.total_cleared;
document.getElementById('val-fairness').innerText = (info.fairness_score || 0).toFixed(2);
document.getElementById('val-congestion').innerText = (info.congestion_score || 0).toFixed(2);
}
if (data.done) {
showToast(`Episode Finished! Score: ${info.total_cleared}`);
if (document.getElementById('auto-play').checked) {
setTimeout(doReset, 1000);
}
}
}
async function doReset() {
try {
const res = await fetch('/reset', { method: 'POST' });
const data = await res.json();
updateUI(data);
showToast("Environment Reset");
} catch(e) { showToast("Error connecting to API"); }
}
async function doStep(action) {
try {
const res = await fetch('/step', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: action })
});
const data = await res.json();
updateUI(data);
} catch(e) { }
}
async function doAutoStep() {
try {
const res = await fetch('/auto_step', { method: 'POST' });
const data = await res.json();
updateUI(data);
if (data.action_taken === 1) {
showToast("Agent triggered phase switch");
}
} catch(e) {
document.getElementById('auto-play').click(); // turn off
showToast("Agent step failed");
}
}
// Initial Load
doReset();
</script>
</body>
</html>