Add Case Advocate tab — partisan brief grounded in Norwegian law

New /advocate.php tab: user selects who they represent (biological
father, mother, foster carer, CWS, etc.) and the agent takes their
side entirely. Adversarial sub-questions target supporting Lovdata
statutes + ECHR precedents; synthesis returns client_strengths[] and
opposing_weaknesses[] alongside the advocate brief.

- DeepResearchAgent: add advocateRole param to run(), interpretSeed(),
  expandQueries(), synthesise(). Neutral path unchanged (empty string).
- api/deep-research.php: extract + validate advocate_role from payload;
  telemetry logs tool='advocate' vs 'deep_research'.
- advocate.php: new page with role dropdown (presets + custom), same
  corpus slices/engine/controls/upload zone as deep research.
- assets/js/advocate.js: page-scoped JS; renders advocate banner,
  client strengths card (teal), advocate brief, opposing weaknesses
  card (amber), sub-Q cards, sources, uncertainty, next step.
- assets/css/tools.css: append .adv-* rules (~120 lines).
- includes/layout.php: add Advocate nav tab between Deep research and
  Summarize.
- index.php: add Advocate cap-card tile.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-15 12:26:05 +02:00
parent 85a6bc8134
commit 640778454f
7 changed files with 1154 additions and 26 deletions
+20 -14
View File
@@ -52,12 +52,16 @@ try {
}
}
$language = dbnToolsNormalizeLanguage($input['language'] ?? 'en');
$seedQuery = trim((string)($input['query'] ?? ''));
$pastedText = trim((string)($input['paste_text'] ?? ''));
$sliceInput = $input['slices'] ?? [];
$engine = (string)($input['engine'] ?? 'azure_mini');
$controls = is_array($input['controls'] ?? null) ? $input['controls'] : [];
$language = dbnToolsNormalizeLanguage($input['language'] ?? 'en');
$seedQuery = trim((string)($input['query'] ?? ''));
$pastedText = trim((string)($input['paste_text'] ?? ''));
$sliceInput = $input['slices'] ?? [];
$engine = (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');
}
if (mb_strlen($seedQuery, 'UTF-8') > 4000) {
throw new DbnToolsHttpException('Query is too long.', 422, 'query_too_long');
@@ -113,20 +117,22 @@ try {
$engine,
$language,
$controls,
$emit
$emit,
$advocateRole
);
$result['ok'] = true;
$result['latency_ms'] = (int)round((microtime(true) - $startTime) * 1000);
dbnToolsLogMetadata([
'tool' => 'deep_research',
'language' => $language,
'ok' => true,
'latency_ms' => $result['latency_ms'],
'chunk_count' => (int)($result['trace_metadata']['chunk_count'] ?? 0),
'source_count' => (int)($result['trace_metadata']['source_count'] ?? 0),
'deployment' => $result['trace_metadata']['deployment'] ?? null,
'tool' => $advocateRole !== '' ? 'advocate' : 'deep_research',
'language' => $language,
'ok' => true,
'latency_ms' => $result['latency_ms'],
'chunk_count' => (int)($result['trace_metadata']['chunk_count'] ?? 0),
'source_count' => (int)($result['trace_metadata']['source_count'] ?? 0),
'deployment' => $result['trace_metadata']['deployment'] ?? null,
'advocate_role' => $advocateRole !== '' ? $advocateRole : null,
]);
$emit('final', ['result' => $result]);