feat(tools): persona selector across standalone tools + dashboard chat

Wire the legal-domain persona picker into corpus, deep-research, korrespond and
the dashboard chat. Each endpoint reads the chosen profile, resolves its packages
against client 57, and scopes retrieval via package_ids (falling back to family
when omitted). New dashboard tenants now subscribe to all DBN domain packages so
persona switching survives the subscription intersection.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-06-01 23:03:31 +02:00
parent 662fbf7d6d
commit d156f8cf6b
13 changed files with 251 additions and 20 deletions
+32
View File
@@ -7,6 +7,7 @@
let uploadFiles = [];
let lastResult = null;
let branchContext = null;
let persona = '';
const SLICE_DEFS = [
{ id: 'family_core', label: 'Family Law Core' },
@@ -42,6 +43,8 @@
slices: Array.from(document.querySelectorAll('.dr-slice')),
langButtons: Array.from(document.querySelectorAll('#drLangSwitcher .lang-btn')),
engineRadios: Array.from(document.querySelectorAll('input[name="drEngine"]')),
personaControl: document.getElementById('drPersonaControl'),
personaSelect: document.getElementById('drPersonaSelect'),
subQ: document.getElementById('drSubQ'),
subQVal: document.getElementById('drSubQValue'),
chunkLimit: document.getElementById('drChunkLimit'),
@@ -79,6 +82,7 @@
bindUpload();
bindModal();
bindBranch();
loadPersonas();
els.form.addEventListener('submit', onSubmit);
els.results.addEventListener('click', (e) => {
const btn = e.target.closest('.dr-branch-btn');
@@ -325,6 +329,7 @@
language: lang,
controls: getControls(),
};
if (persona) payload.profile = persona;
const _drDocIds = (document.getElementById('docPickerIds')?.value || '').split(',').map(Number).filter(Boolean);
if (_drDocIds.length) payload.doc_ids = _drDocIds;
if (branchContext) {
@@ -644,6 +649,33 @@
</div>`;
}
async function loadPersonas() {
if (!els.personaSelect) return;
try {
const r = await fetch('api/personas.php', { credentials: 'same-origin', headers: { Accept: 'application/json' } });
const data = await r.json().catch(() => ({}));
if (!r.ok || data.ok !== true || !Array.isArray(data.personas) || !data.personas.length) return;
const fallback = data.default_persona || 'family';
els.personaSelect.innerHTML = '';
data.personas.forEach((p) => {
const opt = document.createElement('option');
opt.value = p.slug;
opt.textContent = p.name || p.slug;
els.personaSelect.appendChild(opt);
});
const saved = sessionStorage.getItem('dbnPersona');
const initial = (saved && data.personas.some((p) => p.slug === saved)) ? saved
: (data.personas.some((p) => p.slug === fallback) ? fallback : data.personas[0].slug);
persona = initial;
els.personaSelect.value = initial;
els.personaSelect.addEventListener('change', () => {
persona = els.personaSelect.value;
sessionStorage.setItem('dbnPersona', persona);
});
els.personaControl?.classList.remove('is-hidden');
} catch (_) { /* personas are optional UI sugar; ignore failures */ }
}
function bindBranch() {
if (!els.branchClear) return;
els.branchClear.addEventListener('click', clearBranch);