/** * summarize.js — Custom handler for the Summarize Document tool. * * Handles file upload (via api/extract.php), corpus slice toggles, * engine selection, JSON submission, and NDJSON streaming response. */ (function () { 'use strict'; // ── Element refs ────────────────────────────────────────────────────────── var form = document.getElementById('sumForm'); var runBtn = document.getElementById('sumRunButton'); var statusEl = document.getElementById('sumStatus'); var resultsEl = document.getElementById('sumResults'); var traceList = document.getElementById('traceList'); var textarea = document.getElementById('sumInput'); var uploadZone = document.getElementById('sumUploadZone'); var uploadInput = document.getElementById('sumUploadInput'); var uploadPrompt = document.getElementById('sumUploadPrompt'); var uploadFileInfo = document.getElementById('sumUploadFileInfo'); var uploadFileList = document.getElementById('sumUploadFileList'); var uploadClear = document.getElementById('sumUploadClear'); // ── State ───────────────────────────────────────────────────────────────── var _extractedFiles = []; // [{ name, text, chars }] var _currentLang = 'en'; var _lastPayload = null; // ── Lang switcher ───────────────────────────────────────────────────────── document.querySelectorAll('.sum-lang-btn').forEach(function (btn) { btn.addEventListener('click', function () { document.querySelectorAll('.sum-lang-btn').forEach(function (b) { b.classList.toggle('is-active', b === btn); }); _currentLang = btn.dataset.lang || 'en'; }); }); // ── Corpus slice toggles ────────────────────────────────────────────────── document.querySelectorAll('.sum-slice').forEach(function (btn) { btn.addEventListener('click', function () { var on = btn.classList.toggle('is-on'); btn.setAttribute('aria-pressed', String(on)); var badge = btn.querySelector('.dr-slice__badge'); if (badge) badge.textContent = on ? 'on' : 'off'; }); }); function activeSlices() { var out = []; document.querySelectorAll('.sum-slice.is-on').forEach(function (btn) { if (btn.dataset.slice) out.push(btn.dataset.slice); }); return out; } // ── File upload ─────────────────────────────────────────────────────────── if (uploadZone) { uploadZone.addEventListener('dragover', function (e) { e.preventDefault(); uploadZone.classList.add('is-drag-over'); }); uploadZone.addEventListener('dragleave', function (e) { if (!uploadZone.contains(e.relatedTarget)) { uploadZone.classList.remove('is-drag-over'); } }); uploadZone.addEventListener('drop', function (e) { e.preventDefault(); uploadZone.classList.remove('is-drag-over'); if (e.dataTransfer && e.dataTransfer.files.length) { handleFiles(e.dataTransfer.files); } }); // Stop label-for and the input itself from bubbling into the zone click // handler — otherwise the picker opens twice (native + programmatic). var browseLabel = uploadZone.querySelector('label[for="' + (uploadInput && uploadInput.id) + '"]'); if (browseLabel) { browseLabel.addEventListener('click', function (e) { e.stopPropagation(); }); } if (uploadInput) { uploadInput.addEventListener('click', function (e) { e.stopPropagation(); }); } uploadZone.addEventListener('click', function (e) { if (e.target === uploadClear || (uploadClear && uploadClear.contains(e.target))) return; if (e.target === uploadInput) return; var lbl = e.target.closest && e.target.closest('label'); if (lbl && lbl.getAttribute('for') === uploadInput.id) return; if (uploadInput) uploadInput.click(); }); } if (uploadInput) { uploadInput.addEventListener('change', function () { if (uploadInput.files && uploadInput.files.length) { handleFiles(uploadInput.files); } }); } if (uploadClear) { uploadClear.addEventListener('click', function (e) { e.stopPropagation(); resetUpload(); }); } function resetUpload() { _extractedFiles = []; if (uploadInput) uploadInput.value = ''; if (uploadPrompt) uploadPrompt.classList.remove('is-hidden'); if (uploadFileInfo) uploadFileInfo.classList.add('is-hidden'); if (uploadFileList) uploadFileList.innerHTML = ''; } function handleFiles(fileList) { var files = Array.from(fileList).slice(0, 5); if (!files.length) return; setStatus('Extracting text from ' + files.length + ' file(s)…'); setBusy(true); var promises = files.map(function (file) { var fd = new FormData(); fd.append('file', file); return fetch('api/extract.php', { method: 'POST', credentials: 'same-origin', body: fd }) .then(function (r) { return r.json(); }) .then(function (data) { if (!data.ok) throw new Error(data.error || 'Extraction failed for ' + file.name); return { name: file.name, text: data.text || '', chars: data.chars || 0 }; }); }); Promise.all(promises) .then(function (results) { _extractedFiles = results; renderFileList(results); setStatus(''); setBusy(false); }) .catch(function (err) { setStatus('Error: ' + err.message); setBusy(false); }); } function renderFileList(files) { if (!uploadFileList) return; uploadFileList.innerHTML = files.map(function (f) { return '
  • ' + esc(f.name) + '' + ' ' + f.chars.toLocaleString() + ' chars
  • '; }).join(''); if (uploadPrompt) uploadPrompt.classList.add('is-hidden'); if (uploadFileInfo) uploadFileInfo.classList.remove('is-hidden'); } // ── Form submission ─────────────────────────────────────────────────────── if (form) { form.addEventListener('submit', function (e) { e.preventDefault(); runSummarize(); }); } async function runSummarize() { // Build text: extracted files + textarea var pastedText = textarea ? textarea.value.trim() : ''; var fileText = _extractedFiles.map(function (f) { return f.text; }).join('\n\n---\n\n'); var combined = [fileText, pastedText].filter(Boolean).join('\n\n---\n\n'); // Doc picker ids var docIdsEl = document.getElementById('docPickerIds'); var rawDocIds = docIdsEl ? docIdsEl.value.trim() : ''; var docIds = rawDocIds ? rawDocIds.split(',').map(Number).filter(Boolean) : []; if (!combined && !docIds.length) { setStatus('Paste text, upload a file, or select a document before running.'); return; } var engine = (document.querySelector('input[name="sumEngine"]:checked') || {}).value || 'azure_mini'; var slices = activeSlices(); var payload = { text: combined, language: _currentLang, engine: engine, slices: slices, }; if (docIds.length) payload.doc_ids = docIds; _lastPayload = payload; setBusy(true); setStatus('Running…'); showProgress([]); try { var resp = await fetch('api/summarize.php', { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }); if (!resp.ok || !resp.body) { throw new Error('Server returned ' + resp.status); } var reader = resp.body.getReader(); var decoder = new TextDecoder(); var buffer = ''; var steps = []; while (true) { var _ref = await reader.read(); var done = _ref.done; var value = _ref.value; if (done) break; buffer += decoder.decode(value, { stream: true }); var lines = buffer.split('\n'); buffer = lines.pop(); // keep incomplete line for (var i = 0; i < lines.length; i++) { var line = lines[i].trim(); if (!line) continue; var data; try { data = JSON.parse(line); } catch (_) { continue; } if (data.event === 'progress') { steps.push(data); showProgress(steps); setStatus(data.detail || ''); } else if (data.event === 'final') { renderFinal(data); if (typeof window.dbnShowSaveResultButton === 'function') { window.dbnShowSaveResultButton( 'summarize', _lastPayload || {}, data, { model: data.engine || null, latency_ms: data.latency_ms || 0 }, resultsEl ); } // Offer deep legal analysis on the summarised text if (typeof window.dbnInjectLegalAnalysisButton === 'function') { var sourceText = combined || (_lastPayload && _lastPayload.text) || ''; if (sourceText && sourceText.length >= 80) { window.dbnInjectLegalAnalysisButton(sourceText, _currentLang, 'summarize', resultsEl); } } if (data.balance != null) { var credEl = document.getElementById('creditsRemaining'); if (credEl) credEl.textContent = data.balance; } } else if (data.event === 'error') { showError(data.error || 'An error occurred.'); } } } } catch (err) { showError(err.message || 'Request failed.'); } finally { setBusy(false); setStatus(''); } } // ── Result rendering ────────────────────────────────────────────────────── function showProgress(steps) { if (!resultsEl) return; var items = steps.map(function (s) { return '
  • ' + '
    ' + esc(stepLabel(s.step)) + '' + '

    ' + esc(s.detail || '') + '

  • '; }).join(''); resultsEl.innerHTML = '
      ' + items + '
    1. Working…
    '; } function stepLabel(step) { var labels = { text_ready: 'Document prepared', corpus_search: 'Searching legal corpus', corpus_done: 'Corpus search done', generating: 'Generating summary', }; return labels[step] || step; } function renderFinal(data) { if (!resultsEl) return; var sections = []; if (data.what_we_found) { sections.push( '
    ' + '

    Summary

    ' + '

    ' + esc(data.what_we_found) + '

    ' + '
    ' ); } if (Array.isArray(data.key_facts) && data.key_facts.length) { sections.push(detailBlock('Key Facts', data.key_facts)); } if (Array.isArray(data.dates) && data.dates.length) { sections.push(detailBlock('Dates', data.dates)); } if (Array.isArray(data.parties) && data.parties.length) { sections.push(detailBlock('Parties', data.parties)); } if (Array.isArray(data.legal_references_detected) && data.legal_references_detected.length) { sections.push(detailBlock('Legal References Detected', data.legal_references_detected)); } if (Array.isArray(data.what_remains_uncertain) && data.what_remains_uncertain.length) { sections.push(detailBlock('What Remains Uncertain', data.what_remains_uncertain)); } if (data.next_practical_step) { sections.push( '
    ' + '

    Next Practical Step

    ' + '

    ' + esc(data.next_practical_step) + '

    ' + '
    ' ); } if (data.corpus_used) { sections.push( '

    ' + 'Summary enriched with relevant passages from the Do Better Norge legal corpus.' + '

    ' ); } if (data.disclaimer) { sections.push('

    ' + esc(data.disclaimer) + '

    '); } resultsEl.innerHTML = sections.join(''); // Update reasoning panel trace if (traceList && Array.isArray(data.trace) && data.trace.length) { traceList.innerHTML = data.trace.map(function (item) { return '
  • ' + '' + '
    ' + esc(item.label || '') + '' + '

    ' + esc(item.detail || '') + '

    ' + '
  • '; }).join(''); } } function showError(msg) { if (resultsEl) { resultsEl.innerHTML = '

    Error

    ' + esc(msg) + '

    '; } setStatus(''); } function detailBlock(title, items) { return '

    ' + esc(title) + '

    ' + '' + '
    '; } // ── Helpers ─────────────────────────────────────────────────────────────── function setBusy(on) { if (runBtn) runBtn.disabled = on; if (runBtn) runBtn.textContent = on ? 'Running…' : 'Summarize'; } function setStatus(msg) { if (statusEl) statusEl.textContent = msg; } function esc(s) { return String(s == null ? '' : s) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } }());