Files
ladill-transfer/app/Services/Afia/AfiaService.php
T
isaaccladandCursor 311b5b40bb
Deploy Ladill Transfer / deploy (push) Successful in 51s
Bill transfer storage monthly with a 15-day grace period before deletion.
Files stay available while wallet renewals succeed each month. Failed renewals
keep files for TRANSFER_GRACE_PERIOD_DAYS (default 15) before automatic cleanup.

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

133 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 is billed monthly at GHS {$pricePerGb} per GB (metered on actual storage; no free tier).
- Transfers stay active while the wallet can pay each monthly renewal.
- If a renewal fails, files are kept for the grace period (default 15 days) before automatic deletion.
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;
}
}