Spaces:
Runtime error
Runtime error
| // ============================================================================== | |
| // DriveSafe HUD Dashboard JavaScript | |
| // Establishes real-time EventSource connection, draws Chart.js EKG, and manages HUD state. | |
| // ============================================================================== | |
| let earChart; | |
| const maxChartPoints = 40; | |
| let chartData = Array(maxChartPoints).fill(0.3); | |
| let chartLabels = Array(maxChartPoints).fill(""); | |
| let renderedChatLogsCount = 0; | |
| // --- Initialize Chart.js on DOM Load --- | |
| document.addEventListener("DOMContentLoaded", () => { | |
| const ctx = document.getElementById('earChart').getContext('2d'); | |
| // Create futuristic neon line chart | |
| earChart = new Chart(ctx, { | |
| type: 'line', | |
| data: { | |
| labels: chartLabels, | |
| datasets: [{ | |
| label: 'Eye Aspect Ratio (EAR)', | |
| data: chartData, | |
| borderColor: '#00bfff', | |
| borderWidth: 2, | |
| pointRadius: 0, | |
| fill: true, | |
| backgroundColor: 'rgba(0, 191, 255, 0.05)', | |
| tension: 0.4 | |
| }] | |
| }, | |
| options: { | |
| responsive: true, | |
| maintainAspectRatio: false, | |
| plugins: { | |
| legend: { display: false } | |
| }, | |
| scales: { | |
| x: { display: false }, | |
| y: { | |
| min: 0.0, | |
| max: 0.45, | |
| grid: { color: 'rgba(255, 255, 255, 0.03)' }, | |
| ticks: { | |
| color: '#64748b', | |
| font: { family: 'Share Tech Mono', size: 10 } | |
| } | |
| } | |
| }, | |
| animation: { duration: 0 } // Disable default anims for absolute high-speed rendering | |
| } | |
| }); | |
| // Initialize Telemetry EventSource listener | |
| initTelemetry(); | |
| }); | |
| // --- Server-Sent Events (SSE) Telemetry Listener --- | |
| function initTelemetry() { | |
| const sse = new EventSource('/telemetry'); | |
| sse.onmessage = (event) => { | |
| const data = JSON.parse(event.data); | |
| // 1. Update Core Readouts | |
| updateMetrics(data); | |
| // 2. Shift EAR Graph Data | |
| updateChart(data.ear); | |
| // 3. Update Conversation Chat Logs | |
| updateChat(data.chat_history); | |
| // 4. Update HUD panel glowing states | |
| updateStateOverlays(data.state, data.alert_message); | |
| }; | |
| sse.onerror = (err) => { | |
| console.error("SSE connection dropped:", err); | |
| }; | |
| } | |
| // --- Update Circular Gauge & Numeric Metrics --- | |
| function updateMetrics(data) { | |
| // Current numeric readout | |
| document.getElementById("ear-val").innerText = data.ear.toFixed(2); | |
| // Circular radial progress math | |
| const gauge = document.getElementById("ear-gauge"); | |
| const radius = gauge.r.baseVal.value; | |
| const circumference = 2 * Math.PI * radius; | |
| // EAR bounds are normally between 0.10 (fully closed) and 0.35 (wide open) | |
| // Normalize EAR to 0% - 100% range | |
| let percentage = (data.ear - 0.10) / (0.35 - 0.10); | |
| percentage = Math.max(0, Math.min(1, percentage)); // Clamp between 0 and 1 | |
| const offset = circumference - (percentage * circumference); | |
| gauge.style.strokeDashoffset = offset; | |
| // Change gauge color relative to EAR thresholds | |
| const warnThreshold = 0.23; | |
| const closedThreshold = 0.20; | |
| if (data.ear <= closedThreshold) { | |
| gauge.style.stroke = "var(--danger-color)"; | |
| } else if (data.ear <= warnThreshold) { | |
| gauge.style.stroke = "var(--warn-color)"; | |
| } else { | |
| gauge.style.stroke = "var(--safe-color)"; | |
| } | |
| // Update Drowsiness Count | |
| const countElement = document.getElementById("drowsiness-count"); | |
| countElement.innerText = data.drowsiness_count; | |
| if (data.drowsiness_count > 0) { | |
| countElement.style.color = "var(--danger-color)"; | |
| countElement.style.textShadow = "0 0 10px rgba(255, 40, 80, 0.4)"; | |
| } else { | |
| countElement.style.color = "var(--safe-color)"; | |
| countElement.style.textShadow = "none"; | |
| } | |
| // Update Engine FPS | |
| document.getElementById("engine-fps").innerText = `${data.fps} FPS`; | |
| // Update Tracking active button toggle | |
| const trackingBtn = document.getElementById("toggle-tracking-btn"); | |
| const label = trackingBtn.querySelector("span"); | |
| if (data.detection_active) { | |
| trackingBtn.classList.add("active"); | |
| label.innerText = "ACTIVE TRACKING"; | |
| } else { | |
| trackingBtn.classList.remove("active"); | |
| label.innerText = "TRACKING PAUSED"; | |
| } | |
| } | |
| // --- EKG EAR Waveform Chart Shifter --- | |
| function updateChart(newEar) { | |
| chartData.push(newEar); | |
| chartData.shift(); | |
| // Swap graph colors based on EAR values | |
| if (newEar < 0.20) { | |
| earChart.data.datasets[0].borderColor = "#ff2850"; | |
| earChart.data.datasets[0].backgroundColor = "rgba(255, 40, 80, 0.05)"; | |
| } else if (newEar < 0.23) { | |
| earChart.data.datasets[0].borderColor = "#ffaa00"; | |
| earChart.data.datasets[0].backgroundColor = "rgba(255, 170, 0, 0.05)"; | |
| } else { | |
| earChart.data.datasets[0].borderColor = "#00ff80"; | |
| earChart.data.datasets[0].backgroundColor = "rgba(0, 255, 128, 0.05)"; | |
| } | |
| earChart.update(); | |
| } | |
| // --- Dynamic Conversational Chat Logs renderer --- | |
| function updateChat(history) { | |
| if (history.length === renderedChatLogsCount) return; | |
| const chatBox = document.getElementById("chat-box"); | |
| // Render only new logs added since last pass | |
| for (let i = renderedChatLogsCount; i < history.length; i++) { | |
| const log = history[i]; | |
| // System logs represent state announcements | |
| if (log.speaker === "System") { | |
| const systemDiv = document.createElement("div"); | |
| systemDiv.className = "chat-bubble system-bubble"; | |
| systemDiv.innerText = log.message; | |
| chatBox.appendChild(systemDiv); | |
| } else { | |
| // User query | |
| if (log.query) { | |
| const userDiv = document.createElement("div"); | |
| userDiv.className = "chat-bubble driver-bubble"; | |
| userDiv.innerHTML = `<span class="bubble-label">Driver</span>${escapeHTML(log.query)}`; | |
| chatBox.appendChild(userDiv); | |
| } | |
| // AI response | |
| if (log.message) { | |
| const aiDiv = document.createElement("div"); | |
| aiDiv.className = "chat-bubble slm-bubble"; | |
| aiDiv.innerHTML = `<span class="bubble-label">DriveSafe SLM</span>${escapeHTML(log.message)}`; | |
| chatBox.appendChild(aiDiv); | |
| } | |
| } | |
| } | |
| renderedChatLogsCount = history.length; | |
| // Auto-scroll chat box down with a short delay for fluid rendering | |
| setTimeout(() => { | |
| chatBox.scrollTop = chatBox.scrollHeight; | |
| }, 50); | |
| } | |
| // --- Toggle HUD Glowing Cockpit States --- | |
| function updateStateOverlays(state, alertMsg) { | |
| const mainPanel = document.getElementById("main-panel"); | |
| const badge = document.getElementById("hud-state-badge"); | |
| const slmIndicator = document.getElementById("slm-indicator"); | |
| // Clean current state classes | |
| mainPanel.className = "glass-panel video-panel"; | |
| if (state === "NORMAL") { | |
| mainPanel.classList.add("state-normal"); | |
| badge.innerText = alertMsg ? alertMsg : "NORMAL"; | |
| badge.style.borderColor = "var(--safe-color)"; | |
| badge.style.color = "var(--safe-color)"; | |
| badge.style.boxShadow = "0 0 10px rgba(0, 255, 128, 0.2)"; | |
| slmIndicator.innerText = "SLM STANDBY"; | |
| slmIndicator.style.color = "var(--safe-color)"; | |
| } | |
| else if (state === "CLOSED_3S") { | |
| mainPanel.classList.add("state-warn"); | |
| badge.innerText = alertMsg ? alertMsg : "EYES CLOSED (3-5s)"; | |
| badge.style.borderColor = "var(--warn-color)"; | |
| badge.style.color = "var(--warn-color)"; | |
| badge.style.boxShadow = "0 0 15px rgba(255, 170, 0, 0.3)"; | |
| } | |
| else if (state === "CLOSED_5S" || state === "WAITING_REST_RESPONSE" || state === "WAITING_SONG_RESPONSE") { | |
| mainPanel.classList.add("state-danger"); | |
| badge.innerText = alertMsg ? alertMsg : "CRITICAL WARNING"; | |
| badge.style.borderColor = "var(--danger-color)"; | |
| badge.style.color = "var(--danger-color)"; | |
| badge.style.boxShadow = "0 0 25px rgba(255, 40, 80, 0.5)"; | |
| if (state.startsWith("WAITING")) { | |
| slmIndicator.innerText = "SLM ACTIVE DIALOGUE"; | |
| slmIndicator.style.color = "var(--accent-color)"; | |
| } | |
| } | |
| else if (state === "PLAYING_MUSIC") { | |
| mainPanel.classList.add("state-normal"); | |
| badge.innerText = "BEATS PLAYING"; | |
| badge.style.borderColor = "var(--accent-color)"; | |
| badge.style.color = "var(--accent-color)"; | |
| badge.style.boxShadow = "0 0 10px rgba(0, 191, 255, 0.2)"; | |
| } | |
| } | |
| // --- Control Desk AJAX REST Bridge --- | |
| function toggleTracking() { | |
| fetch('/api/toggle_detection', { method: 'POST' }) | |
| .then(response => response.json()) | |
| .then(data => { | |
| console.log("Tracking toggled, active state:", data.detection_active); | |
| }) | |
| .catch(err => console.error("Error toggling tracking:", err)); | |
| } | |
| function triggerReset() { | |
| fetch('/api/reset', { method: 'POST' }) | |
| .then(response => response.json()) | |
| .then(data => { | |
| console.log("System reset:", data.message); | |
| }) | |
| .catch(err => console.error("Error triggering reset:", err)); | |
| } | |
| function triggerMusic() { | |
| fetch('/api/trigger_music', { method: 'POST' }) | |
| .then(response => response.json()) | |
| .then(data => { | |
| console.log("Music play request sent:", data.message); | |
| }) | |
| .catch(err => console.error("Error playing energetic beats:", err)); | |
| } | |
| // --- Helper Functions --- | |
| function escapeHTML(str) { | |
| return str.replace(/[&<>'"]/g, | |
| tag => ({ | |
| '&': '&', | |
| '<': '<', | |
| '>': '>', | |
| "'": ''', | |
| '"': '"' | |
| }[tag] || tag) | |
| ); | |
| } | |