(() => { const cfg = window.BULK_CONFIG; if (!cfg) return; const $ = id => document.getElementById(id); const t = (key, fallback = key) => (cfg.texts && cfg.texts[key]) || fallback; let lastResult = null; const apiKeyInput = $('bulk-api-key'); if (apiKeyInput) { apiKeyInput.value = sessionStorage.getItem('aiDisclosureBulkApiKey') || ''; apiKeyInput.addEventListener('input', () => sessionStorage.setItem('aiDisclosureBulkApiKey', apiKeyInput.value)); } function parseLines() { const raw = ($('bulk-items')?.value || '').split(/\r?\n/).map(v => v.trim()).filter(Boolean); return raw.map((line, index) => { let id = ''; let subject = ''; if (line.includes('|')) { const [left, ...right] = line.split('|'); id = left.trim(); subject = right.join('|').trim(); } else if (line.includes('\t')) { const [left, ...right] = line.split('\t'); id = left.trim(); subject = right.join('\t').trim(); } else { subject = line; } if (!id) id = `item-${index + 1}`; return {id, subject}; }); } function updateCount() { const count = parseLines().length; const el = $('bulk-item-count'); if (el) { el.textContent = `${count} / ${cfg.maxItems}`; el.classList.toggle('over-limit', count > cfg.maxItems); } } function normalizeReview(extentId, reviewId) { const extent = $(extentId); const review = $(reviewId); if (!extent || !review) return; if (extent.value === 'none') review.value = 'none'; else if (review.value === 'none') review.value = 'editorial'; } const componentPairs = [ ['bulk-text-extent', 'bulk-text-review'], ['bulk-cover-extent', 'bulk-cover-review'], ['bulk-image-extent', 'bulk-image-review'], ['bulk-research-extent', 'bulk-research-review'], ['bulk-translation-extent', 'bulk-translation-review'], ['bulk-code-extent', 'bulk-code-review'] ]; componentPairs.forEach(([extentId, reviewId]) => { $(extentId)?.addEventListener('change', () => normalizeReview(extentId, reviewId)); }); function commonParameters(subject) { const params = { mode: 'article', lang: $('bulk-output-language').value, assurance: $('bulk-assurance').value, textExtent: $('bulk-text-extent').value, textReview: $('bulk-text-review').value, coverImageExtent: $('bulk-cover-extent').value, coverImageReview: $('bulk-cover-review').value, imageExtent: $('bulk-image-extent').value, imageReview: $('bulk-image-review').value, researchExtent: $('bulk-research-extent').value, researchReview: $('bulk-research-review').value, translationExtent: $('bulk-translation-extent').value, translationReview: $('bulk-translation-review').value, codeExtent: $('bulk-code-extent').value, codeReview: $('bulk-code-review').value }; if (subject) params.subject = subject; const bools = { 'bulk-public-interest': 'publicInterestText', 'bulk-deepfake': 'deepfake', 'bulk-substantial-review': 'substantialHumanReview', 'bulk-editorial-responsibility': 'editorialResponsibilityConfirmed', 'bulk-first-exposure': 'firstExposureDisclosure', 'bulk-accessibility': 'accessibilityConsidered' }; Object.entries(bools).forEach(([id, key]) => { if ($(id)?.checked) params[key] = 'true'; }); const responsible = $('bulk-responsible')?.value.trim(); const responsibleURL = $('bulk-responsible-url')?.value.trim(); if (responsible) params.responsible = responsible; if (responsibleURL) params.responsibleUrl = responsibleURL; return params; } function buildVisualRequest() { const items = parseLines(); if (!items.length) throw new Error(t('error_no_items', 'Add at least one item.')); if (items.length > cfg.maxItems) throw new Error(t('error_too_many_items', 'The configured item limit has been exceeded.')); return {items: items.map(item => ({id: item.id, parameters: commonParameters(item.subject)}))}; } function headers() { const h = {'Content-Type': 'application/json'}; const key = apiKeyInput?.value.trim(); if (cfg.requireAPIKey) { if (!key) throw new Error(t('error_api_key', 'A bulk API key is required.')); h.Authorization = `Bearer ${key}`; } else if (key) { h.Authorization = `Bearer ${key}`; } return h; } async function run(request) { const status = $('bulk-run-status'); if (status) status.textContent = t('status_running', 'Processing…'); try { const response = await fetch(cfg.endpoint, {method: 'POST', headers: headers(), body: JSON.stringify(request)}); const data = await response.json().catch(() => ({code: 'invalid_response', detail: response.statusText})); if (!response.ok) throw new Error(data.detail || data.title || `${response.status} ${response.statusText}`); lastResult = data; renderResults(data); if (status) status.textContent = t('status_done', 'Completed.'); } catch (err) { if (status) status.textContent = `${t('status_error', 'Error')}: ${err.message}`; throw err; } } function renderResults(data) { const section = $('results'); const body = $('bulk-result-body'); const raw = $('bulk-result-json'); if (!section || !body || !raw) return; section.hidden = false; body.textContent = ''; const items = Array.isArray(data.items) ? data.items : []; const valid = items.filter(item => item.valid).length; const failed = items.length - valid; $('bulk-result-summary').textContent = `${t('summary_processed', 'Processed')}: ${items.length} · ${t('summary_successful', 'Successful')}: ${valid} · ${t('summary_failed', 'Failed')}: ${failed}`; items.forEach(item => { const tr = document.createElement('tr'); const id = document.createElement('td'); id.textContent = item.id || '—'; const status = document.createElement('td'); const pill = document.createElement('span'); pill.className = `bulk-result-pill ${item.valid ? 'ok' : 'error'}`; pill.textContent = item.valid ? t('result_ok', 'OK') : t('result_error', 'Error'); status.appendChild(pill); if (!item.valid && item.error?.detail) { const detail = document.createElement('small'); detail.className = 'bulk-error-detail'; detail.textContent = item.error.detail; status.appendChild(detail); } const assessment = document.createElement('td'); assessment.textContent = item.article50Assessment?.code || '—'; const links = document.createElement('td'); if (item.valid) { const linkData = [ [t('link_declaration', 'Declaration'), item.declarationUrl], [t('link_manifest', 'Manifest'), item.manifestUrl], [t('link_badge', 'Badge'), item.badgeUrl] ]; linkData.forEach(([label, href], index) => { if (!href) return; if (index) links.appendChild(document.createTextNode(' · ')); const a = document.createElement('a'); a.href = href; a.target = '_blank'; a.rel = 'noopener noreferrer'; a.textContent = label; links.appendChild(a); }); } else links.textContent = '—'; tr.append(id, status, assessment, links); body.appendChild(tr); }); raw.textContent = JSON.stringify(data, null, 2); section.scrollIntoView({behavior: 'smooth', block: 'start'}); } function download(filename, content, type) { const blob = new Blob([content], {type}); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = filename; document.body.appendChild(a); a.click(); a.remove(); URL.revokeObjectURL(url); } function csvEscape(value) { const s = String(value ?? ''); return /[",\n\r;]/.test(s) ? `"${s.replaceAll('"', '""')}"` : s; } function resultCSV() { const rows = [['id', 'valid', 'assessment', 'declaration_url', 'manifest_url', 'badge_url', 'error']]; (lastResult?.items || []).forEach(item => rows.push([ item.id || '', item.valid ? 'true' : 'false', item.article50Assessment?.code || '', item.declarationUrl || '', item.manifestUrl || '', item.badgeUrl || '', item.error?.detail || '' ])); return rows.map(row => row.map(csvEscape).join(';')).join('\n'); } $('bulk-items')?.addEventListener('input', updateCount); $('bulk-sample-items')?.addEventListener('click', () => { $('bulk-items').value = 'article-1001 | https://example.org/articles/1001\narticle-1002 | https://example.org/articles/1002\narticle-1003 | https://example.org/articles/1003'; updateCount(); }); $('bulk-run')?.addEventListener('click', async () => { try { await run(buildVisualRequest()); } catch (_) { /* status already rendered */ } }); $('bulk-json-sample')?.addEventListener('click', () => { try { if (!parseLines().length) { $('bulk-items').value = 'article-1001 | https://example.org/articles/1001'; updateCount(); } $('bulk-json').value = JSON.stringify(buildVisualRequest(), null, 2); } catch (err) { $('bulk-run-status').textContent = `${t('status_error', 'Error')}: ${err.message}`; } }); $('bulk-json-run')?.addEventListener('click', async () => { try { const request = JSON.parse($('bulk-json').value); await run(request); } catch (err) { $('bulk-run-status').textContent = `${t('status_error', 'Error')}: ${err.message}`; } }); $('bulk-copy-json')?.addEventListener('click', async () => { if (!lastResult) return; await navigator.clipboard.writeText(JSON.stringify(lastResult, null, 2)); }); $('bulk-download-json')?.addEventListener('click', () => { if (lastResult) download('ai-disclosure-bulk-results.json', JSON.stringify(lastResult, null, 2), 'application/json'); }); $('bulk-download-csv')?.addEventListener('click', () => { if (lastResult) download('ai-disclosure-bulk-results.csv', resultCSV(), 'text/csv;charset=utf-8'); }); $('bulk-language')?.addEventListener('change', event => { const url = new URL(window.location.href); url.searchParams.set('lang', event.target.value); window.location.href = url.toString(); }); updateCount(); componentPairs.forEach(([extentId, reviewId]) => normalizeReview(extentId, reviewId)); })();