feat(timeline): full form UI with engine selection and advanced settings
Add 4-language switcher (EN/NO/UK/PL), engine choice (Azure mini/full, GPU/cuttlefish), and expandable Advanced panel (Focus, Confidence filter, Date types) to timeline.php. Wire new params through api/timeline.php and LegalTools::timeline() with engine routing, focus-aware prompt injection, and confidence/date-type post-filters. Add TIMELINE_I18N to tools.js with improved renderTimeline() confidence colour-coding and new CSS classes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+89
-18
@@ -279,16 +279,35 @@ PROMPT;
|
||||
];
|
||||
}
|
||||
|
||||
public function timeline(string $text, string $language = 'en'): array
|
||||
{
|
||||
$text = $this->requirePasteText($text);
|
||||
$this->azure->requireChat();
|
||||
public function timeline(
|
||||
string $text,
|
||||
string $language = 'en',
|
||||
string $engine = 'azure_mini',
|
||||
string $focus = 'all',
|
||||
string $confidenceFilter = 'all',
|
||||
bool $includeRelative = true
|
||||
): array {
|
||||
$text = $this->requirePasteText($text);
|
||||
$engine = in_array($engine, ['azure_mini', 'azure_full', 'gpu'], true) ? $engine : 'azure_mini';
|
||||
$focus = in_array($focus, ['all', 'deadlines', 'hearings', 'cps'], true) ? $focus : 'all';
|
||||
|
||||
if ($engine !== 'gpu') {
|
||||
$this->azure->requireChat();
|
||||
}
|
||||
|
||||
$locale = $language === 'no' ? 'Norwegian' : 'English';
|
||||
|
||||
$focusInstruction = match ($focus) {
|
||||
'deadlines' => "\nFocus specifically on: legal deadlines, filing dates, response windows, appeal periods, and statutory time limits. Deprioritise narrative events with no legal deadline significance.",
|
||||
'hearings' => "\nFocus specifically on: court hearings, tribunal sessions, mediation sessions, formal meetings, and hearing-related procedural dates.",
|
||||
'cps' => "\nFocus specifically on: CPS (Barnevernet) interventions, home visits, case reviews, acute measures (akuttvedtak), and Fylkesnemnda proceedings.",
|
||||
default => '',
|
||||
};
|
||||
|
||||
$prompt = <<<PROMPT
|
||||
Build a chronological timeline from the pasted text in {$locale}.
|
||||
|
||||
Extract ALL dates, deadlines, milestones, and temporal references.
|
||||
Extract ALL dates, deadlines, milestones, and temporal references.{$focusInstruction}
|
||||
For each temporal reference provide:
|
||||
- "date": ISO 8601 date (YYYY-MM-DD) if determinable, otherwise a human-readable description
|
||||
- "date_type": one of absolute | relative | recurring | conditional | period
|
||||
@@ -314,30 +333,82 @@ Return JSON only:
|
||||
}
|
||||
PROMPT;
|
||||
|
||||
$json = $this->runJsonTool($prompt, $language, 4000);
|
||||
$system = $this->legalJsonSystemPrompt($language);
|
||||
$messages = [
|
||||
['role' => 'system', 'content' => $system],
|
||||
['role' => 'user', 'content' => $prompt],
|
||||
];
|
||||
$chatOptions = ['json' => true, 'temperature' => 0.1, 'max_tokens' => 4000];
|
||||
$deployLabel = $this->azure->chatDeployment();
|
||||
|
||||
try {
|
||||
if ($engine === 'gpu') {
|
||||
$response = $this->callGpuLlm($messages, $chatOptions);
|
||||
$deployLabel = 'GPU (cuttlefish)';
|
||||
} elseif ($engine === 'azure_full') {
|
||||
$response = $this->azure->withDeployment('gpt-4o')->chat($messages, $chatOptions);
|
||||
$deployLabel = 'gpt-4o';
|
||||
} else {
|
||||
$response = $this->azure->chat($messages, $chatOptions);
|
||||
$deployLabel = $this->azure->chatDeployment();
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
dbnToolsAbort('LLM request failed: ' . $e->getMessage(), 502, 'llm_error');
|
||||
}
|
||||
|
||||
$raw = (string)($response['choices'][0]['message']['content'] ?? '');
|
||||
$json = $this->azure->decodeJsonObject($raw);
|
||||
if (!$json) {
|
||||
dbnToolsAbort('The selected engine did not return valid structured JSON.', 502, 'llm_invalid_json');
|
||||
}
|
||||
|
||||
$events = is_array($json['events'] ?? null) ? $json['events'] : [];
|
||||
|
||||
// Post-filter: confidence
|
||||
if ($confidenceFilter === 'high_medium') {
|
||||
$events = array_values(array_filter($events, fn($ev) => ($ev['confidence'] ?? 'low') !== 'low'));
|
||||
}
|
||||
|
||||
// Post-filter: relative/recurring date types
|
||||
if (!$includeRelative) {
|
||||
$events = array_values(array_filter($events, fn($ev) => ($ev['date_type'] ?? 'absolute') === 'absolute'));
|
||||
}
|
||||
|
||||
$engineLabel = match ($engine) {
|
||||
'gpu' => 'GPU (cuttlefish)',
|
||||
'azure_full' => 'gpt-4o',
|
||||
default => $deployLabel ?? $this->azure->chatDeployment(),
|
||||
};
|
||||
|
||||
$focusLabel = match ($focus) {
|
||||
'deadlines' => 'legal deadlines',
|
||||
'hearings' => 'court hearings',
|
||||
'cps' => 'CPS milestones',
|
||||
default => 'all events',
|
||||
};
|
||||
|
||||
$trace = [
|
||||
$this->trace('Query interpretation', 'Extract dated events from pasted text without saving the text or output.', 'complete'),
|
||||
$this->trace('Query interpretation', "Extract {$focusLabel} from pasted text. Engine: {$engineLabel}. Without saving the text or output.", 'complete'),
|
||||
$this->trace('Search tools used', 'No external corpus search; source is the user-pasted text.', 'complete'),
|
||||
$this->trace('Evidence found', count($events) . ' event(s) identified.', count($events) ? 'complete' : 'warning'),
|
||||
$this->trace('Evidence found', count($events) . ' event(s) identified' . ($confidenceFilter === 'high_medium' ? ' (low-confidence filtered out)' : '') . '.', count($events) ? 'complete' : 'warning'),
|
||||
$this->trace('Citation confidence', 'Confidence is per event and based only on the pasted text.', 'complete'),
|
||||
$this->trace('Uncertainty / missing evidence', $this->uncertaintySummary($json['what_remains_uncertain'] ?? []), 'complete'),
|
||||
$this->trace('Next practical step', (string)($json['next_practical_step'] ?? 'Verify dates against original documents.'), 'complete'),
|
||||
];
|
||||
|
||||
return [
|
||||
'tool' => 'timeline',
|
||||
'language' => $language,
|
||||
'what_we_found' => (string)($json['what_we_found'] ?? ''),
|
||||
'events' => $events,
|
||||
'evidence_trail' => $json['evidence_trail'] ?? [['title' => 'Pasted text', 'excerpt' => 'Processed in-memory only; not stored.']],
|
||||
'tool' => 'timeline',
|
||||
'language' => $language,
|
||||
'what_we_found' => (string)($json['what_we_found'] ?? ''),
|
||||
'events' => $events,
|
||||
'evidence_trail' => $json['evidence_trail'] ?? [['title' => 'Pasted text', 'excerpt' => 'Processed in-memory only; not stored.']],
|
||||
'what_remains_uncertain' => $json['what_remains_uncertain'] ?? [],
|
||||
'next_practical_step' => (string)($json['next_practical_step'] ?? ''),
|
||||
'trace' => $trace,
|
||||
'trace_metadata' => [
|
||||
'chunk_count' => count($events),
|
||||
'next_practical_step' => (string)($json['next_practical_step'] ?? ''),
|
||||
'trace' => $trace,
|
||||
'trace_metadata' => [
|
||||
'chunk_count' => count($events),
|
||||
'source_count' => 1,
|
||||
'deployment' => $this->azure->chatDeployment(),
|
||||
'deployment' => $engineLabel,
|
||||
],
|
||||
'disclaimer' => dbnToolsDisclaimer($language),
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user