Initial Ladill Hosting app with Gitea deploy pipeline.
Deploy Ladill Hosting / deploy (push) Failing after 17s

Shared web hosting extracted from the platform monolith, with CI deploy
to /var/www/ladill-hosting matching Bird/Domains/Email.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
isaacclad
2026-06-06 16:24:20 +00:00
co-authored by Cursor
commit e251a4cf60
367 changed files with 66268 additions and 0 deletions
+123
View File
@@ -0,0 +1,123 @@
<?php
namespace App\Services\Afia;
use Illuminate\Support\Facades\Http;
use RuntimeException;
/**
* Afia for Ladill Email an AI assistant scoped to buying & managing mailboxes
* and email domains. Self-contained: builds an email-specific system prompt +
* the user's 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 <<<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;
}
}