// YOLOv5 Web App - Main JavaScript // Dynamic API URL detection for both local and Hugging Face deployment const API_URL = window.location.protocol + '//' + window.location.host; const WS_URL = (window.location.protocol === 'https:' ? 'wss://' : 'ws://') + window.location.host + '/ws'; let ws = null; let webcamStream = null; let isWebcamActive = false; let animationFrame = null; let stats = { fps: 0, latency: 0, objectCount: 0, avgConf: 0 }; let objectsChart = null; let performanceChart = null; let uploadedFiles = []; // Initialize app document.addEventListener('DOMContentLoaded', () => { initializeTabs(); initializeTheme(); initializeWebcam(); initializeUpload(); initializeDashboard(); loadStatistics(); loadHistory(); // Auto-refresh statistics every 5 seconds setInterval(loadStatistics, 5000); }); // Tab Management function initializeTabs() { const tabBtns = document.querySelectorAll('.tab-btn'); const tabContents = document.querySelectorAll('.tab-content'); tabBtns.forEach(btn => { btn.addEventListener('click', () => { const tabName = btn.dataset.tab; // Update active states tabBtns.forEach(b => b.classList.remove('active', 'text-blue-600', 'border-b-2', 'border-blue-600')); tabContents.forEach(c => c.classList.add('hidden')); btn.classList.add('active', 'text-blue-600', 'border-b-2', 'border-blue-600'); document.getElementById(`${tabName}-tab`).classList.remove('hidden'); // Load data when switching tabs if (tabName === 'dashboard') loadStatistics(); if (tabName === 'history') loadHistory(); }); }); // Set initial active state document.querySelector('.tab-btn').classList.add('text-blue-600', 'border-b-2', 'border-blue-600'); } // Theme Management function initializeTheme() { const themeToggle = document.getElementById('themeToggle'); const html = document.documentElement; // Load saved theme const savedTheme = localStorage.getItem('theme') || 'light'; if (savedTheme === 'dark') { html.classList.add('dark'); } themeToggle.addEventListener('click', () => { html.classList.toggle('dark'); const newTheme = html.classList.contains('dark') ? 'dark' : 'light'; localStorage.setItem('theme', newTheme); }); } // Webcam Management function initializeWebcam() { const startBtn = document.getElementById('startWebcam'); const stopBtn = document.getElementById('stopWebcam'); const confThreshold = document.getElementById('confThreshold'); const confValue = document.getElementById('confValue'); startBtn.addEventListener('click', startWebcamDetection); stopBtn.addEventListener('click', stopWebcamDetection); confThreshold.addEventListener('input', (e) => { confValue.textContent = e.target.value; }); } async function startWebcamDetection() { try { const video = document.getElementById('webcam'); const canvas = document.getElementById('webcamCanvas'); const placeholder = document.getElementById('webcamPlaceholder'); const stats = document.getElementById('webcamStats'); const startBtn = document.getElementById('startWebcam'); const stopBtn = document.getElementById('stopWebcam'); // Get webcam stream webcamStream = await navigator.mediaDevices.getUserMedia({ video: { width: 1280, height: 720 } }); video.srcObject = webcamStream; // Show video video.classList.remove('hidden'); canvas.classList.remove('hidden'); placeholder.classList.add('hidden'); stats.classList.remove('hidden'); startBtn.classList.add('hidden'); stopBtn.classList.remove('hidden'); isWebcamActive = true; console.log('đŸŽ„ Webcam dĂ©marrĂ©e, connexion WebSocket...'); // Connect WebSocket connectWebSocket(); } catch (error) { console.error('Error accessing webcam:', error); alert('Erreur: Impossible d\'accĂ©der Ă  la webcam. VĂ©rifiez les permissions.'); } } function stopWebcamDetection() { isWebcamActive = false; if (webcamStream) { webcamStream.getTracks().forEach(track => track.stop()); webcamStream = null; } if (ws) { ws.close(); ws = null; } if (animationFrame) { cancelAnimationFrame(animationFrame); } const video = document.getElementById('webcam'); const canvas = document.getElementById('webcamCanvas'); const placeholder = document.getElementById('webcamPlaceholder'); const stats = document.getElementById('webcamStats'); const startBtn = document.getElementById('startWebcam'); const stopBtn = document.getElementById('stopWebcam'); video.classList.add('hidden'); canvas.classList.add('hidden'); placeholder.classList.remove('hidden'); stats.classList.add('hidden'); startBtn.classList.remove('hidden'); stopBtn.classList.add('hidden'); document.getElementById('liveDetections').innerHTML = '

Aucune détection

'; } function connectWebSocket() { console.log('🔌 Connexion WebSocket Ă :', WS_URL); ws = new WebSocket(WS_URL); ws.onopen = () => { console.log('✅ WebSocket connectĂ© avec succĂšs!'); // Attendre un peu que la vidĂ©o soit prĂȘte, puis commencer Ă  envoyer setTimeout(() => { console.log('DĂ©marrage de l\'envoi de frames...'); sendWebcamFrame(); }, 500); }; ws.onmessage = (event) => { console.log('Frame reçue du serveur'); const data = JSON.parse(event.data); displayWebcamDetections(data); updateWebcamStats(data); }; ws.onerror = (error) => { console.error('❌ WebSocket error:', error); alert('Erreur WebSocket. VĂ©rifiez la connexion au serveur.'); }; ws.onclose = () => { console.log('⚠ WebSocket dĂ©connectĂ©'); if (isWebcamActive) { // RĂ©essayer la connexion si la webcam est toujours active setTimeout(() => { console.log('Tentative de reconnexion...'); connectWebSocket(); }, 2000); } }; } function sendWebcamFrame() { if (!isWebcamActive || !ws || ws.readyState !== WebSocket.OPEN) { console.log('Cannot send frame:', { isWebcamActive, wsState: ws?.readyState }); return; } const video = document.getElementById('webcam'); const canvas = document.getElementById('webcamCanvas'); // VĂ©rifier que la vidĂ©o est prĂȘte if (!video.videoWidth || !video.videoHeight) { console.log('Video not ready, retrying...'); setTimeout(() => sendWebcamFrame(), 100); return; } const ctx = canvas.getContext('2d'); canvas.width = video.videoWidth; canvas.height = video.videoHeight; ctx.drawImage(video, 0, 0); const frameData = canvas.toDataURL('image/jpeg', 0.8); const model = document.getElementById('modelSelect').value; const confidence = parseFloat(document.getElementById('confThreshold').value); const startTime = Date.now(); try { ws.send(JSON.stringify({ frame: frameData, model: model, confidence: confidence, timestamp: startTime })); console.log('Frame sent successfully'); } catch (error) { console.error('Error sending frame:', error); } // Send next frame after 500ms (2 FPS) pour meilleure stabilitĂ© if (isWebcamActive) { setTimeout(() => sendWebcamFrame(), 500); } } function displayWebcamDetections(data) { const canvas = document.getElementById('webcamCanvas'); const img = new Image(); img.onload = () => { const ctx = canvas.getContext('2d'); canvas.width = img.width; canvas.height = img.height; ctx.drawImage(img, 0, 0); }; img.src = data.image; // Update detections list with better display const liveDetections = document.getElementById('liveDetections'); if (data.detections.length === 0) { liveDetections.innerHTML = `

Aucun objet détecté

Montrez des personnes, animaux, ou objets à la caméra

`; } else { // Group detections by class const grouped = {}; data.detections.forEach(det => { if (!grouped[det.class]) { grouped[det.class] = []; } grouped[det.class].push(det); }); liveDetections.innerHTML = Object.entries(grouped).map(([className, dets]) => `
${className}
×${dets.length}
${dets.map(det => ` ${(det.confidence * 100).toFixed(0)}% `).join('')}
`).join(''); } } function updateWebcamStats(data) { const latency = Date.now() - new Date(data.timestamp).getTime(); const fps = 1000 / (data.processing_time * 1000 + latency); document.getElementById('fps').textContent = fps.toFixed(1); document.getElementById('latency').textContent = latency.toFixed(0); document.getElementById('quickObjectCount').textContent = data.detections.length; if (data.detections.length > 0) { const avgConf = data.detections.reduce((sum, d) => sum + d.confidence, 0) / data.detections.length; document.getElementById('quickAvgConf').textContent = (avgConf * 100).toFixed(1) + '%'; } document.getElementById('quickProcessTime').textContent = (data.processing_time * 1000).toFixed(0) + 'ms'; } // Upload Management function initializeUpload() { const dropZone = document.getElementById('dropZone'); const fileInput = document.getElementById('fileInput'); const processBtn = document.getElementById('processUpload'); // Drag and drop dropZone.addEventListener('dragover', (e) => { e.preventDefault(); dropZone.classList.add('border-blue-500', 'bg-blue-50', 'dark:bg-blue-900'); }); dropZone.addEventListener('dragleave', () => { dropZone.classList.remove('border-blue-500', 'bg-blue-50', 'dark:bg-blue-900'); }); dropZone.addEventListener('drop', (e) => { e.preventDefault(); dropZone.classList.remove('border-blue-500', 'bg-blue-50', 'dark:bg-blue-900'); const files = Array.from(e.dataTransfer.files).filter(f => f.type.startsWith('image/')); handleFiles(files); }); fileInput.addEventListener('change', (e) => { const files = Array.from(e.target.files); handleFiles(files); }); processBtn.addEventListener('click', processUploadedImages); } function handleFiles(files) { uploadedFiles = files; const preview = document.getElementById('uploadPreview'); const processBtn = document.getElementById('processUpload'); if (files.length === 0) return; preview.classList.remove('hidden'); processBtn.classList.remove('hidden'); preview.innerHTML = files.map((file, index) => { const url = URL.createObjectURL(file); return `
${file.name}
`; }).join(''); } async function processUploadedImages() { const resultsDiv = document.getElementById('uploadResults'); const processBtn = document.getElementById('processUpload'); processBtn.disabled = true; processBtn.innerHTML = 'Traitement en cours...'; resultsDiv.innerHTML = '
'; try { const formData = new FormData(); uploadedFiles.forEach(file => formData.append('files', file)); const model = document.getElementById('modelSelect').value; const confidence = document.getElementById('confThreshold').value; const response = await fetch(`${API_URL}/detect/batch?model=${model}&confidence=${confidence}`, { method: 'POST', body: formData }); const data = await response.json(); if (data.success) { displayUploadResults(data.results); } else { throw new Error('Traitement échoué'); } } catch (error) { console.error('Error processing images:', error); resultsDiv.innerHTML = '

Erreur lors du traitement

'; } finally { processBtn.disabled = false; processBtn.innerHTML = 'Analyser les images'; } } function displayUploadResults(results) { const resultsDiv = document.getElementById('uploadResults'); resultsDiv.innerHTML = results.map(result => `

${result.filename}

${result.detections && result.detections.length > 0 ? `
${result.detections.length} objet(s) détecté(s)
${result.detections.map(det => `
${det.class}
${(det.confidence * 100).toFixed(1)}%
`).join('')}
` : `

Aucun objet détecté

YOLOv5 détecte : personnes, animaux, véhicules, objets du quotidien

💡 Essayez avec une photo contenant des personnes, voitures, ou objets physiques

`}
Traité en ${(result.processing_time * 1000).toFixed(0)}ms
`).join(''); } // Dashboard Management function initializeDashboard() { // Initialize charts const ctx1 = document.getElementById('objectsChart').getContext('2d'); objectsChart = new Chart(ctx1, { type: 'doughnut', data: { labels: [], datasets: [{ data: [], backgroundColor: [ '#3b82f6', '#8b5cf6', '#ec4899', '#f59e0b', '#10b981', '#ef4444', '#6366f1', '#14b8a6', '#f97316', '#84cc16' ] }] }, options: { responsive: true, plugins: { legend: { position: 'bottom' } } } }); const ctx2 = document.getElementById('performanceChart').getContext('2d'); performanceChart = new Chart(ctx2, { type: 'line', data: { labels: [], datasets: [{ label: 'Temps de traitement (ms)', data: [], borderColor: '#3b82f6', backgroundColor: 'rgba(59, 130, 246, 0.1)', tension: 0.4 }] }, options: { responsive: true, scales: { y: { beginAtZero: true } } } }); } async function loadStatistics() { try { const response = await fetch(`${API_URL}/statistics`); const data = await response.json(); // Update stat cards document.getElementById('totalDetections').textContent = data.total_detections; document.getElementById('totalImages').textContent = data.total_images_processed; document.getElementById('avgFps').textContent = data.fps.toFixed(1); // Update objects chart const labels = Object.keys(data.objects_detected); const values = Object.values(data.objects_detected); objectsChart.data.labels = labels; objectsChart.data.datasets[0].data = values; objectsChart.update(); } catch (error) { console.error('Error loading statistics:', error); } } // History Management async function loadHistory() { try { const response = await fetch(`${API_URL}/history`); const data = await response.json(); const historyList = document.getElementById('historyList'); if (data.history.length === 0) { historyList.innerHTML = '

Aucun historique

'; return; } historyList.innerHTML = data.history.reverse().map(item => `
${item.filename} ${new Date(item.timestamp).toLocaleString()}
${item.detections.map(det => ` ${det.class}: ${(det.confidence * 100).toFixed(0)}% `).join('')}
`).join(''); } catch (error) { console.error('Error loading history:', error); } } document.getElementById('clearHistory').addEventListener('click', async () => { if (confirm('Êtes-vous sĂ»r de vouloir effacer l\'historique ?')) { try { await fetch(`${API_URL}/history`, { method: 'DELETE' }); loadHistory(); } catch (error) { console.error('Error clearing history:', error); } } });