@php /** * Shared AI chat widget — the vendor assistant page and the header off-canvas * both render it. Multi-instance JS (assets/js/ai-chat.js) binds each * [data-ai-chat] widget to its own data-endpoint. Reuses the .qm-chat-* styles; * no new CSS. * * @var string $wrapClass root classes (e.g. 'qm-card qm-chat' or 'qm-chat') * @var bool $ready AI configured + enabled * @var string $reason disabled reason (shown when !$ready) * @var string $disabledNote extra "once enabled…" note * @var string $endpoint chat POST endpoint (role-scoped) * @var string $userAvatar signed-in user's avatar src * @var string $welcome first assistant bubble * @var array $suggestions starter prompt chips (the off-canvas passes none) */ use App\Support\Auth; $suggestions = $suggestions ?? []; /* WHO IS ALLOWED TO SEE THIS AT ALL. * * The widget is a back-office tool: it answers on the platform's or the restaurant's own live * figures. A customer must never reach it, and — the part that actually matters — must never be * shown the setup advice below, which names an administrator's settings page. So the partial * refuses to render rather than rendering a disabled shell: an empty chat box a customer cannot * use is still a support question, and a box explaining how to configure someone else's API key * is worse than either. * * Asked here rather than at each call site, so a surface added later cannot forget it. Today * both callers sit inside the dashboard layout, which already gates on the same two facts; * this is the guarantee rather than a second copy of the rule. */ if (!Auth::check() || !((string) Auth::role() === 'super_admin' || Auth::can('ai.use'))) { return; } // One source of truth for "is this set up?", shared by every AI surface. A caller may still // pass its own answer (both do, from the client it already built); with none, the widget asks // the same question itself rather than assuming it is ready. $aiSetup = \App\Services\Ai::setup(\App\Services\AiFeature::ASSISTANT); $ready = $ready ?? $aiSetup['ready']; $reason = $reason ?? $aiSetup['notice']; // The assistant's face is a platform brand mark, so it is resolved here rather than // passed in by each caller — the vendor page, the header off-canvas and any future // surface all show the same one. The shipped default is the favicon; if the file is // missing the markup falls back to the icon, which is what every install had before. $botAvatar = trim((string) setting('ai_avatar', 'assets/admin/favicon.png')); $botAvatar = ($botAvatar !== '' && is_file(public_path() . '/' . ltrim($botAvatar, '/'))) ? asset($botAvatar) : ''; @endphp
@if (!$ready) @php /* The actionable sentence stays in the banner where it will be read; what the assistant will do ONCE it is set up is secondary, so it moves into the standard .qm-tip rather than doubling the length of the notice. */ @endphp
{{ $reason }} @if (($disabledNote ?? '') !== '') @endif
@endif
@if ($botAvatar !== '')@else@endif
{{ $welcome }}
@if ($ready && $suggestions)
@foreach ($suggestions as $s) @endforeach
@endif