Stage 16 — AI Layer: VIN decoder + diagnostic / parts / price helpers
VinDecoder (deterministic, no API): - ISO 3779/3780 parsing: WMI manufacturer (~60 brands), year (cyclical with post-2010 disambiguation via position 7), region, plant, NA checksum - Strip non-VIN chars, accept dashes/spaces, reject I/O/Q per spec AiAssistantService: - Refactored provider HTTP into postClaude/postOpenAI/postGemini so both chat history and one-shot calls share the same transport - singleShot(system, userPrompt, provider?) for fire-and-forget calls - 4 specialized helpers with tight prompts: - suggestDiagnosis(WO) — diagnostician based on complaint + VIN info - suggestParts(WO, task) — OEM parts list for an operation - suggestPrice(Part) — markup recommendation with justification - vinRecommendations(vin, mileage) — scheduled maintenance from decoded VIN - monthlyUsage() — token spend MTD by provider Filament: - VehicleResource: "Decode VIN" + "AI: recomandări" actions - WorkOrderResource Edit: "AI: sugerează diagnostic" header action - PartResource: "AI: preț recomandat" action - Shared views: filament.tenant.ai-reply, filament.tenant.vin-decode - AiAssistant page shows monthly token usage banner Tests (13 new): - 8 VinDecoder unit tests with real VIN samples (Honda 2003, VW 1999, Audi 2014, Dacia, unknown WMI, lowercase/dashes, forbidden chars) - 5 AiHelpers feature tests with Http::fake covering all providers + no-key fallback + token usage aggregation Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
@php
|
||||
$isError = isset($meta['error']);
|
||||
$provider = $meta['provider'] ?? null;
|
||||
$tokensIn = $meta['tokens_in'] ?? null;
|
||||
$tokensOut = $meta['tokens_out'] ?? null;
|
||||
$latency = $meta['latency_ms'] ?? null;
|
||||
@endphp
|
||||
<div class="space-y-3">
|
||||
@if ($isError)
|
||||
<div class="p-3 rounded-md bg-yellow-50 dark:bg-yellow-900/30 border border-yellow-200 dark:border-yellow-700 text-sm text-yellow-900 dark:text-yellow-100">
|
||||
{{ $reply }}
|
||||
</div>
|
||||
@else
|
||||
<div class="prose prose-sm dark:prose-invert max-w-none whitespace-pre-wrap leading-relaxed text-gray-900 dark:text-gray-100">{{ $reply }}</div>
|
||||
@endif
|
||||
|
||||
@if (! $isError && ($provider || $tokensIn !== null))
|
||||
<div class="text-xs text-gray-500 dark:text-gray-400 border-t border-gray-200 dark:border-gray-700 pt-2 flex flex-wrap gap-3">
|
||||
@if ($provider) <span>Provider: <strong>{{ strtoupper($provider) }}</strong></span> @endif
|
||||
@if ($tokensIn !== null || $tokensOut !== null)
|
||||
<span>Tokens: <strong>{{ ($tokensIn ?? 0) + ($tokensOut ?? 0) }}</strong> ({{ $tokensIn ?? 0 }} in / {{ $tokensOut ?? 0 }} out)</span>
|
||||
@endif
|
||||
@if ($latency !== null) <span>Latency: {{ $latency }} ms</span> @endif
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
Reference in New Issue
Block a user