{"repo": "ateuatado/cerebro", "files": {"app/Services/DeepSeekService.php": {"c6e5667957cdb7a83f1840bc3027673dab31372c": {"t": 1785745779.0777183, "n": 12821, "h": "90b928b34894fa36", "c": "apiKey = getenv('DEEPSEEK_API_KEY') ?: '';\n\n $envPath = defined('ROOTPATH') ? ROOTPATH . '.env' : __DIR__ . '/../../.env';\n if (empty($this->apiKey) && file_exists($envPath)) {\n $lines = file($envPath);\n foreach ($lines as $line) {\n if (strpos(trim($line), 'DEEPSEEK_API_KEY') === 0) {\n $parts = explode('=', $line, 2);\n $this->apiKey = trim($parts[1] ?? '');\n break;\n }\n }\n }\n }\n\n /**\n * Envia requisição para a API DeepSeek\n */\n private function callApi(array $messages): array\n {\n if (empty($this->apiKey)) {\n throw new \\RuntimeException('A chave DEEPSEEK_API_KEY não está configurada no arquivo .env.');\n }\n\n $data = [\n 'model' => $this->model,\n 'messages' => $messages,\n 'temperature' => 0.1,\n 'response_format' => ['type' => 'json_object']\n ];\n\n $ch = \\curl_init($this->apiUrl);\n \\curl_setopt($ch, \\CURLOPT_RETURNTRANSFER, true);\n \\curl_setopt($ch, \\CURLOPT_POST, true);\n \\curl_setopt($ch, \\CURLOPT_HTTPHEADER, [\n 'Content-Type: application/json',\n 'Authorization: Bearer ' . $this->apiKey\n ]);\n \\curl_setopt($ch, \\CURLOPT_POSTFIELDS, \\json_encode($data, \\JSON_UNESCAPED_UNICODE));\n \\curl_setopt($ch, \\CURLOPT_SSL_VERIFYPEER, false);\n \\curl_setopt($ch, \\CURLOPT_TIMEOUT, 90);\n\n $response = \\curl_exec($ch);\n $httpCode = \\curl_getinfo($ch, \\CURLINFO_HTTP_CODE);\n $error = \\curl_error($ch);\n \\curl_close($ch);\n\n if ($error) {\n throw new \\RuntimeException('Erro na chamada da API DeepSeek: ' . $error);\n }\n\n if ($httpCode !== 200) {\n throw new \\RuntimeException('A API DeepSeek retornou status ' . $httpCode . ': ' . \\mb_substr($response, 0, 500));\n }\n\n $json = \\json_decode($response, true);\n $content = $json['choices'][0]['message']['content'] ?? '';\n\n if (empty($content)) {\n throw new \\RuntimeException('A resposta da API DeepSeek veio vazia.');\n }\n\n $cleanedContent = \\preg_replace('/^```json\\s*|\\s*```$/i', '', \\trim($content));\n $extractedData = \\json_decode($cleanedContent, true);\n\n if (\\json_last_error() !== \\JSON_ERROR_NONE) {\n throw new \\RuntimeException('Falha ao decodificar JSON retornado pela IA: ' . \\json_last_error_msg());\n }\n\n return $extractedData ?: [];\n }\n\n /**\n * Lê uma imagem de recorte (Base64) diretamente via DeepSeek Vision\n * e extrai entidades e relações do manuscrito.\n * \n * @param string $docTitle Título do documento\n * @param string $imageBase64 Dados da imagem em Base64 (com prefixo data:image/...)\n * @return array ['transcription', 'entities', 'relationships']\n */\n public function extractFromCropImage(string $docTitle, string $imageBase64): array\n {\n $systemPrompt = << 'system', 'content' => $systemPrompt],\n [\n 'role' => 'user',\n 'content' => [\n [\n 'type' => 'text',\n 'text' => \"Documento: {$docTitle}\\n\\nTranscreva e extraia entidades/relações deste recorte de manuscrito histórico.\"\n ],\n [\n 'type' => 'image_url',\n 'image_url' => [\n 'url' => $imageBase64\n ]\n ]\n ]\n ]\n ];\n\n $result = $this->callApi($messages);\n\n return [\n 'transcription' => $result['transcription'] ?? '',\n 'entities' => $result['entities'] ?? [],\n 'relationships' => $result['relationships'] ?? [],\n ];\n }\n\n /**\n * Transcreve e extrai entidades/relações a partir do texto bruto de um recorte de imagem manuscrita.\n */\n public function extractFromCropText(string $docTitle, string $rawOcrText): array\n {\n if (empty($this->apiKey)) {\n throw new \\RuntimeException('A chave DEEPSEEK_API_KEY não está configurada no arquivo .env.');\n }\n\n $systemPrompt = << 'system', 'content' => $systemPrompt],\n ['role' => 'user', 'content' => $userPrompt]\n ];\n\n $result = $this->callApi($messages);\n\n return [\n 'transcription' => $result['transcription'] ?? $rawOcrText,\n 'entities' => $result['entities'] ?? [],\n 'relationships' => $result['relationships'] ?? [],\n ];\n }\n\n /**\n * Extração de alta densidade em documentos longos (ex: jornais, processos extensos),\n * dividindo o texto em blocos sequenciais para extrair dezenas/centenas de relações sem truncamento da IA.\n */\n public function extractKnowledgeChunked(string $docTitle, string $docText, array $extraAttributes = [], int $chunkSize = 3000): array\n {\n if (\\strlen($docText) <= $chunkSize) {\n return $this->extractKnowledge($docTitle, $docText, $extraAttributes);\n }\n\n $lines = \\explode(\"\\n\", $docText);\n $chunks = [];\n $currentChunk = '';\n\n foreach ($lines as $line) {\n if (\\strlen($currentChunk) + \\strlen($line) > $chunkSize && !empty($currentChunk)) {\n $chunks[] = $currentChunk;\n $currentChunk = '';\n }\n $currentChunk .= $line . \"\\n\";\n }\n if (!empty(\\trim($currentChunk))) {\n $chunks[] = $currentChunk;\n }\n\n $allEntities = [];\n $allRelationships = [];\n\n foreach ($chunks as $index => $chunkText) {\n $chunkTitle = \"{$docTitle} (Parte \" . ($index + 1) . \" de \" . \\count($chunks) . \")\";\n try {\n $res = $this->extractKnowledge($chunkTitle, $chunkText, $extraAttributes);\n\n foreach ($res['entities'] as $entity) {\n $name = \\trim($entity['name'] ?? '');\n $type = $entity['type'] ?? 'person';\n if (!empty($name)) {\n $key = \\mb_strtolower($name) . '|' . $type;\n if (!isset($allEntities[$key])) {\n $allEntities[$key] = $entity;\n } else {\n $allEntities[$key]['attributes'] = \\array_merge(\n $allEntities[$key]['attributes'] ?? [],\n $entity['attributes'] ?? []\n );\n }\n }\n }\n\n foreach ($res['relationships'] as $rel) {\n $allRelationships[] = $rel;\n }\n\n } catch (\\Exception $e) {\n // Log da falha no bloco e continua nos próximos\n }\n }\n\n return [\n 'entities' => \\array_values($allEntities),\n 'relationships' => $allRelationships,\n ];\n }\n\n /**\n * Analisa o texto de um documento histórico e extrai entidades e relações em formato estruturado.\n */\n public function extractKnowledge(string $docTitle, string $docText, array $extraAttributes = []): array\n {\n $systemPrompt = << 'system', 'content' => $systemPrompt],\n ['role' => 'user', 'content' => $userPrompt]\n ];\n\n return $this->callApi($messages);\n }\n}\n"}, "b7f154ff76c886d4cbd7f84b7e2a32ede78ce0dd": {"t": 1785756470.051282, "n": 5897, "h": "f12b0d271c34c069", "c": "apiKey = getenv('DEEPSEEK_API_KEY') ?: '';\n\n $envPath = defined('ROOTPATH') ? ROOTPATH . '.env' : __DIR__ . '/../../.env';\n if (empty($this->apiKey) && file_exists($envPath)) {\n $lines = file($envPath);\n foreach ($lines as $line) {\n if (strpos(trim($line), 'DEEPSEEK_API_KEY') === 0) {\n $parts = explode('=', $line, 2);\n $this->apiKey = trim($parts[1] ?? '');\n break;\n }\n }\n }\n }\n\n /**\n * Analisa o texto de um documento histórico e extrai entidades e relações em formato estruturado.\n *\n * @param string $docTitle Título do documento\n * @param string $docText Conteúdo/transcrição do documento\n * @param array $extraAttributes Atributos bibliográficos do documento\n * @return array Array estruturado com ['entities' => [...], 'relationships' => [...]]\n */\n public function extractKnowledge(string $docTitle, string $docText, array $extraAttributes = []): array\n {\n if (empty($this->apiKey)) {\n throw new \\RuntimeException('A chave DEEPSEEK_API_KEY não está configurada no arquivo .env.');\n }\n\n $systemPrompt = << $this->model,\n 'messages' => [\n ['role' => 'system', 'content' => $systemPrompt],\n ['role' => 'user', 'content' => $userPrompt]\n ],\n 'temperature' => 0.1,\n 'response_format' => ['type' => 'json_object']\n ];\n\n $ch = curl_init($this->apiUrl);\n curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);\n curl_setopt($ch, CURLOPT_POST, true);\n curl_setopt($ch, CURLOPT_HTTPHEADER, [\n 'Content-Type: application/json',\n 'Authorization: Bearer ' . $this->apiKey\n ]);\n curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));\n curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);\n curl_setopt($ch, CURLOPT_TIMEOUT, 60);\n\n $response = curl_exec($ch);\n $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);\n $error = curl_error($ch);\n curl_close($ch);\n\n if ($error) {\n throw new \\RuntimeException('Erro na chamada da API DeepSeek: ' . $error);\n }\n\n if ($httpCode !== 200) {\n throw new \\RuntimeException('A API DeepSeek retornou o status HTTP ' . $httpCode . ': ' . $response);\n }\n\n $json = json_decode($response, true);\n $content = $json['choices'][0]['message']['content'] ?? '';\n\n if (empty($content)) {\n throw new \\RuntimeException('A resposta da API DeepSeek veio vazia.');\n }\n\n // Limpar possíveis blocos de markdown se retornados\n $cleanedContent = preg_replace('/^```json\\s*|\\s*```$/i', '', trim($content));\n $extractedData = json_decode($cleanedContent, true);\n\n if (json_last_error() !== JSON_ERROR_NONE) {\n throw new \\RuntimeException('Falha ao decodificar JSON retornado pela IA: ' . json_last_error_msg());\n }\n\n return [\n 'entities' => $extractedData['entities'] ?? [],\n 'relationships' => $extractedData['relationships'] ?? [],\n ];\n }\n}\n"}}}}