From b09a72cefc02dd4709ea0274d6132a428b7c1faa Mon Sep 17 00:00:00 2001 From: isaacclad Date: Mon, 8 Jun 2026 10:11:27 +0000 Subject: [PATCH] Scope Afia assistant to Ladill Transfer. Replace Mini/QR Plus copy and system prompt with Transfer file-sharing context, suggestions, and dashboard metrics. Co-authored-by: Cursor --- app/Http/Controllers/Qr/AccountController.php | 2 +- app/Http/Controllers/Qr/AfiaController.php | 58 ++++++++++++------- app/Services/Afia/AfiaService.php | 48 ++++++++------- resources/views/partials/afia.blade.php | 12 ++-- resources/views/qr/account/wallet.blade.php | 4 +- tests/Feature/AfiaTest.php | 15 +++-- 6 files changed, 82 insertions(+), 57 deletions(-) diff --git a/app/Http/Controllers/Qr/AccountController.php b/app/Http/Controllers/Qr/AccountController.php index 5551f08..d812e17 100644 --- a/app/Http/Controllers/Qr/AccountController.php +++ b/app/Http/Controllers/Qr/AccountController.php @@ -26,7 +26,7 @@ class AccountController extends Controller } $balanceMinor = $this->billing->balanceMinor($publicId); - $ledger = $this->billing->serviceLedger($publicId, config('billing.service', 'mini')); + $ledger = $this->billing->serviceLedger($publicId, config('billing.service', 'transfer')); return [$balanceMinor, $ledger]; } diff --git a/app/Http/Controllers/Qr/AfiaController.php b/app/Http/Controllers/Qr/AfiaController.php index e6fe0a7..d542a99 100644 --- a/app/Http/Controllers/Qr/AfiaController.php +++ b/app/Http/Controllers/Qr/AfiaController.php @@ -3,10 +3,10 @@ namespace App\Http\Controllers\Qr; use App\Http\Controllers\Controller; -use App\Models\QrCode; +use App\Models\Transfer; +use App\Models\TransferDownloadEvent; use App\Services\Afia\AfiaService; use App\Services\Billing\BillingClient; -use App\Support\Qr\QrTypeCatalog; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; @@ -45,19 +45,32 @@ class AfiaController extends Controller return ['signed_in' => 'no']; } - $types = QrTypeCatalog::eventTypes(); - $codes = $account->qrCodes()->whereIn('type', $types); + $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', - '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', + '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) { @@ -67,18 +80,21 @@ class AfiaController extends Controller } } - $recent = $account->qrCodes() - ->whereIn('type', $types) - ->latest('updated_at') + $recent = Transfer::query() + ->where('user_id', $account->id) + ->where('status', Transfer::STATUS_ACTIVE) + ->with('qrCode:id,short_code') + ->latest() ->limit(3) - ->get(['type', 'label', 'short_code', 'scans_total']); + ->get(['id', 'title', 'qr_code_id', 'download_count', 'expires_at']); 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, + $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('; '); } diff --git a/app/Services/Afia/AfiaService.php b/app/Services/Afia/AfiaService.php index 377ef7e..1ab2838 100644 --- a/app/Services/Afia/AfiaService.php +++ b/app/Services/Afia/AfiaService.php @@ -6,7 +6,7 @@ use Illuminate\Support\Facades\Http; use RuntimeException; /** - * Afia — Ladill in-app AI assistant scoped to a product (QR Plus). + * Afia — Ladill in-app AI assistant scoped to a product. */ class AfiaService { @@ -88,36 +88,40 @@ class AfiaService { $ctx = collect($context)->map(fn ($v, $k) => "- {$k}: {$v}")->implode("\n"); - return match ((string) config('afia.product', 'qr')) { - 'qr' => $this->qrSystemPrompt($ctx), - default => $this->qrSystemPrompt($ctx), + return match ((string) config('afia.product', 'transfer')) { + 'transfer' => $this->transferSystemPrompt($ctx), + default => $this->transferSystemPrompt($ctx), }; } - private function qrSystemPrompt(string $ctx): string + private function transferSystemPrompt(string $ctx): string { + $pricePerGb = number_format((float) config('transfer.price_per_gb_month', 1.0), 2); + return <<

Afia

-

Mini assistant

+

Transfer assistant

diff --git a/resources/views/qr/account/wallet.blade.php b/resources/views/qr/account/wallet.blade.php index cc2074a..15151c8 100644 --- a/resources/views/qr/account/wallet.blade.php +++ b/resources/views/qr/account/wallet.blade.php @@ -3,7 +3,7 @@ @php $fmt = fn ($m) => 'GHS '.number_format($m / 100, 2); @endphp

Wallet

-

Your Ladill wallet funds QR Plus and other Ladill apps.

+

Your Ladill wallet funds Transfer storage and other Ladill apps.

Balance

{{ $fmt($balanceMinor) }}

@@ -11,7 +11,7 @@
-

Spent on QR Plus

+

Spent on Transfer

{{ $fmt($spentMinor) }}

diff --git a/tests/Feature/AfiaTest.php b/tests/Feature/AfiaTest.php index 7868306..31981dd 100644 --- a/tests/Feature/AfiaTest.php +++ b/tests/Feature/AfiaTest.php @@ -37,18 +37,23 @@ class AfiaTest extends TestCase public function test_returns_reply_from_llm(): void { - config(['afia.api_key' => 'sk-test', 'afia.provider' => 'openai', 'afia.model' => 'gpt-4o-mini']); + config([ + 'afia.api_key' => 'sk-test', + 'afia.provider' => 'openai', + 'afia.model' => 'gpt-4o-mini', + 'afia.product' => 'transfer', + ]); Http::fake([ - 'api.openai.com/*' => Http::response(['choices' => [['message' => ['content' => 'Create a Business QR under My Codes.']]]]), + 'api.openai.com/*' => Http::response(['choices' => [['message' => ['content' => 'Open Transfers → New transfer to upload files.']]]]), ]); $this->actingAs($this->user()) - ->postJson('/afia/chat', ['message' => 'How do I create a business QR?']) + ->postJson('/afia/chat', ['message' => 'How do I share files?']) ->assertOk() - ->assertJson(['reply' => 'Create a Business QR under My Codes.']); + ->assertJson(['reply' => 'Open Transfers → New transfer to upload files.']); Http::assertSent(fn ($r) => str_contains($r->url(), 'openai.com') && collect($r['messages'])->first()['role'] === 'system' - && str_contains(collect($r['messages'])->first()['content'], 'Ladill QR Plus')); + && str_contains(collect($r['messages'])->first()['content'], 'Ladill Transfer')); } }