| |
| |
| |
| |
|
|
| |
| const FireWatch = { |
| |
| config: { |
| maxFileSize: 50 * 1024 * 1024, |
| allowedImageTypes: ['image/jpeg', 'image/jpg', 'image/png', 'image/bmp'], |
| allowedVideoTypes: ['video/mp4', 'video/avi', 'video/mov', 'video/mkv'], |
| apiEndpoints: { |
| analyzeImage: '/analyze/image/', |
| analyzeVideo: '/analyze/video/', |
| contact: '/contact/', |
| modelsStatus: '/api/models/status/' |
| } |
| }, |
|
|
| |
| utils: { |
| |
| |
| |
| getCSRFToken: function() { |
| const token = document.querySelector('[name=csrfmiddlewaretoken]'); |
| return token ? token.value : ''; |
| }, |
|
|
| |
| |
| |
| formatFileSize: function(bytes) { |
| if (bytes === 0) return '0 Bytes'; |
| const k = 1024; |
| const sizes = ['Bytes', 'KB', 'MB', 'GB']; |
| const i = Math.floor(Math.log(bytes) / Math.log(k)); |
| return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; |
| }, |
|
|
| |
| |
| |
| validateFile: function(file, allowedTypes) { |
| if (!file) { |
| return { valid: false, error: 'Aucun fichier sélectionné' }; |
| } |
|
|
| if (file.size > FireWatch.config.maxFileSize) { |
| return { |
| valid: false, |
| error: `Fichier trop volumineux. Taille maximale: ${FireWatch.utils.formatFileSize(FireWatch.config.maxFileSize)}` |
| }; |
| } |
|
|
| if (!allowedTypes.includes(file.type)) { |
| return { |
| valid: false, |
| error: `Type de fichier non supporté: ${file.type}` |
| }; |
| } |
|
|
| return { valid: true }; |
| }, |
|
|
| |
| |
| |
| showToast: function(message, type = 'info', duration = 5000) { |
| const toast = document.createElement('div'); |
| toast.className = `firewatch-toast firewatch-toast-${type}`; |
| toast.innerHTML = ` |
| <div class="flex items-center"> |
| <i class="fas fa-${type === 'success' ? 'check-circle' : type === 'error' ? 'exclamation-circle' : 'info-circle'} mr-2"></i> |
| <span>${message}</span> |
| <button class="ml-auto text-lg" onclick="this.parentElement.parentElement.remove()">×</button> |
| </div> |
| `; |
|
|
| |
| Object.assign(toast.style, { |
| position: 'fixed', |
| top: '20px', |
| right: '20px', |
| zIndex: '9999', |
| padding: '1rem', |
| borderRadius: '0.5rem', |
| boxShadow: '0 4px 12px rgba(0,0,0,0.15)', |
| maxWidth: '400px', |
| animation: 'slideInRight 0.3s ease-out' |
| }); |
|
|
| |
| const colors = { |
| success: { bg: '#d1fae5', text: '#065f46', border: '#10b981' }, |
| error: { bg: '#fee2e2', text: '#991b1b', border: '#ef4444' }, |
| info: { bg: '#dbeafe', text: '#1e40af', border: '#3b82f6' } |
| }; |
|
|
| const color = colors[type] || colors.info; |
| Object.assign(toast.style, { |
| backgroundColor: color.bg, |
| color: color.text, |
| borderLeft: `4px solid ${color.border}` |
| }); |
|
|
| document.body.appendChild(toast); |
|
|
| |
| setTimeout(() => { |
| if (toast.parentElement) { |
| toast.remove(); |
| } |
| }, duration); |
| } |
| }, |
|
|
| |
| fileHandler: { |
| |
| |
| |
| setupDragDrop: function(element, callback) { |
| ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => { |
| element.addEventListener(eventName, preventDefaults, false); |
| }); |
|
|
| function preventDefaults(e) { |
| e.preventDefault(); |
| e.stopPropagation(); |
| } |
|
|
| ['dragenter', 'dragover'].forEach(eventName => { |
| element.addEventListener(eventName, () => element.classList.add('dragover'), false); |
| }); |
|
|
| ['dragleave', 'drop'].forEach(eventName => { |
| element.addEventListener(eventName, () => element.classList.remove('dragover'), false); |
| }); |
|
|
| element.addEventListener('drop', function(e) { |
| const files = e.dataTransfer.files; |
| if (files.length > 0) { |
| callback(files[0]); |
| } |
| }, false); |
| }, |
|
|
| |
| |
| |
| createImagePreview: function(file, container) { |
| const reader = new FileReader(); |
| reader.onload = function(e) { |
| container.innerHTML = ` |
| <img src="${e.target.result}" alt="Aperçu" class="file-preview-image"> |
| <p class="mt-2 text-sm text-gray-600">${file.name} (${FireWatch.utils.formatFileSize(file.size)})</p> |
| `; |
| container.classList.remove('hidden'); |
| }; |
| reader.readAsDataURL(file); |
| }, |
|
|
| |
| |
| |
| createVideoPreview: function(file, container) { |
| const url = URL.createObjectURL(file); |
| container.innerHTML = ` |
| <video controls class="file-preview-video"> |
| <source src="${url}" type="${file.type}"> |
| Votre navigateur ne supporte pas la lecture vidéo. |
| </video> |
| <p class="mt-2 text-sm text-gray-600">${file.name} (${FireWatch.utils.formatFileSize(file.size)})</p> |
| `; |
| container.classList.remove('hidden'); |
| } |
| }, |
|
|
| |
| camera: { |
| stream: null, |
| video: null, |
| canvas: null, |
|
|
| |
| |
| |
| init: function(videoElement, canvasElement) { |
| this.video = videoElement; |
| this.canvas = canvasElement; |
| }, |
|
|
| |
| |
| |
| start: function() { |
| return navigator.mediaDevices.getUserMedia({ |
| video: { |
| width: { ideal: 1280 }, |
| height: { ideal: 720 } |
| } |
| }) |
| .then(stream => { |
| this.stream = stream; |
| this.video.srcObject = stream; |
| return stream; |
| }); |
| }, |
|
|
| |
| |
| |
| stop: function() { |
| if (this.stream) { |
| this.stream.getTracks().forEach(track => track.stop()); |
| this.stream = null; |
| this.video.srcObject = null; |
| } |
| }, |
|
|
| |
| |
| |
| captureFrame: function() { |
| if (!this.video || this.video.videoWidth === 0) { |
| throw new Error('Caméra non initialisée ou pas de signal vidéo'); |
| } |
|
|
| this.canvas.width = this.video.videoWidth; |
| this.canvas.height = this.video.videoHeight; |
|
|
| const ctx = this.canvas.getContext('2d'); |
| ctx.drawImage(this.video, 0, 0); |
|
|
| return new Promise(resolve => { |
| this.canvas.toBlob(resolve, 'image/jpeg', 0.8); |
| }); |
| } |
| }, |
|
|
| |
| results: { |
| |
| |
| |
| display: function(data) { |
| const resultsSection = document.getElementById('results'); |
| const resultImage = document.getElementById('result-image'); |
| const detectionList = document.getElementById('detection-list'); |
|
|
| |
| if (data.result_image_url) { |
| resultImage.src = data.result_image_url; |
| resultImage.onerror = function() { |
| this.src = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjQwIiBoZWlnaHQ9IjQ4MCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZGRkIi8+PHRleHQgeD0iNTAlIiB5PSI1MCUiIGZvbnQtZmFtaWx5PSJBcmlhbCIgZm9udC1zaXplPSIxOCIgZmlsbD0iIzk5OSIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZHk9Ii4zZW0iPkltYWdlIG5vbiBkaXNwb25pYmxlPC90ZXh0Pjwvc3ZnPg=='; |
| }; |
| } |
|
|
| |
| detectionList.innerHTML = ''; |
| if (data.detections && data.detections.length > 0) { |
| data.detections.forEach((detection, index) => { |
| const li = document.createElement('li'); |
| li.className = 'detection-item flex items-center justify-between'; |
|
|
| |
| const colorMap = { |
| 'fire': 'bg-red-500', |
| 'smoke': 'bg-orange-500', |
| 'person': 'bg-blue-500', |
| 'intrusion': 'bg-purple-500', |
| 'vehicle': 'bg-green-500' |
| }; |
|
|
| const colorClass = colorMap[detection.class_name] || 'bg-gray-500'; |
| const confidence = (detection.confidence * 100).toFixed(1); |
|
|
| li.innerHTML = ` |
| <div class="flex items-center"> |
| <span class="w-3 h-3 ${colorClass} rounded-full mr-3"></span> |
| <div> |
| <span class="font-medium">${detection.label || detection.class_name}</span> |
| <div class="text-sm text-gray-600"> |
| Confiance: ${confidence}% |
| ${detection.frame_number !== undefined ? ` | Frame: ${detection.frame_number}` : ''} |
| </div> |
| </div> |
| </div> |
| <div class="text-right"> |
| <div class="confidence-bar" style="width: ${confidence}%; background: linear-gradient(90deg, #ef4444, #f59e0b, #10b981);"></div> |
| </div> |
| `; |
|
|
| detectionList.appendChild(li); |
| }); |
| } else { |
| detectionList.innerHTML = '<li class="text-gray-500 text-center py-4">Aucune détection trouvée</li>'; |
| } |
|
|
| |
| resultsSection.classList.remove('hidden'); |
| resultsSection.scrollIntoView({ behavior: 'smooth' }); |
|
|
| |
| if (data.processing_time) { |
| const statsHtml = ` |
| <div class="mt-4 p-3 bg-gray-100 rounded-lg"> |
| <div class="text-sm text-gray-600"> |
| <i class="fas fa-clock mr-1"></i> |
| Temps de traitement: ${data.processing_time.toFixed(2)}s |
| </div> |
| </div> |
| `; |
| detectionList.insertAdjacentHTML('afterend', statsHtml); |
| } |
| } |
| }, |
|
|
| |
| api: { |
| |
| |
| |
| post: function(url, formData, options = {}) { |
| const defaultOptions = { |
| method: 'POST', |
| headers: { |
| 'X-CSRFToken': FireWatch.utils.getCSRFToken() |
| }, |
| body: formData |
| }; |
|
|
| return fetch(url, { ...defaultOptions, ...options }) |
| .then(response => { |
| if (!response.ok) { |
| throw new Error(`HTTP ${response.status}: ${response.statusText}`); |
| } |
| return response.json(); |
| }) |
| .catch(error => { |
| console.error('Erreur API:', error); |
| throw error; |
| }); |
| }, |
|
|
| |
| |
| |
| checkModelsStatus: function() { |
| return fetch(FireWatch.config.apiEndpoints.modelsStatus) |
| .then(response => response.json()) |
| .catch(error => { |
| console.error('Erreur lors de la vérification du statut des modèles:', error); |
| return { success: false, error: error.message }; |
| }); |
| } |
| }, |
|
|
| |
| init: function() { |
| console.log('FireWatch AI - Initialisation par Marino ATOHOUN'); |
| |
| |
| this.api.checkModelsStatus().then(data => { |
| if (data.success && data.models) { |
| console.log('Statut des modèles:', data.models); |
| } |
| }); |
|
|
| |
| if (!document.querySelector('#firewatch-styles')) { |
| const style = document.createElement('style'); |
| style.id = 'firewatch-styles'; |
| style.textContent = ` |
| @keyframes slideInRight { |
| from { transform: translateX(100%); opacity: 0; } |
| to { transform: translateX(0); opacity: 1; } |
| } |
| .firewatch-toast { font-family: system-ui, -apple-system, sans-serif; } |
| `; |
| document.head.appendChild(style); |
| } |
|
|
| |
| if (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') { |
| console.log('%cFireWatch AI - Mode Développement', 'color: #ec4899; font-size: 16px; font-weight: bold;'); |
| console.log('%cCréé par Marino ATOHOUN', 'color: #7c3aed; font-size: 12px;'); |
| } |
| } |
| }; |
|
|
| |
| document.addEventListener('DOMContentLoaded', function() { |
| FireWatch.init(); |
| }); |
|
|
| |
| window.FireWatch = FireWatch; |
|
|
|
|