FireWatch / static /js /firewatch.js
rinogeek's picture
first commit
e9d86db
/**
* JavaScript personnalisé pour FireWatch AI
* Créé par Marino ATOHOUN
*/
// Namespace pour éviter les conflits
const FireWatch = {
// Configuration
config: {
maxFileSize: 50 * 1024 * 1024, // 50MB
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/'
}
},
// Utilitaires
utils: {
/**
* Obtient le token CSRF pour les requêtes AJAX
*/
getCSRFToken: function() {
const token = document.querySelector('[name=csrfmiddlewaretoken]');
return token ? token.value : '';
},
/**
* Formate la taille d'un fichier en format lisible
*/
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];
},
/**
* Valide un fichier selon les critères définis
*/
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 };
},
/**
* Affiche une notification toast
*/
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>
`;
// Styles inline pour le toast
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'
});
// Couleurs selon le type
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);
// Auto-suppression
setTimeout(() => {
if (toast.parentElement) {
toast.remove();
}
}, duration);
}
},
// Gestion des fichiers
fileHandler: {
/**
* Configure le drag & drop pour un élément
*/
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);
},
/**
* Crée un aperçu pour un fichier image
*/
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);
},
/**
* Crée un aperçu pour un fichier vidéo
*/
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');
}
},
// Gestion de la caméra
camera: {
stream: null,
video: null,
canvas: null,
/**
* Initialise l'accès à la caméra
*/
init: function(videoElement, canvasElement) {
this.video = videoElement;
this.canvas = canvasElement;
},
/**
* Démarre le flux de la caméra
*/
start: function() {
return navigator.mediaDevices.getUserMedia({
video: {
width: { ideal: 1280 },
height: { ideal: 720 }
}
})
.then(stream => {
this.stream = stream;
this.video.srcObject = stream;
return stream;
});
},
/**
* Arrête le flux de la caméra
*/
stop: function() {
if (this.stream) {
this.stream.getTracks().forEach(track => track.stop());
this.stream = null;
this.video.srcObject = null;
}
},
/**
* Capture une frame de la caméra
*/
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);
});
}
},
// Gestion des résultats
results: {
/**
* Affiche les résultats de détection
*/
display: function(data) {
const resultsSection = document.getElementById('results');
const resultImage = document.getElementById('result-image');
const detectionList = document.getElementById('detection-list');
// Afficher l'image de résultat
if (data.result_image_url) {
resultImage.src = data.result_image_url;
resultImage.onerror = function() {
this.src = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjQwIiBoZWlnaHQ9IjQ4MCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZGRkIi8+PHRleHQgeD0iNTAlIiB5PSI1MCUiIGZvbnQtZmFtaWx5PSJBcmlhbCIgZm9udC1zaXplPSIxOCIgZmlsbD0iIzk5OSIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZHk9Ii4zZW0iPkltYWdlIG5vbiBkaXNwb25pYmxlPC90ZXh0Pjwvc3ZnPg==';
};
}
// Afficher les détections
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';
// Couleur selon le type de détection
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>';
}
// Afficher la section des résultats avec animation
resultsSection.classList.remove('hidden');
resultsSection.scrollIntoView({ behavior: 'smooth' });
// Ajouter les statistiques si disponibles
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
api: {
/**
* Envoie une requête POST avec gestion d'erreurs
*/
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;
});
},
/**
* Vérifie le statut des modèles IA
*/
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 };
});
}
},
// Initialisation
init: function() {
console.log('FireWatch AI - Initialisation par Marino ATOHOUN');
// Vérifier le statut des modèles au chargement
this.api.checkModelsStatus().then(data => {
if (data.success && data.models) {
console.log('Statut des modèles:', data.models);
}
});
// Ajouter les styles CSS dynamiquement si nécessaire
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);
}
// Message de bienvenue en mode développement
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;');
}
}
};
// Initialisation automatique quand le DOM est prêt
document.addEventListener('DOMContentLoaded', function() {
FireWatch.init();
});
// Export pour utilisation globale
window.FireWatch = FireWatch;