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:
+45
-1
@@ -77,6 +77,10 @@ require_once __DIR__ . '/includes/layout.php';
|
||||
<button class="mode-pill" data-lang="uk" type="button">UK</button>
|
||||
<button class="mode-pill" data-lang="pl" type="button">PL</button>
|
||||
</div>
|
||||
<label class="corpus-persona is-hidden" id="corpusPersonaControl" for="corpusPersonaSelect" title="Legal domain — scopes Hybrid search">
|
||||
<span class="corpus-persona__label">Domain</span>
|
||||
<select id="corpusPersonaSelect" class="drill-sort-select" aria-label="Legal domain persona"></select>
|
||||
</label>
|
||||
</div>
|
||||
<div class="search-cats" role="group" aria-label="Category filter" id="searchCatPills">
|
||||
<button class="mode-pill is-active" data-cat="" type="button">All</button>
|
||||
@@ -743,6 +747,7 @@ require_once __DIR__ . '/includes/layout.php';
|
||||
let searchMode = 'hybrid';
|
||||
let searchLang = 'en';
|
||||
let searchCat = '';
|
||||
let searchPersona = '';
|
||||
|
||||
document.querySelectorAll('.search-modes .mode-pill').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
@@ -775,6 +780,40 @@ require_once __DIR__ . '/includes/layout.php';
|
||||
const searchBtn = document.getElementById('corpusSearchBtn');
|
||||
const searchResults = document.getElementById('corpusSearchResults');
|
||||
|
||||
// ── Persona (legal domain) selector — scopes Hybrid search ──────────────────
|
||||
const personaControl = document.getElementById('corpusPersonaControl');
|
||||
const personaSelect = document.getElementById('corpusPersonaSelect');
|
||||
|
||||
async function loadPersonas() {
|
||||
if (!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';
|
||||
personaSelect.innerHTML = '';
|
||||
for (const p of data.personas) {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = p.slug;
|
||||
opt.textContent = p.name || p.slug;
|
||||
personaSelect.appendChild(opt);
|
||||
}
|
||||
const hashPersona = new URLSearchParams(location.hash.slice(1)).get('persona');
|
||||
const saved = hashPersona || 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);
|
||||
searchPersona = initial;
|
||||
personaSelect.value = initial;
|
||||
personaSelect.addEventListener('change', () => {
|
||||
searchPersona = personaSelect.value;
|
||||
sessionStorage.setItem('dbnPersona', searchPersona);
|
||||
pushHash();
|
||||
});
|
||||
personaControl?.classList.remove('is-hidden');
|
||||
} catch (_) { /* personas are optional UI sugar; ignore failures */ }
|
||||
}
|
||||
loadPersonas();
|
||||
|
||||
function runSearch() {
|
||||
const q = searchInput.value.trim();
|
||||
if (q.length < 3) {
|
||||
@@ -792,7 +831,7 @@ require_once __DIR__ . '/includes/layout.php';
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ query: q, mode: searchMode, language: searchLang, limit: 8, category: searchCat || null }),
|
||||
body: JSON.stringify({ query: q, mode: searchMode, language: searchLang, limit: 8, category: searchCat || null, profile: searchPersona || null }),
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
@@ -872,6 +911,7 @@ require_once __DIR__ . '/includes/layout.php';
|
||||
if (searchMode !== 'hybrid') p.set('mode', searchMode);
|
||||
if (searchLang !== 'en') p.set('lang', searchLang);
|
||||
if (searchCat) p.set('cat', searchCat);
|
||||
if (searchPersona && searchPersona !== 'family') p.set('persona', searchPersona);
|
||||
if (drillPanel && !drillPanel.hidden) {
|
||||
if (drillState.category) p.set('drill', drillState.category);
|
||||
if (drillState.sourceName) p.set('drillsrc', drillState.sourceName);
|
||||
@@ -901,6 +941,10 @@ require_once __DIR__ . '/includes/layout.php';
|
||||
searchCat = p.get('cat');
|
||||
activatePill('#searchCatPills .mode-pill', 'cat', searchCat);
|
||||
}
|
||||
if (p.has('persona')) {
|
||||
searchPersona = p.get('persona');
|
||||
if (personaSelect) personaSelect.value = searchPersona;
|
||||
}
|
||||
if (p.has('drill')) openDrillByCategory(p.get('drill'));
|
||||
if (p.has('drillsrc')) openDrillBySource(p.get('drillsrc'));
|
||||
if (p.has('q') && searchInput) {
|
||||
|
||||
Reference in New Issue
Block a user