Spaces:
Sleeping
Sleeping
File size: 4,684 Bytes
08b99b2 6ef6359 08b99b2 6ef6359 08b99b2 f62fc81 08b99b2 f62fc81 08b99b2 6ef6359 08b99b2 5086de6 08b99b2 b209d78 08b99b2 b209d78 08b99b2 6ef6359 5086de6 08b99b2 5086de6 6ef6359 5086de6 08b99b2 6ef6359 08b99b2 6ef6359 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 | import React, { useState, useEffect, useRef } from 'react';
export default function DevConsole({ actions, onSendCommand }) {
const [cmd, setCmd] = useState("");
const logEndRef = useRef(null);
// Auto-scroll to bottom of logs
useEffect(() => {
if (logEndRef.current) {
logEndRef.current.scrollIntoView({ behavior: "smooth" });
}
}, [actions]);
const handleSend = () => {
if (cmd.trim()) {
if (onSendCommand) {
onSendCommand(cmd);
} else {
console.log("Sending command to backend:", cmd);
}
setCmd("");
}
};
const formatEvent = (act) => {
if (typeof act === 'string') return act;
const type = act.type || "INFO";
const body = act.msg || act.message || JSON.stringify(act);
const preview = typeof body === 'string' && body.length > 240 ? `${body.slice(0, 240)}…` : body;
// Custom formatting for specific types
if (type === "SPAWN") return `[SPAWN] ${act.callsign} (${act.weight_class} ${act.ac_type}) entered airspace`;
if (type === "LANDING") return `[LAND] ${act.callsign} landed successfully (+${act.reward})`;
if (type === "CRASH") return `[CRASH] ${act.callsign} !! CRITICAL SEPARATION LOSS !!`;
if (type === "RUNWAY_CHANGE") return `[RWY] Selection changed to: ${act.to?.join(', ')}`;
if (type === "WAYPOINT_CREATED") return `[INFRA] New waypoint created: ${act.name}`;
if (type === "RUNWAY_CREATED") return `[INFRA] New runway created: ${act.id}`;
if (type === "AIRPORT_CREATED") return `[INFRA] New airport created: ${act.name} (${act.code})`;
if (type === "ATC") return `[ATC] ${body}`;
if (type === "ERROR") return `[ERROR] !! ${body} !!`;
if (type === "INFO" && body.startsWith("CMD:")) return `[SIM] ${body.replace("CMD: ", "")}`;
if (type === "RL_TASK") return `[RL] ${act.phase || 'step'} task=${act.task} model=${act.model} step=${act.step}`;
if (type === "RL_PROMPT") return `[PROMPT] ${preview}`;
if (type === "RL_RESPONSE") return `[MODEL] ${preview}`;
if (type === "RL_ACTION") return `[ACTION] ${act.action}`;
if (type === "RL_REWARD") return `[REWARD] step=${act.step} reward=${act.reward} cumulative=${act.cumulative_reward} done=${act.done}`;
if (type === "RL_ERROR") return `[RL ERROR] ${body}`;
return `[${type}] ${body}`;
};
return (
<div className="console-panel" style={{ display: 'flex', flexDirection: 'column' }}>
<h3 style={{ margin: '0 0 8px 0' }}>Live Simulation Log</h3>
<div style={{
flex: 1,
background: '#0a0a0a',
color: '#00ff00',
padding: '12px',
fontFamily: 'monospace',
fontSize: '0.85rem',
overflowY: 'auto',
borderRadius: '4px',
border: '1px solid #333',
boxShadow: 'inset 0 0 10px rgba(0,0,0,1)'
}}>
{actions.map((act, i) => {
const timestamp = act.timestamp
? new Date(act.timestamp * 1000).toLocaleTimeString([], {hour: '2-digit', minute:'2-digit', second:'2-digit'})
: new Date().toLocaleTimeString([], {hour: '2-digit', minute:'2-digit', second:'2-digit'});
const eventType = act?.type || "INFO";
const eventBody = act?.msg || act?.message || "";
const eventKey = `${act?.timestamp ?? "no-ts"}-${eventType}-${eventBody}-${i}`;
const isError = act.type === "ERROR";
const isATC = act.type === "ATC";
return (
<div key={eventKey} style={{
marginBottom: '4px',
borderLeft: `2px solid ${isError ? '#ff0000' : isATC ? '#007bff' : '#004400'}`,
paddingLeft: '8px',
color: isError ? '#ffaaaa' : isATC ? '#aaaaff' : '#00ff00'
}}>
<span style={{ color: isError ? '#ff5555' : isATC ? '#5555ff' : '#008800', marginRight: '8px' }}>[{timestamp}]</span>
{formatEvent(act)}
</div>
);
})}
<div ref={logEndRef} />
</div>
<div style={{ marginTop: '12px', display: 'flex', gap: '8px' }}>
<input
type="text"
value={cmd}
onChange={e => setCmd(e.target.value)}
onKeyDown={e => { if (e.key === 'Enter') handleSend(); }}
placeholder="Enter SIM/ATC Command..."
style={{
flex: 1,
padding: '8px',
background: '#1a1a1a',
color: '#fff',
border: '1px solid #444',
borderRadius: '4px'
}}
/>
<button className="btn" onClick={handleSend} style={{ padding: '8px 20px' }}>EXEC</button>
</div>
</div>
);
}
|