259 lines
10 KiB
JavaScript
259 lines
10 KiB
JavaScript
(() => {
|
|
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));
|
|
})();
|