Al-HUWAITI Shell
Al-huwaiti


Server : LiteSpeed
System : Linux server335.web-hosting.com 4.18.0-553.62.1.lve.el8.x86_64 #1 SMP Mon Jul 21 17:50:35 UTC 2025 x86_64
User : cardxfeb ( 2452)
PHP Version : 8.1.34
Disable Function : NONE
Directory :  /home/cardxfeb/public_html/app/Services/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Current File : /home/cardxfeb/public_html/app/Services/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',
        ];
    }
}

Al-HUWAITI Shell