{"repo": "asifkst5555/settleanz", "files": {"config/ebook.php": {"2bcf3d38ad459ad986d5ab3129163e74e2dd7ecd": {"t": 1785746144.4379632, "n": 4191, "h": "736d283d83f10752", "c": " [\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": " [\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": "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 = <<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 << $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 <<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>/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, '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, 'wrapInEmailHtml($html);\n }\n\n private function wrapInEmailHtml(string $bodyContent): string\n {\n return '\n\n\n\n\n\n\n
\n\n\n\n\n
\n\n\n\n\n
\n' . $bodyContent . '\n
\n
\n
\n\n';\n }\n\n private function safeFallbackEmailHtml(): string\n {\n return $this->wrapInEmailHtml('

Hi there,

Thank you for your interest in SettleANZ. We received your request and our team will follow up shortly.

Warm regards,
The SettleANZ Team

');\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": "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 = <<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 << $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 <<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>/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, '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, 'wrapInEmailHtml($html);\n }\n\n private function wrapInEmailHtml(string $bodyContent): string\n {\n return '\n\n\n\n\n\n\n
\n\n\n\n\n
\n\n\n\n\n
\n' . $bodyContent . '\n
\n
\n
\n\n';\n }\n\n private function safeFallbackEmailHtml(): string\n {\n return $this->wrapInEmailHtml('

Hi there,

Thank you for your interest in SettleANZ. We received your request and our team will follow up shortly.

Warm regards,
The SettleANZ Team

');\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": "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 = <<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 << $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 <<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>/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, '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, 'wrapInEmailHtml($html);\n }\n\n private function wrapInEmailHtml(string $bodyContent): string\n {\n return '\n\n\n\n\n\n\n
\n\n\n\n\n
\n\n\n\n\n
\n' . $bodyContent . '\n
\n
\n
\n\n';\n }\n\n private function safeFallbackEmailHtml(): string\n {\n return $this->wrapInEmailHtml('

Hi there,

Thank you for your interest in SettleANZ. We received your request and our team will follow up shortly.

Warm regards,
The SettleANZ Team

');\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": "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 = <<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 << $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 <<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": "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 = <<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 <<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 <<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": "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 = <<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 <<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": "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 = <<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 <<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"}}}}