Files
isaaccladandCursor 7c4673adb6
Deploy Ladill Transfer / deploy (push) Successful in 44s
Add recipient email and milestone notifications on transfer create.
Recipients can be emailed the download link immediately and at optional
reminders (7/3/1 days before unavailable, or when grace period starts).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 12:38:58 +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 files, optional recipient email with milestone reminders, 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;
}
}