/* ======================================================== 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 `
${s.label} ${s.score}%
`; }).join(''); // ── Threshold panel ── const thresholdFired = score > r.threshold; document.getElementById('threshold-metrics').innerHTML = `
AI Score ${score}%
Alert Threshold 50%
Threshold Status ${thresholdFired ? '⚠ Exceeded' : '✓ Below'}
`; // Animate progress bars setTimeout(() => { document.querySelectorAll('.progress-fill').forEach(bar => { bar.style.width = bar.dataset.target + '%'; }); }, 120); // ── Twilio Call Panel ── renderCallPanel(r); // ── Show ── previewSection.style.display = 'none'; resultsSection.style.display = 'flex'; const msg = isAI ? `⚠ AI Detected! ${score}% confidence${r.call_placed ? ' — Call placed to ' + r.alert_phone : ''}` : `✅ Image verified as real (${score}% AI score)`; showToast(msg, isAI ? 'error' : 'success'); setTimeout(() => resultsSection.scrollIntoView({ behavior:'smooth', block:'start' }), 150); } // ─── CALL PANEL ─── function renderCallPanel(r) { callPanel.className = 'call-panel'; const icon = document.getElementById('call-panel-icon'); const title = document.getElementById('call-panel-title'); const detail = document.getElementById('call-panel-detail'); const badge = document.getElementById('call-panel-badge'); const sidRow = document.getElementById('call-sid-row'); const sidVal = document.getElementById('call-sid-val'); if (r.call_placed) { callPanel.classList.add('call-fired'); title.textContent = '📞 Twilio Alert Call Placed'; detail.textContent = `Voice call placed to ${r.alert_phone} from +17125825991 · AI confidence was ${r.artificial_score}%`; badge.textContent = 'ALERT FIRED'; if (r.call_sid) { sidRow.style.display = 'flex'; sidVal.textContent = r.call_sid; } } else if (r.call_error) { callPanel.classList.add('call-error'); title.textContent = '❌ Call Failed'; detail.textContent = `Alert would have fired, but Twilio returned an error: ${r.call_error}`; badge.textContent = 'ERROR'; sidRow.style.display = 'none'; } else { callPanel.classList.add('call-safe'); title.textContent = '✅ No Alert Required'; detail.textContent = `AI score (${r.artificial_score}%) is below the 50% threshold — no call placed to ${r.alert_phone}`; badge.textContent = 'SAFE'; sidRow.style.display = 'none'; } if (r._demo) { detail.textContent += ' (demo mode — Flask not running)'; } } // ─── SESSION STATS ─── function updateSessionStats(r) { SESSION.total++; if (r.is_ai) SESSION.ai++; else SESSION.real++; if (r.call_placed) SESSION.calls++; updateStats(); } function updateStats() { document.getElementById('stat-total').textContent = SESSION.total; document.getElementById('stat-ai').textContent = SESSION.ai; document.getElementById('stat-real').textContent = SESSION.real; document.getElementById('stat-calls').textContent = SESSION.calls; } // ─── HISTORY ─── function addHistory(r) { HISTORY.unshift({ name: r.filename, time: 'Just now', score: r.artificial_score, label: r.is_ai ? 'AI-Gen' : 'Real', isAI: r.is_ai, called: r.call_placed, }); if (HISTORY.length > 10) HISTORY.pop(); renderHistory(); } function renderHistory() { if (!HISTORY.length) { historyList.innerHTML = `
No scans yet — upload an image to get started
`; return; } historyList.innerHTML = HISTORY.map(h => { const color = h.isAI ? '#f43f5e' : '#22d3ee'; const bg = h.isAI ? 'rgba(244,63,94,.12)' : 'rgba(34,211,238,.10)'; const callBadge = h.called ? `📞 Alerted` : ''; return `
${h.name}
${h.time}
${h.label} · ${h.score}% ${callBadge}
`; }).join(''); } // ─── RESET ─── function resetAll() { currentFile = null; fileInput.value = ''; uploadSection.style.display = ''; previewSection.style.display = 'none'; resultsSection.style.display = 'none'; previewImg.src = ''; previewOverlay.classList.remove('active'); ringFill.style.strokeDashoffset = 283; ringPct.textContent = '0%'; showToast('Ready for new scan.', 'info'); } // ─── EXPORT ─── function exportReport() { if (!lastResult) { showToast('No result to export yet.', 'error'); return; } const r = lastResult; const date = new Date().toLocaleString(); const divider = '─'.repeat(40); const text = [ `VisionAI — Detection Report`, divider, `Date : ${date}`, `Model : umm-maybe/AI-image-detector`, `File : ${r.filename}`, ``, `Results`, divider, `Verdict : ${r.is_ai ? 'AI-GENERATED' : 'REAL IMAGE'}`, `Artificial Score: ${r.artificial_score}%`, `Real Score : ${r.real_score}%`, `Alert Threshold : ${r.threshold}%`, ``, `Twilio Alert`, divider, `Alert Phone : ${r.alert_phone}`, `Call Placed : ${r.call_placed ? 'YES' : 'NO'}`, r.call_sid ? `Call SID : ${r.call_sid}` : '', r.call_error ? `Call Error : ${r.call_error}` : '', ``, r._demo ? '(Note: Demo mode — Flask backend was not running)' : '', `Generated by VisionAI Pro`, ].filter(l => l !== undefined).join('\n'); const blob = new Blob([text], { type: 'text/plain' }); const a = Object.assign(document.createElement('a'), { href: URL.createObjectURL(blob), download: `visionai_${r.filename}_report.txt` }); a.click(); showToast('Report downloaded!', 'success'); } // ─── TOAST ─── function showToast(msg, type = 'info') { const iconMap = { success: ``, error: ``, info: ``, warning: ``, }; const el = document.createElement('div'); el.className = `toast toast-${type}`; el.innerHTML = `${iconMap[type]}${msg}`; toastWrap.appendChild(el); setTimeout(() => { el.style.opacity = '0'; el.style.transform = 'translateY(8px)'; el.style.transition = '.3s ease'; setTimeout(() => el.remove(), 350); }, 3800); } // ─── HELPERS ─── function formatBytes(b) { if (b < 1024) return b + ' B'; if (b < 1048576) return (b / 1024).toFixed(1) + ' KB'; return (b / 1048576).toFixed(2) + ' MB'; } function animateCount(from, to, dur, cb) { const start = performance.now(); (function step(ts) { const p = Math.min((ts - start) / dur, 1); cb(Math.round(from + (to - from) * (1 - Math.pow(1 - p, 3)))); if (p < 1) requestAnimationFrame(step); })(performance.now()); } function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }