/* ======================================================== VisionAI — script.js Real app flow: - Uploads image to /analyze (Flask + HuggingFace model) - Model: umm-maybe/AI-image-detector - Labels: "artificial" | "real" - Threshold: 50% → Twilio voice call alert ======================================================== */ // ─── SESSION STATS ─── const SESSION = { total: 0, ai: 0, real: 0, calls: 0 }; // ─── HISTORY ─── const HISTORY = []; // ─── DOM REFS ─── const dropZone = document.getElementById('drop-zone'); const fileInput = document.getElementById('file-input'); const uploadSection = document.getElementById('upload-section'); const previewSection = document.getElementById('preview-section'); const previewImg = document.getElementById('preview-img'); const previewOverlay = document.getElementById('preview-overlay'); const analyzeBtn = document.getElementById('analyze-btn'); const resultsSection = document.getElementById('results-section'); const historyList = document.getElementById('history-list'); const toastWrap = document.getElementById('toast-wrap'); const ringFill = document.getElementById('ring-fill'); const ringPct = document.getElementById('ring-pct'); const ringLabelText = document.getElementById('ring-label-text'); const callPanel = document.getElementById('call-panel'); let currentFile = null; let lastResult = null; // ─── INIT ─── document.addEventListener('DOMContentLoaded', () => { // Splash boot const splash = document.getElementById('splash'); const splashBar = document.getElementById('splash-bar'); requestAnimationFrame(() => requestAnimationFrame(() => { splashBar.style.width = '100%'; })); setTimeout(() => { splash.classList.add('hidden'); setTimeout(() => splash.remove(), 650); }, 1800); initDrag(); updateStats(); renderHistory(); dropZone.addEventListener('click', () => fileInput.click()); fileInput.addEventListener('change', e => { if (e.target.files[0]) handleFile(e.target.files[0]); }); analyzeBtn.addEventListener('click', runAnalysis); document.getElementById('export-btn').addEventListener('click', exportReport); initNav(); }); // ─── NAV ─── function initNav() { document.querySelectorAll('.nav-item').forEach(item => { item.addEventListener('click', e => { e.preventDefault(); document.querySelectorAll('.nav-item').forEach(n => n.classList.remove('active')); item.classList.add('active'); }); }); } // ─── DRAG & DROP ─── function initDrag() { ['dragenter','dragover'].forEach(ev => dropZone.addEventListener(ev, e => { e.preventDefault(); dropZone.classList.add('dragover'); }) ); ['dragleave','dragend'].forEach(ev => dropZone.addEventListener(ev, () => dropZone.classList.remove('dragover')) ); dropZone.addEventListener('drop', e => { e.preventDefault(); dropZone.classList.remove('dragover'); if (e.dataTransfer.files[0]) handleFile(e.dataTransfer.files[0]); }); } // ─── FILE HANDLER ─── function handleFile(file) { if (!file.type.startsWith('image/')) { showToast('Only image files are supported.', 'error'); return; } currentFile = file; const reader = new FileReader(); reader.onload = evt => { previewImg.src = evt.target.result; const img = new Image(); img.onload = () => document.getElementById('meta-dims').textContent = `${img.width} × ${img.height} px`; img.src = evt.target.result; }; reader.readAsDataURL(file); document.getElementById('meta-name').textContent = file.name; document.getElementById('meta-size').textContent = formatBytes(file.size); document.getElementById('meta-type').textContent = file.type.split('/')[1].toUpperCase(); uploadSection.style.display = 'none'; previewSection.style.display = 'flex'; resultsSection.style.display = 'none'; showToast('Image ready — click Run Detection', 'info'); } // ─── ANALYSIS ─── async function runAnalysis() { if (!currentFile) return; analyzeBtn.disabled = true; previewOverlay.classList.add('active'); const formData = new FormData(); formData.append('image', currentFile); let result; try { const resp = await fetch('/analyze', { method: 'POST', body: formData }); if (!resp.ok) throw new Error('Server error ' + resp.status); result = await resp.json(); if (result.error) throw new Error(result.error); } catch (err) { // ── DEMO MODE (when Flask is not running) ────────────────── await sleep(2200); const isAI = Math.random() > 0.48; const artScore = isAI ? Math.round(55 + Math.random() * 44) // 55–99 : Math.round(5 + Math.random() * 38); // 5–43 const realScore = 100 - artScore; const callPlaced = isAI; result = { filename: currentFile.name, is_ai: isAI, artificial_score: artScore, real_score: realScore, all_scores: [ { label: 'artificial', score: artScore }, { label: 'real', score: realScore }, ], threshold: 50, call_placed: callPlaced, call_sid: callPlaced ? 'CA' + Math.random().toString(36).substr(2,32).toUpperCase() : null, call_error: null, alert_phone: '+919047432845', _demo: true, }; } previewOverlay.classList.remove('active'); analyzeBtn.disabled = false; lastResult = result; displayResults(result); updateSessionStats(result); addHistory(result); } // ─── DISPLAY RESULTS ─── function displayResults(r) { const isAI = r.is_ai; const score = r.artificial_score; // confidence in "artificial" const color = isAI ? '#f43f5e' : '#22d3ee'; const glow = isAI ? 'rgba(244,63,94,.5)' : 'rgba(34,211,238,.4)'; const labelName = isAI ? 'artificial' : 'real'; // ── Ring ── ringFill.style.stroke = color; ringFill.style.filter = `drop-shadow(0 0 8px ${glow})`; const offset = 283 - (score / 100) * 283; setTimeout(() => { ringFill.style.strokeDashoffset = offset; }, 80); animateCount(0, score, 1200, v => { ringPct.textContent = v + '%'; }); ringLabelText.textContent = labelName; // ── Verdict ── document.getElementById('result-verdict').textContent = isAI ? '⚠ AI-Generated Image Detected' : '✅ Real / Human-Captured Image'; document.getElementById('result-desc').textContent = isAI ? `The model classified this image as AI-generated with ${score}% confidence. ` + `This exceeds the 50% threshold — a Twilio voice call has been placed to ${r.alert_phone}.` : `The model found no AI-generation patterns. Artificial score: ${score}% (below the 50% alert threshold). ` + `This image appears to be real or human-captured.`; // ── Badges ── const badges = isAI ? ['AI-Generated', `${score}% Confidence`, 'Alert Fired'] : ['Image Authentic', `${score}% AI Score`, 'No Alert']; document.getElementById('result-badges').innerHTML = badges.map((b, i) => `${b}` ).join(''); // ── Score Breakdown ── document.getElementById('breakdown-metrics').innerHTML = r.all_scores.map(s => { const c = s.label === 'artificial' ? '#f43f5e' : '#22d3ee'; return `