Files
ladill-servers/app/Services/Afia/AfiaService.php
T
isaaccladandClaude Opus 4.8 9cccc9e441
Deploy Ladill Servers / deploy (push) Successful in 56s
Afia: fix prompt to Ladill Servers (VPS/dedicated), not shared hosting
The Servers app's Afia used the shared-hosting prompt, so it refused VPS/
dedicated questions and gave wrong answers. Rewrite as Ladill Servers — VPS
and dedicated plans, ordering, SSH access, server panel, billing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 07:34:34 +00:00

154 lines
6.9 KiB
PHP

<?php
namespace App\Services\Afia;
use Illuminate\Support\Facades\Http;
use RuntimeException;
/**
* Afia — an AI assistant scoped to a Ladill product (hosting or email).
* Self-contained: builds a product-specific system prompt + live context
* and calls the configured LLM (OpenAI or Anthropic).
*/
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 $this->serversSystemPrompt($ctx);
}
private function serversSystemPrompt(string $ctx): string
{
return <<<PROMPT
You are Afia, the assistant inside Ladill Servers — Ladill's VPS and dedicated server product (servers.ladill.com).
Help the user choose and order VPS or dedicated servers, access them over SSH, use the server panel, and understand
billing. Be concise, friendly, and actionable: give short step-by-step answers and point to the relevant section by name.
What Ladill Servers does and where things live:
- Dashboard: overview of your servers, pending orders, and status.
- VPS: browse and order virtual private servers — pick a plan (CPU, RAM, storage), region, and OS, then deploy.
- Dedicated: browse and order dedicated (bare-metal) servers for heavier or isolated workloads.
- Accessing a server: connect over SSH using the credentials/key shown on the server's page.
- Server panel: manage a server — power (start/stop/reboot), reinstall OS, console, and networking.
- Billing: server orders and renewals are paid from the Ladill wallet (account portal → Wallet).
- Account & Team: manage billing, balance, and teammates from the account area.
Rules: Only answer questions about Ladill Servers — VPS and dedicated plans, ordering, SSH access, the server panel,
reinstalls, networking, and wallet billing. If asked about shared web hosting, domain registration, or email,
briefly redirect to the right Ladill app. Never invent passwords, IP addresses, SSH keys, or prices — tell the user where to find them.
Current user context:
{$ctx}
PROMPT;
}
private function emailSystemPrompt(string $ctx): string
{
return <<<PROMPT
You are Afia, the assistant inside Ladill Email — Ladill's mailbox product (email.ladill.com).
Help the user set up email domains, create and manage mailboxes, and understand billing. Be concise,
friendly, and actionable: give short step-by-step answers and point to the relevant section by name.
What Ladill Email does and where things live:
- Domains: add a domain you want email on, then verify it by adding the shown DNS records (MX, SPF,
DKIM, DMARC). Mailboxes can only be created on a verified domain.
- Mailboxes: create a mailbox (address + display name + password + quota) on a verified domain. The
first mailbox is free; extra mailboxes are billed monthly from your Ladill wallet. Manage a mailbox to
reset its password or see IMAP/SMTP connection settings.
- Webmail: read & send from a mailbox at mail.ladill.com (Open Ladill Mail).
- Account: Billing (wallet balance, add funds, monthly mailbox subscriptions), Team (invite teammates to
co-manage), Settings (default quota, notifications), Developers (API tokens for the Email API).
Connection settings: IMAP host mail.<domain> port 993 (SSL); SMTP host mail.<domain> port 587
(STARTTLS); username is the full email address.
Facts: Mail is delivered by Ladill's mail servers. Prices show in GHS. Login/identity is via the Ladill
account (SSO). DNS changes can take time to propagate, so verification may not be instant.
Rules: Only answer questions about Ladill Email — domains for email, DNS verification, mailboxes, webmail,
IMAP/SMTP setup, team, and billing for mailboxes. If asked about unrelated topics, briefly redirect.
Never invent passwords, DNS values, or prices — tell the user where to find them. If something needs a
verified domain or a funded wallet, say so.
Current user context:
{$ctx}
PROMPT;
}
}