const state = { activeTool: 'ask', authenticated: Boolean(window.DBN_TOOLS_AUTHENTICATED), }; const tools = { ask: { kind: 'Source-grounded Legal Ask', title: 'Ask a legal question', label: 'Question', endpoint: 'api/ask.php', payloadKey: 'question', placeholder: 'Example: What evidence is needed before asking for changes in custody arrangements?', usesLanguage: true, badge: 'family-legal', }, search: { kind: 'Legal Source Search', title: 'Search legal sources', label: 'Search query', endpoint: 'api/search.php', payloadKey: 'query', placeholder: 'Example: barnets beste samvær foreldreansvar', usesLanguage: true, badge: 'family-legal', }, summarize: { kind: 'Document Summarizer', title: 'Summarize pasted text', label: 'Pasted text', endpoint: 'api/summarize.php', payloadKey: 'text', placeholder: 'Paste a case note, letter, or excerpt.', usesLanguage: true, badge: 'process-and-forget', }, timeline: { kind: 'Timeline Builder', title: 'Build a timeline', label: 'Pasted text', endpoint: 'api/timeline.php', payloadKey: 'text', placeholder: 'Paste case notes with dates, actors, and events.', usesLanguage: true, badge: 'process-and-forget', }, redact: { kind: 'Redaction Assistant', title: 'Redact sensitive details', label: 'Pasted text', endpoint: 'api/redact.php', payloadKey: 'text', placeholder: 'Paste text containing names, phone numbers, emails, addresses, or fødselsnummer-like values.', usesLanguage: false, badge: 'deterministic first', }, }; const els = {}; document.addEventListener('DOMContentLoaded', () => { Object.assign(els, { gate: document.querySelector('#passcodeGate'), app: document.querySelector('#appShell'), passcodeForm: document.querySelector('#passcodeForm'), loginEmail: document.querySelector('#loginEmail'), loginPassword: document.querySelector('#loginPassword'), gateStatus: document.querySelector('#gateStatus'), tabs: Array.from(document.querySelectorAll('.tool-tab')), toolKind: document.querySelector('#toolKind'), toolTitle: document.querySelector('#toolTitle'), toolBadge: document.querySelector('#toolBadge'), form: document.querySelector('#toolForm'), inputLabel: document.querySelector('#inputLabel'), input: document.querySelector('#toolInput'), languageControl: document.querySelector('#languageControl'), redactionControl: document.querySelector('#redactionControl'), status: document.querySelector('#toolStatus'), results: document.querySelector('#results'), traceList: document.querySelector('#traceList'), healthButton: document.querySelector('#healthButton'), healthPill: document.querySelector('#healthPill'), }); els.tabs.forEach((button) => { button.addEventListener('click', () => setTool(button.dataset.tool)); }); els.form.addEventListener('submit', runTool); els.passcodeForm.addEventListener('submit', submitPasscode); els.healthButton.addEventListener('click', checkHealth); setTool(state.activeTool); if (state.authenticated) { checkHealth(); } else { els.loginEmail?.focus(); } }); function setTool(toolName) { state.activeTool = toolName; const tool = tools[toolName]; els.tabs.forEach((button) => { const active = button.dataset.tool === toolName; button.classList.toggle('is-active', active); button.setAttribute('aria-pressed', String(active)); }); els.toolKind.textContent = tool.kind; els.toolTitle.textContent = tool.title; els.toolBadge.textContent = tool.badge; els.inputLabel.textContent = tool.label; els.input.value = ''; els.input.placeholder = tool.placeholder; els.languageControl.classList.toggle('is-hidden', !tool.usesLanguage); els.redactionControl.classList.toggle('is-hidden', toolName !== 'redact'); els.status.textContent = ''; renderTrace([]); } async function submitPasscode(event) { event.preventDefault(); els.gateStatus.textContent = 'Signing in…'; try { const data = await postJson('api/session.php', { email: els.loginEmail.value.trim(), password: els.loginPassword.value, }); if (!data.ok) { throw new Error(data.error?.message || 'Credentials were not accepted.'); } state.authenticated = true; els.gate.classList.add('is-hidden'); els.app.classList.remove('is-hidden'); els.loginPassword.value = ''; els.healthPill.textContent = 'Session active'; checkHealth(); els.input.focus(); } catch (error) { els.gateStatus.textContent = error.message; } } async function runTool(event) { event.preventDefault(); const tool = tools[state.activeTool]; const text = els.input.value.trim(); if (!text) { els.status.textContent = 'Add text before running the tool.'; els.input.focus(); return; } const payload = { [tool.payloadKey]: text }; if (tool.usesLanguage) { payload.language = currentLanguage(); } if (state.activeTool === 'search') { payload.limit = 7; } if (state.activeTool === 'redact') { payload.mode = currentRedactionMode(); } setBusy(true); renderTrace([ { label: 'Query interpretation', detail: 'Preparing request.', status: 'running' }, ]); try { const data = await postJson(tool.endpoint, payload); if (!data.ok) { throw new Error(data.error?.message || 'Tool request failed.'); } renderResults(data); renderTrace(data.trace || []); els.status.textContent = `Done in ${data.latency_ms || 0} ms.`; } catch (error) { els.status.textContent = error.message; renderTrace([ { label: 'Tool error', detail: error.message, status: 'warning' }, ]); } finally { setBusy(false); } } async function checkHealth() { els.healthPill.textContent = 'Checking...'; try { const response = await fetch('api/health.php', { method: 'GET', headers: { Accept: 'application/json' }, credentials: 'same-origin', }); const data = await response.json(); els.healthPill.textContent = data.ok ? 'Healthy' : 'Needs config'; els.healthPill.classList.toggle('is-warning', !data.ok); if (!data.ok && data.checks) { renderHealth(data); } } catch (error) { els.healthPill.textContent = 'Health failed'; els.healthPill.classList.add('is-warning'); } } async function postJson(url, payload) { const response = await fetch(url, { method: 'POST', headers: { Accept: 'application/json', 'Content-Type': 'application/json', }, credentials: 'same-origin', body: JSON.stringify(payload), }); const data = await response.json().catch(() => ({})); if (!response.ok) { throw new Error(data.error?.message || `Request failed with HTTP ${response.status}.`); } return data; } function setBusy(isBusy) { const button = document.querySelector('#runButton'); button.disabled = isBusy; button.textContent = isBusy ? 'Running...' : 'Run Tool'; } function currentLanguage() { return document.querySelector('input[name="language"]:checked')?.value || 'en'; } function currentRedactionMode() { return document.querySelector('input[name="redactionMode"]:checked')?.value || 'standard'; } function renderResults(data) { const sections = []; sections.push(sectionHtml('What We Found', renderMainFinding(data))); sections.push(sectionHtml('Evidence Trail', renderEvidence(data))); sections.push(sectionHtml('What Remains Uncertain', renderListish(data.what_remains_uncertain))); sections.push(sectionHtml('Next Practical Step', `

${escapeHtml(data.next_practical_step || 'Review the evidence trail.')}

`)); if (data.disclaimer) { sections.push(`

${escapeHtml(data.disclaimer)}

`); } els.results.innerHTML = sections.join(''); } function renderMainFinding(data) { if (data.tool === 'ask') { return `

${escapeHtml(data.answer || data.what_we_found || '')}

`; } if (data.tool === 'redact') { return `
${escapeHtml(data.redacted_text || '')}
${renderEntityCounts(data.entity_counts)}`; } if (data.tool === 'timeline') { return `

${escapeHtml(data.what_we_found || '')}

${renderTimeline(data.events || [])}`; } if (data.tool === 'summarize') { return [ `

${escapeHtml(data.what_we_found || '')}

`, detailList('Key Facts', data.key_facts), detailList('Dates', data.dates), detailList('Parties', data.parties), detailList('Legal References Detected', data.legal_references_detected), ].join(''); } if (data.tool === 'search') { return `

${escapeHtml(data.what_we_found || '')}

`; } return `

${escapeHtml(data.what_we_found || '')}

`; } function renderEvidence(data) { const items = data.evidence_trail || data.sources || data.hits || []; if (!items.length) { return '

No evidence trail was available for this request.

'; } return `
${items.map(renderEvidenceItem).join('')}
`; } function renderEvidenceItem(item) { const title = item.title || item.citation || 'Source'; const body = item.excerpt || item.why_it_matters || item.citation || ''; const meta = [ item.package_or_corpus, item.section, item.score !== undefined && item.score !== null ? `score ${item.score}` : '', ].filter(Boolean).join(' · '); return `

${escapeHtml(title)}

${meta ? `

${escapeHtml(meta)}

` : ''}

${escapeHtml(body)}

`; } function renderTimeline(events) { if (!events.length) { return '

No events were identified.

'; } return `
    ${events.map((event) => `
  1. ${escapeHtml(event.date || 'unknown')} ${escapeHtml(event.actor || 'unknown actor')}

    ${escapeHtml(event.event || '')}

    ${event.source_excerpt ? `${escapeHtml(event.source_excerpt)}` : ''}
  2. `).join('')}
`; } function renderEntityCounts(counts = {}) { const entries = Object.entries(counts).filter(([, count]) => Number(count) > 0); if (!entries.length) { return '

No deterministic sensitive categories detected.

'; } return ``; } function detailList(title, values = []) { if (!Array.isArray(values) || !values.length) { return ''; } return `

${escapeHtml(title)}

`; } function renderListish(value) { if (Array.isArray(value)) { if (!value.length) { return '

No uncertainty listed.

'; } return ``; } return `

${escapeHtml(value || 'No uncertainty listed.')}

`; } function sectionHtml(title, content) { return `

${escapeHtml(title)}

${content}
`; } function renderTrace(trace) { if (!trace.length) { els.traceList.innerHTML = `
  • Waiting

    Run a tool to see interpretation, retrieval, confidence, uncertainty, and next step.

  • `; return; } els.traceList.innerHTML = trace.map((item) => `
  • ${escapeHtml(item.label || 'Step')}

    ${escapeHtml(item.detail || '')}

  • `).join(''); } function renderHealth(data) { const checks = Object.entries(data.checks || {}).map(([name, check]) => ({ label: name.replaceAll('_', ' '), detail: check.detail || '', status: check.ok ? 'complete' : 'warning', })); renderTrace(checks); } function escapeHtml(value) { return String(value ?? '') .replaceAll('&', '&') .replaceAll('<', '<') .replaceAll('>', '>') .replaceAll('"', '"') .replaceAll("'", '''); }