Add My Docs picker to deep-research, advocate, barnevernet, korrespond, citations

- PHP: Add docPickerSection (button + chips + hidden input) to all 5 tool pages
- JS: Send doc_ids in payload for deep-research, advocate, barnevernet, korrespond
- Backend: Inject selected corpus doc content into paste_text/narrative/notes via dbnToolsInjectDocContent
- Citations: Add upload zone (file → api/extract.php → textarea) + paste textarea with live Norwegian legal reference extraction (regex) + ref chips → title search; doc picker populates titleInput via MutationObserver

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-24 13:04:45 +02:00
parent 88555eb8a7
commit bffc714541
13 changed files with 214 additions and 13 deletions
+2
View File
@@ -393,6 +393,8 @@
advocate_role: advocateRole,
use_my_case: (typeof window.dbnGetUseMyCase === 'function') ? window.dbnGetUseMyCase() : false,
};
const _advDocIds = (document.getElementById('docPickerIds')?.value || '').split(',').map(Number).filter(Boolean);
if (_advDocIds.length) payload.doc_ids = _advDocIds;
if (branchContext) {
payload.prior_context = branchContext;
payload.branch_notes = (els.branchNotes ? els.branchNotes.value : '').trim();
+2
View File
@@ -410,6 +410,8 @@
additional_notes: additionalNotes,
use_my_case: (typeof window.dbnGetUseMyCase === 'function') ? window.dbnGetUseMyCase() : false,
};
const _bvjDocIds = (document.getElementById('docPickerIds')?.value || '').split(',').map(Number).filter(Boolean);
if (_bvjDocIds.length) payload.doc_ids = _bvjDocIds;
if (branchContext) {
payload.prior_context = branchContext;
+122
View File
@@ -115,6 +115,128 @@
titleInput.addEventListener('blur', () => setTimeout(hideAc, 150));
document.addEventListener('keydown', (e) => { if (e.key === 'Escape') hideAc(); });
// ── Legal reference extraction ─────────────────────────────────────────────
const REF_PATTERNS = [
/\bLOV-\d{4}-\d{2}-\d{2}-\d+\b/g,
/\bFOR-\d{4}-\d{2}-\d{2}-\d+\b/g,
/\bHR-\d{4}-\d{3,5}-[AUS]\b/g,
/\bLB-\d{4}-\d+\b/g,
/\bLA-\d{4}-\d+\b/g,
/\bLE-\d{4}-\d+\b/g,
/\bLF-\d{4}-\d+\b/g,
/\bLG-\d{4}-\d+\b/g,
/\bRt\.\s*\d{4}\s+\d+\b/g,
];
const refChipsEl = document.getElementById('citRefChips');
function extractRefs(text) {
const found = new Set();
REF_PATTERNS.forEach((rx) => {
const matches = text.match(new RegExp(rx.source, rx.flags)) || [];
matches.forEach((m) => found.add(m.replace(/\s+/g, ' ').trim()));
});
return Array.from(found).slice(0, 20);
}
function renderRefChips(refs) {
if (!refChipsEl) return;
refChipsEl.innerHTML = refs.map((ref) => {
const safe = ref.replace(/"/g, '&quot;');
return `<button type="button" class="doc-chip" style="cursor:pointer" data-ref="${safe}">`
+ `<span class="doc-chip__label">${ref}</span>`
+ `</button>`;
}).join('');
refChipsEl.querySelectorAll('.doc-chip').forEach((btn) => {
btn.addEventListener('click', () => {
titleInput.value = btn.dataset.ref;
docIdInput.value = '';
fetchAc(btn.dataset.ref);
titleInput.focus();
});
});
}
const citPasteInput = document.getElementById('citPasteInput');
if (citPasteInput) {
citPasteInput.addEventListener('input', debounce(() => {
const refs = extractRefs(citPasteInput.value);
renderRefChips(refs);
}, 400));
}
// ── File upload → extract text → populate paste area ─────────────────────
const citUploadZone = document.getElementById('citUploadZone');
const citUploadInput = document.getElementById('citUploadInput');
const citUploadPrompt = document.getElementById('citUploadPrompt');
const citUploadFileInfo = document.getElementById('citUploadFileInfo');
const citUploadFileList = document.getElementById('citUploadFileList');
const citUploadClear = document.getElementById('citUploadClear');
function handleCitFile(file) {
if (!file) return;
citUploadFileList.innerHTML = `<li><span class="upload-filename">${esc(file.name)}</span> <em class="upload-chars">Extracting…</em></li>`;
citUploadPrompt.classList.add('is-hidden');
citUploadFileInfo.classList.remove('is-hidden');
const fd = new FormData();
fd.append('file', file);
fetch('api/extract.php', { method: 'POST', body: fd, credentials: 'same-origin' })
.then((r) => r.json())
.then((data) => {
const text = data.text || '';
if (citPasteInput) {
citPasteInput.value = text.slice(0, 8000);
citPasteInput.dispatchEvent(new Event('input'));
}
const chars = data.chars || text.length;
citUploadFileList.innerHTML = `<li><span class="upload-filename">${esc(file.name)}</span> <span class="upload-chars">${chars.toLocaleString()} chars extracted</span></li>`;
})
.catch(() => {
citUploadFileList.innerHTML = `<li><span class="upload-filename">${esc(file.name)}</span> <em class="upload-chars" style="color:var(--coral)">Extraction failed</em></li>`;
});
}
if (citUploadInput) {
citUploadInput.addEventListener('change', () => handleCitFile(citUploadInput.files[0]));
}
if (citUploadZone) {
citUploadZone.addEventListener('dragover', (e) => { e.preventDefault(); citUploadZone.classList.add('is-dragover'); });
citUploadZone.addEventListener('dragleave', () => citUploadZone.classList.remove('is-dragover'));
citUploadZone.addEventListener('drop', (e) => {
e.preventDefault();
citUploadZone.classList.remove('is-dragover');
const file = e.dataTransfer?.files?.[0];
if (file) handleCitFile(file);
});
}
if (citUploadClear) {
citUploadClear.addEventListener('click', () => {
if (citUploadInput) citUploadInput.value = '';
if (citUploadFileInfo) citUploadFileInfo.classList.add('is-hidden');
if (citUploadPrompt) citUploadPrompt.classList.remove('is-hidden');
if (citPasteInput) { citPasteInput.value = ''; renderRefChips([]); }
});
}
// ── Doc picker → populate title input ─────────────────────────────────────
// doc-picker.js handles the modal; we hook the confirm callback by listening
// for changes to the hidden docPickerIds input and reading the chip labels.
const docPickerChipsEl = document.getElementById('docPickerChips');
if (docPickerChipsEl) {
const observer = new MutationObserver(() => {
const firstChip = docPickerChipsEl.querySelector('.doc-chip__label');
if (firstChip && firstChip.textContent.trim()) {
titleInput.value = firstChip.textContent.trim();
docIdInput.value = '';
fetchAc(titleInput.value);
}
});
observer.observe(docPickerChipsEl, { childList: true, subtree: true });
}
// ── Action radios + depth slider ──────────────────────────────────────────
document.querySelectorAll('input[name="citAction"]').forEach(r => {
+2
View File
@@ -325,6 +325,8 @@
language: lang,
controls: getControls(),
};
const _drDocIds = (document.getElementById('docPickerIds')?.value || '').split(',').map(Number).filter(Boolean);
if (_drDocIds.length) payload.doc_ids = _drDocIds;
if (branchContext) {
payload.prior_context = branchContext;
payload.branch_notes = (els.branchNotes ? els.branchNotes.value : '').trim();
+4 -1
View File
@@ -260,7 +260,8 @@
function buildPayload(forceDraft) {
const deadlines = [];
if (els.deadline.value.trim()) deadlines.push(els.deadline.value.trim());
return {
const korrDocIds = (document.getElementById('docPickerIds')?.value || '').split(',').map(Number).filter(Boolean);
const payload = {
mode: getRadio(els.modeRadios) || 'initiate',
recipient_body: els.bodySelect.value || 'other',
output_type: getRadio(els.outputRadios) || 'email',
@@ -276,6 +277,8 @@
force_draft: !!forceDraft,
use_my_case: (typeof window.dbnGetUseMyCase === 'function') ? window.dbnGetUseMyCase() : false,
};
if (korrDocIds.length) payload.doc_ids = korrDocIds;
return payload;
}
async function runRequest(forceDraft) {