validate([ 'message' => ['required', 'string', 'max:2000'], 'history' => ['nullable', 'array', 'max:20'], 'history.*.role' => ['nullable', 'string'], 'history.*.text' => ['nullable', 'string'], ]); if (! $afia->enabled()) { return response()->json(['message' => 'Afia is not available right now.'], 503); } try { $reply = $afia->chat(trim($validated['message']), $validated['history'] ?? [], $this->context()); } catch (\Throwable $e) { report($e); return response()->json(['message' => 'Afia could not respond right now. Please try again.'], 502); } return response()->json(['reply' => $reply]); } /** @return array */ private function context(): array { $account = ladill_account(); if (! $account) { return ['signed_in' => 'no']; } $types = QrTypeCatalog::storefrontTypes(); $codes = $account->qrCodes()->whereIn('type', $types); $ctx = [ 'signed_in' => 'yes', 'qr_codes_total' => (clone $codes)->count(), 'qr_codes_active' => (clone $codes)->where('is_active', true)->count(), 'scans_total' => (int) (clone $codes)->sum('scans_total'), 'top_code_type' => (clone $codes) ->selectRaw('type, count(*) as total') ->groupBy('type') ->orderByDesc('total') ->value('type') ?: 'none yet', ]; if ($account->public_id) { try { $ctx['wallet_balance_ghs'] = number_format(app(BillingClient::class)->balanceMinor((string) $account->public_id) / 100, 2); } catch (\Throwable) { } } $recent = $account->qrCodes() ->whereIn('type', $types) ->latest('updated_at') ->limit(3) ->get(['type', 'label', 'short_code', 'scans_total']); if ($recent->isNotEmpty()) { $ctx['recent_codes'] = $recent->map(fn (QrCode $code): string => sprintf( '%s (%s, %d scans)', $code->label ?: $code->short_code, QrTypeCatalog::label($code->type), $code->scans_total, ))->implode('; '); } return $ctx; } }