const $ = selector => document.querySelector(selector); let session = null; let busy = false; async function api(url, options = {}) { const response = await fetch(url, { credentials: 'same-origin', ...options }); let data; try { data = await response.json(); } catch { throw new Error('The submission service is unavailable. Please try again later.'); } if (!response.ok) throw new Error(typeof data.detail === 'string' ? data.detail : 'Request failed. Please check the form.'); return data; } function message(text, error = false) { $('#submission-message').textContent = text; $('#submission-message').className = `notice ${error ? 'error' : 'success'}`; } async function loadSession() { if (busy) return; try { session = await api('/api/submission-session'); $('#hf-username').value = session.username || ''; $('#identity-status').textContent = session.username ? `Signed in as ${session.username}` : 'Sign in to submit your outputs.'; $('#sign-in').classList.toggle('hidden', Boolean(session.username)); $('#sign-out').classList.toggle('hidden', !session.username); $('#submission-fields').disabled = !session.username || !session.storage_ready || session.storage_full; const unavailable = session.storage_full ? session.storage_message : !session.auth_enabled ? 'Hugging Face sign-in has not been configured yet.' : !session.storage_ready ? 'Submissions will open once private persistent storage is configured.' : ''; $('#availability').textContent = unavailable; $('#availability').classList.toggle('hidden', !unavailable); $('#sign-in').classList.toggle('unavailable', !session.auth_enabled); $('#sign-in').setAttribute('aria-disabled', String(!session.auth_enabled)); } catch (error) { $('#submission-fields').disabled = true; $('#identity-status').textContent = 'Submission service unavailable.'; message(error.message, true); } } $('#sign-in').addEventListener('click', event => { if (!session?.auth_enabled) event.preventDefault(); }); $('#sign-out').addEventListener('click', async () => { try { await api('/auth/logout', { method: 'POST', headers: {'X-CSRF-Token': session.csrf} }); await loadSession(); } catch (error) { message(error.message, true); } }); function upload(id, file) { return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); xhr.open('PUT', `/api/submissions/${id}/archive`); xhr.setRequestHeader('X-CSRF-Token', session.csrf); xhr.setRequestHeader('Content-Type', 'application/zip'); xhr.upload.onprogress = event => { if (!event.lengthComputable) return; const percent = Math.round(event.loaded / event.total * 100); $('#progress').value = percent; $('#progress-text').textContent = percent === 100 ? 'Upload complete. Checking ZIP contents; keep this page open…' : `Uploading… ${percent}%`; }; xhr.onerror = () => reject(new Error(`Connection lost. Submission ID: ${id}. The file has not been confirmed as accepted.`)); xhr.onload = () => { let data; try { data = JSON.parse(xhr.responseText); } catch { reject(new Error(`Unexpected server response. Submission ID: ${id}.`)); return; } if (xhr.status >= 200 && xhr.status < 300) resolve(data); else reject(new Error(data.detail || `Upload failed (${xhr.status}).`)); }; xhr.send(file); }); } $('#archive').addEventListener('change', () => { $('#archive-name').textContent = $('#archive').files[0]?.name || 'No file selected'; }); $('#submission-form').addEventListener('submit', async event => { event.preventDefault(); if (busy || !session?.username) return; const file = $('#archive').files[0]; if (!file || !file.name.toLowerCase().endsWith('.zip') || file.size <= 0 || file.size > session.max_zip_bytes) { message('Choose a non-empty .zip file no larger than 10 GB.', true); return; } const values = Object.fromEntries(new FormData(event.currentTarget)); values.username = session.username; busy = true; $('#submission-fields').disabled = true; $('#sign-out').disabled = true; $('#submission-message').className = 'notice hidden'; $('#upload-progress').classList.remove('hidden'); $('#progress').value = 0; $('#progress-text').textContent = 'Verifying model ownership, public access, and license…'; try { const prepared = await api('/api/submissions', {method:'POST', headers:{'Content-Type':'application/json','X-CSRF-Token':session.csrf}, body:JSON.stringify({...values, filename:file.name, size:file.size})}); const result = await upload(prepared.id, file); $('#progress-text').textContent = 'Upload and ZIP checks complete.'; message(`Submission received: ${result.id}. ${result.images} images found. Status: pending review.`); event.target.reset(); $('#archive-name').textContent = 'No file selected'; } catch (error) { message(error.message, true); } finally { busy = false; $('#sign-out').disabled = false; await loadSession(); } }); window.addEventListener('focus', loadSession); window.addEventListener('beforeunload', event => { if (busy) {event.preventDefault(); event.returnValue = '';}}); loadSession(); if (new URLSearchParams(location.search).get("login") === "failed") message("Hugging Face sign-in failed. Please try again.", true);