MaximusCorp's picture
Upload 4 files
f11ff2f verified
Raw
History Blame Contribute Delete
7.37 kB
const form = document.getElementById('uploadForm');
const input = document.getElementById('imageInput');
const preview = document.getElementById('preview');
const dropZone = document.getElementById('dropZone');
const fileName = document.getElementById('fileName');
const button = document.getElementById('analyseButton');
const title = document.getElementById('verdictTitle');
const badge = document.getElementById('verdictBadge');
const note = document.getElementById('reportNote');
const status = document.getElementById('serviceStatus');
const rows = [...document.querySelectorAll('.test-list li')];
let previewUrl = '';
function setServiceState(online) {
status.textContent = online ? 'Service ready' : 'Service unavailable';
status.className = `status ${online ? 'online' : 'offline'}`;
}
fetch('/api/health', { cache: 'no-store' })
.then((response) => setServiceState(response.ok))
.catch(() => setServiceState(false));
function selectFile(file) {
if (!file) return;
if (!['image/jpeg', 'image/png', 'image/webp'].includes(file.type)) {
showError('Select a JPEG, PNG or WebP image.');
input.value = '';
return;
}
if (file.size > 8 * 1024 * 1024) {
showError('The selected image is larger than 8 MB.');
input.value = '';
return;
}
if (previewUrl) URL.revokeObjectURL(previewUrl);
previewUrl = URL.createObjectURL(file);
preview.src = previewUrl;
fileName.textContent = file.name;
dropZone.classList.add('has-preview');
resetReport();
}
input.addEventListener('change', () => selectFile(input.files[0]));
for (const eventName of ['dragenter', 'dragover']) {
dropZone.addEventListener(eventName, (event) => {
event.preventDefault();
dropZone.classList.add('dragging');
});
}
for (const eventName of ['dragleave', 'drop']) {
dropZone.addEventListener(eventName, (event) => {
event.preventDefault();
dropZone.classList.remove('dragging');
});
}
dropZone.addEventListener('drop', (event) => {
const file = event.dataTransfer.files[0];
if (!file) return;
const transfer = new DataTransfer();
transfer.items.add(file);
input.files = transfer.files;
selectFile(file);
});
function resetReport() {
title.textContent = 'Ready to analyse';
badge.textContent = 'Not tested';
badge.className = 'verdict neutral';
note.textContent = 'Tests run in order and stop immediately when one rejects the image.';
rows.forEach((row) => {
row.className = '';
row.querySelector('b').textContent = 'Waiting';
row.removeAttribute('title');
});
}
function showRunning() {
title.textContent = 'Analysis in progress';
badge.textContent = 'Running';
badge.className = 'verdict running';
note.textContent = 'Free Spaces can be slow after waking up. Keep this page open.';
rows.forEach((row) => {
row.className = '';
row.querySelector('b').textContent = 'Waiting';
row.removeAttribute('title');
});
}
function showError(message) {
title.textContent = 'Analysis unavailable';
badge.textContent = 'Error';
badge.className = 'verdict error';
note.textContent = message;
}
function renderReport(report) {
const labels = { approved: 'Approved', rejected: 'Rejected', error: 'Error' };
title.textContent = report.verdict === 'approved' ? 'Image passed every test' :
report.verdict === 'rejected' ? 'Image did not pass moderation' : 'Analysis could not finish';
badge.textContent = labels[report.verdict] || 'Error';
badge.className = `verdict ${report.verdict}`;
rows.forEach((row, index) => {
const result = report.results[index];
if (!result) {
row.className = 'skipped';
row.querySelector('b').textContent = 'Skipped';
return;
}
row.className = result.status;
const score = typeof result.score === 'number' ? ` · ${(result.score * 100).toFixed(1)}%` : '';
row.querySelector('b').textContent = `${result.status === 'passed' ? 'Passed' : result.status === 'rejected' ? 'Failed' : 'Error'}${score}`;
if (result.detail || result.warning) row.title = [result.detail, result.warning].filter(Boolean).join(' ');
});
const failed = report.results.find((result) => !result.passed);
note.textContent = failed
? `${failed.name} stopped the sequence. ${failed.detail || failed.warning || ''}`.trim()
: 'All four automated tests completed successfully.';
}
function renderProgress(event) {
title.textContent = 'Analysis in progress';
badge.textContent = 'Running';
badge.className = 'verdict running';
const active = typeof event.active === 'number' ? event.active : -1;
rows.forEach((row, index) => {
const result = event.results && event.results[index];
row.removeAttribute('title');
if (result) {
row.className = result.status;
const score = typeof result.score === 'number' ? ` · ${(result.score * 100).toFixed(1)}%` : '';
row.querySelector('b').textContent = `${result.status === 'passed' ? 'Passed' : result.status === 'rejected' ? 'Failed' : 'Error'}${score}`;
if (result.detail || result.warning) row.title = [result.detail, result.warning].filter(Boolean).join(' ');
} else if (index === active) {
row.className = 'running';
row.querySelector('b').textContent = 'Running';
} else {
row.className = '';
row.querySelector('b').textContent = 'Waiting';
}
});
note.textContent = active >= 0
? `${event.activeName || rows[active]?.children[1]?.textContent || 'Current test'} is running.`
: 'Tests run in order and stop immediately when one rejects the image.';
}
async function readProgressStream(response) {
if (!response.body) {
const payload = await response.json();
if (!response.ok) throw new Error(payload.detail || `HTTP error ${response.status}`);
renderReport(payload);
return;
}
if (!response.ok) {
let payload = {};
try { payload = await response.json(); } catch {}
throw new Error(payload.detail || `HTTP error ${response.status}`);
}
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
while (true) {
const { value, done } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop() || '';
for (const line of lines) {
if (!line.trim()) continue;
const event = JSON.parse(line);
if (event.type === 'progress') renderProgress(event);
if (event.type === 'complete') renderReport(event.report);
}
}
if (buffer.trim()) {
const event = JSON.parse(buffer);
if (event.type === 'progress') renderProgress(event);
if (event.type === 'complete') renderReport(event.report);
}
}
form.addEventListener('submit', async (event) => {
event.preventDefault();
const file = input.files[0];
if (!file) {
showError('Select an image before running moderation.');
return;
}
showRunning();
button.disabled = true;
button.textContent = 'Analysing...';
const body = new FormData();
body.append('image', file);
try {
const response = await fetch('/api/analyse-stream', { method: 'POST', body });
await readProgressStream(response);
} catch (error) {
showError(error.message || 'The analysis request failed.');
} finally {
button.disabled = false;
button.textContent = 'Run moderation';
}
});