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
@@ -12,6 +12,7 @@
let lastClassify = null;
let lastFinal = null;
let pendingClarifications = {};
let persona = '';
const LANG_LABELS = { en: 'English', no: 'Norsk', uk: 'Українська', pl: 'Polski' };
@@ -116,6 +117,8 @@
clarifyList: document.getElementById('korrClarifyList'),
clarifyContinue:document.getElementById('korrClarifyContinue'),
clarifyForce: document.getElementById('korrClarifyForce'),
personaControl: document.getElementById('korrPersonaControl'),
personaSelect: document.getElementById('korrPersonaSelect'),
});
if (!els.form) return;
@@ -124,6 +127,7 @@
bindGoalChips();
bindUpload();
bindClarify();
loadPersonas();
applyStaticI18n();
els.form.addEventListener('submit', (e) => { e.preventDefault(); runRequest(false); });
});
@@ -279,9 +283,37 @@
engine: (document.querySelector('[name="korrEngine"]:checked')?.value ?? 'azure_mini'),
};
if (korrDocIds.length) payload.doc_ids = korrDocIds;
if (persona) payload.profile = persona;
return payload;
}
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 */ }
}
async function runRequest(forceDraft) {
const payload = buildPayload(forceDraft);
if (!payload.recipient_body) {