/* korrespond.js — page-scoped UI for /korrespond.php Two-pass wizard: Pass 1 may return clarify questions; Pass 2 returns Norwegian + working-language drafts side by side with verified law citations. */ (function () { 'use strict'; const els = {}; let lang = window.DBN_TOOLS_LANG || localStorage.getItem('dbn-ui-lang') || 'en'; let uploadFiles = []; let lastClassify = null; let pendingClarifications = {}; const LANG_LABELS = { en: 'English', no: 'Norsk', uk: 'Українська', pl: 'Polski' }; document.addEventListener('DOMContentLoaded', () => { if (document.body.dataset.activeTool !== 'korrespond') return; Object.assign(els, { form: document.getElementById('korrForm'), status: document.getElementById('korrStatus'), runButton: document.getElementById('korrRunButton'), results: document.getElementById('korrResults'), langButtons: Array.from(document.querySelectorAll('#korrLangSwitcher .lang-btn')), modeRadios: Array.from(document.querySelectorAll('input[name="korrMode"]')), bodySelect: document.getElementById('korrBody'), outputRadios: Array.from(document.querySelectorAll('input[name="korrOutput"]')), toneRadios: Array.from(document.querySelectorAll('input[name="korrTone"]')), caseRef: document.getElementById('korrCaseRef'), where: document.getElementById('korrWhere'), deadline: document.getElementById('korrDeadline'), parties: document.getElementById('korrParties'), narrative: document.getElementById('korrNarrative'), goal: document.getElementById('korrGoal'), goalChips: Array.from(document.querySelectorAll('#korrGoalChips .korr-chip')), uploadZone: document.getElementById('korrUploadZone'), uploadInput: document.getElementById('korrUploadInput'), uploadPrompt: document.getElementById('korrUploadPrompt'), uploadFileInfo: document.getElementById('korrUploadFileInfo'), uploadFileList: document.getElementById('korrUploadFileList'), uploadClear: document.getElementById('korrUploadClear'), clarifyPanel: document.getElementById('korrClarifyPanel'), clarifyList: document.getElementById('korrClarifyList'), clarifyContinue:document.getElementById('korrClarifyContinue'), clarifyForce: document.getElementById('korrClarifyForce'), }); if (!els.form) return; bindLang(); bindGoalChips(); bindUpload(); bindClarify(); els.form.addEventListener('submit', (e) => { e.preventDefault(); runRequest(false); }); }); // ── Language switcher ─────────────────────────────────────────────────────── function bindLang() { els.langButtons.forEach((b) => { b.classList.toggle('is-active', b.dataset.lang === lang); b.addEventListener('click', () => { els.langButtons.forEach((x) => x.classList.remove('is-active')); b.classList.add('is-active'); lang = b.dataset.lang || 'en'; localStorage.setItem('dbn-ui-lang', lang); }); }); } function bindGoalChips() { els.goalChips.forEach((chip) => { chip.addEventListener('click', () => { els.goal.value = chip.dataset.goal || ''; els.goalChips.forEach((c) => c.classList.remove('is-active')); chip.classList.add('is-active'); }); }); } // ── File upload ───────────────────────────────────────────────────────────── function bindUpload() { if (!els.uploadZone) return; const onFiles = (fileList) => { const files = Array.from(fileList || []).slice(0, 4); if (uploadFiles.length + files.length > 4) { setStatus('At most 4 files can be uploaded per request.', 'error'); return; } files.forEach((f) => { if (f.size > 8 * 1024 * 1024) { setStatus(`${f.name} exceeds the 8 MB limit.`, 'error'); return; } const ext = (f.name.split('.').pop() || '').toLowerCase(); if (!['pdf', 'docx', 'txt'].includes(ext)) { setStatus(`${f.name} is not a supported file type (PDF, DOCX, TXT).`, 'error'); return; } uploadFiles.push(f); }); renderUploadList(); }; els.uploadInput.addEventListener('change', (e) => onFiles(e.target.files)); els.uploadZone.addEventListener('dragover', (e) => { e.preventDefault(); els.uploadZone.classList.add('is-drop'); }); els.uploadZone.addEventListener('dragleave', () => els.uploadZone.classList.remove('is-drop')); els.uploadZone.addEventListener('drop', (e) => { e.preventDefault(); els.uploadZone.classList.remove('is-drop'); onFiles(e.dataTransfer?.files); }); els.uploadClear?.addEventListener('click', () => { uploadFiles = []; els.uploadInput.value = ''; renderUploadList(); }); } function renderUploadList() { if (!uploadFiles.length) { els.uploadFileInfo.classList.add('is-hidden'); els.uploadPrompt.classList.remove('is-hidden'); return; } els.uploadPrompt.classList.add('is-hidden'); els.uploadFileInfo.classList.remove('is-hidden'); els.uploadFileList.innerHTML = uploadFiles.map((f) => { const kb = (f.size / 1024).toFixed(0); return `
Pass 1 extracts facts. If anything is missing we'll ask for clarification. Otherwise Pass 2 runs hard-RAG retrieval + draft + self-check + translate.
${esc(c.summary)}
${c.applicable_acts && c.applicable_acts.length ? `Antatt rettslig grunnlag: ${c.applicable_acts.map(esc).join(', ')}
` : ''} ${c.deadlines && c.deadlines.length ? `Frister: ${c.deadlines.map(esc).join(', ')}
` : ''} `; } function renderFinal(data) { const userLang = data.draft_user_lang || 'en'; const userLangLabel = LANG_LABELS[userLang] || userLang.toUpperCase(); const flags = data.self_check || {}; const cited = data.cited_law || []; const flagBadge = (key, label) => { const v = flags[key] || 'ok'; const cls = v === 'ok' ? 'is-ok' : (v === 'warn' ? 'is-warn' : 'is-error'); const icon = v === 'ok' ? '✓' : '!'; return `${icon} ${esc(label)}`; }; const draftNo = data.draft_no || ''; const draftUser = data.draft_user || ''; const isSameLang = userLang === 'no'; els.results.innerHTML = `${esc(draftNo)}
${esc(draftUser)}
${esc(s.excerpt || '')}
${s.source_url ? `View source` : ''}No cited law sources — draft is plain-language (no § references available from corpus).
'} ${data.disclaimer ? `${esc(data.disclaimer)}
` : ''} `; // Wire copy/download els.results.querySelectorAll('[data-copy]').forEach((btn) => { btn.addEventListener('click', () => { const target = btn.dataset.copy === 'no' ? draftNo : draftUser; navigator.clipboard?.writeText(target).then( () => { btn.textContent = 'Copied ✓'; setTimeout(() => btn.textContent = 'Copy', 1500); }, () => { btn.textContent = 'Failed'; } ); }); }); els.results.querySelectorAll('[data-download]').forEach((btn) => { btn.addEventListener('click', () => { const target = btn.dataset.download === 'no' ? draftNo : draftUser; const suffix = btn.dataset.download === 'no' ? 'no' : userLang; downloadText(`korrespond-${data.recipient_body}-${suffix}.txt`, target); }); }); } // ── utils ─────────────────────────────────────────────────────────────────── function setStatus(message, kind) { if (!els.status) return; els.status.textContent = message || ''; els.status.dataset.kind = kind || ''; } function esc(s) { return String(s == null ? '' : s) .replace(/&/g, '&').replace(//g, '>') .replace(/"/g, '"').replace(/'/g, '''); } function downloadText(filename, text) { const blob = new Blob([text], { type: 'text/plain;charset=utf-8' }); 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); } })();