// FaceID Retro Terminal Application Controller document.addEventListener('DOMContentLoaded', () => { // UI Element References const fileInput = document.getElementById('file-input'); const previewContainer = document.getElementById('preview-container'); const canvasOverlay = document.getElementById('canvas-overlay'); const targetGraphic = document.getElementById('target-graphic'); const btnVerify = document.getElementById('btn-verify'); const verifySpinner = document.getElementById('verify-spinner'); const verifyText = document.getElementById('verify-text'); const chkBlockchain = document.getElementById('chk-blockchain'); const btnSample1 = document.getElementById('btn-sample-1'); // Right Panel Elements const seaStatusBadge = document.getElementById('sea-status-badge'); const idleScreen = document.getElementById('idle-screen'); const telemetryScreen = document.getElementById('telemetry-screen'); const teleStatus = document.getElementById('tele-status'); const teleScore = document.getElementById('tele-score'); const teleFaceDetected = document.getElementById('tele-face-detected'); const teleFaceCount = document.getElementById('tele-face-count'); const teleSha = document.getElementById('tele-sha'); const telePhash = document.getElementById('tele-phash'); const teleSearchCount = document.getElementById('tele-search-count'); const teleSearchEngine = document.getElementById('tele-search-engine'); const teleBcStatus = document.getElementById('tele-bc-status'); const teleBcLink = document.getElementById('tele-bc-link'); const teleCandidates = document.getElementById('tele-candidates'); const logLine = document.getElementById('log-line'); const logTime = document.getElementById('log-time'); const btnCopyJson = document.getElementById('btn-copy-json'); const jsonViewer = document.getElementById('json-viewer'); // State let selectedFile = null; let base64Snapshot = null; let currentImageElement = null; // Timer Update setInterval(() => { const now = new Date(); logTime.textContent = now.toTimeString().split(' ')[0]; }, 1000); // File Upload Handlers targetGraphic.addEventListener('click', () => fileInput.click()); fileInput.addEventListener('change', (e) => { if (e.target.files && e.target.files[0]) { handleFileSelect(e.target.files[0]); } }); function handleFileSelect(file) { selectedFile = file; base64Snapshot = null; const reader = new FileReader(); reader.onload = (event) => { displayPreview(event.target.result); }; reader.readAsDataURL(file); } function displayPreview(dataUrl) { const img = new Image(); img.onload = () => { currentImageElement = img; canvasOverlay.width = img.width; canvasOverlay.height = img.height; const ctx = canvasOverlay.getContext('2d'); ctx.drawImage(img, 0, 0); previewContainer.classList.remove('hidden'); targetGraphic.classList.add('hidden'); logLine.textContent = `> IMAGE LOADED: ${img.width}x${img.height} PX. READY FOR SCAN.`; }; img.src = dataUrl; } // Quick Sample Loading btnSample1.addEventListener('click', async () => { logLine.textContent = '> LOADING SAMPLE SCAN PROFILE...'; try { const res = await fetch('/static/sample_preview.jpg'); if (res.ok) { const blob = await res.blob(); handleFileSelect(new File([blob], 'sample.jpg', { type: 'image/jpeg' })); } else { // Generate quick canvas image if sample_preview.jpg is not present const tempCanvas = document.createElement('canvas'); tempCanvas.width = 400; tempCanvas.height = 400; const ctx = tempCanvas.getContext('2d'); ctx.fillStyle = '#F0E6D2'; ctx.fillRect(0, 0, 400, 400); ctx.fillStyle = '#333'; ctx.beginPath(); ctx.arc(150, 160, 20, 0, Math.PI * 2); ctx.arc(250, 160, 20, 0, Math.PI * 2); ctx.fill(); displayPreview(tempCanvas.toDataURL('image/jpeg')); base64Snapshot = tempCanvas.toDataURL('image/jpeg'); } } catch (err) { logLine.textContent = `> ERROR LOADING SAMPLE: ${err.message}`; } }); // Verification Pipeline Execution btnVerify.addEventListener('click', async () => { if (!selectedFile && !base64Snapshot) { alert('Please select a facial image first.'); return; } setLoading(true); logLine.textContent = '> EXECUTING LOCAL ARCFACE & REVERSE PROVENANCE SEARCH...'; seaStatusBadge.textContent = 'SCANNING...'; seaStatusBadge.className = 'font-mono text-[10px] font-bold bg-amber-400 text-black px-2 py-0.5 uppercase tracking-wider animate-pulse'; const writeBlockchain = chkBlockchain.checked; const startTime = Date.now(); try { let response; if (selectedFile) { const formData = new FormData(); formData.append('file', selectedFile); formData.append('write_blockchain', writeBlockchain); response = await fetch('/api/verify', { method: 'POST', body: formData }); } else { response = await fetch('/api/verify-base64', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ image_base64: base64Snapshot, write_blockchain: writeBlockchain }), }); } if (!response.ok) { let errorMsg = `Server error (HTTP ${response.status}): ${response.statusText || 'Request failed'}`; try { const errData = await response.json(); if (errData && errData.detail) { errorMsg = errData.detail; } } catch (_) { // Response was not JSON } throw new Error(errorMsg); } const data = await response.json(); const elapsed = ((Date.now() - startTime) / 1000).toFixed(2); renderTelemetry(data, elapsed); } catch (err) { logLine.textContent = `> PIPELINE ERROR: ${err.message}`; seaStatusBadge.textContent = 'SCAN ERROR'; seaStatusBadge.className = 'font-mono text-[10px] font-bold bg-red-600 text-white px-2 py-0.5 uppercase tracking-wider'; alert(`Pipeline Error: ${err.message}`); } finally { setLoading(false); } }); function setLoading(isLoading) { btnVerify.disabled = isLoading; if (isLoading) { verifySpinner.classList.remove('hidden'); verifyText.textContent = 'PROCESSING PIPELINE...'; } else { verifySpinner.classList.add('hidden'); verifyText.textContent = 'RUN PROVENANCE VERIFICATION →'; } } // Render Telemetry Results function renderTelemetry(data, elapsed) { idleScreen.classList.add('hidden'); telemetryScreen.classList.remove('hidden'); const face = data.face || {}; const img = data.image || {}; const search = data.reverse_search || {}; const v = data.verification || {}; const bc = data.blockchain || {}; // 1. Status Pill if (v.status === 'VERIFIED') { seaStatusBadge.textContent = 'PROOF VERIFIED'; seaStatusBadge.className = 'font-mono text-[10px] font-bold bg-emerald-500 text-black px-2 py-0.5 uppercase tracking-wider'; teleStatus.textContent = 'VERIFIED MATCH DETECTED'; teleStatus.className = 'text-sm font-extrabold text-emerald-400 tracking-wider'; } else { seaStatusBadge.textContent = 'ORIGINAL UNBOUND'; seaStatusBadge.className = 'font-mono text-[10px] font-bold bg-amber-400 text-black px-2 py-0.5 uppercase tracking-wider'; teleStatus.textContent = 'NO CONFIRMED MATCH (LOCAL ORIGINAL)'; teleStatus.className = 'text-sm font-extrabold text-amber-300 tracking-wider'; } const scorePercent = ((v.best_similarity || 0) * 100).toFixed(1); teleScore.textContent = `${scorePercent}%`; // 2. Modules teleFaceDetected.textContent = `Detected: ${face.detected ? 'YES' : 'NO'}`; teleFaceCount.textContent = `Faces Found: ${face.count || 0}`; teleSha.textContent = img.sha256 ? img.sha256.substring(0, 20) + '...' : 'N/A'; telePhash.textContent = img.phash || 'N/A'; if (search.error) { teleSearchCount.textContent = `Error: ${search.error.substring(0, 30)}...`; } else { teleSearchCount.textContent = `Candidates Found: ${search.count || 0}`; } teleSearchEngine.textContent = `Engine: ${search.provider || 'Google Lens'}`; if (bc.submitted) { teleBcStatus.textContent = `Status: ANCHORED (${bc.network})`; teleBcLink.innerHTML = `Tx: ${bc.tx_hash.substring(0, 14)}... ↗`; } else { teleBcStatus.textContent = `Status: NOT SUBMITTED`; teleBcLink.textContent = `Tx: N/A`; } // 3. Candidate Grid const matches = data.matches || []; if (matches.length > 0) { teleCandidates.innerHTML = matches.map(m => `