Files
ladill-transfer/app/Services/Afia/AfiaService.php
T
isaaccladandCursor 65b634bb0b
Deploy Ladill Transfer / deploy (push) Successful in 39s
Add chunked uploads and raise storage rate to GHS 0.30/GB/month.
Large files upload in 5 MB chunks (hosting file manager pattern) for the
transfer UI and Ladill Mail S2S API, with no app-level file size cap when
TRANSFER_MAX_FILE_BYTES=0.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 12:07:27 +00:00

132 lines
5.7 KiB
PHP

<?php
namespace App\Services\Afia;
use Illuminate\Support\Facades\Http;
use RuntimeException;
/**
* Afia — Ladill in-app AI assistant scoped to a product.
*/
class AfiaService
{
public function enabled(): bool
{
return (bool) config('afia.enabled', true) && (string) config('afia.api_key', '') !== '';
}
/**
* @param array<int,array{role:string,text:string}> $history
* @param array<string,mixed> $context
*/
public function chat(string $message, array $history, array $context): string
{
if (! $this->enabled()) {
throw new RuntimeException('Afia is not configured.');
}
$provider = (string) config('afia.provider', 'openai');
$model = (string) config('afia.model', 'gpt-4o-mini');
$apiKey = (string) config('afia.api_key');
$messages = [['role' => 'system', 'content' => $this->systemPrompt($context)]];
foreach (array_slice($history, -8) as $turn) {
$role = ($turn['role'] ?? 'user') === 'assistant' ? 'assistant' : 'user';
$text = trim((string) ($turn['text'] ?? ''));
if ($text !== '') {
$messages[] = ['role' => $role, 'content' => $text];
}
}
$messages[] = ['role' => 'user', 'content' => $message];
return $provider === 'anthropic'
? $this->viaAnthropic($model, $apiKey, $messages)
: $this->viaOpenAi($model, $apiKey, $messages);
}
private function viaOpenAi(string $model, string $apiKey, array $messages): string
{
$res = Http::withToken($apiKey)->acceptJson()->timeout(45)
->post('https://api.openai.com/v1/chat/completions', [
'model' => $model,
'temperature' => 0.3,
'max_tokens' => 600,
'messages' => $messages,
]);
if ($res->failed()) {
throw new RuntimeException('OpenAI request failed: '.$res->status());
}
return trim((string) $res->json('choices.0.message.content', ''));
}
private function viaAnthropic(string $model, string $apiKey, array $messages): string
{
$system = $messages[0]['content'];
$turns = array_values(array_filter($messages, fn ($m) => $m['role'] !== 'system'));
$res = Http::withHeaders([
'x-api-key' => $apiKey,
'anthropic-version' => '2023-06-01',
])->acceptJson()->timeout(45)->post('https://api.anthropic.com/v1/messages', [
'model' => $model,
'max_tokens' => 600,
'system' => $system,
'messages' => array_map(fn ($m) => ['role' => $m['role'], 'content' => $m['content']], $turns),
]);
if ($res->failed()) {
throw new RuntimeException('Anthropic request failed: '.$res->status());
}
return trim((string) $res->json('content.0.text', ''));
}
/** @param array<string,mixed> $context */
private function systemPrompt(array $context): string
{
$ctx = collect($context)->map(fn ($v, $k) => "- {$k}: {$v}")->implode("\n");
return match ((string) config('afia.product', 'transfer')) {
'transfer' => $this->transferSystemPrompt($ctx),
default => $this->transferSystemPrompt($ctx),
};
}
private function transferSystemPrompt(string $ctx): string
{
$pricePerGb = number_format((float) config('transfer.price_per_gb_month', 0.30), 2);
return <<<PROMPT
You are Afia, the assistant inside Ladill Transfer (transfer.ladill.com).
Help users share files securely with QR codes and links, manage retention, track downloads, and understand storage billing.
Be concise, friendly, and actionable: short numbered steps and name the screen or section to use.
What Ladill Transfer does and where things live:
- Overview (Dashboard): active transfers, storage used, downloads in the last 30 days, transfers expiring soon, wallet balance.
- Transfers: list all file shares; create a new transfer (upload one or more files, title, optional message, retention period, optional password).
- After creating a transfer: users get a share link and downloadable QR code; recipients scan ladill.com/q/{code} or open the link.
- Files: browse all stored files across transfers with sizes and download counts.
- Analytics: download trends, top transfers, recent download activity.
- Wallet: Ladill wallet balance and top-up (sidebar → Wallet). Top up at account.ladill.com/wallet.
- Billing: storage usage, per-transfer monthly costs, and spend history (sidebar → Billing).
- Team: invite teammates to manage transfers together (sidebar → Team).
- Settings: notification preferences for transfer activity.
Pricing:
- Storage retention is prepaid at GHS {$pricePerGb} per GB per month (metered on actual storage; no free tier).
- Wallet must have funds to keep transfers active through their retention period.
Rules:
- Only answer questions about Ladill Transfer — uploads, share links, QR codes, passwords, retention/expiry, downloads, storage billing, team, and wallet.
- If asked about payment QRs (Mini), shops (Merchant), events, domains, hosting, or utility QR types, briefly say those live in other Ladill apps and suggest the app launcher.
- Never invent file sizes, download counts, short codes, or wallet balances — use the user context below or tell them where to check in the app.
- Public scan URLs always use ladill.com/q/{code} so printed QRs never need reprinting.
Current user context:
{$ctx}
PROMPT;
}
}