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:
@@ -153,6 +153,15 @@ require_once __DIR__ . '/includes/layout.php';
|
||||
</div>
|
||||
</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">
|
||||
<input type="file" id="advUploadInput" multiple accept=".pdf,.docx,.txt" aria-label="Choose files">
|
||||
<div id="advUploadPrompt" class="upload-prompt">
|
||||
|
||||
+6
-4
@@ -8,7 +8,6 @@ require_once __DIR__ . '/../includes/ToolModels.php';
|
||||
dbnToolsRequireMethod('POST');
|
||||
dbnToolsRequireAuth();
|
||||
$ftUid = dbnToolsFreeTierCheck('barnevernet');
|
||||
$ftRemaining = dbnToolsFreeTierDeduct($ftUid, 'barnevernet');
|
||||
|
||||
@ini_set('output_buffering', '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('Cache-Control: no-store');
|
||||
header('X-Accel-Buffering: no');
|
||||
if ($ftRemaining >= 0) { header('X-Credits-Remaining: ' . $ftRemaining); }
|
||||
|
||||
$language = 'en';
|
||||
$startTime = microtime(true);
|
||||
@@ -58,12 +56,12 @@ try {
|
||||
$engine = ToolModels::engineForUser($ftUid, (string)($input['engine'] ?? 'azure_mini'));
|
||||
$sliceInput = $input['slices'] ?? [];
|
||||
$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) {
|
||||
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');
|
||||
}
|
||||
|
||||
@@ -150,6 +148,10 @@ try {
|
||||
'bvj_doc_type' => $result['doc_meta']['doc_type'] ?? null,
|
||||
]);
|
||||
|
||||
$ftRemaining = dbnToolsFreeTierDeduct($ftUid, 'barnevernet');
|
||||
if ($ftRemaining >= 0) {
|
||||
$result['balance'] = $ftRemaining;
|
||||
}
|
||||
|
||||
$emit('final', ['result' => $result]);
|
||||
|
||||
|
||||
+10
-5
@@ -7,8 +7,7 @@ require_once __DIR__ . '/../includes/ToolModels.php';
|
||||
|
||||
dbnToolsRequireMethod('POST');
|
||||
dbnToolsRequireAuth();
|
||||
$ftUid = dbnToolsFreeTierCheck('deep-research');
|
||||
$ftRemaining = dbnToolsFreeTierDeduct($ftUid, 'deep-research');
|
||||
$ftUid = 0;
|
||||
|
||||
// Stream-friendly response — defeat output buffering so the user's browser
|
||||
// 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('Cache-Control: no-store');
|
||||
header('X-Accel-Buffering: no');
|
||||
if ($ftRemaining >= 0) { header('X-Credits-Remaining: ' . $ftRemaining); }
|
||||
|
||||
$language = 'en';
|
||||
$chargeTool = 'deep-research';
|
||||
$startTime = microtime(true);
|
||||
|
||||
$emit = function (string $event, array $payload = []) use ($startTime): void {
|
||||
@@ -58,14 +57,16 @@ try {
|
||||
|
||||
$language = dbnToolsNormalizeLanguage($input['language'] ?? 'en');
|
||||
$seedQuery = trim((string)($input['query'] ?? ''));
|
||||
$pastedText = trim((string)($input['paste_text'] ?? ''));
|
||||
$pastedText = dbnToolsInjectDocContent($input, trim((string)($input['paste_text'] ?? '')));
|
||||
$sliceInput = $input['slices'] ?? [];
|
||||
$engine = ToolModels::engineForUser($ftUid, (string)($input['engine'] ?? 'azure_mini'));
|
||||
$controls = is_array($input['controls'] ?? null) ? $input['controls'] : [];
|
||||
$advocateRole = trim((string)($input['advocate_role'] ?? ''));
|
||||
if (mb_strlen($advocateRole, 'UTF-8') > 200) {
|
||||
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;
|
||||
$branchNotes = mb_substr(trim((string)($input['branch_notes'] ?? '')), 0, 1000, 'UTF-8');
|
||||
$subQsOverride = is_array($input['sub_questions_override'] ?? null) ? $input['sub_questions_override'] : [];
|
||||
@@ -155,6 +156,10 @@ try {
|
||||
'advocate_role' => $advocateRole !== '' ? $advocateRole : null,
|
||||
]);
|
||||
|
||||
$ftRemaining = dbnToolsFreeTierDeduct($ftUid, $chargeTool);
|
||||
if ($ftRemaining >= 0) {
|
||||
$result['balance'] = $ftRemaining;
|
||||
}
|
||||
|
||||
$emit('final', ['result' => $result]);
|
||||
|
||||
|
||||
+4
-3
@@ -80,6 +80,7 @@ try {
|
||||
'clarifications' => is_array($input['clarifications'] ?? null) ? $input['clarifications'] : [],
|
||||
'use_my_case' => !empty($input['use_my_case']),
|
||||
];
|
||||
$intake['narrative'] = dbnToolsInjectDocContent($input, $intake['narrative']);
|
||||
|
||||
$forceDraft = !empty($input['force_draft']);
|
||||
$hasClarif = !empty($intake['clarifications']);
|
||||
@@ -166,14 +167,14 @@ try {
|
||||
$ftUid = dbnToolsFreeTierCheck('korrespond');
|
||||
$ftRemaining = dbnToolsFreeTierDeduct($ftUid, 'korrespond');
|
||||
$creditDeducted = true;
|
||||
if ($ftRemaining >= 0) {
|
||||
header('X-Credits-Remaining: ' . $ftRemaining);
|
||||
}
|
||||
|
||||
// ── Pass 2: retrieve law → draft → self-check → translate ──────────────────
|
||||
$result = $agent->generate($intake, $classify, $emit);
|
||||
$result['ok'] = true;
|
||||
$result['latency_ms'] = (int)round((microtime(true) - $startTime) * 1000);
|
||||
if ($ftRemaining >= 0) {
|
||||
$result['balance'] = $ftRemaining;
|
||||
}
|
||||
|
||||
dbnToolsLogMetadata([
|
||||
'tool' => 'korrespond',
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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, '"');
|
||||
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 => {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -139,6 +139,15 @@ require_once __DIR__ . '/includes/layout.php';
|
||||
</div>
|
||||
</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) -->
|
||||
<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">
|
||||
|
||||
@@ -99,6 +99,32 @@ require_once __DIR__ . '/includes/layout.php';
|
||||
|
||||
<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">⇧</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> — 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">× 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>
|
||||
<div class="cit-search-wrap">
|
||||
<input type="text" id="titleInput" class="corpus-search-input"
|
||||
|
||||
@@ -119,6 +119,15 @@ require_once __DIR__ . '/includes/layout.php';
|
||||
</div>
|
||||
</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">
|
||||
<input type="file" id="drUploadInput" multiple accept=".pdf,.docx,.txt" aria-label="Choose files">
|
||||
<div id="drUploadPrompt" class="upload-prompt">
|
||||
|
||||
@@ -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>
|
||||
</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) -->
|
||||
<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">
|
||||
|
||||
Reference in New Issue
Block a user