document.addEventListener('DOMContentLoaded', () => { const form = document.getElementById('inference-form'); const fileInput = document.getElementById('image-upload'); const fileNameDisplay = document.getElementById('file-name'); const submitBtn = document.getElementById('submit-btn'); const dropZone = document.getElementById('drop-zone'); const fileLabel = document.querySelector('.file-label'); const imagePreviewContainer = document.getElementById('image-preview-container'); const imagePreview = document.getElementById('image-preview'); const clearImageBtn = document.getElementById('clear-image-btn'); const loadingState = document.getElementById('loading-state'); const resultsPanel = document.getElementById('results-panel'); const errorPanel = document.getElementById('error-panel'); const resultGender = document.getElementById('result-gender'); const resultAge = document.getElementById('result-age'); const resultRace = document.getElementById('result-race'); const errorMessage = document.getElementById('error-message'); // Drag and drop handlers ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => { dropZone.addEventListener(eventName, preventDefaults, false); }); function preventDefaults(e) { e.preventDefault(); e.stopPropagation(); } ['dragenter', 'dragover'].forEach(eventName => { dropZone.addEventListener(eventName, () => { fileLabel.classList.add('dragover'); }, false); }); ['dragleave', 'drop'].forEach(eventName => { dropZone.addEventListener(eventName, () => { fileLabel.classList.remove('dragover'); }, false); }); dropZone.addEventListener('drop', (e) => { const dt = e.dataTransfer; const files = dt.files; if (files && files.length > 0) { fileInput.files = files; handleFileSelection(files[0]); } }, false); // Handle file selection via click fileInput.addEventListener('change', (e) => { const file = e.target.files[0]; handleFileSelection(file); }); function handleFileSelection(file) { if (file && file.type.startsWith('image/')) { // Update text fileNameDisplay.textContent = file.name; fileNameDisplay.style.color = 'var(--text-primary)'; submitBtn.disabled = false; // Generate preview const reader = new FileReader(); reader.onload = (e) => { imagePreview.src = e.target.result; dropZone.classList.add('hidden'); imagePreviewContainer.classList.remove('hidden'); }; reader.readAsDataURL(file); } else { resetFileState(); } // Reset state panels when a new file is chosen hideAllPanels(); } // Clear image clearImageBtn.addEventListener('click', () => { resetFileState(); hideAllPanels(); }); function resetFileState() { fileInput.value = ''; fileNameDisplay.textContent = 'Click or drag an image here'; fileNameDisplay.style.color = 'var(--text-secondary)'; submitBtn.disabled = true; imagePreview.src = ''; imagePreviewContainer.classList.add('hidden'); dropZone.classList.remove('hidden'); } // Handle form submission form.addEventListener('submit', async (e) => { e.preventDefault(); const file = fileInput.files[0]; if (!file) return; // Update UI state submitBtn.disabled = true; hideAllPanels(); loadingState.classList.remove('hidden'); // Prepare data const formData = new FormData(); formData.append('file', file); try { const response = await fetch('/predict', { method: 'POST', body: formData }); if (!response.ok) { throw new Error(`Server responded with status: ${response.status}`); } const data = await response.json(); // Validate response format if (!('gender' in data) || !('age' in data) || !('race' in data)) { throw new Error('Invalid response format from server'); } // Update results UI // Show gender with inline confidence like "Male (87%)" if (data.gender) { if (data.gender_confidence !== undefined) { const pct = Math.round(data.gender_confidence * 100); resultGender.textContent = `${data.gender} (${pct}%)`; } else { resultGender.textContent = data.gender; } } else { resultGender.textContent = '-'; } // Format age (round if it's a number, otherwise just display string) const formattedAge = typeof data.age === 'number' ? Math.round(data.age) : data.age; resultAge.textContent = formattedAge; resultRace.textContent = data.race || '-'; // Show results loadingState.classList.add('hidden'); resultsPanel.classList.remove('hidden'); } catch (error) { console.error('Inference error:', error); // Show error UI errorMessage.textContent = error.message || 'An error occurred during inference.'; loadingState.classList.add('hidden'); errorPanel.classList.remove('hidden'); } finally { submitBtn.disabled = false; } }); function hideAllPanels() { loadingState.classList.add('hidden'); resultsPanel.classList.add('hidden'); errorPanel.classList.add('hidden'); } });