Ali Abdullah
Fix requirements.txt encoding for HF
98a79a7
// Zaytrics - Crowd Monitoring Dashboard
class ZaytricsDashboard {
constructor() {
this.isRunning = false;
this.currentSource = 'camera';
this.peopleCount = 0;
this.fps = 0;
this.init();
}
init() {
console.log('Zaytrics Dashboard Initialized');
this.setupEventListeners();
this.setupFileUpload();
this.startStatsPolling();
this.animateStats();
}
setupEventListeners() {
// Control button interactions
document.querySelectorAll('.control-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
if (!e.currentTarget.id.includes('heatmap')) {
document.querySelectorAll('.control-btn').forEach(b => {
if (!b.id.includes('heatmap')) {
b.classList.remove('active');
}
});
e.currentTarget.classList.add('active');
}
});
});
}
setupFileUpload() {
const uploadZone = document.getElementById('uploadZone');
const fileInput = document.getElementById('fileInput');
const uploadStatus = document.getElementById('uploadStatus');
// Click to upload
uploadZone.addEventListener('click', () => fileInput.click());
// File selection
fileInput.addEventListener('change', (e) => {
const file = e.target.files[0];
if (file) {
this.uploadVideo(file);
}
});
// Drag and drop
uploadZone.addEventListener('dragover', (e) => {
e.preventDefault();
uploadZone.style.borderColor = 'var(--primary)';
uploadZone.style.backgroundColor = 'rgba(99, 102, 241, 0.1)';
});
uploadZone.addEventListener('dragleave', (e) => {
e.preventDefault();
uploadZone.style.borderColor = '';
uploadZone.style.backgroundColor = '';
});
uploadZone.addEventListener('drop', (e) => {
e.preventDefault();
uploadZone.style.borderColor = '';
uploadZone.style.backgroundColor = '';
const file = e.dataTransfer.files[0];
if (file) {
this.uploadVideo(file);
}
});
}
async uploadVideo(file) {
const uploadStatus = document.getElementById('uploadStatus');
const loopVideo = document.getElementById('loopVideo').checked;
// Validate file type
const allowedTypes = ['video/mp4', 'video/avi', 'video/quicktime', 'video/x-matroska', 'video/webm'];
if (!allowedTypes.includes(file.type)) {
uploadStatus.innerHTML = '<div class="error">❌ Invalid file type. Please upload MP4, AVI, MOV, MKV, or WEBM.</div>';
return;
}
// Validate file size (100MB)
if (file.size > 100 * 1024 * 1024) {
uploadStatus.innerHTML = '<div class="error">❌ File too large. Maximum size is 100MB.</div>';
return;
}
uploadStatus.innerHTML = '<div class="info">⏳ Uploading video...</div>';
const formData = new FormData();
formData.append('file', file);
formData.append('loop', loopVideo);
try {
const response = await fetch('/api/upload_video', {
method: 'POST',
body: formData
});
const data = await response.json();
if (response.ok) {
uploadStatus.innerHTML = '<div class="success">✅ Video uploaded successfully!</div>';
this.currentSource = 'video';
// Auto-switch to uploaded video
setTimeout(() => {
uploadStatus.innerHTML = '';
}, 3000);
} else {
uploadStatus.innerHTML = `<div class="error">❌ ${data.error}</div>`;
}
} catch (error) {
console.error('Upload error:', error);
uploadStatus.innerHTML = '<div class="error">❌ Upload failed. Please try again.</div>';
}
}
startStatsPolling() {
// Clear any existing interval
if (this.statsInterval) {
clearInterval(this.statsInterval);
}
// Poll stats from Flask API
this.statsInterval = setInterval(async () => {
if (this.isRunning) {
await this.updateStatsFromAPI();
}
}, 1000);
}
stopStatsPolling() {
if (this.statsInterval) {
clearInterval(this.statsInterval);
this.statsInterval = null;
}
}
async updateStatsFromAPI() {
try {
const response = await fetch('/api/stats');
const data = await response.json();
this.peopleCount = data.count || 0;
this.fps = data.fps || 0;
document.getElementById('peopleCount').textContent = this.peopleCount;
document.getElementById('fpsCount').textContent = this.fps.toFixed(1);
// Update status indicator based on alert level
const statusDot = document.querySelector('.status-dot');
if (statusDot) {
const alertLevel = data.alert_level || 'normal';
statusDot.className = 'status-dot';
if (alertLevel === 'warning') {
statusDot.style.backgroundColor = '#f59e0b';
} else if (alertLevel === 'critical') {
statusDot.style.backgroundColor = '#ef4444';
} else {
statusDot.style.backgroundColor = '#10b981';
}
}
} catch (error) {
console.error('Error fetching stats:', error);
}
}
animateStats() {
// Animate stat numbers when they change
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === 'characterData' || mutation.type === 'childList') {
const element = mutation.target.parentElement;
if (element && element.classList.contains('stat-value')) {
element.style.transform = 'scale(1.1)';
setTimeout(() => {
element.style.transform = 'scale(1)';
}, 300);
}
}
});
});
const statElements = document.querySelectorAll('.stat-value');
statElements.forEach(element => {
observer.observe(element, {
characterData: true,
childList: true,
subtree: true
});
});
}
}
// Global functions for button handlers
let dashboard;
async function switchSource(source) {
const uploadSection = document.getElementById('uploadSection');
const liveCameraBtn = document.getElementById('liveCameraBtn');
const uploadVideoBtn = document.getElementById('uploadVideoBtn');
if (source === 'upload') {
uploadSection.style.display = 'block';
dashboard.currentSource = 'upload';
} else {
uploadSection.style.display = 'none';
dashboard.currentSource = 'camera';
// Switch backend to camera
console.log('Switching to camera source...');
try {
const response = await fetch('/api/switch_source', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ source_type: 'camera' })
});
const data = await response.json();
console.log('Switched to camera:', data);
} catch (err) {
console.error('Error switching source:', err);
}
}
}
async function startMonitoring() {
console.log('Starting monitoring...');
dashboard.isRunning = true;
// Restart stats polling
if (dashboard) {
dashboard.startStatsPolling();
}
const startBtn = document.getElementById('startBtn');
const stopBtn = document.getElementById('stopBtn');
const placeholder = document.getElementById('videoPlaceholder');
const videoFeed = document.getElementById('videoFeed');
startBtn.style.display = 'none';
stopBtn.style.display = 'block';
try {
// First call the start API to set state
const response = await fetch('/api/start', { method: 'POST' });
const data = await response.json();
console.log('Start API response:', data);
if (data.status === 'started') {
// Small delay to let backend initialize
await new Promise(resolve => setTimeout(resolve, 300));
// Now set video source and display
if (videoFeed) {
console.log('Setting video feed source...');
videoFeed.src = '/video_feed?t=' + new Date().getTime();
// Add load event listener for debugging
videoFeed.onload = function() {
console.log('Video feed loaded successfully');
};
videoFeed.onerror = function(e) {
console.error('Video feed error:', e);
alert('Failed to load video stream. Check console for details.');
};
videoFeed.style.display = 'block';
console.log('Video feed displayed');
}
// Hide placeholder
if (placeholder) {
placeholder.style.display = 'none';
console.log('Placeholder hidden');
}
}
} catch (error) {
console.error('Error starting monitoring:', error);
alert('Failed to start monitoring. Error: ' + error.message);
dashboard.isRunning = false;
startBtn.style.display = 'block';
stopBtn.style.display = 'none';
}
}
async function stopMonitoring() {
console.log('Stopping monitoring...');
dashboard.isRunning = false;
// Stop stats polling to prevent unnecessary API calls
if (dashboard) {
dashboard.stopStatsPolling();
}
const startBtn = document.getElementById('startBtn');
const stopBtn = document.getElementById('stopBtn');
startBtn.style.display = 'block';
stopBtn.style.display = 'none';
try {
const response = await fetch('/api/stop', { method: 'POST' });
const data = await response.json();
if (data.status === 'stopped') {
// Hide video feed
const placeholder = document.getElementById('videoPlaceholder');
const videoFeed = document.getElementById('videoFeed');
if (videoFeed) {
videoFeed.style.display = 'none';
videoFeed.removeAttribute('src');
}
if (placeholder) placeholder.style.display = 'flex';
// Reset counts
document.getElementById('peopleCount').textContent = '0';
document.getElementById('fpsCount').textContent = '0';
}
} catch (error) {
console.error('Error stopping monitoring:', error);
}
}
async function toggleHeatmap() {
const heatmapBtn = document.getElementById('heatmapBtn');
try {
const response = await fetch('/api/toggle_heatmap', { method: 'POST' });
const data = await response.json();
if (data.heatmap_enabled) {
heatmapBtn.classList.add('active');
} else {
heatmapBtn.classList.remove('active');
}
} catch (error) {
console.error('Error toggling heatmap:', error);
}
}
async function switchMode(mode) {
try {
const response = await fetch('/api/set_detection_mode', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ mode: mode })
});
if (response.ok) {
const data = await response.json();
console.log(`Switched to ${mode} mode:`, data.settings);
// Update button states
document.querySelectorAll('.mode-btn').forEach(btn => btn.classList.remove('active'));
document.getElementById(`${mode}ModeBtn`).classList.add('active');
// Show notification
console.log(`✓ ${mode === 'normal' ? 'Normal' : 'Dense Crowd'} mode activated`);
} else {
const error = await response.json();
console.error('Mode switch error:', error.error);
}
} catch (error) {
console.error('Error switching mode:', error);
}
}
// Cleanup on page unload
window.addEventListener('beforeunload', async () => {
if (dashboard && dashboard.isRunning) {
// Stop monitoring before page closes
await fetch('/api/stop', { method: 'POST' });
}
});
// Initialize when DOM is loaded
document.addEventListener('DOMContentLoaded', () => {
dashboard = new ZaytricsDashboard();
// Add scroll animations
const observerOptions = {
threshold: 0.1,
rootMargin: '0px 0px -50px 0px'
};
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.style.opacity = '1';
entry.target.style.transform = 'translateY(0)';
}
});
}, observerOptions);
// Observe elements for scroll animations
document.querySelectorAll('.feature-card, .dashboard-card').forEach(el => {
el.style.opacity = '0';
el.style.transform = 'translateY(30px)';
el.style.transition = 'opacity 0.6s ease, transform 0.6s ease';
observer.observe(el);
});
});