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
+9
View File
@@ -153,6 +153,15 @@ require_once __DIR__ . '/includes/layout.php';
</div> </div>
</details> </details>
<div id="docPickerSection" class="doc-picker-section">
<button type="button" id="docPickerBtn" class="doc-picker-btn" aria-haspopup="dialog">
<svg class="doc-picker-btn__icon" width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><rect x="2" y="1" width="9" height="12" rx="1.5" stroke="currentColor" stroke-width="1.4"/><path d="M5 5h5M5 8h3" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/><rect x="7" y="9" width="6" height="5" rx="1" fill="white" stroke="currentColor" stroke-width="1.3"/><path d="M9 11h2M9 12.5h1" stroke="currentColor" stroke-width="1" stroke-linecap="round"/></svg>
<span><?= htmlspecialchars(dbnToolsT('doc_picker_btn', $uiLang)) ?></span>
</button>
<div id="docPickerChips" class="doc-picker-chips" aria-label="Selected documents"></div>
<input type="hidden" id="docPickerIds" name="doc_ids" value="">
</div>
<div class="upload-zone" id="advUploadZone" role="region" aria-label="File upload"> <div class="upload-zone" id="advUploadZone" role="region" aria-label="File upload">
<input type="file" id="advUploadInput" multiple accept=".pdf,.docx,.txt" aria-label="Choose files"> <input type="file" id="advUploadInput" multiple accept=".pdf,.docx,.txt" aria-label="Choose files">
<div id="advUploadPrompt" class="upload-prompt"> <div id="advUploadPrompt" class="upload-prompt">
+6 -4
View File
@@ -8,7 +8,6 @@ require_once __DIR__ . '/../includes/ToolModels.php';
dbnToolsRequireMethod('POST'); dbnToolsRequireMethod('POST');
dbnToolsRequireAuth(); dbnToolsRequireAuth();
$ftUid = dbnToolsFreeTierCheck('barnevernet'); $ftUid = dbnToolsFreeTierCheck('barnevernet');
$ftRemaining = dbnToolsFreeTierDeduct($ftUid, 'barnevernet');
@ini_set('output_buffering', '0'); @ini_set('output_buffering', '0');
@ini_set('zlib.output_compression', '0'); @ini_set('zlib.output_compression', '0');
@@ -19,7 +18,6 @@ ob_implicit_flush(true);
header('Content-Type: application/x-ndjson; charset=utf-8'); header('Content-Type: application/x-ndjson; charset=utf-8');
header('Cache-Control: no-store'); header('Cache-Control: no-store');
header('X-Accel-Buffering: no'); header('X-Accel-Buffering: no');
if ($ftRemaining >= 0) { header('X-Credits-Remaining: ' . $ftRemaining); }
$language = 'en'; $language = 'en';
$startTime = microtime(true); $startTime = microtime(true);
@@ -58,12 +56,12 @@ try {
$engine = ToolModels::engineForUser($ftUid, (string)($input['engine'] ?? 'azure_mini')); $engine = ToolModels::engineForUser($ftUid, (string)($input['engine'] ?? 'azure_mini'));
$sliceInput = $input['slices'] ?? []; $sliceInput = $input['slices'] ?? [];
$controls = is_array($input['controls'] ?? null) ? $input['controls'] : []; $controls = is_array($input['controls'] ?? null) ? $input['controls'] : [];
$additionalNotes = mb_substr(trim((string)($input['additional_notes'] ?? '')), 0, 2000, 'UTF-8'); $additionalNotes = mb_substr(dbnToolsInjectDocContent($input, trim((string)($input['additional_notes'] ?? ''))), 0, 8000, 'UTF-8');
if (mb_strlen($advocateRole, 'UTF-8') > 200) { if (mb_strlen($advocateRole, 'UTF-8') > 200) {
throw new DbnToolsHttpException('advocate_role is too long.', 422, 'advocate_role_too_long'); throw new DbnToolsHttpException('advocate_role is too long.', 422, 'advocate_role_too_long');
} }
if (mb_strlen($additionalNotes, 'UTF-8') > 2000) { if (mb_strlen($additionalNotes, 'UTF-8') > 8000) {
throw new DbnToolsHttpException('additional_notes is too long.', 422, 'notes_too_long'); throw new DbnToolsHttpException('additional_notes is too long.', 422, 'notes_too_long');
} }
@@ -150,6 +148,10 @@ try {
'bvj_doc_type' => $result['doc_meta']['doc_type'] ?? null, 'bvj_doc_type' => $result['doc_meta']['doc_type'] ?? null,
]); ]);
$ftRemaining = dbnToolsFreeTierDeduct($ftUid, 'barnevernet');
if ($ftRemaining >= 0) {
$result['balance'] = $ftRemaining;
}
$emit('final', ['result' => $result]); $emit('final', ['result' => $result]);
+10 -5
View File
@@ -7,8 +7,7 @@ require_once __DIR__ . '/../includes/ToolModels.php';
dbnToolsRequireMethod('POST'); dbnToolsRequireMethod('POST');
dbnToolsRequireAuth(); dbnToolsRequireAuth();
$ftUid = dbnToolsFreeTierCheck('deep-research'); $ftUid = 0;
$ftRemaining = dbnToolsFreeTierDeduct($ftUid, 'deep-research');
// Stream-friendly response — defeat output buffering so the user's browser // Stream-friendly response — defeat output buffering so the user's browser
// receives progress events while the agent runs (can take 60-180s for // receives progress events while the agent runs (can take 60-180s for
@@ -22,9 +21,9 @@ ob_implicit_flush(true);
header('Content-Type: application/x-ndjson; charset=utf-8'); header('Content-Type: application/x-ndjson; charset=utf-8');
header('Cache-Control: no-store'); header('Cache-Control: no-store');
header('X-Accel-Buffering: no'); header('X-Accel-Buffering: no');
if ($ftRemaining >= 0) { header('X-Credits-Remaining: ' . $ftRemaining); }
$language = 'en'; $language = 'en';
$chargeTool = 'deep-research';
$startTime = microtime(true); $startTime = microtime(true);
$emit = function (string $event, array $payload = []) use ($startTime): void { $emit = function (string $event, array $payload = []) use ($startTime): void {
@@ -58,14 +57,16 @@ try {
$language = dbnToolsNormalizeLanguage($input['language'] ?? 'en'); $language = dbnToolsNormalizeLanguage($input['language'] ?? 'en');
$seedQuery = trim((string)($input['query'] ?? '')); $seedQuery = trim((string)($input['query'] ?? ''));
$pastedText = trim((string)($input['paste_text'] ?? '')); $pastedText = dbnToolsInjectDocContent($input, trim((string)($input['paste_text'] ?? '')));
$sliceInput = $input['slices'] ?? []; $sliceInput = $input['slices'] ?? [];
$engine = ToolModels::engineForUser($ftUid, (string)($input['engine'] ?? 'azure_mini'));
$controls = is_array($input['controls'] ?? null) ? $input['controls'] : []; $controls = is_array($input['controls'] ?? null) ? $input['controls'] : [];
$advocateRole = trim((string)($input['advocate_role'] ?? '')); $advocateRole = trim((string)($input['advocate_role'] ?? ''));
if (mb_strlen($advocateRole, 'UTF-8') > 200) { if (mb_strlen($advocateRole, 'UTF-8') > 200) {
throw new DbnToolsHttpException('advocate_role is too long.', 422, 'advocate_role_too_long'); throw new DbnToolsHttpException('advocate_role is too long.', 422, 'advocate_role_too_long');
} }
$chargeTool = $advocateRole !== '' ? 'advocate' : 'deep-research';
$ftUid = dbnToolsFreeTierCheck($chargeTool);
$engine = ToolModels::engineForUser($ftUid, (string)($input['engine'] ?? 'azure_mini'));
$priorContext = is_array($input['prior_context'] ?? null) ? $input['prior_context'] : null; $priorContext = is_array($input['prior_context'] ?? null) ? $input['prior_context'] : null;
$branchNotes = mb_substr(trim((string)($input['branch_notes'] ?? '')), 0, 1000, 'UTF-8'); $branchNotes = mb_substr(trim((string)($input['branch_notes'] ?? '')), 0, 1000, 'UTF-8');
$subQsOverride = is_array($input['sub_questions_override'] ?? null) ? $input['sub_questions_override'] : []; $subQsOverride = is_array($input['sub_questions_override'] ?? null) ? $input['sub_questions_override'] : [];
@@ -155,6 +156,10 @@ try {
'advocate_role' => $advocateRole !== '' ? $advocateRole : null, 'advocate_role' => $advocateRole !== '' ? $advocateRole : null,
]); ]);
$ftRemaining = dbnToolsFreeTierDeduct($ftUid, $chargeTool);
if ($ftRemaining >= 0) {
$result['balance'] = $ftRemaining;
}
$emit('final', ['result' => $result]); $emit('final', ['result' => $result]);
+4 -3
View File
@@ -80,6 +80,7 @@ try {
'clarifications' => is_array($input['clarifications'] ?? null) ? $input['clarifications'] : [], 'clarifications' => is_array($input['clarifications'] ?? null) ? $input['clarifications'] : [],
'use_my_case' => !empty($input['use_my_case']), 'use_my_case' => !empty($input['use_my_case']),
]; ];
$intake['narrative'] = dbnToolsInjectDocContent($input, $intake['narrative']);
$forceDraft = !empty($input['force_draft']); $forceDraft = !empty($input['force_draft']);
$hasClarif = !empty($intake['clarifications']); $hasClarif = !empty($intake['clarifications']);
@@ -166,14 +167,14 @@ try {
$ftUid = dbnToolsFreeTierCheck('korrespond'); $ftUid = dbnToolsFreeTierCheck('korrespond');
$ftRemaining = dbnToolsFreeTierDeduct($ftUid, 'korrespond'); $ftRemaining = dbnToolsFreeTierDeduct($ftUid, 'korrespond');
$creditDeducted = true; $creditDeducted = true;
if ($ftRemaining >= 0) {
header('X-Credits-Remaining: ' . $ftRemaining);
}
// ── Pass 2: retrieve law → draft → self-check → translate ────────────────── // ── Pass 2: retrieve law → draft → self-check → translate ──────────────────
$result = $agent->generate($intake, $classify, $emit); $result = $agent->generate($intake, $classify, $emit);
$result['ok'] = true; $result['ok'] = true;
$result['latency_ms'] = (int)round((microtime(true) - $startTime) * 1000); $result['latency_ms'] = (int)round((microtime(true) - $startTime) * 1000);
if ($ftRemaining >= 0) {
$result['balance'] = $ftRemaining;
}
dbnToolsLogMetadata([ dbnToolsLogMetadata([
'tool' => 'korrespond', 'tool' => 'korrespond',
+2
View File
@@ -393,6 +393,8 @@
advocate_role: advocateRole, advocate_role: advocateRole,
use_my_case: (typeof window.dbnGetUseMyCase === 'function') ? window.dbnGetUseMyCase() : false, 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) { if (branchContext) {
payload.prior_context = branchContext; payload.prior_context = branchContext;
payload.branch_notes = (els.branchNotes ? els.branchNotes.value : '').trim(); payload.branch_notes = (els.branchNotes ? els.branchNotes.value : '').trim();
+2
View File
@@ -410,6 +410,8 @@
additional_notes: additionalNotes, additional_notes: additionalNotes,
use_my_case: (typeof window.dbnGetUseMyCase === 'function') ? window.dbnGetUseMyCase() : false, 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) { if (branchContext) {
payload.prior_context = branchContext; payload.prior_context = branchContext;
+122
View File
@@ -115,6 +115,128 @@
titleInput.addEventListener('blur', () => setTimeout(hideAc, 150)); titleInput.addEventListener('blur', () => setTimeout(hideAc, 150));
document.addEventListener('keydown', (e) => { if (e.key === 'Escape') hideAc(); }); 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 ────────────────────────────────────────── // ── Action radios + depth slider ──────────────────────────────────────────
document.querySelectorAll('input[name="citAction"]').forEach(r => { document.querySelectorAll('input[name="citAction"]').forEach(r => {
+2
View File
@@ -325,6 +325,8 @@
language: lang, language: lang,
controls: getControls(), controls: getControls(),
}; };
const _drDocIds = (document.getElementById('docPickerIds')?.value || '').split(',').map(Number).filter(Boolean);
if (_drDocIds.length) payload.doc_ids = _drDocIds;
if (branchContext) { if (branchContext) {
payload.prior_context = branchContext; payload.prior_context = branchContext;
payload.branch_notes = (els.branchNotes ? els.branchNotes.value : '').trim(); payload.branch_notes = (els.branchNotes ? els.branchNotes.value : '').trim();
+4 -1
View File
@@ -260,7 +260,8 @@
function buildPayload(forceDraft) { function buildPayload(forceDraft) {
const deadlines = []; const deadlines = [];
if (els.deadline.value.trim()) deadlines.push(els.deadline.value.trim()); 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', mode: getRadio(els.modeRadios) || 'initiate',
recipient_body: els.bodySelect.value || 'other', recipient_body: els.bodySelect.value || 'other',
output_type: getRadio(els.outputRadios) || 'email', output_type: getRadio(els.outputRadios) || 'email',
@@ -276,6 +277,8 @@
force_draft: !!forceDraft, force_draft: !!forceDraft,
use_my_case: (typeof window.dbnGetUseMyCase === 'function') ? window.dbnGetUseMyCase() : false, use_my_case: (typeof window.dbnGetUseMyCase === 'function') ? window.dbnGetUseMyCase() : false,
}; };
if (korrDocIds.length) payload.doc_ids = korrDocIds;
return payload;
} }
async function runRequest(forceDraft) { async function runRequest(forceDraft) {
+9
View File
@@ -139,6 +139,15 @@ require_once __DIR__ . '/includes/layout.php';
</div> </div>
</details> </details>
<div id="docPickerSection" class="doc-picker-section">
<button type="button" id="docPickerBtn" class="doc-picker-btn" aria-haspopup="dialog">
<svg class="doc-picker-btn__icon" width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><rect x="2" y="1" width="9" height="12" rx="1.5" stroke="currentColor" stroke-width="1.4"/><path d="M5 5h5M5 8h3" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/><rect x="7" y="9" width="6" height="5" rx="1" fill="white" stroke="currentColor" stroke-width="1.3"/><path d="M9 11h2M9 12.5h1" stroke="currentColor" stroke-width="1" stroke-linecap="round"/></svg>
<span><?= htmlspecialchars(dbnToolsT('doc_picker_btn', $uiLang)) ?></span>
</button>
<div id="docPickerChips" class="doc-picker-chips" aria-label="Selected documents"></div>
<input type="hidden" id="docPickerIds" name="doc_ids" value="">
</div>
<!-- File upload (required) --> <!-- File upload (required) -->
<div class="upload-zone" id="bvjUploadZone" role="region" aria-label="File upload"> <div class="upload-zone" id="bvjUploadZone" role="region" aria-label="File upload">
<input type="file" id="bvjUploadInput" multiple accept=".pdf,.docx,.txt" aria-label="Choose BVJ document files"> <input type="file" id="bvjUploadInput" multiple accept=".pdf,.docx,.txt" aria-label="Choose BVJ document files">
+26
View File
@@ -99,6 +99,32 @@ require_once __DIR__ . '/includes/layout.php';
<form id="citationsForm" class="tool-form" autocomplete="off" novalidate> <form id="citationsForm" class="tool-form" autocomplete="off" novalidate>
<div id="docPickerSection" class="doc-picker-section">
<button type="button" id="docPickerBtn" class="doc-picker-btn" aria-haspopup="dialog">
<svg class="doc-picker-btn__icon" width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><rect x="2" y="1" width="9" height="12" rx="1.5" stroke="currentColor" stroke-width="1.4"/><path d="M5 5h5M5 8h3" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/><rect x="7" y="9" width="6" height="5" rx="1" fill="white" stroke="currentColor" stroke-width="1.3"/><path d="M9 11h2M9 12.5h1" stroke="currentColor" stroke-width="1" stroke-linecap="round"/></svg>
<span><?= htmlspecialchars(dbnToolsT('doc_picker_btn', $uiLang)) ?></span>
</button>
<div id="docPickerChips" class="doc-picker-chips" aria-label="Selected documents"></div>
<input type="hidden" id="docPickerIds" name="doc_ids" value="">
</div>
<div class="upload-zone" id="citUploadZone" role="region" aria-label="File upload">
<input type="file" id="citUploadInput" accept=".pdf,.docx,.txt" aria-label="Choose a document to scan for legal references">
<div id="citUploadPrompt" class="upload-prompt">
<span class="upload-icon" aria-hidden="true">&#8679;</span>
<p>Upload a document to extract legal references, or <label for="citUploadInput" class="upload-browse">browse</label></p>
<p class="upload-hint"><strong>PDF</strong>, <strong>DOCX</strong>, <strong>TXT</strong> &mdash; text extracted locally, never stored</p>
</div>
<div id="citUploadFileInfo" class="upload-file is-hidden">
<ul id="citUploadFileList" class="upload-file-list"></ul>
<button type="button" id="citUploadClear" class="upload-clear">&times; Clear</button>
</div>
</div>
<label class="input-label" for="citPasteInput">Paste document text <small class="control-hint">(optional — to extract legal references)</small></label>
<textarea id="citPasteInput" rows="5" placeholder="Paste a court decision, letter, or legal text. Legal references found in the text will be offered as quick-search links below."></textarea>
<div id="citRefChips" class="doc-picker-chips" style="margin-bottom:0.75rem" aria-label="Detected legal references" aria-live="polite"></div>
<label class="input-label" for="titleInput">Document title or ID</label> <label class="input-label" for="titleInput">Document title or ID</label>
<div class="cit-search-wrap"> <div class="cit-search-wrap">
<input type="text" id="titleInput" class="corpus-search-input" <input type="text" id="titleInput" class="corpus-search-input"
+9
View File
@@ -119,6 +119,15 @@ require_once __DIR__ . '/includes/layout.php';
</div> </div>
</details> </details>
<div id="docPickerSection" class="doc-picker-section">
<button type="button" id="docPickerBtn" class="doc-picker-btn" aria-haspopup="dialog">
<svg class="doc-picker-btn__icon" width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><rect x="2" y="1" width="9" height="12" rx="1.5" stroke="currentColor" stroke-width="1.4"/><path d="M5 5h5M5 8h3" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/><rect x="7" y="9" width="6" height="5" rx="1" fill="white" stroke="currentColor" stroke-width="1.3"/><path d="M9 11h2M9 12.5h1" stroke="currentColor" stroke-width="1" stroke-linecap="round"/></svg>
<span><?= htmlspecialchars(dbnToolsT('doc_picker_btn', $uiLang)) ?></span>
</button>
<div id="docPickerChips" class="doc-picker-chips" aria-label="Selected documents"></div>
<input type="hidden" id="docPickerIds" name="doc_ids" value="">
</div>
<div class="upload-zone" id="drUploadZone" role="region" aria-label="File upload"> <div class="upload-zone" id="drUploadZone" role="region" aria-label="File upload">
<input type="file" id="drUploadInput" multiple accept=".pdf,.docx,.txt" aria-label="Choose files"> <input type="file" id="drUploadInput" multiple accept=".pdf,.docx,.txt" aria-label="Choose files">
<div id="drUploadPrompt" class="upload-prompt"> <div id="drUploadPrompt" class="upload-prompt">
+9
View File
@@ -104,6 +104,15 @@ require_once __DIR__ . '/includes/layout.php';
<button type="button" class="korr-chip" data-goal="Clarify status / timeline of the case">Clarify timeline</button> <button type="button" class="korr-chip" data-goal="Clarify status / timeline of the case">Clarify timeline</button>
</div> </div>
<div id="docPickerSection" class="doc-picker-section">
<button type="button" id="docPickerBtn" class="doc-picker-btn" aria-haspopup="dialog">
<svg class="doc-picker-btn__icon" width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><rect x="2" y="1" width="9" height="12" rx="1.5" stroke="currentColor" stroke-width="1.4"/><path d="M5 5h5M5 8h3" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/><rect x="7" y="9" width="6" height="5" rx="1" fill="white" stroke="currentColor" stroke-width="1.3"/><path d="M9 11h2M9 12.5h1" stroke="currentColor" stroke-width="1" stroke-linecap="round"/></svg>
<span><?= htmlspecialchars(dbnToolsT('doc_picker_btn', $uiLang)) ?></span>
</button>
<div id="docPickerChips" class="doc-picker-chips" aria-label="Selected documents"></div>
<input type="hidden" id="docPickerIds" name="doc_ids" value="">
</div>
<!-- File upload (required for Reply mode) --> <!-- File upload (required for Reply mode) -->
<div class="upload-zone" id="korrUploadZone" role="region" aria-label="File upload"> <div class="upload-zone" id="korrUploadZone" role="region" aria-label="File upload">
<input type="file" id="korrUploadInput" multiple accept=".pdf,.docx,.txt" aria-label="Choose received letter or attachments"> <input type="file" id="korrUploadInput" multiple accept=".pdf,.docx,.txt" aria-label="Choose received letter or attachments">