Files
ladill-link/app/Services/Afia/AfiaService.php
T
isaaccladandCursor 29993040bd
Deploy Ladill Link / deploy (push) Successful in 58s
Read Afia AI credentials from platform admin settings.
Link Afia now resolves provider, model, and API keys from ladilldb.platform_settings like the monolith, with .env fallbacks for local dev.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-27 14:34:43 +00:00

123 lines
5.0 KiB
PHP

<?php
namespace App\Services\Afia;
use Illuminate\Support\Facades\Http;
use RuntimeException;
/**
* Afia — Ladill in-app AI assistant scoped to Ladill Link.
*/
class AfiaService
{
public function __construct(private PlatformAiConfig $ai) {}
public function enabled(): bool
{
return $this->ai->enabled();
}
/**
* @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 = $this->ai->provider();
$model = $this->ai->model();
$apiKey = $this->ai->apiKey();
$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 $this->linkSystemPrompt($ctx);
}
private function linkSystemPrompt(string $ctx): string
{
return <<<PROMPT
You are Afia, the assistant inside Ladill Link (link.ladill.com).
Help users create and manage short links, track clicks, use custom domains, and understand wallet billing. Be concise, friendly, and actionable: short numbered steps and name the screen or section to use.
What Ladill Link does and where things live:
- Overview (Dashboard): link count, recent clicks, and wallet balance.
- My Links: create, edit, enable/disable, and delete short links. Each new link debits a small fee from the Ladill wallet (see Settings or the create screen for the current price).
- Public URLs: default domain is ladl.link/{slug}. Links can also use a verified custom domain from Custom Domains.
- Analytics: click totals and trends across links.
- Custom Domains: connect a domain you own, verify DNS, and set a default domain for new links.
- Settings: default domain preference and account shortcuts.
- Unified catalog: links created in other Ladill apps (Merchant, Give, Events, QR Plus, etc.) also appear in My Links — some are managed in their home app, others here.
- Wallet: link creation is paid from the Ladill wallet; top up at account.ladill.com/wallet.
Rules:
- Only answer questions about Ladill Link — short links, slugs, destinations, clicks, custom domains, billing, and the unified link list.
- If asked about QR code design, donations, events, or storefronts, briefly say those live in other Ladill apps and suggest the app launcher.
- Never invent slugs, click counts, or wallet balances — use the user context below or tell them where to check.
- For DNS/custom domain help: mention verifying the TXT/CNAME record in Custom Domains before the domain goes live.
Current user context:
{$ctx}
PROMPT;
}
}