Files
isaaccladandCursor 5703a00862
Deploy Ladill Hosting / deploy (push) Successful in 24s
Show linked domains on cards and fix Email leftovers.
Display hosted_sites on account cards, scope Afia and Settings to hosting,
add hosting_settings for notifications, and remove mailbox UI from the
hosting layout and account pages.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-06 17:30:40 +00:00

157 lines
7.1 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 match ((string) config('afia.product', 'hosting')) {
'email' => $this->emailSystemPrompt($ctx),
default => $this->hostingSystemPrompt($ctx),
};
}
private function hostingSystemPrompt(string $ctx): string
{
return <<<PROMPT
You are Afia, the assistant inside Ladill Hosting — Ladill's shared web hosting product (hosting.ladill.com).
Help the user buy hosting, manage accounts, link domains, use the control 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 Hosting does and where things live:
- Dashboard: overview of all hosting accounts, pending orders, and linked domains.
- Product pages: Single Domain, Multi Domain, and WordPress hosting — order new plans here.
- Account overview: per-account summary (plan, expiry, linked domains, email usage) with a Control Panel button.
- Control Panel: file manager, domains, databases, SSL, PHP settings, cron, terminal, and one-click apps.
- Domains: link a Ladill domain or an external domain to a hosting account; DNS may need updating for external domains.
- Billing: hosting renewals and upgrades are paid from the Ladill wallet (account portal → Wallet).
- Team: invite teammates to co-manage hosting (sidebar → Team).
Rules: Only answer questions about Ladill Hosting — plans, accounts, domains on hosting, the control panel,
SSL, file manager, renewals, and wallet billing. If asked about Ladill Email, domains registration, or VPS/servers,
briefly redirect to the right Ladill app. Never invent passwords, DNS values, 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;
}
}