1 line
159 KiB
JSON
1 line
159 KiB
JSON
{"repo": "asifkst5555/settleanz", "files": {"config/ebook.php": {"2bcf3d38ad459ad986d5ab3129163e74e2dd7ecd": {"t": 1785746144.4379632, "n": 4191, "h": "736d283d83f10752", "c": "<?php\n\nreturn [\n /*\n |--------------------------------------------------------------------------\n | Storage Configuration\n |--------------------------------------------------------------------------\n */\n 'storage' => [\n 'disk' => env('EBOOK_STORAGE_DISK', 'local'),\n 'prefix' => 'ebooks',\n 'thumbnail_prefix' => 'thumbnails',\n 'allowed_extensions' => ['pdf', 'zip', 'docx', 'png', 'jpg', 'jpeg', 'gif', 'epub', 'mobi'],\n 'max_file_size' => env('EBOOK_MAX_FILE_SIZE', 50 * 1024 * 1024), // 50MB\n ],\n\n /*\n |--------------------------------------------------------------------------\n | Download Settings\n |--------------------------------------------------------------------------\n */\n 'download' => [\n 'token_expiry_hours' => env('EBOOK_TOKEN_EXPIRY_HOURS', 72),\n 'max_downloads_per_token' => env('EBOOK_MAX_DOWNLOADS_PER_TOKEN', 5),\n 'require_email_verification' => env('EBOOK_REQUIRE_EMAIL_VERIFICATION', false),\n 'rate_limit_per_ip' => env('EBOOK_RATE_LIMIT_PER_IP', 10),\n 'rate_limit_decay_minutes' => env('EBOOK_RATE_LIMIT_DECAY_MINUTES', 60),\n ],\n\n /*\n |--------------------------------------------------------------------------\n | Thumbnail Settings\n |--------------------------------------------------------------------------\n */\n 'thumbnail' => [\n 'width' => 300,\n 'height' => 400,\n 'format' => 'webp',\n 'quality' => 80,\n ],\n\n /*\n |--------------------------------------------------------------------------\n | AI Email Assistant\n |--------------------------------------------------------------------------\n */\n 'ai' => [\n 'provider' => env('AI_EMAIL_PROVIDER', 'openai'),\n 'model' => env('AI_EMAIL_MODEL', 'gpt-4o'),\n 'api_key' => env('AI_EMAIL_API_KEY'),\n 'max_tokens' => (int) env('AI_EMAIL_MAX_TOKENS', 2000),\n 'temperature' => (float) env('AI_EMAIL_TEMPERATURE', 0.7),\n 'timeout' => (int) env('AI_EMAIL_TIMEOUT', 60),\n\n 'providers' => [\n 'openai' => [\n 'url' => 'https://api.openai.com/v1/chat/completions',\n 'model' => 'gpt-4o',\n ],\n 'gemini' => [\n 'url' => 'https://generativelanguage.googleapis.com/v1/models/gemini-pro:generateContent',\n 'model' => 'gemini-pro',\n ],\n 'claude' => [\n 'url' => 'https://api.anthropic.com/v1/messages',\n 'model' => 'claude-3-opus-20240229',\n ],\n 'deepseek' => [\n 'url' => 'https://api.deepseek.com/v1/chat/completions',\n 'model' => 'deepseek-chat',\n ],\n ],\n ],\n\n /*\n |--------------------------------------------------------------------------\n | Admin Settings\n |--------------------------------------------------------------------------\n */\n 'admin' => [\n 'pagination' => [\n 'per_page' => 20,\n ],\n 'dashboard' => [\n 'recent_downloads_limit' => 10,\n 'top_ebooks_limit' => 5,\n 'days_range' => 30,\n ],\n ],\n\n /*\n |--------------------------------------------------------------------------\n | Lead Form Settings\n |--------------------------------------------------------------------------\n */\n /*\n |--------------------------------------------------------------------------\n | Default Lead Magnet Ebook\n |--------------------------------------------------------------------------\n |\n | The slug of the ebook used as the default lead magnet on the homepage\n | and other lead capture forms. Change this to match your primary ebook.\n |\n */\n 'default_ebook_slug' => env('EBOOK_DEFAULT_SLUG', 'settleanZ-new-arrival-checklist'),\n\n 'lead_form' => [\n 'require_phone' => env('EBOOK_REQUIRE_PHONE', false),\n 'require_company' => env('EBOOK_REQUIRE_COMPANY', false),\n 'require_country' => env('EBOOK_REQUIRE_COUNTRY', false),\n 'consent_required' => true,\n 'honeypot_field' => 'website_url',\n 'minimum_time_seconds' => 3,\n ],\n];\n"}, "ed8891e91686e9276fef65a24e9658c8bbc80d4e": {"t": 1785760147.9538133, "n": 4191, "h": "736d283d83f10752", "c": "<?php\n\nreturn [\n /*\n |--------------------------------------------------------------------------\n | Storage Configuration\n |--------------------------------------------------------------------------\n */\n 'storage' => [\n 'disk' => env('EBOOK_STORAGE_DISK', 'local'),\n 'prefix' => 'ebooks',\n 'thumbnail_prefix' => 'thumbnails',\n 'allowed_extensions' => ['pdf', 'zip', 'docx', 'png', 'jpg', 'jpeg', 'gif', 'epub', 'mobi'],\n 'max_file_size' => env('EBOOK_MAX_FILE_SIZE', 50 * 1024 * 1024), // 50MB\n ],\n\n /*\n |--------------------------------------------------------------------------\n | Download Settings\n |--------------------------------------------------------------------------\n */\n 'download' => [\n 'token_expiry_hours' => env('EBOOK_TOKEN_EXPIRY_HOURS', 72),\n 'max_downloads_per_token' => env('EBOOK_MAX_DOWNLOADS_PER_TOKEN', 5),\n 'require_email_verification' => env('EBOOK_REQUIRE_EMAIL_VERIFICATION', false),\n 'rate_limit_per_ip' => env('EBOOK_RATE_LIMIT_PER_IP', 10),\n 'rate_limit_decay_minutes' => env('EBOOK_RATE_LIMIT_DECAY_MINUTES', 60),\n ],\n\n /*\n |--------------------------------------------------------------------------\n | Thumbnail Settings\n |--------------------------------------------------------------------------\n */\n 'thumbnail' => [\n 'width' => 300,\n 'height' => 400,\n 'format' => 'webp',\n 'quality' => 80,\n ],\n\n /*\n |--------------------------------------------------------------------------\n | AI Email Assistant\n |--------------------------------------------------------------------------\n */\n 'ai' => [\n 'provider' => env('AI_EMAIL_PROVIDER', 'openai'),\n 'model' => env('AI_EMAIL_MODEL', 'gpt-4o'),\n 'api_key' => env('AI_EMAIL_API_KEY'),\n 'max_tokens' => (int) env('AI_EMAIL_MAX_TOKENS', 2000),\n 'temperature' => (float) env('AI_EMAIL_TEMPERATURE', 0.7),\n 'timeout' => (int) env('AI_EMAIL_TIMEOUT', 60),\n\n 'providers' => [\n 'openai' => [\n 'url' => 'https://api.openai.com/v1/chat/completions',\n 'model' => 'gpt-4o',\n ],\n 'gemini' => [\n 'url' => 'https://generativelanguage.googleapis.com/v1/models/gemini-pro:generateContent',\n 'model' => 'gemini-pro',\n ],\n 'claude' => [\n 'url' => 'https://api.anthropic.com/v1/messages',\n 'model' => 'claude-3-opus-20240229',\n ],\n 'deepseek' => [\n 'url' => 'https://api.deepseek.com/v1/chat/completions',\n 'model' => 'deepseek-chat',\n ],\n ],\n ],\n\n /*\n |--------------------------------------------------------------------------\n | Admin Settings\n |--------------------------------------------------------------------------\n */\n 'admin' => [\n 'pagination' => [\n 'per_page' => 20,\n ],\n 'dashboard' => [\n 'recent_downloads_limit' => 10,\n 'top_ebooks_limit' => 5,\n 'days_range' => 30,\n ],\n ],\n\n /*\n |--------------------------------------------------------------------------\n | Lead Form Settings\n |--------------------------------------------------------------------------\n */\n /*\n |--------------------------------------------------------------------------\n | Default Lead Magnet Ebook\n |--------------------------------------------------------------------------\n |\n | The slug of the ebook used as the default lead magnet on the homepage\n | and other lead capture forms. Change this to match your primary ebook.\n |\n */\n 'default_ebook_slug' => env('EBOOK_DEFAULT_SLUG', 'settleanZ-new-arrival-checklist'),\n\n 'lead_form' => [\n 'require_phone' => env('EBOOK_REQUIRE_PHONE', false),\n 'require_company' => env('EBOOK_REQUIRE_COMPANY', false),\n 'require_country' => env('EBOOK_REQUIRE_COUNTRY', false),\n 'consent_required' => true,\n 'honeypot_field' => 'website_url',\n 'minimum_time_seconds' => 3,\n ],\n];\n"}}, "app/Services/AiEmailService.php": {"2bcf3d38ad459ad986d5ab3129163e74e2dd7ecd": {"t": 1785746145.562572, "n": 30365, "h": "935b281a0f29b8f2", "c": "<?php\n\nnamespace App\\Services;\n\nuse App\\Models\\AiConversation;\nuse App\\Models\\AiMessage;\nuse App\\Models\\SiteSetting;\nuse Exception;\nuse Illuminate\\Http\\Client\\PendingRequest;\nuse Illuminate\\Support\\Facades\\Http;\nuse Illuminate\\Support\\Facades\\Log;\n\nclass AiEmailService\n{\n private string $provider;\n private string $model;\n private string $apiKey;\n private int $maxTokens;\n private float $temperature;\n private int $timeout;\n private string $baseUrl;\n\n public function __construct()\n {\n $this->provider = config('ebook.ai.provider', 'openai');\n $this->model = trim((string) SiteSetting::getValue('ai_openai_model', config('ebook.ai.model', 'gpt-4o')));\n $this->apiKey = trim((string) SiteSetting::getValue('ai_openai_api_key', config('ebook.ai.api_key')));\n $this->maxTokens = config('ebook.ai.max_tokens', 2000);\n $this->temperature = config('ebook.ai.temperature', 0.7);\n $this->timeout = config('ebook.ai.timeout', 60);\n $this->baseUrl = rtrim((string) SiteSetting::getValue('ai_openai_base_url', config('ebook.ai.providers.openai.url', 'https://api.openai.com/v1/chat/completions')), '/');\n }\n\n public function generateDownloadEmail(array $data, string $tone = 'professional', string $language = 'en'): array\n {\n $prompt = $this->buildDownloadEmailPrompt($data, $tone, $language);\n return $this->callAI($prompt, $this->getSystemPrompt());\n }\n\n public function rewriteEmail(string $content, string $tone, string $language = 'en'): array\n {\n $prompt = \"Rewrite the following email content in a {$tone} tone and in {$language} language.\\n\\nOriginal:\\n{$content}\\n\\nRewritten version:\\n\\nReturn ONLY valid JSON with exactly these keys:\\n- \\\"subject\\\": max 80 chars\\n- \\\"body_html\\\": full responsive HTML email (DOCTYPE, inline CSS, table-based, 600px max width)\\n- \\\"body_text\\\": plain text version\\n\\nDo NOT include markdown, code fences, explanations, or any text outside the JSON. Output ONLY the JSON object.\";\n return $this->callAI($prompt, $this->getSystemPrompt());\n }\n\n public function generateFollowUpEmail(array $context, string $tone = 'professional', string $language = 'en'): array\n {\n $prompt = $this->buildFollowUpPrompt($context, $tone, $language);\n return $this->callAI($prompt, $this->getSystemPrompt());\n }\n\n public function generateCampaignEmail(array $campaignData, string $tone = 'marketing', string $language = 'en'): array\n {\n $prompt = $this->buildCampaignPrompt($campaignData, $tone, $language);\n return $this->callAI($prompt, $this->getSystemPrompt());\n }\n\n public function copilotChat(string $message, string $systemPrompt, string $conversationHistory = ''): array\n {\n $text = $this->callAIRaw($message, $systemPrompt);\n return ['body_text' => $text];\n }\n\n public function copilotChatStructured(string $message, string $systemPrompt, array $historyMessages, float $temperature = 0.2): array\n {\n $text = $this->callAIRaw($message, $systemPrompt, $historyMessages);\n return ['body_text' => $text];\n }\n\n private function callAIStructured(string $message, string $systemPrompt, array $historyMessages, float $temperature): array\n {\n return match ($this->provider) {\n 'openai' => $this->callOpenAIStructured($message, $systemPrompt, $historyMessages, $temperature),\n 'gemini' => $this->callGeminiStructured($message, $systemPrompt, $historyMessages, $temperature),\n 'claude' => $this->callClaudeStructured($message, $systemPrompt, $historyMessages, $temperature),\n 'deepseek' => $this->callDeepSeekStructured($message, $systemPrompt, $historyMessages, $temperature),\n default => throw new Exception(\"Unsupported AI provider: {$this->provider}\"),\n };\n }\n\n private function callOpenAIStructured(string $message, string $systemPrompt, array $historyMessages, float $temperature): array\n {\n $messages = [['role' => 'system', 'content' => $systemPrompt]];\n\n foreach ($historyMessages as $msg) {\n $messages[] = [\n 'role' => $msg['role'] === 'assistant' ? 'assistant' : 'user',\n 'content' => $msg['content'],\n ];\n }\n\n $messages[] = ['role' => 'user', 'content' => $message];\n\n $url = str_ends_with($this->baseUrl, '/chat/completions')\n ? $this->baseUrl\n : $this->baseUrl . '/chat/completions';\n\n $response = $this->httpClient()->post($url, [\n 'model' => $this->model,\n 'messages' => $messages,\n 'max_tokens' => $this->maxTokens,\n 'temperature' => $temperature,\n ]);\n\n return $this->parseResponse($response->json(), 'openai');\n }\n\n private function callGeminiStructured(string $message, string $systemPrompt, array $historyMessages, float $temperature): array\n {\n $contents = [];\n\n foreach ($historyMessages as $msg) {\n $role = $msg['role'] === 'assistant' ? 'model' : 'user';\n $contents[] = ['role' => $role, 'parts' => [['text' => $msg['content']]]];\n }\n\n $contents[] = ['role' => 'user', 'parts' => [['text' => $message]]];\n\n $url = config('ebook.ai.providers.gemini.url') . \"?key={$this->apiKey}\";\n\n $response = $this->httpClient()->post($url, [\n 'contents' => $contents,\n 'systemInstruction' => $systemPrompt ? ['parts' => [['text' => $systemPrompt]]] : null,\n 'generationConfig' => [\n 'maxOutputTokens' => $this->maxTokens,\n 'temperature' => $temperature,\n ],\n ]);\n\n return $this->parseResponse($response->json(), 'gemini');\n }\n\n private function callClaudeStructured(string $message, string $systemPrompt, array $historyMessages, float $temperature): array\n {\n $claudeMessages = [];\n\n foreach ($historyMessages as $msg) {\n $claudeMessages[] = [\n 'role' => $msg['role'] === 'assistant' ? 'assistant' : 'user',\n 'content' => $msg['content'],\n ];\n }\n\n $claudeMessages[] = ['role' => 'user', 'content' => $message];\n\n $response = $this->httpClient()\n ->withHeaders([\n 'x-api-key' => $this->apiKey,\n 'anthropic-version' => '2023-06-01',\n ])\n ->post(config('ebook.ai.providers.claude.url', 'https://api.anthropic.com/v1/messages'), [\n 'model' => $this->model,\n 'system' => $systemPrompt ?: 'You are a helpful email marketing assistant.',\n 'messages' => $claudeMessages,\n 'max_tokens' => $this->maxTokens,\n 'temperature' => $temperature,\n ]);\n\n return $this->parseResponse($response->json(), 'claude');\n }\n\n private function callDeepSeekStructured(string $message, string $systemPrompt, array $historyMessages, float $temperature): array\n {\n $messages = [['role' => 'system', 'content' => $systemPrompt]];\n\n foreach ($historyMessages as $msg) {\n $messages[] = [\n 'role' => $msg['role'] === 'assistant' ? 'assistant' : 'user',\n 'content' => $msg['content'],\n ];\n }\n\n $messages[] = ['role' => 'user', 'content' => $message];\n\n $response = $this->httpClient()->post(config('ebook.ai.providers.deepseek.url', 'https://api.deepseek.com/v1/chat/completions'), [\n 'model' => $this->model,\n 'messages' => $messages,\n 'max_tokens' => $this->maxTokens,\n 'temperature' => $temperature,\n ]);\n\n return $this->parseResponse($response->json(), 'deepseek');\n }\n\n public function chat(string $message, string $conversationHistory = ''): array\n {\n $systemPrompt = <<<PROMPT\nYou are an AI Admin Assistant for an Ebook Lead Magnet & Digital Asset Delivery System.\n\nYou can help admins with:\n1. Sending download emails to leads\n2. Resending expired download links\n3. Generating follow-up emails\n4. Creating campaign emails\n5. Viewing download analytics\n6. Managing ebook leads\n\nIMPORTANT RULES:\n- Never share the actual file paths or internal URLs\n- Never expose API keys or sensitive configuration\n- Use the tools and commands available to you through the system\n- When asked to send emails, confirm with the admin before sending\n- Keep responses concise and actionable\n\nCurrent context: Admin managing ebook downloads and leads.\nPROMPT;\n\n $text = $this->callAIRaw($message, $systemPrompt . \"\\n\\n\" . $conversationHistory);\n return ['body_text' => $text];\n }\n\n private function buildDownloadEmailPrompt(array $data, string $tone, string $language): string\n {\n $author = $data['ebook_author'] ?? 'Our Team';\n $company = $data['company_name'] ?? config('app.name');\n\n return <<<PROMPT\nGenerate a professional download email in {$language} language with a {$tone} tone.\n\nLead Information:\n- Name: {$data['lead_name']}\n- Email: {$data['lead_email']}\n\nEbook Information:\n- Title: {$data['ebook_title']}\n- Description: {$data['ebook_description']}\n- Author: {$author}\n\nDownload Link: {$data['download_url']}\nExpires: {$data['expires_at']}\nCompany: {$company}\n\nPlease include:\n1. A warm greeting using the lead's name\n2. Thank them for their interest\n3. Brief description of what they'll learn\n4. Clear CTA with the download link\n5. Note about expiration\n6. Professional signature with company details\n7. Unsubscribe link at the bottom\n\nReturn ONLY valid JSON with exactly these keys:\n- \"subject\": max 80 chars, friendly and professional\n- \"body_html\": complete responsive HTML email with DOCTYPE, inline CSS, table-based layout, max width 600px, white background\n- \"body_text\": plain text version\n\nDo NOT include markdown, code fences, explanations, or any text outside the JSON. Output ONLY the JSON object.\nPROMPT;\n }\n\n private function buildFollowUpPrompt(array $context, string $tone, string $language): string\n {\n return <<<PROMPT\nGenerate a follow-up email in {$language} with a {$tone} tone.\n\nContext:\n- Lead Name: {$context['lead_name']}\n- Ebook: {$context['ebook_title']}\n- Days since download: {$context['days_since_download']}\n- Download Count: {$context['download_count']}\n\nReturn ONLY valid JSON with exactly these keys:\n- \"subject\": max 80 chars, friendly and professional\n- \"body_html\": complete responsive HTML email with DOCTYPE, inline CSS, table-based layout, max width 600px, white background\n- \"body_text\": plain text version\n\nDo NOT include markdown, code fences, explanations, or any text outside the JSON. Output ONLY the JSON object.\nPROMPT;\n }\n\n public function generateAutoReplyEmail(array $data, string $tone = 'warm', string $language = 'en'): array\n {\n $leadEmail = $data['lead_email'] ?? 'unknown';\n $formType = $data['form_type'] ?? 'unknown';\n\n Log::debug('[TRACE] AiEmailService::generateAutoReplyEmail', [\n 'form_type' => $formType,\n 'lead_email' => $leadEmail,\n 'provider' => $this->provider,\n 'model' => $this->model,\n 'api_key_set' => !empty($this->apiKey),\n 'api_key_length' => strlen($this->apiKey),\n ]);\n\n $prompt = $this->buildAutoReplyPrompt($data, $tone, $language);\n\n Log::debug('[TRACE] Calling AiEmailService::callAI', [\n 'form_type' => $formType,\n 'prompt_length' => strlen($prompt),\n ]);\n\n $start = microtime(true);\n try {\n $result = $this->callAI($prompt);\n $duration = round((microtime(true) - $start) * 1000);\n Log::debug('[TRACE] AiEmailService::callAI succeeded', [\n 'form_type' => $formType,\n 'duration_ms' => $duration,\n 'has_subject' => isset($result['subject']),\n 'has_body_html' => isset($result['body_html']),\n ]);\n return $result;\n } catch (\\Throwable $e) {\n $duration = round((microtime(true) - $start) * 1000);\n Log::debug('[TRACE] AiEmailService::callAI FAILED', [\n 'form_type' => $formType,\n 'duration_ms' => $duration,\n 'error' => $e->getMessage(),\n 'error_class' => get_class($e),\n ]);\n throw $e;\n }\n }\n\n private function buildAutoReplyPrompt(array $data, string $tone, string $language): string\n {\n $formType = $data['form_type'] ?? 'general';\n $formLabel = match ($formType) {\n 'contact-page' => 'Contact Form enquiry',\n 'package_booking' => 'Settlement Package booking request',\n 'homepage_roadmap' => 'Roadmap download request',\n 'ebook_download' => 'Ebook download request',\n 'ai_chat' => 'AI Chat consultation',\n default => 'enquiry',\n };\n\n $name = $data['lead_name'] ?? 'there';\n $company = $data['company_name'] ?? config('app.name');\n\n return <<<PROMPT\nGenerate a warm, professional auto-reply email in {$language} language with a {$tone} tone.\n\nContext:\n- This is an automated acknowledgment for a {$formLabel} submitted on {$company}.\n- Lead name: {$name}\n- Form type: {$formType}\n\nThe email must:\n1. Greet the lead by name warmly\n2. Acknowledge receipt of their {$formLabel}\n3. Reassure them that our team has received their request\n4. State clearly that a team member will respond within 24 hours\n5. If it's a contact enquiry, mention that we'll review their message carefully\n6. If it's a package booking, mention that we'll confirm their session details\n7. Close with a professional signature: \"Warm regards, The {$company} Team\"\n8. Include a brief P.S. that they can reply to this email if they have urgent questions\n\nImportant:\n- Keep the tone warm and human, not robotic\n- Do NOT include download links or promotional content\n- Do NOT mention AI or automation\n- Use the lead's name naturally in the greeting\n\nReturn ONLY valid JSON with exactly these keys:\n- \"subject\": a concise, friendly subject line (max 80 chars)\n- \"body_html\": complete responsive HTML email with DOCTYPE, inline CSS, table-based layout, max width 600px, white card background (#FFFFFF), body background #F8F4EC, professional typography (Arial/Helvetica/sans-serif), heading 28px, body 16px, primary color #0F766E\n- \"body_text\": plain text version\n\nDo NOT include markdown, code fences, explanations, or any text outside the JSON. Output ONLY the JSON object.\nPROMPT;\n }\n\n private function buildCampaignPrompt(array $data, string $tone, string $language): string\n {\n return <<<PROMPT\nGenerate a marketing campaign email in {$language} with a {$tone} tone.\n\nCampaign: {$data['campaign_name']}\nEbook: {$data['ebook_title']}\nDescription: {$data['campaign_description']}\n\nReturn ONLY valid JSON with exactly these keys:\n- \"subject\": max 80 chars\n- \"body_html\": complete responsive HTML email with DOCTYPE, inline CSS, table-based layout, max width 600px, white card\n- \"body_text\": plain text version\n\nDo NOT include markdown, code fences, explanations, or any text outside the JSON. Output ONLY the JSON object.\nPROMPT;\n }\n\n private function callAI(string $prompt, ?string $systemPrompt = null): array\n {\n return match ($this->provider) {\n 'openai' => $this->callOpenAI($prompt, $systemPrompt),\n 'gemini' => $this->callGemini($prompt, $systemPrompt),\n 'claude' => $this->callClaude($prompt, $systemPrompt),\n 'deepseek' => $this->callDeepSeek($prompt, $systemPrompt),\n default => throw new Exception(\"Unsupported AI provider: {$this->provider}\"),\n };\n }\n\n private function callOpenAI(string $prompt, ?string $systemPrompt = null): array\n {\n $messages = [];\n\n if ($systemPrompt) {\n $messages[] = ['role' => 'system', 'content' => $systemPrompt];\n }\n\n $messages[] = ['role' => 'user', 'content' => $prompt];\n\n $url = str_ends_with($this->baseUrl, '/chat/completions')\n ? $this->baseUrl\n : $this->baseUrl . '/chat/completions';\n\n $response = $this->httpClient()->post($url, [\n 'model' => $this->model,\n 'messages' => $messages,\n 'max_tokens' => $this->maxTokens,\n 'temperature' => $this->temperature,\n 'response_format' => ['type' => 'json_object'],\n ]);\n\n return $this->parseResponse($response->json(), 'openai');\n }\n\n private function callGemini(string $prompt, ?string $systemPrompt = null): array\n {\n $contents = [];\n\n if ($systemPrompt) {\n $contents[] = ['role' => 'user', 'parts' => [['text' => $systemPrompt]]];\n }\n\n $contents[] = ['role' => 'user', 'parts' => [['text' => $prompt]]];\n\n $url = config('ebook.ai.providers.gemini.url') . \"?key={$this->apiKey}\";\n\n $response = $this->httpClient()->post($url, [\n 'contents' => $contents,\n 'generationConfig' => [\n 'maxOutputTokens' => $this->maxTokens,\n 'temperature' => $this->temperature,\n ],\n ]);\n\n return $this->parseResponse($response->json(), 'gemini');\n }\n\n private function callClaude(string $prompt, ?string $systemPrompt = null): array\n {\n $response = $this->httpClient()\n ->withHeaders([\n 'x-api-key' => $this->apiKey,\n 'anthropic-version' => '2023-06-01',\n ])\n ->post(config('ebook.ai.providers.claude.url', 'https://api.anthropic.com/v1/messages'), [\n 'model' => $this->model,\n 'system' => $systemPrompt ?? 'You are a helpful email marketing assistant.',\n 'messages' => [\n ['role' => 'user', 'content' => $prompt],\n ],\n 'max_tokens' => $this->maxTokens,\n 'temperature' => $this->temperature,\n ]);\n\n return $this->parseResponse($response->json(), 'claude');\n }\n\n private function callDeepSeek(string $prompt, ?string $systemPrompt = null): array\n {\n $messages = [];\n\n if ($systemPrompt) {\n $messages[] = ['role' => 'system', 'content' => $systemPrompt];\n }\n\n $messages[] = ['role' => 'user', 'content' => $prompt];\n\n $response = $this->httpClient()->post(config('ebook.ai.providers.deepseek.url', 'https://api.deepseek.com/v1/chat/completions'), [\n 'model' => $this->model,\n 'messages' => $messages,\n 'max_tokens' => $this->maxTokens,\n 'temperature' => $this->temperature,\n ]);\n\n return $this->parseResponse($response->json(), 'deepseek');\n }\n\n private function parseResponse(array $response, string $provider): array\n {\n $content = match ($provider) {\n 'openai', 'deepseek' => $response['choices'][0]['message']['content'] ?? '',\n 'gemini' => $response['candidates'][0]['content']['parts'][0]['text'] ?? '',\n 'claude' => $response['content'][0]['text'] ?? '',\n default => throw new Exception(\"Unsupported provider for response parsing: {$provider}\"),\n };\n\n $content = $this->stripReasoningText($content);\n $content = preg_replace('/^```(?:json)?\\s*\\n?(.*?)\\n?```$/s', '$1', trim($content));\n\n $json = $this->decodeEmailJson($content);\n if ($json) {\n return $json;\n }\n\n foreach ($this->extractJsonCandidates($content) as $candidate) {\n $json = $this->decodeEmailJson($candidate);\n if ($json) {\n return $json;\n }\n }\n\n Log::warning('AI email response did not contain valid email JSON; using safe fallback.', [\n 'provider' => $provider,\n 'content_preview' => mb_substr($content, 0, 500),\n ]);\n\n return [\n 'subject' => 'Thank you from SettleANZ',\n 'body_html' => $this->safeFallbackEmailHtml(),\n 'body_text' => \"Hi there,\\n\\nThank you for your interest in SettleANZ. We received your request and our team will follow up shortly.\\n\\nWarm regards,\\nThe SettleANZ Team\",\n ];\n }\n\n private function stripReasoningText(string $content): string\n {\n $content = trim($content);\n $content = preg_replace('/<think>.*?<\\/think>/is', '', $content) ?? $content;\n return trim($content);\n }\n\n private function decodeEmailJson(string $content): ?array\n {\n $json = json_decode(trim($content), true);\n if (!is_array($json) || !isset($json['subject'], $json['body_html'])) {\n return null;\n }\n\n $subject = trim((string) $json['subject']);\n $bodyHtml = $this->normalizeEmailBody((string) $json['body_html']);\n $bodyText = isset($json['body_text']) && trim((string) $json['body_text']) !== ''\n ? $this->normalizePlainText((string) $json['body_text'])\n : $this->normalizePlainText(strip_tags($bodyHtml));\n\n if ($subject === '' || $bodyHtml === '') {\n return null;\n }\n\n if (!str_contains($bodyHtml, '<!DOCTYPE') && !str_contains($bodyHtml, '<html')) {\n $bodyHtml = $this->wrapInEmailHtml($bodyHtml);\n }\n\n return [\n 'subject' => mb_substr($subject, 0, 80),\n 'body_html' => $bodyHtml,\n 'body_text' => $bodyText,\n ];\n }\n\n private function extractJsonCandidates(string $content): array\n {\n $candidates = [];\n $start = strpos($content, '{');\n $end = strrpos($content, '}');\n\n if ($start !== false && $end !== false && $end > $start) {\n $candidates[] = substr($content, $start, $end - $start + 1);\n }\n\n if (preg_match_all('/\\{(?:(?:[^{}]|(?R))*)\\}/s', $content, $matches)) {\n $candidates = array_merge($candidates, $matches[0]);\n }\n\n return array_values(array_unique($candidates));\n }\n\n private function normalizeEmailBody(string $body): string\n {\n $body = trim($body);\n $body = str_replace([\"\\\\r\\\\n\", \"\\\\n\", \"\\\\r\", \"\\\\t\"], [\"\\n\", \"\\n\", \"\\n\", \"\\t\"], $body);\n return $body;\n }\n\n private function normalizePlainText(string $text): string\n {\n $text = html_entity_decode($text, ENT_QUOTES | ENT_HTML5, 'UTF-8');\n $text = str_replace([\"\\\\r\\\\n\", \"\\\\n\", \"\\\\r\", \"\\\\t\"], [\"\\n\", \"\\n\", \"\\n\", \"\\t\"], $text);\n return trim(preg_replace(\"/\\n{3,}/\", \"\\n\\n\", $text) ?? $text);\n }\n\n private function ensureValidEmailHtml(string $html): string\n {\n if (str_contains($html, '<!DOCTYPE') || str_contains($html, '<html')) {\n return $html;\n }\n return $this->wrapInEmailHtml($html);\n }\n\n private function wrapInEmailHtml(string $bodyContent): string\n {\n return '<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n</head>\n<body style=\"margin:0;padding:0;background-color:#F8F4EC;font-family:Arial,Helvetica,sans-serif;\">\n<center style=\"width:100%;background-color:#F8F4EC;\">\n<table role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" style=\"background-color:#F8F4EC;\">\n<tr>\n<td align=\"center\" style=\"padding:32px 16px;\">\n<table role=\"presentation\" width=\"600\" cellpadding=\"0\" cellspacing=\"0\" style=\"max-width:600px;width:100%;background-color:#FFFFFF;border-radius:12px;border:1px solid #E5E7EB;\">\n<tr>\n<td style=\"padding:40px;font-size:16px;line-height:1.6;color:#1F2937;\">\n' . $bodyContent . '\n</td>\n</tr>\n</table>\n</td>\n</tr>\n</table>\n</center>\n</body>\n</html>';\n }\n\n private function safeFallbackEmailHtml(): string\n {\n return $this->wrapInEmailHtml('<p style=\"margin:0 0 16px;\">Hi there,</p><p style=\"margin:0 0 16px;\">Thank you for your interest in SettleANZ. We received your request and our team will follow up shortly.</p><p style=\"margin:0;\">Warm regards,<br>The SettleANZ Team</p>');\n }\n\n private function httpClient(): PendingRequest\n {\n return Http::timeout($this->timeout)\n ->withToken($this->apiKey)\n ->withHeaders([\n 'Content-Type' => 'application/json',\n ])\n ->throw();\n }\n\n public function getConversationHistory(AiConversation $conversation): string\n {\n return $conversation->messages()\n ->orderBy('created_at')\n ->get()\n ->map(fn (AiMessage $msg) => \"{$msg->role}: {$msg->content}\")\n ->implode(\"\\n\");\n }\n\n public function getAvailableModels(): array\n {\n return [\n 'openai' => ['gpt-4o', 'gpt-4o-mini', 'gpt-4-turbo', 'gpt-3.5-turbo'],\n 'gemini' => ['gemini-pro', 'gemini-1.5-pro', 'gemini-1.5-flash'],\n 'claude' => ['claude-3-opus-20240229', 'claude-3-sonnet-20240229', 'claude-3-haiku-20240307'],\n 'deepseek' => ['deepseek-chat', 'deepseek-reasoner'],\n ];\n }\n\n private function getSystemPrompt(): string\n {\n return 'You are a professional email template generator. You output ONLY valid JSON. Never include markdown, code fences (```json), explanations, reasoning, or any text outside the JSON object. Your response must be parseable by json_decode() with no modification. The JSON must have exactly three keys: \"subject\" (string, max 80 chars), \"body_html\" (string, complete responsive HTML email document), \"body_text\" (string, plain text version).';\n }\n\n private function callAIRaw(string $prompt, ?string $systemPrompt = null, array $history = []): string\n {\n return match ($this->provider) {\n 'openai' => $this->callOpenAIRaw($prompt, $systemPrompt, $history),\n 'gemini' => $this->callGeminiRaw($prompt, $systemPrompt, $history),\n 'claude' => $this->callClaudeRaw($prompt, $systemPrompt, $history),\n 'deepseek' => $this->callDeepSeekRaw($prompt, $systemPrompt, $history),\n default => throw new Exception(\"Unsupported AI provider: {$this->provider}\"),\n };\n }\n\n private function callOpenAIRaw(string $prompt, ?string $systemPrompt = null, array $history = []): string\n {\n $messages = [];\n if ($systemPrompt) {\n $messages[] = ['role' => 'system', 'content' => $systemPrompt];\n }\n foreach ($history as $msg) {\n $messages[] = [\n 'role' => $msg['role'] === 'assistant' ? 'assistant' : 'user',\n 'content' => $msg['content'],\n ];\n }\n $messages[] = ['role' => 'user', 'content' => $prompt];\n\n $url = str_ends_with($this->baseUrl, '/chat/completions')\n ? $this->baseUrl\n : $this->baseUrl . '/chat/completions';\n\n $response = $this->httpClient()->post($url, [\n 'model' => $this->model,\n 'messages' => $messages,\n 'max_tokens' => $this->maxTokens,\n 'temperature' => $this->temperature,\n ]);\n\n $json = $response->json();\n return $json['choices'][0]['message']['content'] ?? '';\n }\n\n private function callGeminiRaw(string $prompt, ?string $systemPrompt = null, array $history = []): string\n {\n $contents = [];\n if ($systemPrompt) {\n $contents[] = ['role' => 'user', 'parts' => [['text' => $systemPrompt]]];\n }\n foreach ($history as $msg) {\n $parts = [['text' => $msg['content']]];\n $contents[] = [\n 'role' => $msg['role'] === 'assistant' ? 'model' : 'user',\n 'parts' => $parts,\n ];\n }\n $contents[] = ['role' => 'user', 'parts' => [['text' => $prompt]]];\n\n $url = config('ebook.ai.providers.gemini.url') . \"?key={$this->apiKey}\";\n $response = $this->httpClient()->post($url, [\n 'contents' => $contents,\n 'generationConfig' => [\n 'maxOutputTokens' => $this->maxTokens,\n 'temperature' => $this->temperature,\n ],\n ]);\n\n $json = $response->json();\n return $json['candidates'][0]['content']['parts'][0]['text'] ?? '';\n }\n\n private function callClaudeRaw(string $prompt, ?string $systemPrompt = null, array $history = []): string\n {\n $messages = [];\n foreach ($history as $msg) {\n $messages[] = [\n 'role' => $msg['role'] === 'assistant' ? 'assistant' : 'user',\n 'content' => $msg['content'],\n ];\n }\n $messages[] = ['role' => 'user', 'content' => $prompt];\n\n $response = $this->httpClient()\n ->withHeaders([\n 'x-api-key' => $this->apiKey,\n 'anthropic-version' => '2023-06-01',\n ])\n ->post(config('ebook.ai.providers.claude.url', 'https://api.anthropic.com/v1/messages'), [\n 'model' => $this->model,\n 'system' => $systemPrompt ?? 'You are a helpful assistant.',\n 'messages' => $messages,\n 'max_tokens' => $this->maxTokens,\n 'temperature' => $this->temperature,\n ]);\n\n $json = $response->json();\n return $json['content'][0]['text'] ?? '';\n }\n\n private function callDeepSeekRaw(string $prompt, ?string $systemPrompt = null, array $history = []): string\n {\n $messages = [];\n if ($systemPrompt) {\n $messages[] = ['role' => 'system', 'content' => $systemPrompt];\n }\n foreach ($history as $msg) {\n $messages[] = [\n 'role' => $msg['role'] === 'assistant' ? 'assistant' : 'user',\n 'content' => $msg['content'],\n ];\n }\n $messages[] = ['role' => 'user', 'content' => $prompt];\n\n $response = $this->httpClient()->post(config('ebook.ai.providers.deepseek.url', 'https://api.deepseek.com/v1/chat/completions'), [\n 'model' => $this->model,\n 'messages' => $messages,\n 'max_tokens' => $this->maxTokens,\n 'temperature' => $this->temperature,\n ]);\n\n $json = $response->json();\n return $json['choices'][0]['message']['content'] ?? '';\n }\n}\n"}, "949bb49fe944e97edafcc48ed207519e2c15c319": {"t": 1785760147.1979, "n": 30365, "h": "935b281a0f29b8f2", "c": "<?php\n\nnamespace App\\Services;\n\nuse App\\Models\\AiConversation;\nuse App\\Models\\AiMessage;\nuse App\\Models\\SiteSetting;\nuse Exception;\nuse Illuminate\\Http\\Client\\PendingRequest;\nuse Illuminate\\Support\\Facades\\Http;\nuse Illuminate\\Support\\Facades\\Log;\n\nclass AiEmailService\n{\n private string $provider;\n private string $model;\n private string $apiKey;\n private int $maxTokens;\n private float $temperature;\n private int $timeout;\n private string $baseUrl;\n\n public function __construct()\n {\n $this->provider = config('ebook.ai.provider', 'openai');\n $this->model = trim((string) SiteSetting::getValue('ai_openai_model', config('ebook.ai.model', 'gpt-4o')));\n $this->apiKey = trim((string) SiteSetting::getValue('ai_openai_api_key', config('ebook.ai.api_key')));\n $this->maxTokens = config('ebook.ai.max_tokens', 2000);\n $this->temperature = config('ebook.ai.temperature', 0.7);\n $this->timeout = config('ebook.ai.timeout', 60);\n $this->baseUrl = rtrim((string) SiteSetting::getValue('ai_openai_base_url', config('ebook.ai.providers.openai.url', 'https://api.openai.com/v1/chat/completions')), '/');\n }\n\n public function generateDownloadEmail(array $data, string $tone = 'professional', string $language = 'en'): array\n {\n $prompt = $this->buildDownloadEmailPrompt($data, $tone, $language);\n return $this->callAI($prompt, $this->getSystemPrompt());\n }\n\n public function rewriteEmail(string $content, string $tone, string $language = 'en'): array\n {\n $prompt = \"Rewrite the following email content in a {$tone} tone and in {$language} language.\\n\\nOriginal:\\n{$content}\\n\\nRewritten version:\\n\\nReturn ONLY valid JSON with exactly these keys:\\n- \\\"subject\\\": max 80 chars\\n- \\\"body_html\\\": full responsive HTML email (DOCTYPE, inline CSS, table-based, 600px max width)\\n- \\\"body_text\\\": plain text version\\n\\nDo NOT include markdown, code fences, explanations, or any text outside the JSON. Output ONLY the JSON object.\";\n return $this->callAI($prompt, $this->getSystemPrompt());\n }\n\n public function generateFollowUpEmail(array $context, string $tone = 'professional', string $language = 'en'): array\n {\n $prompt = $this->buildFollowUpPrompt($context, $tone, $language);\n return $this->callAI($prompt, $this->getSystemPrompt());\n }\n\n public function generateCampaignEmail(array $campaignData, string $tone = 'marketing', string $language = 'en'): array\n {\n $prompt = $this->buildCampaignPrompt($campaignData, $tone, $language);\n return $this->callAI($prompt, $this->getSystemPrompt());\n }\n\n public function copilotChat(string $message, string $systemPrompt, string $conversationHistory = ''): array\n {\n $text = $this->callAIRaw($message, $systemPrompt);\n return ['body_text' => $text];\n }\n\n public function copilotChatStructured(string $message, string $systemPrompt, array $historyMessages, float $temperature = 0.2): array\n {\n $text = $this->callAIRaw($message, $systemPrompt, $historyMessages);\n return ['body_text' => $text];\n }\n\n private function callAIStructured(string $message, string $systemPrompt, array $historyMessages, float $temperature): array\n {\n return match ($this->provider) {\n 'openai' => $this->callOpenAIStructured($message, $systemPrompt, $historyMessages, $temperature),\n 'gemini' => $this->callGeminiStructured($message, $systemPrompt, $historyMessages, $temperature),\n 'claude' => $this->callClaudeStructured($message, $systemPrompt, $historyMessages, $temperature),\n 'deepseek' => $this->callDeepSeekStructured($message, $systemPrompt, $historyMessages, $temperature),\n default => throw new Exception(\"Unsupported AI provider: {$this->provider}\"),\n };\n }\n\n private function callOpenAIStructured(string $message, string $systemPrompt, array $historyMessages, float $temperature): array\n {\n $messages = [['role' => 'system', 'content' => $systemPrompt]];\n\n foreach ($historyMessages as $msg) {\n $messages[] = [\n 'role' => $msg['role'] === 'assistant' ? 'assistant' : 'user',\n 'content' => $msg['content'],\n ];\n }\n\n $messages[] = ['role' => 'user', 'content' => $message];\n\n $url = str_ends_with($this->baseUrl, '/chat/completions')\n ? $this->baseUrl\n : $this->baseUrl . '/chat/completions';\n\n $response = $this->httpClient()->post($url, [\n 'model' => $this->model,\n 'messages' => $messages,\n 'max_tokens' => $this->maxTokens,\n 'temperature' => $temperature,\n ]);\n\n return $this->parseResponse($response->json(), 'openai');\n }\n\n private function callGeminiStructured(string $message, string $systemPrompt, array $historyMessages, float $temperature): array\n {\n $contents = [];\n\n foreach ($historyMessages as $msg) {\n $role = $msg['role'] === 'assistant' ? 'model' : 'user';\n $contents[] = ['role' => $role, 'parts' => [['text' => $msg['content']]]];\n }\n\n $contents[] = ['role' => 'user', 'parts' => [['text' => $message]]];\n\n $url = config('ebook.ai.providers.gemini.url') . \"?key={$this->apiKey}\";\n\n $response = $this->httpClient()->post($url, [\n 'contents' => $contents,\n 'systemInstruction' => $systemPrompt ? ['parts' => [['text' => $systemPrompt]]] : null,\n 'generationConfig' => [\n 'maxOutputTokens' => $this->maxTokens,\n 'temperature' => $temperature,\n ],\n ]);\n\n return $this->parseResponse($response->json(), 'gemini');\n }\n\n private function callClaudeStructured(string $message, string $systemPrompt, array $historyMessages, float $temperature): array\n {\n $claudeMessages = [];\n\n foreach ($historyMessages as $msg) {\n $claudeMessages[] = [\n 'role' => $msg['role'] === 'assistant' ? 'assistant' : 'user',\n 'content' => $msg['content'],\n ];\n }\n\n $claudeMessages[] = ['role' => 'user', 'content' => $message];\n\n $response = $this->httpClient()\n ->withHeaders([\n 'x-api-key' => $this->apiKey,\n 'anthropic-version' => '2023-06-01',\n ])\n ->post(config('ebook.ai.providers.claude.url', 'https://api.anthropic.com/v1/messages'), [\n 'model' => $this->model,\n 'system' => $systemPrompt ?: 'You are a helpful email marketing assistant.',\n 'messages' => $claudeMessages,\n 'max_tokens' => $this->maxTokens,\n 'temperature' => $temperature,\n ]);\n\n return $this->parseResponse($response->json(), 'claude');\n }\n\n private function callDeepSeekStructured(string $message, string $systemPrompt, array $historyMessages, float $temperature): array\n {\n $messages = [['role' => 'system', 'content' => $systemPrompt]];\n\n foreach ($historyMessages as $msg) {\n $messages[] = [\n 'role' => $msg['role'] === 'assistant' ? 'assistant' : 'user',\n 'content' => $msg['content'],\n ];\n }\n\n $messages[] = ['role' => 'user', 'content' => $message];\n\n $response = $this->httpClient()->post(config('ebook.ai.providers.deepseek.url', 'https://api.deepseek.com/v1/chat/completions'), [\n 'model' => $this->model,\n 'messages' => $messages,\n 'max_tokens' => $this->maxTokens,\n 'temperature' => $temperature,\n ]);\n\n return $this->parseResponse($response->json(), 'deepseek');\n }\n\n public function chat(string $message, string $conversationHistory = ''): array\n {\n $systemPrompt = <<<PROMPT\nYou are an AI Admin Assistant for an Ebook Lead Magnet & Digital Asset Delivery System.\n\nYou can help admins with:\n1. Sending download emails to leads\n2. Resending expired download links\n3. Generating follow-up emails\n4. Creating campaign emails\n5. Viewing download analytics\n6. Managing ebook leads\n\nIMPORTANT RULES:\n- Never share the actual file paths or internal URLs\n- Never expose API keys or sensitive configuration\n- Use the tools and commands available to you through the system\n- When asked to send emails, confirm with the admin before sending\n- Keep responses concise and actionable\n\nCurrent context: Admin managing ebook downloads and leads.\nPROMPT;\n\n $text = $this->callAIRaw($message, $systemPrompt . \"\\n\\n\" . $conversationHistory);\n return ['body_text' => $text];\n }\n\n private function buildDownloadEmailPrompt(array $data, string $tone, string $language): string\n {\n $author = $data['ebook_author'] ?? 'Our Team';\n $company = $data['company_name'] ?? config('app.name');\n\n return <<<PROMPT\nGenerate a professional download email in {$language} language with a {$tone} tone.\n\nLead Information:\n- Name: {$data['lead_name']}\n- Email: {$data['lead_email']}\n\nEbook Information:\n- Title: {$data['ebook_title']}\n- Description: {$data['ebook_description']}\n- Author: {$author}\n\nDownload Link: {$data['download_url']}\nExpires: {$data['expires_at']}\nCompany: {$company}\n\nPlease include:\n1. A warm greeting using the lead's name\n2. Thank them for their interest\n3. Brief description of what they'll learn\n4. Clear CTA with the download link\n5. Note about expiration\n6. Professional signature with company details\n7. Unsubscribe link at the bottom\n\nReturn ONLY valid JSON with exactly these keys:\n- \"subject\": max 80 chars, friendly and professional\n- \"body_html\": complete responsive HTML email with DOCTYPE, inline CSS, table-based layout, max width 600px, white background\n- \"body_text\": plain text version\n\nDo NOT include markdown, code fences, explanations, or any text outside the JSON. Output ONLY the JSON object.\nPROMPT;\n }\n\n private function buildFollowUpPrompt(array $context, string $tone, string $language): string\n {\n return <<<PROMPT\nGenerate a follow-up email in {$language} with a {$tone} tone.\n\nContext:\n- Lead Name: {$context['lead_name']}\n- Ebook: {$context['ebook_title']}\n- Days since download: {$context['days_since_download']}\n- Download Count: {$context['download_count']}\n\nReturn ONLY valid JSON with exactly these keys:\n- \"subject\": max 80 chars, friendly and professional\n- \"body_html\": complete responsive HTML email with DOCTYPE, inline CSS, table-based layout, max width 600px, white background\n- \"body_text\": plain text version\n\nDo NOT include markdown, code fences, explanations, or any text outside the JSON. Output ONLY the JSON object.\nPROMPT;\n }\n\n public function generateAutoReplyEmail(array $data, string $tone = 'warm', string $language = 'en'): array\n {\n $leadEmail = $data['lead_email'] ?? 'unknown';\n $formType = $data['form_type'] ?? 'unknown';\n\n Log::debug('[TRACE] AiEmailService::generateAutoReplyEmail', [\n 'form_type' => $formType,\n 'lead_email' => $leadEmail,\n 'provider' => $this->provider,\n 'model' => $this->model,\n 'api_key_set' => !empty($this->apiKey),\n 'api_key_length' => strlen($this->apiKey),\n ]);\n\n $prompt = $this->buildAutoReplyPrompt($data, $tone, $language);\n\n Log::debug('[TRACE] Calling AiEmailService::callAI', [\n 'form_type' => $formType,\n 'prompt_length' => strlen($prompt),\n ]);\n\n $start = microtime(true);\n try {\n $result = $this->callAI($prompt);\n $duration = round((microtime(true) - $start) * 1000);\n Log::debug('[TRACE] AiEmailService::callAI succeeded', [\n 'form_type' => $formType,\n 'duration_ms' => $duration,\n 'has_subject' => isset($result['subject']),\n 'has_body_html' => isset($result['body_html']),\n ]);\n return $result;\n } catch (\\Throwable $e) {\n $duration = round((microtime(true) - $start) * 1000);\n Log::debug('[TRACE] AiEmailService::callAI FAILED', [\n 'form_type' => $formType,\n 'duration_ms' => $duration,\n 'error' => $e->getMessage(),\n 'error_class' => get_class($e),\n ]);\n throw $e;\n }\n }\n\n private function buildAutoReplyPrompt(array $data, string $tone, string $language): string\n {\n $formType = $data['form_type'] ?? 'general';\n $formLabel = match ($formType) {\n 'contact-page' => 'Contact Form enquiry',\n 'package_booking' => 'Settlement Package booking request',\n 'homepage_roadmap' => 'Roadmap download request',\n 'ebook_download' => 'Ebook download request',\n 'ai_chat' => 'AI Chat consultation',\n default => 'enquiry',\n };\n\n $name = $data['lead_name'] ?? 'there';\n $company = $data['company_name'] ?? config('app.name');\n\n return <<<PROMPT\nGenerate a warm, professional auto-reply email in {$language} language with a {$tone} tone.\n\nContext:\n- This is an automated acknowledgment for a {$formLabel} submitted on {$company}.\n- Lead name: {$name}\n- Form type: {$formType}\n\nThe email must:\n1. Greet the lead by name warmly\n2. Acknowledge receipt of their {$formLabel}\n3. Reassure them that our team has received their request\n4. State clearly that a team member will respond within 24 hours\n5. If it's a contact enquiry, mention that we'll review their message carefully\n6. If it's a package booking, mention that we'll confirm their session details\n7. Close with a professional signature: \"Warm regards, The {$company} Team\"\n8. Include a brief P.S. that they can reply to this email if they have urgent questions\n\nImportant:\n- Keep the tone warm and human, not robotic\n- Do NOT include download links or promotional content\n- Do NOT mention AI or automation\n- Use the lead's name naturally in the greeting\n\nReturn ONLY valid JSON with exactly these keys:\n- \"subject\": a concise, friendly subject line (max 80 chars)\n- \"body_html\": complete responsive HTML email with DOCTYPE, inline CSS, table-based layout, max width 600px, white card background (#FFFFFF), body background #F8F4EC, professional typography (Arial/Helvetica/sans-serif), heading 28px, body 16px, primary color #0F766E\n- \"body_text\": plain text version\n\nDo NOT include markdown, code fences, explanations, or any text outside the JSON. Output ONLY the JSON object.\nPROMPT;\n }\n\n private function buildCampaignPrompt(array $data, string $tone, string $language): string\n {\n return <<<PROMPT\nGenerate a marketing campaign email in {$language} with a {$tone} tone.\n\nCampaign: {$data['campaign_name']}\nEbook: {$data['ebook_title']}\nDescription: {$data['campaign_description']}\n\nReturn ONLY valid JSON with exactly these keys:\n- \"subject\": max 80 chars\n- \"body_html\": complete responsive HTML email with DOCTYPE, inline CSS, table-based layout, max width 600px, white card\n- \"body_text\": plain text version\n\nDo NOT include markdown, code fences, explanations, or any text outside the JSON. Output ONLY the JSON object.\nPROMPT;\n }\n\n private function callAI(string $prompt, ?string $systemPrompt = null): array\n {\n return match ($this->provider) {\n 'openai' => $this->callOpenAI($prompt, $systemPrompt),\n 'gemini' => $this->callGemini($prompt, $systemPrompt),\n 'claude' => $this->callClaude($prompt, $systemPrompt),\n 'deepseek' => $this->callDeepSeek($prompt, $systemPrompt),\n default => throw new Exception(\"Unsupported AI provider: {$this->provider}\"),\n };\n }\n\n private function callOpenAI(string $prompt, ?string $systemPrompt = null): array\n {\n $messages = [];\n\n if ($systemPrompt) {\n $messages[] = ['role' => 'system', 'content' => $systemPrompt];\n }\n\n $messages[] = ['role' => 'user', 'content' => $prompt];\n\n $url = str_ends_with($this->baseUrl, '/chat/completions')\n ? $this->baseUrl\n : $this->baseUrl . '/chat/completions';\n\n $response = $this->httpClient()->post($url, [\n 'model' => $this->model,\n 'messages' => $messages,\n 'max_tokens' => $this->maxTokens,\n 'temperature' => $this->temperature,\n 'response_format' => ['type' => 'json_object'],\n ]);\n\n return $this->parseResponse($response->json(), 'openai');\n }\n\n private function callGemini(string $prompt, ?string $systemPrompt = null): array\n {\n $contents = [];\n\n if ($systemPrompt) {\n $contents[] = ['role' => 'user', 'parts' => [['text' => $systemPrompt]]];\n }\n\n $contents[] = ['role' => 'user', 'parts' => [['text' => $prompt]]];\n\n $url = config('ebook.ai.providers.gemini.url') . \"?key={$this->apiKey}\";\n\n $response = $this->httpClient()->post($url, [\n 'contents' => $contents,\n 'generationConfig' => [\n 'maxOutputTokens' => $this->maxTokens,\n 'temperature' => $this->temperature,\n ],\n ]);\n\n return $this->parseResponse($response->json(), 'gemini');\n }\n\n private function callClaude(string $prompt, ?string $systemPrompt = null): array\n {\n $response = $this->httpClient()\n ->withHeaders([\n 'x-api-key' => $this->apiKey,\n 'anthropic-version' => '2023-06-01',\n ])\n ->post(config('ebook.ai.providers.claude.url', 'https://api.anthropic.com/v1/messages'), [\n 'model' => $this->model,\n 'system' => $systemPrompt ?? 'You are a helpful email marketing assistant.',\n 'messages' => [\n ['role' => 'user', 'content' => $prompt],\n ],\n 'max_tokens' => $this->maxTokens,\n 'temperature' => $this->temperature,\n ]);\n\n return $this->parseResponse($response->json(), 'claude');\n }\n\n private function callDeepSeek(string $prompt, ?string $systemPrompt = null): array\n {\n $messages = [];\n\n if ($systemPrompt) {\n $messages[] = ['role' => 'system', 'content' => $systemPrompt];\n }\n\n $messages[] = ['role' => 'user', 'content' => $prompt];\n\n $response = $this->httpClient()->post(config('ebook.ai.providers.deepseek.url', 'https://api.deepseek.com/v1/chat/completions'), [\n 'model' => $this->model,\n 'messages' => $messages,\n 'max_tokens' => $this->maxTokens,\n 'temperature' => $this->temperature,\n ]);\n\n return $this->parseResponse($response->json(), 'deepseek');\n }\n\n private function parseResponse(array $response, string $provider): array\n {\n $content = match ($provider) {\n 'openai', 'deepseek' => $response['choices'][0]['message']['content'] ?? '',\n 'gemini' => $response['candidates'][0]['content']['parts'][0]['text'] ?? '',\n 'claude' => $response['content'][0]['text'] ?? '',\n default => throw new Exception(\"Unsupported provider for response parsing: {$provider}\"),\n };\n\n $content = $this->stripReasoningText($content);\n $content = preg_replace('/^```(?:json)?\\s*\\n?(.*?)\\n?```$/s', '$1', trim($content));\n\n $json = $this->decodeEmailJson($content);\n if ($json) {\n return $json;\n }\n\n foreach ($this->extractJsonCandidates($content) as $candidate) {\n $json = $this->decodeEmailJson($candidate);\n if ($json) {\n return $json;\n }\n }\n\n Log::warning('AI email response did not contain valid email JSON; using safe fallback.', [\n 'provider' => $provider,\n 'content_preview' => mb_substr($content, 0, 500),\n ]);\n\n return [\n 'subject' => 'Thank you from SettleANZ',\n 'body_html' => $this->safeFallbackEmailHtml(),\n 'body_text' => \"Hi there,\\n\\nThank you for your interest in SettleANZ. We received your request and our team will follow up shortly.\\n\\nWarm regards,\\nThe SettleANZ Team\",\n ];\n }\n\n private function stripReasoningText(string $content): string\n {\n $content = trim($content);\n $content = preg_replace('/<think>.*?<\\/think>/is', '', $content) ?? $content;\n return trim($content);\n }\n\n private function decodeEmailJson(string $content): ?array\n {\n $json = json_decode(trim($content), true);\n if (!is_array($json) || !isset($json['subject'], $json['body_html'])) {\n return null;\n }\n\n $subject = trim((string) $json['subject']);\n $bodyHtml = $this->normalizeEmailBody((string) $json['body_html']);\n $bodyText = isset($json['body_text']) && trim((string) $json['body_text']) !== ''\n ? $this->normalizePlainText((string) $json['body_text'])\n : $this->normalizePlainText(strip_tags($bodyHtml));\n\n if ($subject === '' || $bodyHtml === '') {\n return null;\n }\n\n if (!str_contains($bodyHtml, '<!DOCTYPE') && !str_contains($bodyHtml, '<html')) {\n $bodyHtml = $this->wrapInEmailHtml($bodyHtml);\n }\n\n return [\n 'subject' => mb_substr($subject, 0, 80),\n 'body_html' => $bodyHtml,\n 'body_text' => $bodyText,\n ];\n }\n\n private function extractJsonCandidates(string $content): array\n {\n $candidates = [];\n $start = strpos($content, '{');\n $end = strrpos($content, '}');\n\n if ($start !== false && $end !== false && $end > $start) {\n $candidates[] = substr($content, $start, $end - $start + 1);\n }\n\n if (preg_match_all('/\\{(?:(?:[^{}]|(?R))*)\\}/s', $content, $matches)) {\n $candidates = array_merge($candidates, $matches[0]);\n }\n\n return array_values(array_unique($candidates));\n }\n\n private function normalizeEmailBody(string $body): string\n {\n $body = trim($body);\n $body = str_replace([\"\\\\r\\\\n\", \"\\\\n\", \"\\\\r\", \"\\\\t\"], [\"\\n\", \"\\n\", \"\\n\", \"\\t\"], $body);\n return $body;\n }\n\n private function normalizePlainText(string $text): string\n {\n $text = html_entity_decode($text, ENT_QUOTES | ENT_HTML5, 'UTF-8');\n $text = str_replace([\"\\\\r\\\\n\", \"\\\\n\", \"\\\\r\", \"\\\\t\"], [\"\\n\", \"\\n\", \"\\n\", \"\\t\"], $text);\n return trim(preg_replace(\"/\\n{3,}/\", \"\\n\\n\", $text) ?? $text);\n }\n\n private function ensureValidEmailHtml(string $html): string\n {\n if (str_contains($html, '<!DOCTYPE') || str_contains($html, '<html')) {\n return $html;\n }\n return $this->wrapInEmailHtml($html);\n }\n\n private function wrapInEmailHtml(string $bodyContent): string\n {\n return '<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n</head>\n<body style=\"margin:0;padding:0;background-color:#F8F4EC;font-family:Arial,Helvetica,sans-serif;\">\n<center style=\"width:100%;background-color:#F8F4EC;\">\n<table role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" style=\"background-color:#F8F4EC;\">\n<tr>\n<td align=\"center\" style=\"padding:32px 16px;\">\n<table role=\"presentation\" width=\"600\" cellpadding=\"0\" cellspacing=\"0\" style=\"max-width:600px;width:100%;background-color:#FFFFFF;border-radius:12px;border:1px solid #E5E7EB;\">\n<tr>\n<td style=\"padding:40px;font-size:16px;line-height:1.6;color:#1F2937;\">\n' . $bodyContent . '\n</td>\n</tr>\n</table>\n</td>\n</tr>\n</table>\n</center>\n</body>\n</html>';\n }\n\n private function safeFallbackEmailHtml(): string\n {\n return $this->wrapInEmailHtml('<p style=\"margin:0 0 16px;\">Hi there,</p><p style=\"margin:0 0 16px;\">Thank you for your interest in SettleANZ. We received your request and our team will follow up shortly.</p><p style=\"margin:0;\">Warm regards,<br>The SettleANZ Team</p>');\n }\n\n private function httpClient(): PendingRequest\n {\n return Http::timeout($this->timeout)\n ->withToken($this->apiKey)\n ->withHeaders([\n 'Content-Type' => 'application/json',\n ])\n ->throw();\n }\n\n public function getConversationHistory(AiConversation $conversation): string\n {\n return $conversation->messages()\n ->orderBy('created_at')\n ->get()\n ->map(fn (AiMessage $msg) => \"{$msg->role}: {$msg->content}\")\n ->implode(\"\\n\");\n }\n\n public function getAvailableModels(): array\n {\n return [\n 'openai' => ['gpt-4o', 'gpt-4o-mini', 'gpt-4-turbo', 'gpt-3.5-turbo'],\n 'gemini' => ['gemini-pro', 'gemini-1.5-pro', 'gemini-1.5-flash'],\n 'claude' => ['claude-3-opus-20240229', 'claude-3-sonnet-20240229', 'claude-3-haiku-20240307'],\n 'deepseek' => ['deepseek-chat', 'deepseek-reasoner'],\n ];\n }\n\n private function getSystemPrompt(): string\n {\n return 'You are a professional email template generator. You output ONLY valid JSON. Never include markdown, code fences (```json), explanations, reasoning, or any text outside the JSON object. Your response must be parseable by json_decode() with no modification. The JSON must have exactly three keys: \"subject\" (string, max 80 chars), \"body_html\" (string, complete responsive HTML email document), \"body_text\" (string, plain text version).';\n }\n\n private function callAIRaw(string $prompt, ?string $systemPrompt = null, array $history = []): string\n {\n return match ($this->provider) {\n 'openai' => $this->callOpenAIRaw($prompt, $systemPrompt, $history),\n 'gemini' => $this->callGeminiRaw($prompt, $systemPrompt, $history),\n 'claude' => $this->callClaudeRaw($prompt, $systemPrompt, $history),\n 'deepseek' => $this->callDeepSeekRaw($prompt, $systemPrompt, $history),\n default => throw new Exception(\"Unsupported AI provider: {$this->provider}\"),\n };\n }\n\n private function callOpenAIRaw(string $prompt, ?string $systemPrompt = null, array $history = []): string\n {\n $messages = [];\n if ($systemPrompt) {\n $messages[] = ['role' => 'system', 'content' => $systemPrompt];\n }\n foreach ($history as $msg) {\n $messages[] = [\n 'role' => $msg['role'] === 'assistant' ? 'assistant' : 'user',\n 'content' => $msg['content'],\n ];\n }\n $messages[] = ['role' => 'user', 'content' => $prompt];\n\n $url = str_ends_with($this->baseUrl, '/chat/completions')\n ? $this->baseUrl\n : $this->baseUrl . '/chat/completions';\n\n $response = $this->httpClient()->post($url, [\n 'model' => $this->model,\n 'messages' => $messages,\n 'max_tokens' => $this->maxTokens,\n 'temperature' => $this->temperature,\n ]);\n\n $json = $response->json();\n return $json['choices'][0]['message']['content'] ?? '';\n }\n\n private function callGeminiRaw(string $prompt, ?string $systemPrompt = null, array $history = []): string\n {\n $contents = [];\n if ($systemPrompt) {\n $contents[] = ['role' => 'user', 'parts' => [['text' => $systemPrompt]]];\n }\n foreach ($history as $msg) {\n $parts = [['text' => $msg['content']]];\n $contents[] = [\n 'role' => $msg['role'] === 'assistant' ? 'model' : 'user',\n 'parts' => $parts,\n ];\n }\n $contents[] = ['role' => 'user', 'parts' => [['text' => $prompt]]];\n\n $url = config('ebook.ai.providers.gemini.url') . \"?key={$this->apiKey}\";\n $response = $this->httpClient()->post($url, [\n 'contents' => $contents,\n 'generationConfig' => [\n 'maxOutputTokens' => $this->maxTokens,\n 'temperature' => $this->temperature,\n ],\n ]);\n\n $json = $response->json();\n return $json['candidates'][0]['content']['parts'][0]['text'] ?? '';\n }\n\n private function callClaudeRaw(string $prompt, ?string $systemPrompt = null, array $history = []): string\n {\n $messages = [];\n foreach ($history as $msg) {\n $messages[] = [\n 'role' => $msg['role'] === 'assistant' ? 'assistant' : 'user',\n 'content' => $msg['content'],\n ];\n }\n $messages[] = ['role' => 'user', 'content' => $prompt];\n\n $response = $this->httpClient()\n ->withHeaders([\n 'x-api-key' => $this->apiKey,\n 'anthropic-version' => '2023-06-01',\n ])\n ->post(config('ebook.ai.providers.claude.url', 'https://api.anthropic.com/v1/messages'), [\n 'model' => $this->model,\n 'system' => $systemPrompt ?? 'You are a helpful assistant.',\n 'messages' => $messages,\n 'max_tokens' => $this->maxTokens,\n 'temperature' => $this->temperature,\n ]);\n\n $json = $response->json();\n return $json['content'][0]['text'] ?? '';\n }\n\n private function callDeepSeekRaw(string $prompt, ?string $systemPrompt = null, array $history = []): string\n {\n $messages = [];\n if ($systemPrompt) {\n $messages[] = ['role' => 'system', 'content' => $systemPrompt];\n }\n foreach ($history as $msg) {\n $messages[] = [\n 'role' => $msg['role'] === 'assistant' ? 'assistant' : 'user',\n 'content' => $msg['content'],\n ];\n }\n $messages[] = ['role' => 'user', 'content' => $prompt];\n\n $response = $this->httpClient()->post(config('ebook.ai.providers.deepseek.url', 'https://api.deepseek.com/v1/chat/completions'), [\n 'model' => $this->model,\n 'messages' => $messages,\n 'max_tokens' => $this->maxTokens,\n 'temperature' => $this->temperature,\n ]);\n\n $json = $response->json();\n return $json['choices'][0]['message']['content'] ?? '';\n }\n}\n"}, "266fd7e5e796110e2cbe5b89d68ecb556bc8b1bc": {"t": 1785760149.84859, "n": 25586, "h": "1f24a30311697ede", "c": "<?php\n\nnamespace App\\Services;\n\nuse App\\Models\\AiConversation;\nuse App\\Models\\AiMessage;\nuse App\\Models\\SiteSetting;\nuse Exception;\nuse Illuminate\\Http\\Client\\PendingRequest;\nuse Illuminate\\Support\\Facades\\Http;\nuse Illuminate\\Support\\Facades\\Log;\n\nclass AiEmailService\n{\n private string $provider;\n private string $model;\n private string $apiKey;\n private int $maxTokens;\n private float $temperature;\n private int $timeout;\n private string $baseUrl;\n\n public function __construct()\n {\n $this->provider = config('ebook.ai.provider', 'openai');\n $this->model = trim((string) SiteSetting::getValue('ai_openai_model', config('ebook.ai.model', 'gpt-4o')));\n $this->apiKey = trim((string) SiteSetting::getValue('ai_openai_api_key', config('ebook.ai.api_key')));\n $this->maxTokens = config('ebook.ai.max_tokens', 2000);\n $this->temperature = config('ebook.ai.temperature', 0.7);\n $this->timeout = config('ebook.ai.timeout', 60);\n $this->baseUrl = rtrim((string) SiteSetting::getValue('ai_openai_base_url', config('ebook.ai.providers.openai.url', 'https://api.openai.com/v1/chat/completions')), '/');\n }\n\n public function generateDownloadEmail(array $data, string $tone = 'professional', string $language = 'en'): array\n {\n $prompt = $this->buildDownloadEmailPrompt($data, $tone, $language);\n return $this->callAI($prompt, $this->getSystemPrompt());\n }\n\n public function rewriteEmail(string $content, string $tone, string $language = 'en'): array\n {\n $prompt = \"Rewrite the following email content in a {$tone} tone and in {$language} language.\\n\\nOriginal:\\n{$content}\\n\\nRewritten version:\\n\\nReturn ONLY valid JSON with exactly these keys:\\n- \\\"subject\\\": max 80 chars\\n- \\\"body_html\\\": full responsive HTML email (DOCTYPE, inline CSS, table-based, 600px max width)\\n- \\\"body_text\\\": plain text version\\n\\nDo NOT include markdown, code fences, explanations, or any text outside the JSON. Output ONLY the JSON object.\";\n return $this->callAI($prompt, $this->getSystemPrompt());\n }\n\n public function generateFollowUpEmail(array $context, string $tone = 'professional', string $language = 'en'): array\n {\n $prompt = $this->buildFollowUpPrompt($context, $tone, $language);\n return $this->callAI($prompt, $this->getSystemPrompt());\n }\n\n public function generateCampaignEmail(array $campaignData, string $tone = 'marketing', string $language = 'en'): array\n {\n $prompt = $this->buildCampaignPrompt($campaignData, $tone, $language);\n return $this->callAI($prompt, $this->getSystemPrompt());\n }\n\n public function copilotChat(string $message, string $systemPrompt, string $conversationHistory = ''): array\n {\n return $this->callAI($message, $systemPrompt . \"\\n\\n\" . $conversationHistory);\n }\n\n public function copilotChatStructured(string $message, string $systemPrompt, array $historyMessages, float $temperature = 0.2): array\n {\n return $this->callAIStructured($message, $systemPrompt, $historyMessages, $temperature);\n }\n\n private function callAIStructured(string $message, string $systemPrompt, array $historyMessages, float $temperature): array\n {\n return match ($this->provider) {\n 'openai' => $this->callOpenAIStructured($message, $systemPrompt, $historyMessages, $temperature),\n 'gemini' => $this->callGeminiStructured($message, $systemPrompt, $historyMessages, $temperature),\n 'claude' => $this->callClaudeStructured($message, $systemPrompt, $historyMessages, $temperature),\n 'deepseek' => $this->callDeepSeekStructured($message, $systemPrompt, $historyMessages, $temperature),\n default => throw new Exception(\"Unsupported AI provider: {$this->provider}\"),\n };\n }\n\n private function callOpenAIStructured(string $message, string $systemPrompt, array $historyMessages, float $temperature): array\n {\n $messages = [['role' => 'system', 'content' => $systemPrompt]];\n\n foreach ($historyMessages as $msg) {\n $messages[] = [\n 'role' => $msg['role'] === 'assistant' ? 'assistant' : 'user',\n 'content' => $msg['content'],\n ];\n }\n\n $messages[] = ['role' => 'user', 'content' => $message];\n\n $url = str_ends_with($this->baseUrl, '/chat/completions')\n ? $this->baseUrl\n : $this->baseUrl . '/chat/completions';\n\n $response = $this->httpClient()->post($url, [\n 'model' => $this->model,\n 'messages' => $messages,\n 'max_tokens' => $this->maxTokens,\n 'temperature' => $temperature,\n ]);\n\n return $this->parseResponse($response->json(), 'openai');\n }\n\n private function callGeminiStructured(string $message, string $systemPrompt, array $historyMessages, float $temperature): array\n {\n $contents = [];\n\n foreach ($historyMessages as $msg) {\n $role = $msg['role'] === 'assistant' ? 'model' : 'user';\n $contents[] = ['role' => $role, 'parts' => [['text' => $msg['content']]]];\n }\n\n $contents[] = ['role' => 'user', 'parts' => [['text' => $message]]];\n\n $url = config('ebook.ai.providers.gemini.url') . \"?key={$this->apiKey}\";\n\n $response = $this->httpClient()->post($url, [\n 'contents' => $contents,\n 'systemInstruction' => $systemPrompt ? ['parts' => [['text' => $systemPrompt]]] : null,\n 'generationConfig' => [\n 'maxOutputTokens' => $this->maxTokens,\n 'temperature' => $temperature,\n ],\n ]);\n\n return $this->parseResponse($response->json(), 'gemini');\n }\n\n private function callClaudeStructured(string $message, string $systemPrompt, array $historyMessages, float $temperature): array\n {\n $claudeMessages = [];\n\n foreach ($historyMessages as $msg) {\n $claudeMessages[] = [\n 'role' => $msg['role'] === 'assistant' ? 'assistant' : 'user',\n 'content' => $msg['content'],\n ];\n }\n\n $claudeMessages[] = ['role' => 'user', 'content' => $message];\n\n $response = $this->httpClient()\n ->withHeaders([\n 'x-api-key' => $this->apiKey,\n 'anthropic-version' => '2023-06-01',\n ])\n ->post(config('ebook.ai.providers.claude.url', 'https://api.anthropic.com/v1/messages'), [\n 'model' => $this->model,\n 'system' => $systemPrompt ?: 'You are a helpful email marketing assistant.',\n 'messages' => $claudeMessages,\n 'max_tokens' => $this->maxTokens,\n 'temperature' => $temperature,\n ]);\n\n return $this->parseResponse($response->json(), 'claude');\n }\n\n private function callDeepSeekStructured(string $message, string $systemPrompt, array $historyMessages, float $temperature): array\n {\n $messages = [['role' => 'system', 'content' => $systemPrompt]];\n\n foreach ($historyMessages as $msg) {\n $messages[] = [\n 'role' => $msg['role'] === 'assistant' ? 'assistant' : 'user',\n 'content' => $msg['content'],\n ];\n }\n\n $messages[] = ['role' => 'user', 'content' => $message];\n\n $response = $this->httpClient()->post(config('ebook.ai.providers.deepseek.url', 'https://api.deepseek.com/v1/chat/completions'), [\n 'model' => $this->model,\n 'messages' => $messages,\n 'max_tokens' => $this->maxTokens,\n 'temperature' => $temperature,\n ]);\n\n return $this->parseResponse($response->json(), 'deepseek');\n }\n\n public function chat(string $message, string $conversationHistory = ''): array\n {\n $systemPrompt = <<<PROMPT\nYou are an AI Admin Assistant for an Ebook Lead Magnet & Digital Asset Delivery System.\n\nYou can help admins with:\n1. Sending download emails to leads\n2. Resending expired download links\n3. Generating follow-up emails\n4. Creating campaign emails\n5. Viewing download analytics\n6. Managing ebook leads\n\nIMPORTANT RULES:\n- Never share the actual file paths or internal URLs\n- Never expose API keys or sensitive configuration\n- Use the tools and commands available to you through the system\n- When asked to send emails, confirm with the admin before sending\n- Keep responses concise and actionable\n\nCurrent context: Admin managing ebook downloads and leads.\nPROMPT;\n\n return $this->callAI($message, $systemPrompt . \"\\n\\n\" . $conversationHistory);\n }\n\n private function buildDownloadEmailPrompt(array $data, string $tone, string $language): string\n {\n $author = $data['ebook_author'] ?? 'Our Team';\n $company = $data['company_name'] ?? config('app.name');\n\n return <<<PROMPT\nGenerate a professional download email in {$language} language with a {$tone} tone.\n\nLead Information:\n- Name: {$data['lead_name']}\n- Email: {$data['lead_email']}\n\nEbook Information:\n- Title: {$data['ebook_title']}\n- Description: {$data['ebook_description']}\n- Author: {$author}\n\nDownload Link: {$data['download_url']}\nExpires: {$data['expires_at']}\nCompany: {$company}\n\nPlease include:\n1. A warm greeting using the lead's name\n2. Thank them for their interest\n3. Brief description of what they'll learn\n4. Clear CTA with the download link\n5. Note about expiration\n6. Professional signature with company details\n7. Unsubscribe link at the bottom\n\nReturn ONLY valid JSON with exactly these keys:\n- \"subject\": max 80 chars, friendly and professional\n- \"body_html\": complete responsive HTML email with DOCTYPE, inline CSS, table-based layout, max width 600px, white background\n- \"body_text\": plain text version\n\nDo NOT include markdown, code fences, explanations, or any text outside the JSON. Output ONLY the JSON object.\nPROMPT;\n }\n\n private function buildFollowUpPrompt(array $context, string $tone, string $language): string\n {\n return <<<PROMPT\nGenerate a follow-up email in {$language} with a {$tone} tone.\n\nContext:\n- Lead Name: {$context['lead_name']}\n- Ebook: {$context['ebook_title']}\n- Days since download: {$context['days_since_download']}\n- Download Count: {$context['download_count']}\n\nReturn ONLY valid JSON with exactly these keys:\n- \"subject\": max 80 chars, friendly and professional\n- \"body_html\": complete responsive HTML email with DOCTYPE, inline CSS, table-based layout, max width 600px, white background\n- \"body_text\": plain text version\n\nDo NOT include markdown, code fences, explanations, or any text outside the JSON. Output ONLY the JSON object.\nPROMPT;\n }\n\n public function generateAutoReplyEmail(array $data, string $tone = 'warm', string $language = 'en'): array\n {\n $leadEmail = $data['lead_email'] ?? 'unknown';\n $formType = $data['form_type'] ?? 'unknown';\n\n Log::debug('[TRACE] AiEmailService::generateAutoReplyEmail', [\n 'form_type' => $formType,\n 'lead_email' => $leadEmail,\n 'provider' => $this->provider,\n 'model' => $this->model,\n 'api_key_set' => !empty($this->apiKey),\n 'api_key_length' => strlen($this->apiKey),\n ]);\n\n $prompt = $this->buildAutoReplyPrompt($data, $tone, $language);\n\n Log::debug('[TRACE] Calling AiEmailService::callAI', [\n 'form_type' => $formType,\n 'prompt_length' => strlen($prompt),\n ]);\n\n $start = microtime(true);\n try {\n $result = $this->callAI($prompt);\n $duration = round((microtime(true) - $start) * 1000);\n Log::debug('[TRACE] AiEmailService::callAI succeeded', [\n 'form_type' => $formType,\n 'duration_ms' => $duration,\n 'has_subject' => isset($result['subject']),\n 'has_body_html' => isset($result['body_html']),\n ]);\n return $result;\n } catch (\\Throwable $e) {\n $duration = round((microtime(true) - $start) * 1000);\n Log::debug('[TRACE] AiEmailService::callAI FAILED', [\n 'form_type' => $formType,\n 'duration_ms' => $duration,\n 'error' => $e->getMessage(),\n 'error_class' => get_class($e),\n ]);\n throw $e;\n }\n }\n\n private function buildAutoReplyPrompt(array $data, string $tone, string $language): string\n {\n $formType = $data['form_type'] ?? 'general';\n $formLabel = match ($formType) {\n 'contact-page' => 'Contact Form enquiry',\n 'package_booking' => 'Settlement Package booking request',\n 'homepage_roadmap' => 'Roadmap download request',\n 'ebook_download' => 'Ebook download request',\n 'ai_chat' => 'AI Chat consultation',\n default => 'enquiry',\n };\n\n $name = $data['lead_name'] ?? 'there';\n $company = $data['company_name'] ?? config('app.name');\n\n return <<<PROMPT\nGenerate a warm, professional auto-reply email in {$language} language with a {$tone} tone.\n\nContext:\n- This is an automated acknowledgment for a {$formLabel} submitted on {$company}.\n- Lead name: {$name}\n- Form type: {$formType}\n\nThe email must:\n1. Greet the lead by name warmly\n2. Acknowledge receipt of their {$formLabel}\n3. Reassure them that our team has received their request\n4. State clearly that a team member will respond within 24 hours\n5. If it's a contact enquiry, mention that we'll review their message carefully\n6. If it's a package booking, mention that we'll confirm their session details\n7. Close with a professional signature: \"Warm regards, The {$company} Team\"\n8. Include a brief P.S. that they can reply to this email if they have urgent questions\n\nImportant:\n- Keep the tone warm and human, not robotic\n- Do NOT include download links or promotional content\n- Do NOT mention AI or automation\n- Use the lead's name naturally in the greeting\n\nReturn ONLY valid JSON with exactly these keys:\n- \"subject\": a concise, friendly subject line (max 80 chars)\n- \"body_html\": complete responsive HTML email with DOCTYPE, inline CSS, table-based layout, max width 600px, white card background (#FFFFFF), body background #F8F4EC, professional typography (Arial/Helvetica/sans-serif), heading 28px, body 16px, primary color #0F766E\n- \"body_text\": plain text version\n\nDo NOT include markdown, code fences, explanations, or any text outside the JSON. Output ONLY the JSON object.\nPROMPT;\n }\n\n private function buildCampaignPrompt(array $data, string $tone, string $language): string\n {\n return <<<PROMPT\nGenerate a marketing campaign email in {$language} with a {$tone} tone.\n\nCampaign: {$data['campaign_name']}\nEbook: {$data['ebook_title']}\nDescription: {$data['campaign_description']}\n\nReturn ONLY valid JSON with exactly these keys:\n- \"subject\": max 80 chars\n- \"body_html\": complete responsive HTML email with DOCTYPE, inline CSS, table-based layout, max width 600px, white card\n- \"body_text\": plain text version\n\nDo NOT include markdown, code fences, explanations, or any text outside the JSON. Output ONLY the JSON object.\nPROMPT;\n }\n\n private function callAI(string $prompt, ?string $systemPrompt = null): array\n {\n return match ($this->provider) {\n 'openai' => $this->callOpenAI($prompt, $systemPrompt),\n 'gemini' => $this->callGemini($prompt, $systemPrompt),\n 'claude' => $this->callClaude($prompt, $systemPrompt),\n 'deepseek' => $this->callDeepSeek($prompt, $systemPrompt),\n default => throw new Exception(\"Unsupported AI provider: {$this->provider}\"),\n };\n }\n\n private function callOpenAI(string $prompt, ?string $systemPrompt = null): array\n {\n $messages = [];\n\n if ($systemPrompt) {\n $messages[] = ['role' => 'system', 'content' => $systemPrompt];\n }\n\n $messages[] = ['role' => 'user', 'content' => $prompt];\n\n $url = str_ends_with($this->baseUrl, '/chat/completions')\n ? $this->baseUrl\n : $this->baseUrl . '/chat/completions';\n\n $response = $this->httpClient()->post($url, [\n 'model' => $this->model,\n 'messages' => $messages,\n 'max_tokens' => $this->maxTokens,\n 'temperature' => $this->temperature,\n 'response_format' => ['type' => 'json_object'],\n ]);\n\n return $this->parseResponse($response->json(), 'openai');\n }\n\n private function callGemini(string $prompt, ?string $systemPrompt = null): array\n {\n $contents = [];\n\n if ($systemPrompt) {\n $contents[] = ['role' => 'user', 'parts' => [['text' => $systemPrompt]]];\n }\n\n $contents[] = ['role' => 'user', 'parts' => [['text' => $prompt]]];\n\n $url = config('ebook.ai.providers.gemini.url') . \"?key={$this->apiKey}\";\n\n $response = $this->httpClient()->post($url, [\n 'contents' => $contents,\n 'generationConfig' => [\n 'maxOutputTokens' => $this->maxTokens,\n 'temperature' => $this->temperature,\n ],\n ]);\n\n return $this->parseResponse($response->json(), 'gemini');\n }\n\n private function callClaude(string $prompt, ?string $systemPrompt = null): array\n {\n $response = $this->httpClient()\n ->withHeaders([\n 'x-api-key' => $this->apiKey,\n 'anthropic-version' => '2023-06-01',\n ])\n ->post(config('ebook.ai.providers.claude.url', 'https://api.anthropic.com/v1/messages'), [\n 'model' => $this->model,\n 'system' => $systemPrompt ?? 'You are a helpful email marketing assistant.',\n 'messages' => [\n ['role' => 'user', 'content' => $prompt],\n ],\n 'max_tokens' => $this->maxTokens,\n 'temperature' => $this->temperature,\n ]);\n\n return $this->parseResponse($response->json(), 'claude');\n }\n\n private function callDeepSeek(string $prompt, ?string $systemPrompt = null): array\n {\n $messages = [];\n\n if ($systemPrompt) {\n $messages[] = ['role' => 'system', 'content' => $systemPrompt];\n }\n\n $messages[] = ['role' => 'user', 'content' => $prompt];\n\n $response = $this->httpClient()->post(config('ebook.ai.providers.deepseek.url', 'https://api.deepseek.com/v1/chat/completions'), [\n 'model' => $this->model,\n 'messages' => $messages,\n 'max_tokens' => $this->maxTokens,\n 'temperature' => $this->temperature,\n ]);\n\n return $this->parseResponse($response->json(), 'deepseek');\n }\n\n private function parseResponse(array $response, string $provider): array\n {\n $content = match ($provider) {\n 'openai', 'deepseek' => $response['choices'][0]['message']['content'] ?? '',\n 'gemini' => $response['candidates'][0]['content']['parts'][0]['text'] ?? '',\n 'claude' => $response['content'][0]['text'] ?? '',\n default => throw new Exception(\"Unsupported provider for response parsing: {$provider}\"),\n };\n\n $content = $this->stripReasoningText($content);\n $content = preg_replace('/^```(?:json)?\\s*\\n?(.*?)\\n?```$/s', '$1', trim($content));\n\n $json = $this->decodeEmailJson($content);\n if ($json) {\n return $json;\n }\n\n foreach ($this->extractJsonCandidates($content) as $candidate) {\n $json = $this->decodeEmailJson($candidate);\n if ($json) {\n return $json;\n }\n }\n\n Log::warning('AI email response did not contain valid email JSON; using safe fallback.', [\n 'provider' => $provider,\n 'content_preview' => mb_substr($content, 0, 500),\n ]);\n\n return [\n 'subject' => 'Thank you from SettleANZ',\n 'body_html' => $this->safeFallbackEmailHtml(),\n 'body_text' => \"Hi there,\\n\\nThank you for your interest in SettleANZ. We received your request and our team will follow up shortly.\\n\\nWarm regards,\\nThe SettleANZ Team\",\n ];\n }\n\n private function stripReasoningText(string $content): string\n {\n $content = trim($content);\n $content = preg_replace('/<think>.*?<\\/think>/is', '', $content) ?? $content;\n return trim($content);\n }\n\n private function decodeEmailJson(string $content): ?array\n {\n $json = json_decode(trim($content), true);\n if (!is_array($json) || !isset($json['subject'], $json['body_html'])) {\n return null;\n }\n\n $subject = trim((string) $json['subject']);\n $bodyHtml = $this->normalizeEmailBody((string) $json['body_html']);\n $bodyText = isset($json['body_text']) && trim((string) $json['body_text']) !== ''\n ? $this->normalizePlainText((string) $json['body_text'])\n : $this->normalizePlainText(strip_tags($bodyHtml));\n\n if ($subject === '' || $bodyHtml === '') {\n return null;\n }\n\n if (!str_contains($bodyHtml, '<!DOCTYPE') && !str_contains($bodyHtml, '<html')) {\n $bodyHtml = $this->wrapInEmailHtml($bodyHtml);\n }\n\n return [\n 'subject' => mb_substr($subject, 0, 80),\n 'body_html' => $bodyHtml,\n 'body_text' => $bodyText,\n ];\n }\n\n private function extractJsonCandidates(string $content): array\n {\n $candidates = [];\n $start = strpos($content, '{');\n $end = strrpos($content, '}');\n\n if ($start !== false && $end !== false && $end > $start) {\n $candidates[] = substr($content, $start, $end - $start + 1);\n }\n\n if (preg_match_all('/\\{(?:(?:[^{}]|(?R))*)\\}/s', $content, $matches)) {\n $candidates = array_merge($candidates, $matches[0]);\n }\n\n return array_values(array_unique($candidates));\n }\n\n private function normalizeEmailBody(string $body): string\n {\n $body = trim($body);\n $body = str_replace([\"\\\\r\\\\n\", \"\\\\n\", \"\\\\r\", \"\\\\t\"], [\"\\n\", \"\\n\", \"\\n\", \"\\t\"], $body);\n return $body;\n }\n\n private function normalizePlainText(string $text): string\n {\n $text = html_entity_decode($text, ENT_QUOTES | ENT_HTML5, 'UTF-8');\n $text = str_replace([\"\\\\r\\\\n\", \"\\\\n\", \"\\\\r\", \"\\\\t\"], [\"\\n\", \"\\n\", \"\\n\", \"\\t\"], $text);\n return trim(preg_replace(\"/\\n{3,}/\", \"\\n\\n\", $text) ?? $text);\n }\n\n private function ensureValidEmailHtml(string $html): string\n {\n if (str_contains($html, '<!DOCTYPE') || str_contains($html, '<html')) {\n return $html;\n }\n return $this->wrapInEmailHtml($html);\n }\n\n private function wrapInEmailHtml(string $bodyContent): string\n {\n return '<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n</head>\n<body style=\"margin:0;padding:0;background-color:#F8F4EC;font-family:Arial,Helvetica,sans-serif;\">\n<center style=\"width:100%;background-color:#F8F4EC;\">\n<table role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" style=\"background-color:#F8F4EC;\">\n<tr>\n<td align=\"center\" style=\"padding:32px 16px;\">\n<table role=\"presentation\" width=\"600\" cellpadding=\"0\" cellspacing=\"0\" style=\"max-width:600px;width:100%;background-color:#FFFFFF;border-radius:12px;border:1px solid #E5E7EB;\">\n<tr>\n<td style=\"padding:40px;font-size:16px;line-height:1.6;color:#1F2937;\">\n' . $bodyContent . '\n</td>\n</tr>\n</table>\n</td>\n</tr>\n</table>\n</center>\n</body>\n</html>';\n }\n\n private function safeFallbackEmailHtml(): string\n {\n return $this->wrapInEmailHtml('<p style=\"margin:0 0 16px;\">Hi there,</p><p style=\"margin:0 0 16px;\">Thank you for your interest in SettleANZ. We received your request and our team will follow up shortly.</p><p style=\"margin:0;\">Warm regards,<br>The SettleANZ Team</p>');\n }\n\n private function httpClient(): PendingRequest\n {\n return Http::timeout($this->timeout)\n ->withToken($this->apiKey)\n ->withHeaders([\n 'Content-Type' => 'application/json',\n ])\n ->throw();\n }\n\n public function getConversationHistory(AiConversation $conversation): string\n {\n return $conversation->messages()\n ->orderBy('created_at')\n ->get()\n ->map(fn (AiMessage $msg) => \"{$msg->role}: {$msg->content}\")\n ->implode(\"\\n\");\n }\n\n public function getAvailableModels(): array\n {\n return [\n 'openai' => ['gpt-4o', 'gpt-4o-mini', 'gpt-4-turbo', 'gpt-3.5-turbo'],\n 'gemini' => ['gemini-pro', 'gemini-1.5-pro', 'gemini-1.5-flash'],\n 'claude' => ['claude-3-opus-20240229', 'claude-3-sonnet-20240229', 'claude-3-haiku-20240307'],\n 'deepseek' => ['deepseek-chat', 'deepseek-reasoner'],\n ];\n }\n\n private function getSystemPrompt(): string\n {\n return 'You are a professional email template generator. You output ONLY valid JSON. Never include markdown, code fences (```json), explanations, reasoning, or any text outside the JSON object. Your response must be parseable by json_decode() with no modification. The JSON must have exactly three keys: \"subject\" (string, max 80 chars), \"body_html\" (string, complete responsive HTML email document), \"body_text\" (string, plain text version).';\n }\n}\n"}, "3ec72af0e94645addff391185ac3e901099516cb": {"t": 1785760152.0852735, "n": 18948, "h": "ad0c142c55f2fa2a", "c": "<?php\n\nnamespace App\\Services;\n\nuse App\\Models\\AiConversation;\nuse App\\Models\\AiMessage;\nuse App\\Models\\SiteSetting;\nuse Exception;\nuse Illuminate\\Http\\Client\\PendingRequest;\nuse Illuminate\\Support\\Facades\\Http;\nuse Illuminate\\Support\\Facades\\Log;\n\nclass AiEmailService\n{\n private string $provider;\n private string $model;\n private string $apiKey;\n private int $maxTokens;\n private float $temperature;\n private int $timeout;\n private string $baseUrl;\n\n public function __construct()\n {\n $this->provider = config('ebook.ai.provider', 'openai');\n $this->model = trim((string) SiteSetting::getValue('ai_openai_model', config('ebook.ai.model', 'gpt-4o')));\n $this->apiKey = trim((string) SiteSetting::getValue('ai_openai_api_key', config('ebook.ai.api_key')));\n $this->maxTokens = config('ebook.ai.max_tokens', 2000);\n $this->temperature = config('ebook.ai.temperature', 0.7);\n $this->timeout = config('ebook.ai.timeout', 60);\n $this->baseUrl = rtrim((string) SiteSetting::getValue('ai_openai_base_url', config('ebook.ai.providers.openai.url', 'https://api.openai.com/v1/chat/completions')), '/');\n }\n\n public function generateDownloadEmail(array $data, string $tone = 'professional', string $language = 'en'): array\n {\n $prompt = $this->buildDownloadEmailPrompt($data, $tone, $language);\n\n return $this->callAI($prompt);\n }\n\n public function rewriteEmail(string $content, string $tone, string $language = 'en'): array\n {\n $prompt = \"Rewrite the following email content in a {$tone} tone and in {$language} language.\\n\\nOriginal:\\n{$content}\\n\\nRewritten version:\";\n\n return $this->callAI($prompt);\n }\n\n public function generateFollowUpEmail(array $context, string $tone = 'professional', string $language = 'en'): array\n {\n $prompt = $this->buildFollowUpPrompt($context, $tone, $language);\n\n return $this->callAI($prompt);\n }\n\n public function generateCampaignEmail(array $campaignData, string $tone = 'marketing', string $language = 'en'): array\n {\n $prompt = $this->buildCampaignPrompt($campaignData, $tone, $language);\n\n return $this->callAI($prompt);\n }\n\n public function copilotChat(string $message, string $systemPrompt, string $conversationHistory = ''): array\n {\n return $this->callAI($message, $systemPrompt . \"\\n\\n\" . $conversationHistory);\n }\n\n public function copilotChatStructured(string $message, string $systemPrompt, array $historyMessages, float $temperature = 0.2): array\n {\n return $this->callAIStructured($message, $systemPrompt, $historyMessages, $temperature);\n }\n\n private function callAIStructured(string $message, string $systemPrompt, array $historyMessages, float $temperature): array\n {\n return match ($this->provider) {\n 'openai' => $this->callOpenAIStructured($message, $systemPrompt, $historyMessages, $temperature),\n 'gemini' => $this->callGeminiStructured($message, $systemPrompt, $historyMessages, $temperature),\n 'claude' => $this->callClaudeStructured($message, $systemPrompt, $historyMessages, $temperature),\n 'deepseek' => $this->callDeepSeekStructured($message, $systemPrompt, $historyMessages, $temperature),\n default => throw new Exception(\"Unsupported AI provider: {$this->provider}\"),\n };\n }\n\n private function callOpenAIStructured(string $message, string $systemPrompt, array $historyMessages, float $temperature): array\n {\n $messages = [['role' => 'system', 'content' => $systemPrompt]];\n\n foreach ($historyMessages as $msg) {\n $messages[] = [\n 'role' => $msg['role'] === 'assistant' ? 'assistant' : 'user',\n 'content' => $msg['content'],\n ];\n }\n\n $messages[] = ['role' => 'user', 'content' => $message];\n\n $url = str_ends_with($this->baseUrl, '/chat/completions')\n ? $this->baseUrl\n : $this->baseUrl . '/chat/completions';\n\n $response = $this->httpClient()->post($url, [\n 'model' => $this->model,\n 'messages' => $messages,\n 'max_tokens' => $this->maxTokens,\n 'temperature' => $temperature,\n ]);\n\n return $this->parseResponse($response->json(), 'openai');\n }\n\n private function callGeminiStructured(string $message, string $systemPrompt, array $historyMessages, float $temperature): array\n {\n $contents = [];\n\n foreach ($historyMessages as $msg) {\n $role = $msg['role'] === 'assistant' ? 'model' : 'user';\n $contents[] = ['role' => $role, 'parts' => [['text' => $msg['content']]]];\n }\n\n $contents[] = ['role' => 'user', 'parts' => [['text' => $message]]];\n\n $url = config('ebook.ai.providers.gemini.url') . \"?key={$this->apiKey}\";\n\n $response = $this->httpClient()->post($url, [\n 'contents' => $contents,\n 'systemInstruction' => $systemPrompt ? ['parts' => [['text' => $systemPrompt]]] : null,\n 'generationConfig' => [\n 'maxOutputTokens' => $this->maxTokens,\n 'temperature' => $temperature,\n ],\n ]);\n\n return $this->parseResponse($response->json(), 'gemini');\n }\n\n private function callClaudeStructured(string $message, string $systemPrompt, array $historyMessages, float $temperature): array\n {\n $claudeMessages = [];\n\n foreach ($historyMessages as $msg) {\n $claudeMessages[] = [\n 'role' => $msg['role'] === 'assistant' ? 'assistant' : 'user',\n 'content' => $msg['content'],\n ];\n }\n\n $claudeMessages[] = ['role' => 'user', 'content' => $message];\n\n $response = $this->httpClient()\n ->withHeaders([\n 'x-api-key' => $this->apiKey,\n 'anthropic-version' => '2023-06-01',\n ])\n ->post(config('ebook.ai.providers.claude.url', 'https://api.anthropic.com/v1/messages'), [\n 'model' => $this->model,\n 'system' => $systemPrompt ?: 'You are a helpful email marketing assistant.',\n 'messages' => $claudeMessages,\n 'max_tokens' => $this->maxTokens,\n 'temperature' => $temperature,\n ]);\n\n return $this->parseResponse($response->json(), 'claude');\n }\n\n private function callDeepSeekStructured(string $message, string $systemPrompt, array $historyMessages, float $temperature): array\n {\n $messages = [['role' => 'system', 'content' => $systemPrompt]];\n\n foreach ($historyMessages as $msg) {\n $messages[] = [\n 'role' => $msg['role'] === 'assistant' ? 'assistant' : 'user',\n 'content' => $msg['content'],\n ];\n }\n\n $messages[] = ['role' => 'user', 'content' => $message];\n\n $response = $this->httpClient()->post(config('ebook.ai.providers.deepseek.url', 'https://api.deepseek.com/v1/chat/completions'), [\n 'model' => $this->model,\n 'messages' => $messages,\n 'max_tokens' => $this->maxTokens,\n 'temperature' => $temperature,\n ]);\n\n return $this->parseResponse($response->json(), 'deepseek');\n }\n\n public function chat(string $message, string $conversationHistory = ''): array\n {\n $systemPrompt = <<<PROMPT\nYou are an AI Admin Assistant for an Ebook Lead Magnet & Digital Asset Delivery System.\n\nYou can help admins with:\n1. Sending download emails to leads\n2. Resending expired download links\n3. Generating follow-up emails\n4. Creating campaign emails\n5. Viewing download analytics\n6. Managing ebook leads\n\nIMPORTANT RULES:\n- Never share the actual file paths or internal URLs\n- Never expose API keys or sensitive configuration\n- Use the tools and commands available to you through the system\n- When asked to send emails, confirm with the admin before sending\n- Keep responses concise and actionable\n\nCurrent context: Admin managing ebook downloads and leads.\nPROMPT;\n\n return $this->callAI($message, $systemPrompt . \"\\n\\n\" . $conversationHistory);\n }\n\n private function buildDownloadEmailPrompt(array $data, string $tone, string $language): string\n {\n $author = $data['ebook_author'] ?? 'Our Team';\n $company = $data['company_name'] ?? config('app.name');\n\n return <<<PROMPT\nGenerate a professional download email in {$language} language with a {$tone} tone.\n\nLead Information:\n- Name: {$data['lead_name']}\n- Email: {$data['lead_email']}\n\nEbook Information:\n- Title: {$data['ebook_title']}\n- Description: {$data['ebook_description']}\n- Author: {$author}\n\nDownload Link: {$data['download_url']}\nExpires: {$data['expires_at']}\nCompany: {$company}\n\nPlease include:\n1. A warm greeting using the lead's name\n2. Thank them for their interest\n3. Brief description of what they'll learn\n4. Clear CTA with the download link\n5. Note about expiration\n6. Professional signature with company details\n7. Unsubscribe link at the bottom\n\nReturn a JSON with keys: \"subject\", \"body_html\", \"body_text\"\nPROMPT;\n }\n\n private function buildFollowUpPrompt(array $context, string $tone, string $language): string\n {\n return <<<PROMPT\nGenerate a follow-up email in {$language} with a {$tone} tone.\n\nContext:\n- Lead Name: {$context['lead_name']}\n- Ebook: {$context['ebook_title']}\n- Days since download: {$context['days_since_download']}\n- Download Count: {$context['download_count']}\n\nReturn a JSON with keys: \"subject\", \"body_html\", \"body_text\"\nPROMPT;\n }\n\n public function generateAutoReplyEmail(array $data, string $tone = 'warm', string $language = 'en'): array\n {\n $leadEmail = $data['lead_email'] ?? 'unknown';\n $formType = $data['form_type'] ?? 'unknown';\n\n Log::debug('[TRACE] AiEmailService::generateAutoReplyEmail', [\n 'form_type' => $formType,\n 'lead_email' => $leadEmail,\n 'provider' => $this->provider,\n 'model' => $this->model,\n 'api_key_set' => !empty($this->apiKey),\n 'api_key_length' => strlen($this->apiKey),\n ]);\n\n $prompt = $this->buildAutoReplyPrompt($data, $tone, $language);\n\n Log::debug('[TRACE] Calling AiEmailService::callAI', [\n 'form_type' => $formType,\n 'prompt_length' => strlen($prompt),\n ]);\n\n $start = microtime(true);\n try {\n $result = $this->callAI($prompt);\n $duration = round((microtime(true) - $start) * 1000);\n Log::debug('[TRACE] AiEmailService::callAI succeeded', [\n 'form_type' => $formType,\n 'duration_ms' => $duration,\n 'has_subject' => isset($result['subject']),\n 'has_body_html' => isset($result['body_html']),\n ]);\n return $result;\n } catch (\\Throwable $e) {\n $duration = round((microtime(true) - $start) * 1000);\n Log::debug('[TRACE] AiEmailService::callAI FAILED', [\n 'form_type' => $formType,\n 'duration_ms' => $duration,\n 'error' => $e->getMessage(),\n 'error_class' => get_class($e),\n ]);\n throw $e;\n }\n }\n\n private function buildAutoReplyPrompt(array $data, string $tone, string $language): string\n {\n $formType = $data['form_type'] ?? 'general';\n $formLabel = match ($formType) {\n 'contact-page' => 'Contact Form enquiry',\n 'package_booking' => 'Settlement Package booking request',\n 'homepage_roadmap' => 'Roadmap download request',\n 'ebook_download' => 'Ebook download request',\n 'ai_chat' => 'AI Chat consultation',\n default => 'enquiry',\n };\n\n $name = $data['lead_name'] ?? 'there';\n $company = $data['company_name'] ?? config('app.name');\n\n return <<<PROMPT\nGenerate a warm, professional auto-reply email in {$language} language with a {$tone} tone.\n\nContext:\n- This is an automated acknowledgment for a {$formLabel} submitted on {$company}.\n- Lead name: {$name}\n- Form type: {$formType}\n\nThe email must:\n1. Greet the lead by name warmly\n2. Acknowledge receipt of their {$formLabel}\n3. Reassure them that our team has received their request\n4. State clearly that a team member will respond within 24 hours\n5. If it's a contact enquiry, mention that we'll review their message carefully\n6. If it's a package booking, mention that we'll confirm their session details\n7. Close with a professional signature: \"Warm regards, The {$company} Team\"\n8. Include a brief P.S. that they can reply to this email if they have urgent questions\n\nImportant:\n- Keep the tone warm and human, not robotic\n- Do NOT include download links or promotional content\n- Do NOT mention AI or automation\n- Use the lead's name naturally in the greeting\n\nReturn ONLY valid JSON with exactly these keys:\n- \"subject\": a concise, friendly subject line (max 80 chars)\n- \"body_html\": the full HTML email body with inline styling (professional, responsive)\n- \"body_text\": plain text version of the email\n\nJSON:\nPROMPT;\n }\n\n private function buildCampaignPrompt(array $data, string $tone, string $language): string\n {\n return <<<PROMPT\nGenerate a marketing campaign email in {$language} with a {$tone} tone.\n\nCampaign: {$data['campaign_name']}\nEbook: {$data['ebook_title']}\nDescription: {$data['campaign_description']}\n\nReturn a JSON with keys: \"subject\", \"body_html\", \"body_text\"\nPROMPT;\n }\n\n private function callAI(string $prompt, ?string $systemPrompt = null): array\n {\n return match ($this->provider) {\n 'openai' => $this->callOpenAI($prompt, $systemPrompt),\n 'gemini' => $this->callGemini($prompt, $systemPrompt),\n 'claude' => $this->callClaude($prompt, $systemPrompt),\n 'deepseek' => $this->callDeepSeek($prompt, $systemPrompt),\n default => throw new Exception(\"Unsupported AI provider: {$this->provider}\"),\n };\n }\n\n private function callOpenAI(string $prompt, ?string $systemPrompt = null): array\n {\n $messages = [];\n\n if ($systemPrompt) {\n $messages[] = ['role' => 'system', 'content' => $systemPrompt];\n }\n\n $messages[] = ['role' => 'user', 'content' => $prompt];\n\n $url = str_ends_with($this->baseUrl, '/chat/completions')\n ? $this->baseUrl\n : $this->baseUrl . '/chat/completions';\n\n $response = $this->httpClient()->post($url, [\n 'model' => $this->model,\n 'messages' => $messages,\n 'max_tokens' => $this->maxTokens,\n 'temperature' => $this->temperature,\n ]);\n\n return $this->parseResponse($response->json(), 'openai');\n }\n\n private function callGemini(string $prompt, ?string $systemPrompt = null): array\n {\n $contents = [];\n\n if ($systemPrompt) {\n $contents[] = ['role' => 'user', 'parts' => [['text' => $systemPrompt]]];\n }\n\n $contents[] = ['role' => 'user', 'parts' => [['text' => $prompt]]];\n\n $url = config('ebook.ai.providers.gemini.url') . \"?key={$this->apiKey}\";\n\n $response = $this->httpClient()->post($url, [\n 'contents' => $contents,\n 'generationConfig' => [\n 'maxOutputTokens' => $this->maxTokens,\n 'temperature' => $this->temperature,\n ],\n ]);\n\n return $this->parseResponse($response->json(), 'gemini');\n }\n\n private function callClaude(string $prompt, ?string $systemPrompt = null): array\n {\n $response = $this->httpClient()\n ->withHeaders([\n 'x-api-key' => $this->apiKey,\n 'anthropic-version' => '2023-06-01',\n ])\n ->post(config('ebook.ai.providers.claude.url', 'https://api.anthropic.com/v1/messages'), [\n 'model' => $this->model,\n 'system' => $systemPrompt ?? 'You are a helpful email marketing assistant.',\n 'messages' => [\n ['role' => 'user', 'content' => $prompt],\n ],\n 'max_tokens' => $this->maxTokens,\n 'temperature' => $this->temperature,\n ]);\n\n return $this->parseResponse($response->json(), 'claude');\n }\n\n private function callDeepSeek(string $prompt, ?string $systemPrompt = null): array\n {\n $messages = [];\n\n if ($systemPrompt) {\n $messages[] = ['role' => 'system', 'content' => $systemPrompt];\n }\n\n $messages[] = ['role' => 'user', 'content' => $prompt];\n\n $response = $this->httpClient()->post(config('ebook.ai.providers.deepseek.url', 'https://api.deepseek.com/v1/chat/completions'), [\n 'model' => $this->model,\n 'messages' => $messages,\n 'max_tokens' => $this->maxTokens,\n 'temperature' => $this->temperature,\n ]);\n\n return $this->parseResponse($response->json(), 'deepseek');\n }\n\n private function parseResponse(array $response, string $provider): array\n {\n $content = match ($provider) {\n 'openai', 'deepseek' => $response['choices'][0]['message']['content'] ?? '',\n 'gemini' => $response['candidates'][0]['content']['parts'][0]['text'] ?? '',\n 'claude' => $response['content'][0]['text'] ?? '',\n default => throw new Exception(\"Unsupported provider for response parsing: {$provider}\"),\n };\n\n $json = json_decode($content, true);\n\n if ($json && isset($json['subject'], $json['body_html'])) {\n return $json;\n }\n\n return [\n 'subject' => 'Your Download from ' . config('app.name'),\n 'body_html' => $content,\n 'body_text' => strip_tags($content),\n ];\n }\n\n private function httpClient(): PendingRequest\n {\n return Http::timeout($this->timeout)\n ->withToken($this->apiKey)\n ->withHeaders([\n 'Content-Type' => 'application/json',\n ])\n ->throw();\n }\n\n public function getConversationHistory(AiConversation $conversation): string\n {\n return $conversation->messages()\n ->orderBy('created_at')\n ->get()\n ->map(fn (AiMessage $msg) => \"{$msg->role}: {$msg->content}\")\n ->implode(\"\\n\");\n }\n\n public function getAvailableModels(): array\n {\n return [\n 'openai' => ['gpt-4o', 'gpt-4o-mini', 'gpt-4-turbo', 'gpt-3.5-turbo'],\n 'gemini' => ['gemini-pro', 'gemini-1.5-pro', 'gemini-1.5-flash'],\n 'claude' => ['claude-3-opus-20240229', 'claude-3-sonnet-20240229', 'claude-3-haiku-20240307'],\n 'deepseek' => ['deepseek-chat', 'deepseek-reasoner'],\n ];\n }\n}\n"}, "a902aa71932b09142da8f35ec3591c14d07504f8": {"t": 1785760153.5721076, "n": 17490, "h": "c124705ab5e00330", "c": "<?php\n\nnamespace App\\Services;\n\nuse App\\Models\\AiConversation;\nuse App\\Models\\AiMessage;\nuse App\\Models\\SiteSetting;\nuse Exception;\nuse Illuminate\\Http\\Client\\PendingRequest;\nuse Illuminate\\Support\\Facades\\Http;\nuse Illuminate\\Support\\Facades\\Log;\n\nclass AiEmailService\n{\n private string $provider;\n private string $model;\n private string $apiKey;\n private int $maxTokens;\n private float $temperature;\n private int $timeout;\n private string $baseUrl;\n\n public function __construct()\n {\n $this->provider = config('ebook.ai.provider', 'openai');\n $this->model = trim((string) SiteSetting::getValue('ai_openai_model', config('ebook.ai.model', 'gpt-4o')));\n $this->apiKey = trim((string) SiteSetting::getValue('ai_openai_api_key', config('ebook.ai.api_key')));\n $this->maxTokens = config('ebook.ai.max_tokens', 2000);\n $this->temperature = config('ebook.ai.temperature', 0.7);\n $this->timeout = config('ebook.ai.timeout', 60);\n $this->baseUrl = rtrim((string) SiteSetting::getValue('ai_openai_base_url', config('ebook.ai.providers.openai.url', 'https://api.openai.com/v1/chat/completions')), '/');\n }\n\n public function generateDownloadEmail(array $data, string $tone = 'professional', string $language = 'en'): array\n {\n $prompt = $this->buildDownloadEmailPrompt($data, $tone, $language);\n\n return $this->callAI($prompt);\n }\n\n public function rewriteEmail(string $content, string $tone, string $language = 'en'): array\n {\n $prompt = \"Rewrite the following email content in a {$tone} tone and in {$language} language.\\n\\nOriginal:\\n{$content}\\n\\nRewritten version:\";\n\n return $this->callAI($prompt);\n }\n\n public function generateFollowUpEmail(array $context, string $tone = 'professional', string $language = 'en'): array\n {\n $prompt = $this->buildFollowUpPrompt($context, $tone, $language);\n\n return $this->callAI($prompt);\n }\n\n public function generateCampaignEmail(array $campaignData, string $tone = 'marketing', string $language = 'en'): array\n {\n $prompt = $this->buildCampaignPrompt($campaignData, $tone, $language);\n\n return $this->callAI($prompt);\n }\n\n public function copilotChat(string $message, string $systemPrompt, string $conversationHistory = ''): array\n {\n return $this->callAI($message, $systemPrompt . \"\\n\\n\" . $conversationHistory);\n }\n\n public function copilotChatStructured(string $message, string $systemPrompt, array $historyMessages, float $temperature = 0.2): array\n {\n return $this->callAIStructured($message, $systemPrompt, $historyMessages, $temperature);\n }\n\n private function callAIStructured(string $message, string $systemPrompt, array $historyMessages, float $temperature): array\n {\n return match ($this->provider) {\n 'openai' => $this->callOpenAIStructured($message, $systemPrompt, $historyMessages, $temperature),\n 'gemini' => $this->callGeminiStructured($message, $systemPrompt, $historyMessages, $temperature),\n 'claude' => $this->callClaudeStructured($message, $systemPrompt, $historyMessages, $temperature),\n 'deepseek' => $this->callDeepSeekStructured($message, $systemPrompt, $historyMessages, $temperature),\n default => throw new Exception(\"Unsupported AI provider: {$this->provider}\"),\n };\n }\n\n private function callOpenAIStructured(string $message, string $systemPrompt, array $historyMessages, float $temperature): array\n {\n $messages = [['role' => 'system', 'content' => $systemPrompt]];\n\n foreach ($historyMessages as $msg) {\n $messages[] = [\n 'role' => $msg['role'] === 'assistant' ? 'assistant' : 'user',\n 'content' => $msg['content'],\n ];\n }\n\n $messages[] = ['role' => 'user', 'content' => $message];\n\n $url = str_ends_with($this->baseUrl, '/chat/completions')\n ? $this->baseUrl\n : $this->baseUrl . '/chat/completions';\n\n $response = $this->httpClient()->post($url, [\n 'model' => $this->model,\n 'messages' => $messages,\n 'max_tokens' => $this->maxTokens,\n 'temperature' => $temperature,\n ]);\n\n return $this->parseResponse($response->json(), 'openai');\n }\n\n private function callGeminiStructured(string $message, string $systemPrompt, array $historyMessages, float $temperature): array\n {\n $contents = [];\n\n foreach ($historyMessages as $msg) {\n $role = $msg['role'] === 'assistant' ? 'model' : 'user';\n $contents[] = ['role' => $role, 'parts' => [['text' => $msg['content']]]];\n }\n\n $contents[] = ['role' => 'user', 'parts' => [['text' => $message]]];\n\n $url = config('ebook.ai.providers.gemini.url') . \"?key={$this->apiKey}\";\n\n $response = $this->httpClient()->post($url, [\n 'contents' => $contents,\n 'systemInstruction' => $systemPrompt ? ['parts' => [['text' => $systemPrompt]]] : null,\n 'generationConfig' => [\n 'maxOutputTokens' => $this->maxTokens,\n 'temperature' => $temperature,\n ],\n ]);\n\n return $this->parseResponse($response->json(), 'gemini');\n }\n\n private function callClaudeStructured(string $message, string $systemPrompt, array $historyMessages, float $temperature): array\n {\n $claudeMessages = [];\n\n foreach ($historyMessages as $msg) {\n $claudeMessages[] = [\n 'role' => $msg['role'] === 'assistant' ? 'assistant' : 'user',\n 'content' => $msg['content'],\n ];\n }\n\n $claudeMessages[] = ['role' => 'user', 'content' => $message];\n\n $response = $this->httpClient()\n ->withHeaders([\n 'x-api-key' => $this->apiKey,\n 'anthropic-version' => '2023-06-01',\n ])\n ->post(config('ebook.ai.providers.claude.url', 'https://api.anthropic.com/v1/messages'), [\n 'model' => $this->model,\n 'system' => $systemPrompt ?: 'You are a helpful email marketing assistant.',\n 'messages' => $claudeMessages,\n 'max_tokens' => $this->maxTokens,\n 'temperature' => $temperature,\n ]);\n\n return $this->parseResponse($response->json(), 'claude');\n }\n\n private function callDeepSeekStructured(string $message, string $systemPrompt, array $historyMessages, float $temperature): array\n {\n $messages = [['role' => 'system', 'content' => $systemPrompt]];\n\n foreach ($historyMessages as $msg) {\n $messages[] = [\n 'role' => $msg['role'] === 'assistant' ? 'assistant' : 'user',\n 'content' => $msg['content'],\n ];\n }\n\n $messages[] = ['role' => 'user', 'content' => $message];\n\n $response = $this->httpClient()->post(config('ebook.ai.providers.deepseek.url', 'https://api.deepseek.com/v1/chat/completions'), [\n 'model' => $this->model,\n 'messages' => $messages,\n 'max_tokens' => $this->maxTokens,\n 'temperature' => $temperature,\n ]);\n\n return $this->parseResponse($response->json(), 'deepseek');\n }\n\n public function chat(string $message, string $conversationHistory = ''): array\n {\n $systemPrompt = <<<PROMPT\nYou are an AI Admin Assistant for an Ebook Lead Magnet & Digital Asset Delivery System.\n\nYou can help admins with:\n1. Sending download emails to leads\n2. Resending expired download links\n3. Generating follow-up emails\n4. Creating campaign emails\n5. Viewing download analytics\n6. Managing ebook leads\n\nIMPORTANT RULES:\n- Never share the actual file paths or internal URLs\n- Never expose API keys or sensitive configuration\n- Use the tools and commands available to you through the system\n- When asked to send emails, confirm with the admin before sending\n- Keep responses concise and actionable\n\nCurrent context: Admin managing ebook downloads and leads.\nPROMPT;\n\n return $this->callAI($message, $systemPrompt . \"\\n\\n\" . $conversationHistory);\n }\n\n private function buildDownloadEmailPrompt(array $data, string $tone, string $language): string\n {\n $author = $data['ebook_author'] ?? 'Our Team';\n $company = $data['company_name'] ?? config('app.name');\n\n return <<<PROMPT\nGenerate a professional download email in {$language} language with a {$tone} tone.\n\nLead Information:\n- Name: {$data['lead_name']}\n- Email: {$data['lead_email']}\n\nEbook Information:\n- Title: {$data['ebook_title']}\n- Description: {$data['ebook_description']}\n- Author: {$author}\n\nDownload Link: {$data['download_url']}\nExpires: {$data['expires_at']}\nCompany: {$company}\n\nPlease include:\n1. A warm greeting using the lead's name\n2. Thank them for their interest\n3. Brief description of what they'll learn\n4. Clear CTA with the download link\n5. Note about expiration\n6. Professional signature with company details\n7. Unsubscribe link at the bottom\n\nReturn a JSON with keys: \"subject\", \"body_html\", \"body_text\"\nPROMPT;\n }\n\n private function buildFollowUpPrompt(array $context, string $tone, string $language): string\n {\n return <<<PROMPT\nGenerate a follow-up email in {$language} with a {$tone} tone.\n\nContext:\n- Lead Name: {$context['lead_name']}\n- Ebook: {$context['ebook_title']}\n- Days since download: {$context['days_since_download']}\n- Download Count: {$context['download_count']}\n\nReturn a JSON with keys: \"subject\", \"body_html\", \"body_text\"\nPROMPT;\n }\n\n public function generateAutoReplyEmail(array $data, string $tone = 'warm', string $language = 'en'): array\n {\n $prompt = $this->buildAutoReplyPrompt($data, $tone, $language);\n return $this->callAI($prompt);\n }\n\n private function buildAutoReplyPrompt(array $data, string $tone, string $language): string\n {\n $formType = $data['form_type'] ?? 'general';\n $formLabel = match ($formType) {\n 'contact-page' => 'Contact Form enquiry',\n 'package_booking' => 'Settlement Package booking request',\n 'homepage_roadmap' => 'Roadmap download request',\n 'ebook_download' => 'Ebook download request',\n 'ai_chat' => 'AI Chat consultation',\n default => 'enquiry',\n };\n\n $name = $data['lead_name'] ?? 'there';\n $company = $data['company_name'] ?? config('app.name');\n\n return <<<PROMPT\nGenerate a warm, professional auto-reply email in {$language} language with a {$tone} tone.\n\nContext:\n- This is an automated acknowledgment for a {$formLabel} submitted on {$company}.\n- Lead name: {$name}\n- Form type: {$formType}\n\nThe email must:\n1. Greet the lead by name warmly\n2. Acknowledge receipt of their {$formLabel}\n3. Reassure them that our team has received their request\n4. State clearly that a team member will respond within 24 hours\n5. If it's a contact enquiry, mention that we'll review their message carefully\n6. If it's a package booking, mention that we'll confirm their session details\n7. Close with a professional signature: \"Warm regards, The {$company} Team\"\n8. Include a brief P.S. that they can reply to this email if they have urgent questions\n\nImportant:\n- Keep the tone warm and human, not robotic\n- Do NOT include download links or promotional content\n- Do NOT mention AI or automation\n- Use the lead's name naturally in the greeting\n\nReturn ONLY valid JSON with exactly these keys:\n- \"subject\": a concise, friendly subject line (max 80 chars)\n- \"body_html\": the full HTML email body with inline styling (professional, responsive)\n- \"body_text\": plain text version of the email\n\nJSON:\nPROMPT;\n }\n\n private function buildCampaignPrompt(array $data, string $tone, string $language): string\n {\n return <<<PROMPT\nGenerate a marketing campaign email in {$language} with a {$tone} tone.\n\nCampaign: {$data['campaign_name']}\nEbook: {$data['ebook_title']}\nDescription: {$data['campaign_description']}\n\nReturn a JSON with keys: \"subject\", \"body_html\", \"body_text\"\nPROMPT;\n }\n\n private function callAI(string $prompt, ?string $systemPrompt = null): array\n {\n return match ($this->provider) {\n 'openai' => $this->callOpenAI($prompt, $systemPrompt),\n 'gemini' => $this->callGemini($prompt, $systemPrompt),\n 'claude' => $this->callClaude($prompt, $systemPrompt),\n 'deepseek' => $this->callDeepSeek($prompt, $systemPrompt),\n default => throw new Exception(\"Unsupported AI provider: {$this->provider}\"),\n };\n }\n\n private function callOpenAI(string $prompt, ?string $systemPrompt = null): array\n {\n $messages = [];\n\n if ($systemPrompt) {\n $messages[] = ['role' => 'system', 'content' => $systemPrompt];\n }\n\n $messages[] = ['role' => 'user', 'content' => $prompt];\n\n $url = str_ends_with($this->baseUrl, '/chat/completions')\n ? $this->baseUrl\n : $this->baseUrl . '/chat/completions';\n\n $response = $this->httpClient()->post($url, [\n 'model' => $this->model,\n 'messages' => $messages,\n 'max_tokens' => $this->maxTokens,\n 'temperature' => $this->temperature,\n ]);\n\n return $this->parseResponse($response->json(), 'openai');\n }\n\n private function callGemini(string $prompt, ?string $systemPrompt = null): array\n {\n $contents = [];\n\n if ($systemPrompt) {\n $contents[] = ['role' => 'user', 'parts' => [['text' => $systemPrompt]]];\n }\n\n $contents[] = ['role' => 'user', 'parts' => [['text' => $prompt]]];\n\n $url = config('ebook.ai.providers.gemini.url') . \"?key={$this->apiKey}\";\n\n $response = $this->httpClient()->post($url, [\n 'contents' => $contents,\n 'generationConfig' => [\n 'maxOutputTokens' => $this->maxTokens,\n 'temperature' => $this->temperature,\n ],\n ]);\n\n return $this->parseResponse($response->json(), 'gemini');\n }\n\n private function callClaude(string $prompt, ?string $systemPrompt = null): array\n {\n $response = $this->httpClient()\n ->withHeaders([\n 'x-api-key' => $this->apiKey,\n 'anthropic-version' => '2023-06-01',\n ])\n ->post(config('ebook.ai.providers.claude.url', 'https://api.anthropic.com/v1/messages'), [\n 'model' => $this->model,\n 'system' => $systemPrompt ?? 'You are a helpful email marketing assistant.',\n 'messages' => [\n ['role' => 'user', 'content' => $prompt],\n ],\n 'max_tokens' => $this->maxTokens,\n 'temperature' => $this->temperature,\n ]);\n\n return $this->parseResponse($response->json(), 'claude');\n }\n\n private function callDeepSeek(string $prompt, ?string $systemPrompt = null): array\n {\n $messages = [];\n\n if ($systemPrompt) {\n $messages[] = ['role' => 'system', 'content' => $systemPrompt];\n }\n\n $messages[] = ['role' => 'user', 'content' => $prompt];\n\n $response = $this->httpClient()->post(config('ebook.ai.providers.deepseek.url', 'https://api.deepseek.com/v1/chat/completions'), [\n 'model' => $this->model,\n 'messages' => $messages,\n 'max_tokens' => $this->maxTokens,\n 'temperature' => $this->temperature,\n ]);\n\n return $this->parseResponse($response->json(), 'deepseek');\n }\n\n private function parseResponse(array $response, string $provider): array\n {\n $content = match ($provider) {\n 'openai', 'deepseek' => $response['choices'][0]['message']['content'] ?? '',\n 'gemini' => $response['candidates'][0]['content']['parts'][0]['text'] ?? '',\n 'claude' => $response['content'][0]['text'] ?? '',\n default => throw new Exception(\"Unsupported provider for response parsing: {$provider}\"),\n };\n\n $json = json_decode($content, true);\n\n if ($json && isset($json['subject'], $json['body_html'])) {\n return $json;\n }\n\n return [\n 'subject' => 'Your Download from ' . config('app.name'),\n 'body_html' => $content,\n 'body_text' => strip_tags($content),\n ];\n }\n\n private function httpClient(): PendingRequest\n {\n return Http::timeout($this->timeout)\n ->withToken($this->apiKey)\n ->withHeaders([\n 'Content-Type' => 'application/json',\n ])\n ->throw();\n }\n\n public function getConversationHistory(AiConversation $conversation): string\n {\n return $conversation->messages()\n ->orderBy('created_at')\n ->get()\n ->map(fn (AiMessage $msg) => \"{$msg->role}: {$msg->content}\")\n ->implode(\"\\n\");\n }\n\n public function getAvailableModels(): array\n {\n return [\n 'openai' => ['gpt-4o', 'gpt-4o-mini', 'gpt-4-turbo', 'gpt-3.5-turbo'],\n 'gemini' => ['gemini-pro', 'gemini-1.5-pro', 'gemini-1.5-flash'],\n 'claude' => ['claude-3-opus-20240229', 'claude-3-sonnet-20240229', 'claude-3-haiku-20240307'],\n 'deepseek' => ['deepseek-chat', 'deepseek-reasoner'],\n ];\n }\n}\n"}, "ebd1380ae8b8aac1cbc572146bc122b5c2315ae6": {"t": 1785760155.5721753, "n": 15353, "h": "3c738e31810d888e", "c": "<?php\n\nnamespace App\\Services;\n\nuse App\\Models\\AiConversation;\nuse App\\Models\\AiMessage;\nuse App\\Models\\SiteSetting;\nuse Exception;\nuse Illuminate\\Http\\Client\\PendingRequest;\nuse Illuminate\\Support\\Facades\\Http;\nuse Illuminate\\Support\\Facades\\Log;\n\nclass AiEmailService\n{\n private string $provider;\n private string $model;\n private string $apiKey;\n private int $maxTokens;\n private float $temperature;\n private int $timeout;\n private string $baseUrl;\n\n public function __construct()\n {\n $this->provider = config('ebook.ai.provider', 'openai');\n $this->model = trim((string) SiteSetting::getValue('ai_openai_model', config('ebook.ai.model', 'gpt-4o')));\n $this->apiKey = trim((string) SiteSetting::getValue('ai_openai_api_key', config('ebook.ai.api_key')));\n $this->maxTokens = config('ebook.ai.max_tokens', 2000);\n $this->temperature = config('ebook.ai.temperature', 0.7);\n $this->timeout = config('ebook.ai.timeout', 60);\n $this->baseUrl = rtrim((string) SiteSetting::getValue('ai_openai_base_url', config('ebook.ai.providers.openai.url', 'https://api.openai.com/v1/chat/completions')), '/');\n }\n\n public function generateDownloadEmail(array $data, string $tone = 'professional', string $language = 'en'): array\n {\n $prompt = $this->buildDownloadEmailPrompt($data, $tone, $language);\n\n return $this->callAI($prompt);\n }\n\n public function rewriteEmail(string $content, string $tone, string $language = 'en'): array\n {\n $prompt = \"Rewrite the following email content in a {$tone} tone and in {$language} language.\\n\\nOriginal:\\n{$content}\\n\\nRewritten version:\";\n\n return $this->callAI($prompt);\n }\n\n public function generateFollowUpEmail(array $context, string $tone = 'professional', string $language = 'en'): array\n {\n $prompt = $this->buildFollowUpPrompt($context, $tone, $language);\n\n return $this->callAI($prompt);\n }\n\n public function generateCampaignEmail(array $campaignData, string $tone = 'marketing', string $language = 'en'): array\n {\n $prompt = $this->buildCampaignPrompt($campaignData, $tone, $language);\n\n return $this->callAI($prompt);\n }\n\n public function copilotChat(string $message, string $systemPrompt, string $conversationHistory = ''): array\n {\n return $this->callAI($message, $systemPrompt . \"\\n\\n\" . $conversationHistory);\n }\n\n public function copilotChatStructured(string $message, string $systemPrompt, array $historyMessages, float $temperature = 0.2): array\n {\n return $this->callAIStructured($message, $systemPrompt, $historyMessages, $temperature);\n }\n\n private function callAIStructured(string $message, string $systemPrompt, array $historyMessages, float $temperature): array\n {\n return match ($this->provider) {\n 'openai' => $this->callOpenAIStructured($message, $systemPrompt, $historyMessages, $temperature),\n 'gemini' => $this->callGeminiStructured($message, $systemPrompt, $historyMessages, $temperature),\n 'claude' => $this->callClaudeStructured($message, $systemPrompt, $historyMessages, $temperature),\n 'deepseek' => $this->callDeepSeekStructured($message, $systemPrompt, $historyMessages, $temperature),\n default => throw new Exception(\"Unsupported AI provider: {$this->provider}\"),\n };\n }\n\n private function callOpenAIStructured(string $message, string $systemPrompt, array $historyMessages, float $temperature): array\n {\n $messages = [['role' => 'system', 'content' => $systemPrompt]];\n\n foreach ($historyMessages as $msg) {\n $messages[] = [\n 'role' => $msg['role'] === 'assistant' ? 'assistant' : 'user',\n 'content' => $msg['content'],\n ];\n }\n\n $messages[] = ['role' => 'user', 'content' => $message];\n\n $url = str_ends_with($this->baseUrl, '/chat/completions')\n ? $this->baseUrl\n : $this->baseUrl . '/chat/completions';\n\n $response = $this->httpClient()->post($url, [\n 'model' => $this->model,\n 'messages' => $messages,\n 'max_tokens' => $this->maxTokens,\n 'temperature' => $temperature,\n ]);\n\n return $this->parseResponse($response->json(), 'openai');\n }\n\n private function callGeminiStructured(string $message, string $systemPrompt, array $historyMessages, float $temperature): array\n {\n $contents = [];\n\n foreach ($historyMessages as $msg) {\n $role = $msg['role'] === 'assistant' ? 'model' : 'user';\n $contents[] = ['role' => $role, 'parts' => [['text' => $msg['content']]]];\n }\n\n $contents[] = ['role' => 'user', 'parts' => [['text' => $message]]];\n\n $url = config('ebook.ai.providers.gemini.url') . \"?key={$this->apiKey}\";\n\n $response = $this->httpClient()->post($url, [\n 'contents' => $contents,\n 'systemInstruction' => $systemPrompt ? ['parts' => [['text' => $systemPrompt]]] : null,\n 'generationConfig' => [\n 'maxOutputTokens' => $this->maxTokens,\n 'temperature' => $temperature,\n ],\n ]);\n\n return $this->parseResponse($response->json(), 'gemini');\n }\n\n private function callClaudeStructured(string $message, string $systemPrompt, array $historyMessages, float $temperature): array\n {\n $claudeMessages = [];\n\n foreach ($historyMessages as $msg) {\n $claudeMessages[] = [\n 'role' => $msg['role'] === 'assistant' ? 'assistant' : 'user',\n 'content' => $msg['content'],\n ];\n }\n\n $claudeMessages[] = ['role' => 'user', 'content' => $message];\n\n $response = $this->httpClient()\n ->withHeaders([\n 'x-api-key' => $this->apiKey,\n 'anthropic-version' => '2023-06-01',\n ])\n ->post(config('ebook.ai.providers.claude.url', 'https://api.anthropic.com/v1/messages'), [\n 'model' => $this->model,\n 'system' => $systemPrompt ?: 'You are a helpful email marketing assistant.',\n 'messages' => $claudeMessages,\n 'max_tokens' => $this->maxTokens,\n 'temperature' => $temperature,\n ]);\n\n return $this->parseResponse($response->json(), 'claude');\n }\n\n private function callDeepSeekStructured(string $message, string $systemPrompt, array $historyMessages, float $temperature): array\n {\n $messages = [['role' => 'system', 'content' => $systemPrompt]];\n\n foreach ($historyMessages as $msg) {\n $messages[] = [\n 'role' => $msg['role'] === 'assistant' ? 'assistant' : 'user',\n 'content' => $msg['content'],\n ];\n }\n\n $messages[] = ['role' => 'user', 'content' => $message];\n\n $response = $this->httpClient()->post(config('ebook.ai.providers.deepseek.url', 'https://api.deepseek.com/v1/chat/completions'), [\n 'model' => $this->model,\n 'messages' => $messages,\n 'max_tokens' => $this->maxTokens,\n 'temperature' => $temperature,\n ]);\n\n return $this->parseResponse($response->json(), 'deepseek');\n }\n\n public function chat(string $message, string $conversationHistory = ''): array\n {\n $systemPrompt = <<<PROMPT\nYou are an AI Admin Assistant for an Ebook Lead Magnet & Digital Asset Delivery System.\n\nYou can help admins with:\n1. Sending download emails to leads\n2. Resending expired download links\n3. Generating follow-up emails\n4. Creating campaign emails\n5. Viewing download analytics\n6. Managing ebook leads\n\nIMPORTANT RULES:\n- Never share the actual file paths or internal URLs\n- Never expose API keys or sensitive configuration\n- Use the tools and commands available to you through the system\n- When asked to send emails, confirm with the admin before sending\n- Keep responses concise and actionable\n\nCurrent context: Admin managing ebook downloads and leads.\nPROMPT;\n\n return $this->callAI($message, $systemPrompt . \"\\n\\n\" . $conversationHistory);\n }\n\n private function buildDownloadEmailPrompt(array $data, string $tone, string $language): string\n {\n $author = $data['ebook_author'] ?? 'Our Team';\n $company = $data['company_name'] ?? config('app.name');\n\n return <<<PROMPT\nGenerate a professional download email in {$language} language with a {$tone} tone.\n\nLead Information:\n- Name: {$data['lead_name']}\n- Email: {$data['lead_email']}\n\nEbook Information:\n- Title: {$data['ebook_title']}\n- Description: {$data['ebook_description']}\n- Author: {$author}\n\nDownload Link: {$data['download_url']}\nExpires: {$data['expires_at']}\nCompany: {$company}\n\nPlease include:\n1. A warm greeting using the lead's name\n2. Thank them for their interest\n3. Brief description of what they'll learn\n4. Clear CTA with the download link\n5. Note about expiration\n6. Professional signature with company details\n7. Unsubscribe link at the bottom\n\nReturn a JSON with keys: \"subject\", \"body_html\", \"body_text\"\nPROMPT;\n }\n\n private function buildFollowUpPrompt(array $context, string $tone, string $language): string\n {\n return <<<PROMPT\nGenerate a follow-up email in {$language} with a {$tone} tone.\n\nContext:\n- Lead Name: {$context['lead_name']}\n- Ebook: {$context['ebook_title']}\n- Days since download: {$context['days_since_download']}\n- Download Count: {$context['download_count']}\n\nReturn a JSON with keys: \"subject\", \"body_html\", \"body_text\"\nPROMPT;\n }\n\n private function buildCampaignPrompt(array $data, string $tone, string $language): string\n {\n return <<<PROMPT\nGenerate a marketing campaign email in {$language} with a {$tone} tone.\n\nCampaign: {$data['campaign_name']}\nEbook: {$data['ebook_title']}\nDescription: {$data['campaign_description']}\n\nReturn a JSON with keys: \"subject\", \"body_html\", \"body_text\"\nPROMPT;\n }\n\n private function callAI(string $prompt, ?string $systemPrompt = null): array\n {\n return match ($this->provider) {\n 'openai' => $this->callOpenAI($prompt, $systemPrompt),\n 'gemini' => $this->callGemini($prompt, $systemPrompt),\n 'claude' => $this->callClaude($prompt, $systemPrompt),\n 'deepseek' => $this->callDeepSeek($prompt, $systemPrompt),\n default => throw new Exception(\"Unsupported AI provider: {$this->provider}\"),\n };\n }\n\n private function callOpenAI(string $prompt, ?string $systemPrompt = null): array\n {\n $messages = [];\n\n if ($systemPrompt) {\n $messages[] = ['role' => 'system', 'content' => $systemPrompt];\n }\n\n $messages[] = ['role' => 'user', 'content' => $prompt];\n\n $url = str_ends_with($this->baseUrl, '/chat/completions')\n ? $this->baseUrl\n : $this->baseUrl . '/chat/completions';\n\n $response = $this->httpClient()->post($url, [\n 'model' => $this->model,\n 'messages' => $messages,\n 'max_tokens' => $this->maxTokens,\n 'temperature' => $this->temperature,\n ]);\n\n return $this->parseResponse($response->json(), 'openai');\n }\n\n private function callGemini(string $prompt, ?string $systemPrompt = null): array\n {\n $contents = [];\n\n if ($systemPrompt) {\n $contents[] = ['role' => 'user', 'parts' => [['text' => $systemPrompt]]];\n }\n\n $contents[] = ['role' => 'user', 'parts' => [['text' => $prompt]]];\n\n $url = config('ebook.ai.providers.gemini.url') . \"?key={$this->apiKey}\";\n\n $response = $this->httpClient()->post($url, [\n 'contents' => $contents,\n 'generationConfig' => [\n 'maxOutputTokens' => $this->maxTokens,\n 'temperature' => $this->temperature,\n ],\n ]);\n\n return $this->parseResponse($response->json(), 'gemini');\n }\n\n private function callClaude(string $prompt, ?string $systemPrompt = null): array\n {\n $response = $this->httpClient()\n ->withHeaders([\n 'x-api-key' => $this->apiKey,\n 'anthropic-version' => '2023-06-01',\n ])\n ->post(config('ebook.ai.providers.claude.url', 'https://api.anthropic.com/v1/messages'), [\n 'model' => $this->model,\n 'system' => $systemPrompt ?? 'You are a helpful email marketing assistant.',\n 'messages' => [\n ['role' => 'user', 'content' => $prompt],\n ],\n 'max_tokens' => $this->maxTokens,\n 'temperature' => $this->temperature,\n ]);\n\n return $this->parseResponse($response->json(), 'claude');\n }\n\n private function callDeepSeek(string $prompt, ?string $systemPrompt = null): array\n {\n $messages = [];\n\n if ($systemPrompt) {\n $messages[] = ['role' => 'system', 'content' => $systemPrompt];\n }\n\n $messages[] = ['role' => 'user', 'content' => $prompt];\n\n $response = $this->httpClient()->post(config('ebook.ai.providers.deepseek.url', 'https://api.deepseek.com/v1/chat/completions'), [\n 'model' => $this->model,\n 'messages' => $messages,\n 'max_tokens' => $this->maxTokens,\n 'temperature' => $this->temperature,\n ]);\n\n return $this->parseResponse($response->json(), 'deepseek');\n }\n\n private function parseResponse(array $response, string $provider): array\n {\n $content = match ($provider) {\n 'openai', 'deepseek' => $response['choices'][0]['message']['content'] ?? '',\n 'gemini' => $response['candidates'][0]['content']['parts'][0]['text'] ?? '',\n 'claude' => $response['content'][0]['text'] ?? '',\n default => throw new Exception(\"Unsupported provider for response parsing: {$provider}\"),\n };\n\n $json = json_decode($content, true);\n\n if ($json && isset($json['subject'], $json['body_html'])) {\n return $json;\n }\n\n return [\n 'subject' => 'Your Download from ' . config('app.name'),\n 'body_html' => $content,\n 'body_text' => strip_tags($content),\n ];\n }\n\n private function httpClient(): PendingRequest\n {\n return Http::timeout($this->timeout)\n ->withToken($this->apiKey)\n ->withHeaders([\n 'Content-Type' => 'application/json',\n ])\n ->throw();\n }\n\n public function getConversationHistory(AiConversation $conversation): string\n {\n return $conversation->messages()\n ->orderBy('created_at')\n ->get()\n ->map(fn (AiMessage $msg) => \"{$msg->role}: {$msg->content}\")\n ->implode(\"\\n\");\n }\n\n public function getAvailableModels(): array\n {\n return [\n 'openai' => ['gpt-4o', 'gpt-4o-mini', 'gpt-4-turbo', 'gpt-3.5-turbo'],\n 'gemini' => ['gemini-pro', 'gemini-1.5-pro', 'gemini-1.5-flash'],\n 'claude' => ['claude-3-opus-20240229', 'claude-3-sonnet-20240229', 'claude-3-haiku-20240307'],\n 'deepseek' => ['deepseek-chat', 'deepseek-reasoner'],\n ];\n }\n}\n"}, "ed8891e91686e9276fef65a24e9658c8bbc80d4e": {"t": 1785760157.0236528, "n": 9901, "h": "840bb2d6f3844708", "c": "<?php\n\nnamespace App\\Services;\n\nuse App\\Models\\AiConversation;\nuse App\\Models\\AiMessage;\nuse App\\Models\\SiteSetting;\nuse Exception;\nuse Illuminate\\Http\\Client\\PendingRequest;\nuse Illuminate\\Support\\Facades\\Http;\nuse Illuminate\\Support\\Facades\\Log;\n\nclass AiEmailService\n{\n private string $provider;\n private string $model;\n private string $apiKey;\n private int $maxTokens;\n private float $temperature;\n private int $timeout;\n\n public function __construct()\n {\n $this->provider = config('ebook.ai.provider', 'openai');\n $this->model = config('ebook.ai.model', 'gpt-4o');\n $this->apiKey = trim((string) SiteSetting::getValue('ai_openai_api_key', config('ebook.ai.api_key')));\n $this->maxTokens = config('ebook.ai.max_tokens', 2000);\n $this->temperature = config('ebook.ai.temperature', 0.7);\n $this->timeout = config('ebook.ai.timeout', 60);\n }\n\n public function generateDownloadEmail(array $data, string $tone = 'professional', string $language = 'en'): array\n {\n $prompt = $this->buildDownloadEmailPrompt($data, $tone, $language);\n\n return $this->callAI($prompt);\n }\n\n public function rewriteEmail(string $content, string $tone, string $language = 'en'): array\n {\n $prompt = \"Rewrite the following email content in a {$tone} tone and in {$language} language.\\n\\nOriginal:\\n{$content}\\n\\nRewritten version:\";\n\n return $this->callAI($prompt);\n }\n\n public function generateFollowUpEmail(array $context, string $tone = 'professional', string $language = 'en'): array\n {\n $prompt = $this->buildFollowUpPrompt($context, $tone, $language);\n\n return $this->callAI($prompt);\n }\n\n public function generateCampaignEmail(array $campaignData, string $tone = 'marketing', string $language = 'en'): array\n {\n $prompt = $this->buildCampaignPrompt($campaignData, $tone, $language);\n\n return $this->callAI($prompt);\n }\n\n public function chat(string $message, string $conversationHistory = ''): array\n {\n $systemPrompt = <<<PROMPT\nYou are an AI Admin Assistant for an Ebook Lead Magnet & Digital Asset Delivery System.\n\nYou can help admins with:\n1. Sending download emails to leads\n2. Resending expired download links\n3. Generating follow-up emails\n4. Creating campaign emails\n5. Viewing download analytics\n6. Managing ebook leads\n\nIMPORTANT RULES:\n- Never share the actual file paths or internal URLs\n- Never expose API keys or sensitive configuration\n- Use the tools and commands available to you through the system\n- When asked to send emails, confirm with the admin before sending\n- Keep responses concise and actionable\n\nCurrent context: Admin managing ebook downloads and leads.\nPROMPT;\n\n return $this->callAI($message, $systemPrompt . \"\\n\\n\" . $conversationHistory);\n }\n\n private function buildDownloadEmailPrompt(array $data, string $tone, string $language): string\n {\n $author = $data['ebook_author'] ?? 'Our Team';\n $company = $data['company_name'] ?? config('app.name');\n\n return <<<PROMPT\nGenerate a professional download email in {$language} language with a {$tone} tone.\n\nLead Information:\n- Name: {$data['lead_name']}\n- Email: {$data['lead_email']}\n\nEbook Information:\n- Title: {$data['ebook_title']}\n- Description: {$data['ebook_description']}\n- Author: {$author}\n\nDownload Link: {$data['download_url']}\nExpires: {$data['expires_at']}\nCompany: {$company}\n\nPlease include:\n1. A warm greeting using the lead's name\n2. Thank them for their interest\n3. Brief description of what they'll learn\n4. Clear CTA with the download link\n5. Note about expiration\n6. Professional signature with company details\n7. Unsubscribe link at the bottom\n\nReturn a JSON with keys: \"subject\", \"body_html\", \"body_text\"\nPROMPT;\n }\n\n private function buildFollowUpPrompt(array $context, string $tone, string $language): string\n {\n return <<<PROMPT\nGenerate a follow-up email in {$language} with a {$tone} tone.\n\nContext:\n- Lead Name: {$context['lead_name']}\n- Ebook: {$context['ebook_title']}\n- Days since download: {$context['days_since_download']}\n- Download Count: {$context['download_count']}\n\nReturn a JSON with keys: \"subject\", \"body_html\", \"body_text\"\nPROMPT;\n }\n\n private function buildCampaignPrompt(array $data, string $tone, string $language): string\n {\n return <<<PROMPT\nGenerate a marketing campaign email in {$language} with a {$tone} tone.\n\nCampaign: {$data['campaign_name']}\nEbook: {$data['ebook_title']}\nDescription: {$data['campaign_description']}\n\nReturn a JSON with keys: \"subject\", \"body_html\", \"body_text\"\nPROMPT;\n }\n\n private function callAI(string $prompt, ?string $systemPrompt = null): array\n {\n return match ($this->provider) {\n 'openai' => $this->callOpenAI($prompt, $systemPrompt),\n 'gemini' => $this->callGemini($prompt, $systemPrompt),\n 'claude' => $this->callClaude($prompt, $systemPrompt),\n 'deepseek' => $this->callDeepSeek($prompt, $systemPrompt),\n default => throw new Exception(\"Unsupported AI provider: {$this->provider}\"),\n };\n }\n\n private function callOpenAI(string $prompt, ?string $systemPrompt = null): array\n {\n $messages = [];\n\n if ($systemPrompt) {\n $messages[] = ['role' => 'system', 'content' => $systemPrompt];\n }\n\n $messages[] = ['role' => 'user', 'content' => $prompt];\n\n $response = $this->httpClient()->post(config('ebook.ai.providers.openai.url', 'https://api.openai.com/v1/chat/completions'), [\n 'model' => $this->model,\n 'messages' => $messages,\n 'max_tokens' => $this->maxTokens,\n 'temperature' => $this->temperature,\n ]);\n\n return $this->parseResponse($response->json(), 'openai');\n }\n\n private function callGemini(string $prompt, ?string $systemPrompt = null): array\n {\n $contents = [];\n\n if ($systemPrompt) {\n $contents[] = ['role' => 'user', 'parts' => [['text' => $systemPrompt]]];\n }\n\n $contents[] = ['role' => 'user', 'parts' => [['text' => $prompt]]];\n\n $url = config('ebook.ai.providers.gemini.url') . \"?key={$this->apiKey}\";\n\n $response = $this->httpClient()->post($url, [\n 'contents' => $contents,\n 'generationConfig' => [\n 'maxOutputTokens' => $this->maxTokens,\n 'temperature' => $this->temperature,\n ],\n ]);\n\n return $this->parseResponse($response->json(), 'gemini');\n }\n\n private function callClaude(string $prompt, ?string $systemPrompt = null): array\n {\n $response = $this->httpClient()\n ->withHeaders([\n 'x-api-key' => $this->apiKey,\n 'anthropic-version' => '2023-06-01',\n ])\n ->post(config('ebook.ai.providers.claude.url', 'https://api.anthropic.com/v1/messages'), [\n 'model' => $this->model,\n 'system' => $systemPrompt ?? 'You are a helpful email marketing assistant.',\n 'messages' => [\n ['role' => 'user', 'content' => $prompt],\n ],\n 'max_tokens' => $this->maxTokens,\n 'temperature' => $this->temperature,\n ]);\n\n return $this->parseResponse($response->json(), 'claude');\n }\n\n private function callDeepSeek(string $prompt, ?string $systemPrompt = null): array\n {\n $messages = [];\n\n if ($systemPrompt) {\n $messages[] = ['role' => 'system', 'content' => $systemPrompt];\n }\n\n $messages[] = ['role' => 'user', 'content' => $prompt];\n\n $response = $this->httpClient()->post(config('ebook.ai.providers.deepseek.url', 'https://api.deepseek.com/v1/chat/completions'), [\n 'model' => $this->model,\n 'messages' => $messages,\n 'max_tokens' => $this->maxTokens,\n 'temperature' => $this->temperature,\n ]);\n\n return $this->parseResponse($response->json(), 'deepseek');\n }\n\n private function parseResponse(array $response, string $provider): array\n {\n $content = match ($provider) {\n 'openai', 'deepseek' => $response['choices'][0]['message']['content'] ?? '',\n 'gemini' => $response['candidates'][0]['content']['parts'][0]['text'] ?? '',\n 'claude' => $response['content'][0]['text'] ?? '',\n default => throw new Exception(\"Unsupported provider for response parsing: {$provider}\"),\n };\n\n $json = json_decode($content, true);\n\n if ($json && isset($json['subject'], $json['body_html'])) {\n return $json;\n }\n\n return [\n 'subject' => 'Your Download from ' . config('app.name'),\n 'body_html' => $content,\n 'body_text' => strip_tags($content),\n ];\n }\n\n private function httpClient(): PendingRequest\n {\n return Http::timeout($this->timeout)\n ->withToken($this->apiKey)\n ->withHeaders([\n 'Content-Type' => 'application/json',\n ])\n ->throw();\n }\n\n public function getConversationHistory(AiConversation $conversation): string\n {\n return $conversation->messages()\n ->orderBy('created_at')\n ->get()\n ->map(fn (AiMessage $msg) => \"{$msg->role}: {$msg->content}\")\n ->implode(\"\\n\");\n }\n\n public function getAvailableModels(): array\n {\n return [\n 'openai' => ['gpt-4o', 'gpt-4o-mini', 'gpt-4-turbo', 'gpt-3.5-turbo'],\n 'gemini' => ['gemini-pro', 'gemini-1.5-pro', 'gemini-1.5-flash'],\n 'claude' => ['claude-3-opus-20240229', 'claude-3-sonnet-20240229', 'claude-3-haiku-20240307'],\n 'deepseek' => ['deepseek-chat', 'deepseek-reasoner'],\n ];\n }\n}\n"}}}} |