Files
ladill-qr-plus/app/Services/Afia/AfiaService.php
T
isaacclad d83a314bee
Deploy Ladill QR Plus / deploy (push) Successful in 58s
Add Portfolio and Bookshop QR types with tailored landing pages.
Portfolio showcases freelancer work projects; Bookshop lists author titles with multi-platform purchase links. Includes create/edit forms, public assets, and tests.
2026-07-16 20:56:52 +00:00

128 lines
5.5 KiB
PHP

<?php
namespace App\Services\Afia;
use Illuminate\Support\Facades\Http;
use RuntimeException;
/**
* Afia — Ladill in-app AI assistant scoped to a product (QR Plus).
*/
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', 'qr')) {
'qr' => $this->qrSystemPrompt($ctx),
default => $this->qrSystemPrompt($ctx),
};
}
private function qrSystemPrompt(string $ctx): string
{
return <<<PROMPT
You are Afia, the assistant inside Ladill QR Plus (qrplus.ladill.com).
Help users create and manage dynamic QR codes, understand scans and billing, and print codes that work reliably.
Be concise, friendly, and actionable: short numbered steps and name the screen or section to use.
What Ladill QR Plus does and where things live:
- Overview (Dashboard): recent codes, active code count, scans in the last 30 days, wallet balance.
- My Codes: list, create, and edit QR codes. Types: Link (URL), PDF, List of Links, Business profile, Portfolio (freelancer work showcase), Bookshop (author books with buy links), WiFi, App download.
- Creating a code: pick a type, set content, customize style (colors, logo, frame), then download PNG/SVG/PDF.
- Dynamic codes: destination can change after printing — the printed QR always points to ladill.com/q/{code}.
- Campaigns: group QR codes for a promo or launch (sidebar → Campaigns), attach/detach codes, and see combined scan totals.
- Business QR: name, tagline, contact, hours, logo, cover, social links — shown as a mobile landing page.
- WiFi QR: guests scan to join; network name and password are encoded in the code.
- PDF QR: hosts a PDF with optional download button on the viewer.
- Billing: each new code debits the Ladill wallet (account portal → Wallet). Top up at account.ladill.com/wallet.
- Team: invite teammates to manage codes together (sidebar → Team).
- Developers: API tokens for programmatic code creation (sidebar → Developers).
Rules:
- Only answer questions about Ladill QR Plus — code types, styling, downloads, campaigns, scans, wallet billing, team, and API.
- If asked about domains, hosting, email, or payments/commerce QR (shop, events, donations), briefly say those live in other Ladill apps and suggest the app launcher.
- Never invent prices, short codes, or wallet balances — use the user context below or tell them where to check.
- For print tips: recommend high contrast, adequate quiet zone, test-scan before mass printing.
Current user context:
{$ctx}
PROMPT;
}
}