JamesNR/patchnet
0
1// PatchNet Frontend2 3const form = document.getElementById('predict-form');4const fileInput = document.getElementById('file-input');5const dropZone = document.getElementById('drop-zone');6const placeholder = document.getElementById('upload-placeholder');7const previewImg = document.getElementById('preview-img');8const promptInput = document.getElementById('prompt-input');9const submitBtn = document.getElementById('submit-btn');10const btnText = submitBtn.querySelector('.btn-text');11const btnLoading = submitBtn.querySelector('.btn-loading');12const resultsEl = document.getElementById('results-section');13 14let selectedFile = null;15 16// ──────────── Drag & Drop / File Select ──────────────────────────────17 18dropZone.addEventListener('click', () => fileInput.click());19 20dropZone.addEventListener('dragover', e => {21 e.preventDefault();22 dropZone.classList.add('dragover');23});24dropZone.addEventListener('dragleave', () => dropZone.classList.remove('dragover'));25dropZone.addEventListener('drop', e => {26 e.preventDefault();27 dropZone.classList.remove('dragover');28 if (e.dataTransfer.files.length) handleFile(e.dataTransfer.files[0]);29});30 31fileInput.addEventListener('change', () => {32 if (fileInput.files.length) handleFile(fileInput.files[0]);33});34 35function handleFile(file) {36 if (!file.type.startsWith('image/')) return;37 selectedFile = file;38 const reader = new FileReader();39 reader.onload = e => {40 previewImg.src = e.target.result;41 previewImg.classList.remove('hidden');42 placeholder.classList.add('hidden');43 };44 reader.readAsDataURL(file);45 checkReady();46}47 48// ──────────── Enable button when both fields filled ──────────────────49 50promptInput.addEventListener('input', checkReady);51function checkReady() {52 submitBtn.disabled = !(selectedFile && promptInput.value.trim());53}54 55// ──────────── Submit ─────────────────────────────────────────────────56 57form.addEventListener('submit', async e => {58 e.preventDefault();59 if (!selectedFile || !promptInput.value.trim()) return;60 61 // UI → loading state62 submitBtn.disabled = true;63 btnText.classList.add('hidden');64 btnLoading.classList.remove('hidden');65 resultsEl.classList.add('hidden');66 67 const formData = new FormData();68 formData.append('image', selectedFile);69 formData.append('prompt', promptInput.value.trim());70 71 try {72 const res = await fetch('/api/predict', { method: 'POST', body: formData });73 if (!res.ok) throw new Error(`Server error: ${res.status}`);74 const data = await res.json();75 renderResults(data);76 } catch (err) {77 alert('Error: ' + err.message);78 } finally {79 btnText.classList.remove('hidden');80 btnLoading.classList.add('hidden');81 submitBtn.disabled = false;82 }83});84 85// ──────────── Render Results ─────────────────────────────────────────86 87function renderResults(data) {88 const maxScore = data.max_score;89 90 setScore('consistency', data.consistency_score, maxScore);91 setScore('quality', data.quality_score, maxScore);92 setScore('authenticity', data.authenticity_score, maxScore);93 94 // Annotated image95 document.getElementById('annotated-img').src = 'data:image/jpeg;base64,' + data.annotated_image;96 document.getElementById('info-grid').textContent = `${data.num_grid_patches} grid patches`;97 document.getElementById('info-surf').textContent = `${data.num_surf_keypoints} SURF keypoints`;98 99 // SURF patches gallery100 const gallery = document.getElementById('surf-gallery');101 gallery.innerHTML = '';102 data.surf_patches.forEach((b64, i) => {103 const img = document.createElement('img');104 img.src = 'data:image/jpeg;base64,' + b64;105 img.alt = `SURF Patch ${i + 1}`;106 gallery.appendChild(img);107 });108 109 resultsEl.classList.remove('hidden');110 resultsEl.scrollIntoView({ behavior: 'smooth', block: 'start' });111}112 113function setScore(name, score, max) {114 const pct = Math.min(score / max, 1);115 const circumference = 2 * Math.PI * 52; // r=52116 117 // Value text118 document.getElementById(`val-${name}`).textContent = score.toFixed(2);119 120 // Ring animation121 const ring = document.getElementById(`ring-${name}`);122 ring.style.strokeDashoffset = circumference * (1 - pct);123 124 // Colour based on score ratio125 ring.classList.remove('ring-good', 'ring-mid', 'ring-low');126 if (pct >= 0.65) ring.classList.add('ring-good');127 else if (pct >= 0.35) ring.classList.add('ring-mid');128 else ring.classList.add('ring-low');129}130 