PATH:
home
/
cardxfeb
/
public_html
/
app
/
Services
/
Editing: LlmAnalyzerService.php
<?php namespace App\Services; use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Log; class LlmAnalyzerService { /** * Analyze MULTIPLE source data (social + corporate) with Claude API. * Strategy: corporate branding for colors, social personality for bio. */ public function analyzeMultiple(array $multiSourceData, string $region = 'BR'): array { $apiKey = config('services.claude.api_key'); if (empty($apiKey) || $apiKey === 'sk-ant-CONFIGURE_ME') { Log::warning('[LlmAnalyzer] CLAUDE_API_KEY not configured using fallback'); return $this->fallbackResult($multiSourceData['merged'] ?? []); } $lang = $region === 'PT' ? 'portugus europeu formal' : 'portugus brasileiro profissional'; $merged = $multiSourceData['merged'] ?? []; $social = $multiSourceData['social'] ?? []; $corporate = $multiSourceData['corporate'] ?? []; // Build color hints string $colorHints = ''; $brandColors = $merged['brand_colors'] ?? []; if (!empty($brandColors)) { $colorParts = []; if (!empty($brandColors['theme_color'])) $colorParts[] = "theme-color: {$brandColors['theme_color']}"; if (!empty($brandColors['css_primary'])) $colorParts[] = "CSS primary: {$brandColors['css_primary']}"; if (!empty($brandColors['tile_color'])) $colorParts[] = "tile-color: {$brandColors['tile_color']}"; if (!empty($brandColors['body_bg'])) $colorParts[] = "body-bg: {$brandColors['body_bg']}"; $colorHints = implode(', ', $colorParts); } // Build social profiles summary $socialSummary = ''; foreach ($social as $s) { $socialSummary .= "- [{$s['platform']}] Ttulo: \"{$s['title']}\" | Bio: \"{$s['description']}\"\n"; } // Build corporate summary $corporateSummary = ''; foreach ($corporate as $c) { $corporateSummary .= "- [Website] Nome da empresa: \"{$c['title']}\" | Descrio: \"{$c['description']}\" | Keywords: \"{$c['keywords']}\"\n"; } $sourcesCount = ($merged['sources'] ?? 1); $prompt = "Voc um especialista snior em branding digital e identidade visual corporativa. Voc recebeu dados de {$sourcesCount} fonte(s) distintas para criar um carto de visita digital profissional. === FONTES DE DADOS === PERFIS SOCIAIS (Use para: personalidade, bio, tom de voz do indivduo): {$socialSummary} SITE CORPORATIVO (Use para: cores da marca, nome da empresa, posicionamento): {$corporateSummary} CORES DETECTADAS NO SITE DA EMPRESA: {$colorHints} === INSTRUO CRTICA === - Use as CORES DO SITE CORPORATIVO como base para color_primary e color_secondary. - Se no houver cores do site, use um tom que combine com a personalidade social detectada. - Mantenha a BIO e PROFISSO baseadas nos perfis sociais (voz autntica do indivduo). - O NOME deve priorizar o perfil social; use o site apenas como confirmao. - O TEMPLATE deve refletir o posicionamento: corporativo=1-8, criativo=9-16, empreendedor=17-24. RETORNE APENAS este JSON vlido (sem markdown, sem texto extra): { \"name\": \"nome completo ou profissional\", \"profession\": \"cargo ou rea de atuao\", \"bio\": \"bio profissional em at 160 caracteres em {$lang}\", \"company\": \"nome da empresa ou marca\", \"color_primary\": \"#HEXCOLOR da marca corporativa ou personalidade\", \"color_secondary\": \"#HEXCOLOR complementar\", \"template_id\": \"1 a 24\", \"links\": [\"URLs relevantes\"], \"social_handles\": {}, \"brand_personality\": \"1 palavra: corporativo|criativo|empreendedor|profissional\", \"confidence\": \"high|medium|low\" }"; try { $response = Http::withHeaders([ 'x-api-key' => $apiKey, 'anthropic-version' => '2023-06-01', 'content-type' => 'application/json', ])->timeout(35)->post('https://api.anthropic.com/v1/messages', [ 'model' => config('services.claude.model', 'claude-haiku-4-5'), 'max_tokens' => 700, 'messages' => [['role' => 'user', 'content' => $prompt]], ]); if (!$response->ok()) { Log::error('[LlmAnalyzer] API error: ' . $response->status() . ' - ' . $response->body()); return $this->fallbackResult($merged); } $text = $response->json('content.0.text', ''); // Strip markdown code blocks if present $text = preg_replace('/^```(?:json)?\s*/m', '', $text); $text = preg_replace('/\s*```$/m', '', $text); $text = trim($text); $data = json_decode($text, true); if (json_last_error() !== JSON_ERROR_NONE) { Log::warning('[LlmAnalyzer] Invalid JSON from LLM: ' . substr($text, 0, 200)); return $this->fallbackResult($merged); } // Sanitize template_id $data['template_id'] = max(1, min(24, (int)($data['template_id'] ?? 1))); // If no valid color from LLM, use detected brand color if (empty($data['color_primary']) || !preg_match('/^#[0-9a-fA-F]{3,6}$/', $data['color_primary'])) { $data['color_primary'] = $brandColors['theme_color'] ?? $brandColors['css_primary'] ?? '#2563EB'; } return $data; } catch (\Exception $e) { Log::error('[LlmAnalyzer] Exception: ' . $e->getMessage()); return $this->fallbackResult($merged); } } /** * Legacy single-source analyze (backward compatibility) */ public function analyze(array $scrapedData, string $region = 'BR'): array { // Wrap single source as multi-source format $wrapped = [ 'social' => [$scrapedData], 'corporate' => [], 'merged' => [ 'name' => $scrapedData['title'] ?? '', 'bio_hint' => $scrapedData['description'] ?? '', 'brand_name' => '', 'brand_desc' => '', 'brand_colors' => [], 'platforms' => [$scrapedData['platform'] ?? 'website'], 'sources' => 1, ], ]; return $this->analyzeMultiple($wrapped, $region); } private function fallbackResult(array $merged): array { $brandColor = $merged['brand_colors']['theme_color'] ?? $merged['brand_colors']['css_primary'] ?? '#2563EB'; return [ 'name' => $merged['name'] ?? 'Meu Carto Digital', 'profession' => '', 'bio' => $merged['bio_hint'] ?? '', 'company' => $merged['brand_name'] ?? '', 'color_primary' => $brandColor, 'color_secondary' => '#F59E0B', 'template_id' => 1, 'links' => [], 'social_handles' => [], 'brand_personality' => 'profissional', 'confidence' => 'low', ]; } }
SAVE
CANCEL