timeline: remove GPU, add SSE status updates, DOCX export, single-file, engine-aware credits

- Remove GPU/cuttlefish engine from timeline.php, api/timeline.php, LegalTools.php, tools.js (all 4 langs)
- Add engine-aware credit cost: gpt-4o-mini=1 credit, gpt-4o=2 credits (matches redact pattern)
- Remove multiple attribute from file input (single document only)
- New api/timeline-stream.php: SSE endpoint emitting status events + final result
- New api/timeline-download.php: DOCX export of timeline events
- LegalTools::timeline() gains ?callable $onProgress for live status updates
- tools.js: spinner on run, SSE streaming fetch, Export to Word button
- Save to My Docs was already wired (showSaveResultButton at line 1136)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-25 09:32:28 +02:00
parent 4b8b675a64
commit d47024ed67
7 changed files with 406 additions and 43 deletions
+13 -19
View File
@@ -350,15 +350,15 @@ PROMPT;
string $confidenceFilter = 'all',
bool $includeRelative = true,
bool $includeBackground = true,
string $userNotes = ''
string $userNotes = '',
?callable $onProgress = null
): array {
$text = $this->requirePasteText($text);
$engine = in_array($engine, ['azure_mini', 'azure_full', 'gpu'], true) ? $engine : 'azure_mini';
$engine = in_array($engine, ['azure_mini', 'azure_full'], true) ? $engine : 'azure_mini';
$focus = in_array($focus, ['all', 'deadlines', 'hearings', 'cps'], true) ? $focus : 'all';
if ($engine !== 'gpu') {
$this->azure->requireChat();
}
$this->azure->requireChat();
$onProgress && $onProgress("Preparing document\u{2026}");
$locale = dbnToolsLanguageName($language);
@@ -451,23 +451,21 @@ PROMPT;
['role' => 'user', 'content' => $prompt],
];
$chatOptions = ['json' => true, 'temperature' => 0.1, 'max_tokens' => ($engine === 'azure_full' ? 8000 : 4000), 'timeout' => 120];
$deployLabel = $this->azure->chatDeployment();
$deployLabel = $engine === 'azure_full' ? 'gpt-4o' : 'gpt-4o-mini';
$onProgress && $onProgress("Calling {$deployLabel}\u{2026}");
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';
if ($engine === 'azure_full') {
$response = $this->azure->withDeployment('gpt-4o')->chat($messages, $chatOptions);
} else {
$response = $this->azure->withDeployment('gpt-4o-mini')->chat($messages, $chatOptions);
$deployLabel = 'gpt-4o-mini';
$response = $this->azure->withDeployment('gpt-4o-mini')->chat($messages, $chatOptions);
}
} catch (Throwable $e) {
dbnToolsAbort('LLM request failed: ' . $e->getMessage(), 502, 'llm_error');
}
$onProgress && $onProgress("Parsing events\u{2026}");
$raw = (string)($response['choices'][0]['message']['content'] ?? '');
$json = $this->azure->decodeJsonObject($raw);
if (!$json) {
@@ -486,11 +484,7 @@ PROMPT;
$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(),
};
$engineLabel = $engine === 'azure_full' ? 'gpt-4o' : 'gpt-4o-mini';
$focusLabel = match ($focus) {
'deadlines' => 'legal deadlines',