feat(tools): persona-driven multi-domain corpus + model routing
Generalize the family-locked legal tools into caveauAI persona profiles (client 57 chat profiles, resolved in-process via the chat_profiles bridge). Each tool accepts an optional `profile` slug that scopes the corpus package(s), search method, system prompt and synthesis model; omitting it falls back to the family-legal package so existing behaviour is unchanged. - dbnToolsResolvePersona / dbnToolsListPersonas / dbnToolsBootChatProfiles in bootstrap.php; new api/personas.php + dbn.list_personas MCP tool. - LegalTools search/ask/corpusContextForSummarize and the BvjAnalyzer / LegalAnalysis / translate paths take the persona's packages + prompt + model. - Persona <select> on ask/search/summarize (populated from api/personas.php). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -894,6 +894,7 @@ const tools = {
|
||||
payloadKey: 'question',
|
||||
placeholder: 'Example: What evidence is needed before asking for changes in custody arrangements?',
|
||||
usesLanguage: true,
|
||||
usesPersona: true,
|
||||
badge: 'family-legal',
|
||||
},
|
||||
search: {
|
||||
@@ -904,6 +905,7 @@ const tools = {
|
||||
payloadKey: 'query',
|
||||
placeholder: 'Example: barnets beste samvær foreldreansvar',
|
||||
usesLanguage: true,
|
||||
usesPersona: true,
|
||||
badge: 'family-legal',
|
||||
},
|
||||
summarize: {
|
||||
@@ -914,6 +916,7 @@ const tools = {
|
||||
payloadKey: 'text',
|
||||
placeholder: 'Paste a case note, letter, or excerpt.',
|
||||
usesLanguage: true,
|
||||
usesPersona: true,
|
||||
badge: 'process-and-forget',
|
||||
},
|
||||
timeline: {
|
||||
@@ -980,6 +983,8 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
uploadClear: document.querySelector('#uploadClear'),
|
||||
aliasSection: document.querySelector('#aliasSection'),
|
||||
corpusScopeControl: document.querySelector('#corpusScopeControl'),
|
||||
personaControl: document.querySelector('#personaControl'),
|
||||
personaSelect: document.querySelector('#personaSelect'),
|
||||
addAliasRow: document.querySelector('#addAliasRow'),
|
||||
aliasRows: document.querySelector('#aliasRows'),
|
||||
audioZone: document.querySelector('#audioZone'),
|
||||
@@ -1057,6 +1062,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
if (state.authenticated) {
|
||||
checkHealth();
|
||||
if (els.personaSelect) loadPersonas();
|
||||
} else {
|
||||
els.loginEmail?.focus();
|
||||
}
|
||||
@@ -1084,6 +1090,7 @@ function setTool(toolName) {
|
||||
els.input.placeholder = tool.placeholder;
|
||||
}
|
||||
els.languageControl.classList.toggle('is-hidden', !tool.usesLanguage);
|
||||
els.personaControl?.classList.toggle('is-hidden', !tool.usesPersona || !personaState.options.length);
|
||||
els.corpusScopeControl?.classList.toggle('is-hidden', toolName !== 'search');
|
||||
els.redactionControl.classList.toggle('is-hidden', toolName !== 'redact');
|
||||
els.uploadZone.classList.toggle('is-hidden', toolName !== 'redact' && toolName !== 'timeline');
|
||||
@@ -1154,6 +1161,10 @@ async function runTool(event) {
|
||||
if (tool.usesLanguage) {
|
||||
payload.language = currentLanguage();
|
||||
}
|
||||
if (tool.usesPersona) {
|
||||
const profile = currentPersona();
|
||||
if (profile) payload.profile = profile;
|
||||
}
|
||||
if (state.activeTool === 'search') {
|
||||
payload.limit = 7;
|
||||
payload.corpus_scope = currentCorpusScope();
|
||||
@@ -1530,6 +1541,47 @@ function currentCorpusScope() {
|
||||
return document.querySelector('input[name="corpusScope"]:checked')?.value || 'both';
|
||||
}
|
||||
|
||||
const personaState = { options: [], default: 'family' };
|
||||
|
||||
function currentPersona() {
|
||||
return els.personaSelect?.value || personaState.default || '';
|
||||
}
|
||||
|
||||
async function loadPersonas() {
|
||||
if (!els.personaSelect) return;
|
||||
try {
|
||||
const response = await fetch('api/personas.php', {
|
||||
method: 'GET',
|
||||
headers: { Accept: 'application/json' },
|
||||
credentials: 'same-origin',
|
||||
});
|
||||
const data = await response.json().catch(() => ({}));
|
||||
if (!response.ok || !data || data.ok !== true || !Array.isArray(data.personas) || !data.personas.length) return;
|
||||
personaState.options = data.personas;
|
||||
personaState.default = data.default_persona || 'family';
|
||||
const saved = sessionStorage.getItem('dbnPersona');
|
||||
els.personaSelect.innerHTML = '';
|
||||
for (const p of data.personas) {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = p.slug;
|
||||
opt.textContent = p.name || p.slug;
|
||||
els.personaSelect.appendChild(opt);
|
||||
}
|
||||
const initial = (saved && data.personas.some((p) => p.slug === saved))
|
||||
? saved
|
||||
: (data.personas.some((p) => p.slug === personaState.default) ? personaState.default : data.personas[0].slug);
|
||||
els.personaSelect.value = initial;
|
||||
els.personaSelect.addEventListener('change', () => {
|
||||
sessionStorage.setItem('dbnPersona', els.personaSelect.value);
|
||||
});
|
||||
if (tools[state.activeTool]?.usesPersona) {
|
||||
els.personaControl?.classList.remove('is-hidden');
|
||||
}
|
||||
} catch (_) {
|
||||
// Personas are optional UI sugar; ignore failures (e.g. pre-seed environments).
|
||||
}
|
||||
}
|
||||
|
||||
function currentRedactionMode() {
|
||||
return document.querySelector('input[name="redactionMode"]:checked')?.value || 'standard';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user