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']; } $active = Transfer::query() ->where('user_id', $account->id) ->where('status', Transfer::STATUS_ACTIVE); $storageBytes = (int) (clone $active)->sum('storage_bytes'); $storageGb = round($storageBytes / (1024 * 1024 * 1024), 2); $transferIds = Transfer::query() ->where('user_id', $account->id) ->pluck('id'); $ctx = [ 'signed_in' => 'yes', 'active_transfers' => (clone $active)->count(), 'storage_gb' => $storageGb, 'downloads_30d' => TransferDownloadEvent::query() ->whereIn('transfer_id', $transferIds) ->where('downloaded_at', '>=', now()->subDays(30)) ->count(), 'expiring_within_7_days' => Transfer::query() ->where('user_id', $account->id) ->where('status', Transfer::STATUS_ACTIVE) ->whereNotNull('expires_at') ->whereBetween('expires_at', [now(), now()->addDays(7)]) ->count(), 'storage_price_per_gb_month_ghs' => number_format((float) config('transfer.price_per_gb_month', 1.0), 2), ]; if ($account->public_id) { try { $ctx['wallet_balance_ghs'] = number_format(app(BillingClient::class)->balanceMinor((string) $account->public_id) / 100, 2); } catch (\Throwable) { } } $recent = Transfer::query() ->where('user_id', $account->id) ->where('status', Transfer::STATUS_ACTIVE) ->with('qrCode:id,short_code') ->latest() ->limit(3) ->get(['id', 'title', 'qr_code_id', 'download_count', 'expires_at']); if ($recent->isNotEmpty()) { $ctx['recent_transfers'] = $recent->map(fn (Transfer $transfer): string => sprintf( '%s (%s, %d downloads%s)', $transfer->title, $transfer->qrCode?->short_code ?? 'no QR', $transfer->download_count, $transfer->expires_at ? ', expires '.$transfer->expires_at->toDateString() : '', ))->implode('; '); } return $ctx; } }