// Live Camera JavaScript let detectionCount = 0; let alertCount = 0; let personDetectedCount = 0; let personPresent = false; let availableModels = {}; let selectedModels = []; let cameraRunning = false; let sessionStartTime = null; let sessionData = { startTime: null, endTime: null, duration: 0, selectedModels: [], detections: [], alerts: [], totalDetections: 0, totalAlerts: 0, personDetected: 0, personPresent: false, }; // Initialize model selection on page load document.addEventListener("DOMContentLoaded", async () => { await loadAvailableModels(); await loadSelectedModels(); await loadAvailableCameras(); startDetectionHistoryPolling(); // Start loading detection history // Do NOT auto-start camera - wait for user to click Start // initializeCameraFeed(); }); // Load available cameras and populate dropdown async function loadAvailableCameras() { try { const response = await fetch("/api/cameras"); const result = await response.json(); if (!result.success) { console.error("Failed to load cameras:", result.error); document.getElementById("camera-select").innerHTML = ''; return; } const cameras = result.cameras || []; const selected = result.selected || 0; const cameraSelect = document.getElementById("camera-select"); if (cameras.length === 0) { cameraSelect.innerHTML = ''; return; } // Populate dropdown with available cameras cameraSelect.innerHTML = cameras .map( (cam) => ``, ) .join(""); // Add change listener to camera select cameraSelect.addEventListener("change", handleCameraChange); } catch (error) { console.error("Error loading cameras:", error); document.getElementById("camera-select").innerHTML = ''; } } // Handle camera selection change async function handleCameraChange(event) { const cameraIndex = parseInt(event.target.value); if (isNaN(cameraIndex)) return; // Stop current camera if running if (cameraRunning) { await stopCamera(); } try { const response = await fetch("/api/select-camera", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ camera_index: cameraIndex }), }); const result = await response.json(); if (result.success) { showToast(`πŸ“· Camera ${cameraIndex} selected`, "success"); console.log("Camera selected:", result.message); } else { showToast("Failed to select camera: " + result.error, "error"); // Reload cameras list await loadAvailableCameras(); } } catch (error) { console.error("Error selecting camera:", error); showToast("Error selecting camera", "error"); } } // Start camera stream with selected models async function startCamera() { if (cameraRunning) { showToast("Camera is already running", "warning"); return; } // Check if models are selected const checkboxes = document.querySelectorAll(".model-checkbox:checked"); const selected = Array.from(checkboxes).map((cb) => cb.dataset.modelId); if (selected.length === 0) { showToast( "Please select at least one model before starting the camera", "warning", ); return; } try { // Load previous person detection data await initializePersonDetection(); // Apply selected models const response = await fetch("/api/set-models", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ models: selected }), }); const data = await response.json(); if (!data.success) { showToast("Failed to apply model selection", "error"); return; } // Start monitoring session const sessionResponse = await fetch("/api/start-camera", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ selectedModels: selected }), }); const sessionData = await sessionResponse.json(); if (!sessionData.success) { showToast("Failed to start camera session", "error"); return; } // Update UI cameraRunning = true; sessionStartTime = new Date(); sessionData.startTime = sessionStartTime; sessionData.selectedModels = selected; // Show camera feed const cameraFeed = document.getElementById("camera-feed"); const cameraPlaceholder = document.getElementById("camera-placeholder"); const videoOverlay = document.getElementById("video-overlay"); cameraFeed.style.display = "block"; cameraFeed.src = "/camera_feed?" + new Date().getTime(); cameraPlaceholder.style.display = "none"; videoOverlay.style.display = "block"; // Update controls document.getElementById("start-btn").style.display = "none"; document.getElementById("stop-btn").style.display = "block"; // Show session info document.getElementById("camera-status-info").style.display = "block"; document.getElementById("session-models").textContent = selected.join(", "); // Initialize camera feed monitoring initializeCameraFeed(); // Start recording the session automatically try { const recordResponse = await fetch("/api/start-recording", { method: "POST", }); const recordData = await recordResponse.json(); if (recordData.success) { isRecording = true; showToast("Recording live session...", "info"); } } catch (error) { console.error("Error starting recording:", error); } // Update session duration every second setInterval(() => { if (cameraRunning) { const elapsed = Math.floor((new Date() - sessionStartTime) / 1000); const minutes = Math.floor(elapsed / 60); const seconds = elapsed % 60; document.getElementById("session-duration").textContent = `${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}`; } }, 1000); showToast("Camera started with selected models", "success"); } catch (error) { console.error("Error starting camera:", error); showToast("Failed to start camera", "error"); } } // Initialize and monitor camera feed function initializeCameraFeed() { const cameraFeed = document.getElementById("camera-feed"); if (!cameraFeed) return; // Add error handling cameraFeed.addEventListener("error", function () { console.error("Camera feed error - reloading..."); if (cameraRunning) { document.getElementById("status-badge").innerHTML = ` RECONNECTING... `; // Reload feed after 2 seconds setTimeout(() => { cameraFeed.src = "/camera_feed?" + new Date().getTime(); }, 2000); } }); // Refresh camera feed periodically to ensure stream stays active const feedRefreshInterval = setInterval(() => { if (!cameraRunning) { clearInterval(feedRefreshInterval); return; } const isStreaming = cameraFeed.src && cameraFeed.src.includes("/camera_feed"); if (!isStreaming && cameraRunning) { console.log("Restarting camera feed..."); cameraFeed.src = "/camera_feed?" + new Date().getTime(); } }, 30000); // Every 30 seconds // Start polling for stats updateLiveStats(); // Initial call const statsInterval = setInterval(() => { if (cameraRunning) { updateLiveStats(); } else { clearInterval(statsInterval); } }, 1000); } async function loadAvailableModels() { try { const response = await fetch("/api/available-models"); const data = await response.json(); availableModels = data.models || {}; renderModelSelection(); } catch (error) { console.error("Error loading models:", error); showToast("Failed to load available models", "error"); } } async function loadSelectedModels() { try { const response = await fetch("/api/get-selected-models"); const data = await response.json(); selectedModels = data.selected_models || []; updateModelCheckboxes(); } catch (error) { console.error("Error loading selected models:", error); } } function renderModelSelection() { const grid = document.getElementById("models-grid"); if (!grid) return; const modelCards = Object.entries(availableModels).map( ([modelKey, modelInfo]) => { const isSelected = selectedModels.includes(modelKey); return `
`; }, ); grid.innerHTML = modelCards.length > 0 ? modelCards.join("") : "

No models available

"; } function updateModelCheckboxes() { const checkboxes = document.querySelectorAll(".model-checkbox"); checkboxes.forEach((checkbox) => { checkbox.checked = selectedModels.includes(checkbox.dataset.modelId); }); } function toggleModelPanel() { const content = document.getElementById("model-panel-content"); const icon = document.getElementById("model-toggle-icon"); if (!content) return; content.classList.toggle("open"); icon.textContent = content.classList.contains("open") ? "β–²" : "β–Ό"; } function selectAllModels() { const checkboxes = document.querySelectorAll(".model-checkbox"); checkboxes.forEach((checkbox) => { checkbox.checked = true; }); } function deselectAllModels() { const checkboxes = document.querySelectorAll(".model-checkbox"); checkboxes.forEach((checkbox) => { checkbox.checked = false; }); } async function applyModelSelection() { const checkboxes = document.querySelectorAll(".model-checkbox:checked"); const selected = Array.from(checkboxes).map((cb) => cb.dataset.modelId); try { const response = await fetch("/api/set-models", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ models: selected }), }); const data = await response.json(); if (data.success) { selectedModels = selected; showToast( `Applied ${selected.length > 0 ? selected.length + " model(s)" : "all models"}`, "success", ); } else { showToast("Failed to apply model selection", "error"); } } catch (error) { console.error("Error applying model selection:", error); showToast("Error applying model selection", "error"); } } function showToast(message, type = "info") { const container = document.getElementById("toast-container"); if (!container) return; const toast = document.createElement("div"); toast.className = `toast ${type}`; toast.textContent = message; container.appendChild(toast); setTimeout(() => { toast.remove(); }, 2600); } // ===================== Person Detection Tracking ===================== // Load previous person detection data from database async function loadPersonDetectionHistory() { try { const response = await fetch("/api/person-detection-history"); const data = await response.json(); if (data.success && data.data) { personDetectedCount = data.data.total_detections || 0; personPresent = data.data.currently_present || false; updatePersonDetectionUI(); } } catch (error) { console.error("Error loading person detection history:", error); } } // Save person detection data to database async function savePersonDetection( count, isPresent, confidence = 0.0, detailsObj = null, ) { try { const detailsData = detailsObj || { timestamp: new Date().toISOString(), session_active: cameraRunning, }; const response = await fetch("/api/save-person-detection", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ person_count: count, is_present: isPresent, confidence: confidence, detection_details: JSON.stringify(detailsData), }), }); const data = await response.json(); if (!data.success) { console.error("Failed to save person detection:", data.message); } } catch (error) { console.error("Error saving person detection:", error); } } // Update person detection UI elements function updatePersonDetectionUI() { const detectedEl = document.getElementById("person-detected"); const presentEl = document.getElementById("person-present"); if (detectedEl) { detectedEl.textContent = personDetectedCount; } if (presentEl) { presentEl.textContent = personPresent ? "βœ… Yes" : "❌ No"; presentEl.style.color = personPresent ? "var(--ok)" : "var(--danger)"; } } // Track person detected in detection results (call this when processing detections) // Supports detections from YOLO with class='person' and bounding boxes function recordPersonDetection(detectionData) { let hasPersonDetection = false; let personCount = 0; let boundingBoxes = []; let avgConfidence = 0.0; if (detectionData && typeof detectionData === "object") { // Check for detections array (from YOLO/frame processing) if (Array.isArray(detectionData)) { for (const detection of detectionData) { if ( detection.class && (detection.class.toLowerCase() === "person" || detection.class.toLowerCase() === "human") ) { hasPersonDetection = true; personCount++; if (detection.bbox) { boundingBoxes.push({ bbox: detection.bbox, confidence: detection.confidence || 0.0, type: detection.type || "object", }); } if (detection.confidence) { avgConfidence += detection.confidence; } } } if (personCount > 0) { avgConfidence = avgConfidence / personCount; } } // Legacy support for object detection structure else if (detectionData.objects) { for (const obj of detectionData.objects) { if ( obj.label && (obj.label.toLowerCase() === "person" || obj.label.toLowerCase() === "human") ) { hasPersonDetection = true; personCount++; if (obj.bbox) { boundingBoxes.push({ bbox: obj.bbox, confidence: obj.confidence || 0.0, label: obj.label, }); } if (obj.confidence) { avgConfidence += obj.confidence; } } } if (personCount > 0) { avgConfidence = avgConfidence / personCount; } } // Update person detected count if person detected if (hasPersonDetection) { personDetectedCount++; personPresent = true; updatePersonDetectionUI(); // Save with bounding box information savePersonDetection(personDetectedCount, true, avgConfidence, { person_count: personCount, bounding_boxes: boundingBoxes, timestamp: new Date().toISOString(), session_active: cameraRunning, }); } } } // Call this on camera start to load previous data async function initializePersonDetection() { await loadPersonDetectionHistory(); } async function stopCamera() { if (!cameraRunning) { showToast("Camera is not running", "warning"); return; } if (!confirm("Stop camera and save the monitoring session?")) { return; } // Stop recording if active and get video filename let recordingFilename = null; if (isRecording) { isRecording = false; clearInterval(recordingInterval); try { const stopResponse = await fetch("/api/stop-recording", { method: "POST", }); const stopData = await stopResponse.json(); if (stopData.success) { recordingFilename = stopData.filename; sessionData.recordingFilename = recordingFilename; showToast("Recording saved", "success"); } } catch (error) { console.error("Error stopping recording:", error); } } // Prepare session data sessionData.endTime = new Date(); sessionData.duration = Math.floor( (sessionData.endTime - sessionData.startTime) / 1000, ); sessionData.totalDetections = detectionCount; sessionData.totalAlerts = alertCount; // Stop camera cameraRunning = false; const cameraFeed = document.getElementById("camera-feed"); cameraFeed.src = ""; cameraFeed.style.display = "none"; // Hide controls document.getElementById("stop-btn").style.display = "none"; document.getElementById("record-btn").style.display = "none"; document.getElementById("start-btn").style.display = "block"; document.getElementById("camera-status-info").style.display = "none"; // Show camera placeholder document.getElementById("camera-placeholder").style.display = "block"; document.getElementById("video-overlay").style.display = "none"; // Save session to database saveMonitoringSession(sessionData); } async function saveMonitoringSession(session) { try { const response = await fetch("/api/save-monitoring-session", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(session), }); const data = await response.json(); if (data.success) { showToast("Monitoring session saved successfully", "success"); // Show session summary const summary = `

πŸ“Š Session Summary

Duration: ${Math.floor(session.duration / 60)}:${String(session.duration % 60).padStart(2, "0")}

Total Detections: ${session.totalDetections}

Total Alerts: ${session.totalAlerts}

Models Used: ${session.selectedModels.join(", ")}

`; // You could show this in a modal or in the alerts panel setTimeout(() => { alert( `Session saved!\n\nDuration: ${Math.floor(session.duration / 60)}:${String(session.duration % 60).padStart(2, "0")}\nDetections: ${session.totalDetections}\nAlerts: ${session.totalAlerts}`, ); }, 500); // Reset counters detectionCount = 0; alertCount = 0; document.getElementById("total-detections").textContent = "0"; document.getElementById("total-alerts").textContent = "0"; } else { showToast("Failed to save monitoring session: " + data.error, "error"); } } catch (error) { console.error("Error saving monitoring session:", error); showToast("Error saving session to database", "error"); } } function toggleFullscreen() { const videoContainer = document.querySelector(".video-container"); if (!document.fullscreenElement) { if (videoContainer.requestFullscreen) { videoContainer.requestFullscreen(); } else if (videoContainer.webkitRequestFullscreen) { videoContainer.webkitRequestFullscreen(); } else if (videoContainer.msRequestFullscreen) { videoContainer.msRequestFullscreen(); } } else { if (document.exitFullscreen) { document.exitFullscreen(); } } } function addAlert(alertData) { const alertsContainer = document.getElementById("alerts-container"); // Remove placeholder if exists const placeholder = alertsContainer.querySelector(".alert-placeholder"); if (placeholder) { placeholder.remove(); } const alertDiv = document.createElement("div"); alertDiv.className = "alert-item"; if (alertData.severity === "HIGH" || alertData.severity === "HIGH RISK") { alertDiv.classList.add("high"); } const timestamp = new Date().toLocaleTimeString(); alertDiv.innerHTML = `
${alertData.type || "Alert"} ${timestamp}
${alertData.message}
${alertData.confidence ? `
Confidence: ${(alertData.confidence * 100).toFixed(1)}%
` : ""} `; // Add to top of list alertsContainer.insertBefore(alertDiv, alertsContainer.firstChild); // Update counter alertCount++; document.getElementById("total-alerts").textContent = alertCount; // Keep only last 10 alerts visible while (alertsContainer.children.length > 10) { alertsContainer.removeChild(alertsContainer.lastChild); } } // Monitor camera feed for errors (when element exists) document.addEventListener("DOMContentLoaded", function () { const cameraFeedElement = document.getElementById("camera-feed"); if (cameraFeedElement) { cameraFeedElement.addEventListener("error", function () { console.error("Camera feed error"); const statusBadge = document.getElementById("status-badge"); if (statusBadge) { statusBadge.innerHTML = ` DISCONNECTED `; } }); } }); // Poll for live statistics updates async function updateLiveStats() { try { const response = await fetch("/api/live-stats"); const data = await response.json(); if (data.error) { console.error("Stats error:", data.error); return; } // Update detection count based on actual detections detectionCount = data.person_detections + data.weapon_detections; document.getElementById("total-detections").textContent = detectionCount; // Update alert count (only if alerts have been triggered) if (data.alert_count > 0) { document.getElementById("total-alerts").textContent = data.alert_count; alertCount = data.alert_count; } // Update pose analysis information if (data.pose_analysis && Object.keys(data.pose_analysis).length > 0) { const pose = data.pose_analysis; document.getElementById("pose-risk-level").textContent = pose.risk_level || "SAFE"; document.getElementById("pose-action").textContent = pose.action || "β€”"; document.getElementById("pose-score").textContent = ( pose.risk_score || 0 ).toFixed(2); // Update color based on risk level const riskElement = document.getElementById("pose-risk-level"); if (pose.risk_level === "HIGH_RISK") { riskElement.style.color = "var(--danger)"; } else if (pose.risk_level === "LOW_RISK") { riskElement.style.color = "var(--orange-600)"; } else { riskElement.style.color = "var(--ok)"; } } // Show visual indicators for active detections updateDetectionIndicators(data); } catch (error) { console.error("Error fetching live stats:", error); } } function updateDetectionIndicators(data) { // Visual feedback when person or weapon is visible (3+ frames) const cameraFeed = document.getElementById("camera-feed"); if (data.person_visible || data.weapon_visible) { if (!cameraFeed.classList.contains("detection-active")) { cameraFeed.classList.add("detection-active"); } } else { cameraFeed.classList.remove("detection-active"); } } // Update detection count periodically (now fetches from backend) // Moved to initializeCameraFeed function // Load and display detection history async function loadDetectionHistory() { try { const response = await fetch("/api/detection-history?limit=12"); const result = await response.json(); if (!result.success) { console.error("Failed to load detection history"); return; } const detections = result.data || []; const gallery = document.getElementById("detection-gallery"); // Update badge document.getElementById("detection-count-badge").textContent = detections.length; if (detections.length === 0) { gallery.innerHTML = `

πŸ“Έ No detections yet

Detected threats will appear here
`; return; } // Build gallery gallery.innerHTML = detections .map((det) => { const typeEmoji = { weapon: "πŸ”«", risk: "⚠️", unusual: "❓", }[det.detection_type] || "πŸ“Έ"; const levelColor = { LOW: "var(--blue-600)", MEDIUM: "var(--orange-600)", HIGH: "var(--danger)", CRITICAL: "var(--danger)", }[det.alert_level] || "var(--slate-600)"; const timestamp = new Date(det.detected_at).toLocaleTimeString(); return `
Detection
${typeEmoji} ${det.detection_type.toUpperCase()}
${timestamp}
${det.alert_level}
`; }) .join(""); } catch (error) { console.error("Error loading detection history:", error); } } function openDetectionDetail(id, filename, type, level, timestamp) { // Create modal with detection details const modal = `
Detection

Type: ${type.toUpperCase()}

Alert Level: ${level}

Detected: ${timestamp}

`; new Modal(`Detection #${id}`, modal, { footer: false }).show(); } // Refresh detection history periodically function startDetectionHistoryPolling() { // Load immediately loadDetectionHistory(); // Then every 5 seconds setInterval(loadDetectionHistory, 5000); } // Video Recording Functions let isRecording = false; let recordingStartTime = null; let recordingInterval = null; async function toggleRecording() { if (!cameraRunning) { showToast("❌ Camera is not running. Start the camera first.", "error"); return; } if (isRecording) { // Stop recording try { const response = await fetch("/api/stop-recording", { method: "POST" }); const result = await response.json(); if (result.success) { isRecording = false; clearInterval(recordingInterval); document.getElementById("record-btn").classList.remove("btn-danger"); document.getElementById("record-btn").textContent = "πŸ”΄ Record"; document.getElementById("record-btn").classList.add("btn-secondary"); const duration = result.duration ? ` (${result.duration}s)` : ""; showToast(`βœ… Recording stopped${duration}`, "success"); } else { showToast("❌ Error stopping recording: " + result.error, "error"); } } catch (error) { console.error("Error stopping recording:", error); showToast("❌ Error stopping recording", "error"); } } else { // Start recording try { const response = await fetch("/api/start-recording", { method: "POST" }); const result = await response.json(); if (result.success) { isRecording = true; recordingStartTime = new Date(); document.getElementById("record-btn").classList.remove("btn-secondary"); document.getElementById("record-btn").classList.add("btn-danger"); document.getElementById("record-btn").innerHTML = "⏹️ Stop Recording"; showToast("πŸ”΄ Recording started", "success"); // Update recording duration every second recordingInterval = setInterval(() => { if (isRecording && recordingStartTime) { const elapsed = Math.round( (new Date() - recordingStartTime) / 1000, ); document.getElementById("record-btn").innerHTML = `⏹️ Stop (${elapsed}s)`; } }, 1000); } else { showToast("❌ Error starting recording: " + result.error, "error"); } } catch (error) { console.error("Error starting recording:", error); showToast("❌ Error starting recording", "error"); } } } // ─── Storage Drawer ────────────────────────────────────────────────────────── let _storageData = { sessions: [], videos: [], images: [] }; let _activeStorageTab = "sessions"; async function openStorageManager() { document.getElementById("storage-drawer").classList.add("open"); document.getElementById("storage-backdrop").classList.add("open"); await refreshStorageDrawer(); } function closeStorageDrawer() { document.getElementById("storage-drawer").classList.remove("open"); document.getElementById("storage-backdrop").classList.remove("open"); closeInlinePlayer(); } async function refreshStorageDrawer() { document.getElementById("sd-content").innerHTML = '
⏳

Loading…

'; try { const [sessRes, vidRes, imgRes] = await Promise.all([ fetch("/api/detection-history?type=session&limit=50").then((r) => r.json()), fetch("/api/videos").then((r) => r.json()), fetch("/api/images").then((r) => r.json()), ]); _storageData.sessions = (sessRes.data || []).filter((d) => d.type === "session"); _storageData.videos = vidRes.videos || []; _storageData.images = imgRes.images || []; // Update tab labels with counts document.getElementById("sd-tab-sessions").textContent = `πŸŽ₯ Sessions (${_storageData.sessions.length})`; document.getElementById("sd-tab-recordings").textContent = `🎬 Recordings (${_storageData.videos.length})`; document.getElementById("sd-tab-images").textContent = `πŸ“· Images (${_storageData.images.length})`; // Stats row const totalSecs = _storageData.sessions.reduce((a, s) => a + (s.duration || 0), 0); const totalMins = Math.floor(totalSecs / 60); document.getElementById("sd-stats-row").innerHTML = `
${_storageData.sessions.length} sessions
${totalMins}m recorded
${_storageData.images.length} detection shots
`; renderStorageTab(_activeStorageTab); } catch (e) { document.getElementById("sd-content").innerHTML = '
⚠️

Failed to load storage data.

'; } } function switchStorageTab(tab) { _activeStorageTab = tab; ["sessions", "recordings", "images"].forEach((t) => { document.getElementById("sd-tab-" + t).classList.toggle("active", t === tab); }); closeInlinePlayer(); renderStorageTab(tab); } function renderStorageTab(tab) { const el = document.getElementById("sd-content"); if (tab === "sessions") el.innerHTML = buildSessionsHTML(_storageData.sessions); if (tab === "recordings") el.innerHTML = buildRecordingsHTML(_storageData.videos); if (tab === "images") el.innerHTML = buildImagesHTML(_storageData.images); } // ── Sessions ───────────────────────────────────────────────────────────────── function buildSessionsHTML(sessions) { if (sessions.length === 0) return `
πŸŽ₯

No sessions saved yet.
Sessions are recorded automatically when you stop the camera.

`; return `
${sessions.map(renderSessionCard).join("")}
`; } function renderSessionCard(s) { const dur = s.duration || 0; const durStr = `${Math.floor(dur / 60)}m ${String(dur % 60).padStart(2, "0")}s`; const ts = new Date(s.created_at).toLocaleString(); const videoUrl = s.video_filename ? `/processed/${s.video_filename}` : null; const previewUrl = s.preview_image ? `/processed/${s.preview_image}` : null; const criticalBadge = s.is_critical ? `⚑ CRITICAL` : ""; return `
${previewUrl ? `` : `
πŸŽ₯
`}
${s.session_name || "Monitoring Session"}
⏱ ${durStr}  β€’  ${ts}
πŸ” ${s.detection_count} detections ${s.alert_count > 0 ? `🚨 ${s.alert_count} alerts` : `βœ“ No alerts`} ${criticalBadge}
${videoUrl ? ` ⬇ Download` : `No video`}
`; } async function deleteSession(id) { if (!confirm("Delete this session and its video file?")) return; const card = document.getElementById("sess-card-" + id); if (card) card.style.opacity = "0.4"; try { const r = await fetch(`/api/sessions/${id}`, { method: "DELETE" }); const d = await r.json(); if (d.success) { showToast("Session deleted", "success"); _storageData.sessions = _storageData.sessions.filter((s) => s.id !== id); document.getElementById("sd-tab-sessions").textContent = `πŸŽ₯ Sessions (${_storageData.sessions.length})`; document.getElementById("sd-content").innerHTML = buildSessionsHTML(_storageData.sessions); } else { showToast("Delete failed: " + (d.error || "error"), "error"); if (card) card.style.opacity = "1"; } } catch (e) { showToast("Delete error", "error"); if (card) card.style.opacity = "1"; } } // ── Recordings ──────────────────────────────────────────────────────────────── function buildRecordingsHTML(videos) { if (videos.length === 0) return `
🎬

No raw recordings yet.
Use the Record button during live monitoring.

`; return videos.map((v) => { const ts = new Date(v.created).toLocaleString(); return `
🎬
${v.filename}
${v.size} MB  β€’  ${ts}
⬇
`; }).join(""); } async function deleteVideo(filename) { if (!confirm(`Delete "${filename}"?`)) return; try { const r = await fetch(`/api/videos/${encodeURIComponent(filename)}`, { method: "DELETE" }); const d = await r.json(); if (d.success) { showToast("Recording deleted", "success"); _storageData.videos = _storageData.videos.filter((v) => v.filename !== filename); document.getElementById("sd-tab-recordings").textContent = `🎬 Recordings (${_storageData.videos.length})`; document.getElementById("sd-content").innerHTML = buildRecordingsHTML(_storageData.videos); } else { showToast("Delete failed", "error"); } } catch (e) { showToast("Delete error", "error"); } } // ── Images ──────────────────────────────────────────────────────────────────── function buildImagesHTML(images) { if (images.length === 0) return `
πŸ“·

No detection images yet.
High-risk detections are automatically captured.

`; const tiles = images.map((img) => { const levelColor = img.filename.includes('weapon') ? 'var(--danger)' : img.filename.includes('violence') ? '#f97316' : 'var(--blue-600)'; return `
Detection
⬇
${img.size}KB
`; }).join(""); return `
${tiles}
`; } function viewStorageImage(filename) { const url = `/api/detection-image/${filename}`; new Modal("πŸ“· Detection Image", ` Detection
⬇️ Download
`, { width: "580px" }).show(); } async function deleteImage(filename) { if (!confirm("Delete this detection image?")) return; try { const r = await fetch(`/api/images/${encodeURIComponent(filename)}`, { method: "DELETE" }); const d = await r.json(); if (d.success) { showToast("Image deleted", "success"); _storageData.images = _storageData.images.filter((i) => i.filename !== filename); document.getElementById("sd-tab-images").textContent = `πŸ“· Images (${_storageData.images.length})`; document.getElementById("sd-content").innerHTML = buildImagesHTML(_storageData.images); } else { showToast("Delete failed", "error"); } } catch (e) { showToast("Delete error", "error"); } } // ── Inline player ───────────────────────────────────────────────────────────── function playInline(url) { const player = document.getElementById("sd-inline-player"); const vid = document.getElementById("sd-video-el"); vid.src = url; player.style.display = "block"; vid.play().catch(() => {}); // Scroll player into view player.scrollIntoView({ behavior: "smooth", block: "start" }); } function closeInlinePlayer() { const player = document.getElementById("sd-inline-player"); const vid = document.getElementById("sd-video-el"); vid.pause(); vid.src = ""; player.style.display = "none"; } function viewImage(filename) { viewStorageImage(filename); } function downloadAllImages() { showToast("πŸ’‘ Download images individually from the Images tab", "info"); } // Example: Add test alert (remove in production) // setTimeout(() => { // addAlert({ // type: 'OBJECT DETECTED', // message: 'Person detected in frame', // severity: 'MEDIUM', // confidence: 0.95 // }); // }, 5000);