PDO::ERRMODE_EXCEPTION]); } catch (PDOException $e) { logMessage("Ошибка подключения к БД: " . $e->getMessage()); die("Ошибка подключения к БД"); } function logMessage($message) { if (!is_dir('aiassist/logs')) { mkdir('aiassist/logs', 0777, true); } file_put_contents(LOG_FILE, date('Y-m-d H:i:s') . " - " . $message . "\n", FILE_APPEND | LOCK_EX); } function normalizeFilename($filename) { $filename = iconv('UTF-8', 'UTF-8//IGNORE', $filename); return preg_replace('/[^\w\.]+/u', '_', $filename); } function cleanQueryText($text) { // Удаляем непечатные символы (например, управляющие символы) $text = preg_replace('/[\x00-\x1F\x7F]/u', ' ', $text); // Заменяем множественные пробелы на один $text = preg_replace('/\s+/', ' ', $text); // Обрезаем пробелы в начале и конце строки return trim($text); } /* ===================== Основной скрипт обработки ===================== */ if ($_SERVER['REQUEST_METHOD'] === 'POST') { // 1. Получаем ID кейса из $_POST или JSON-тела запроса $id = $_POST['id'] ?? null; if (!$id) { $input = json_decode(file_get_contents('php://input'), true); $id = $input['id'] ?? null; } if (!$id || !is_numeric($id)) { logMessage("Ошибка: Некорректный ID."); die(json_encode(["status" => "error", "message" => "Некорректный ID."], JSON_UNESCAPED_UNICODE)); } $GLOBALS['caseId'] = $id; // 2. Извлекаем документы из CRM $documents = fetchDocumentData($pdo, $id); if (empty($documents)) { logMessage("Документы не найдены для ID: $id"); die("Документы не найдены для ID: $id"); } logMessage("Документы получены из БД: " . json_encode($documents, JSON_UNESCAPED_UNICODE)); // Создаем массив путей к файлам $filePathList = array_map(function($doc) { return $doc['filepath']; }, $documents); // 3. Проверка наличия ранее сохранённого анализа (если реализовано кеширование) $previousAnalysis = checkPreviousAnalysis($id, $filePathList); logMessage("DEBUG: Значение предыдущего анализа: " . print_r($previousAnalysis, true)); if ($previousAnalysis) { logMessage("Найден сохранённый анализ, отправляем его в CRM."); # sendAnalysisToCRM($id, $previousAnalysis); exit; } else { logMessage("Сохранённый анализ не найден, продолжаем обработку."); } // 4. Подготовка документов: загрузка файлов в vector store, извлечение текста (pdftotext/OCR/NSFW) $uploadResult = createVectorStoreAndUploadFiles($filePathList); if (!$uploadResult) { logMessage("Ошибка создания Vector Store или загрузки файлов"); die("Ошибка создания Vector Store или загрузки файлов"); } $vectorStoreId = $uploadResult['vectorStoreId']; $uploadedFileIds = $uploadResult['fileIds']; if (!updateAssistantWithVectorStore($vectorStoreId)) { logMessage("Ошибка обновления ассистента с Vector Store"); die("Ошибка обновления ассистента"); } $combinedContent = analyzeDocuments($documents, $uploadedFileIds); logMessage("Собранный контент для анализа:\n" . $combinedContent); if (empty($combinedContent)) { logMessage("Ошибка: анализ документов не вернул результатов"); die("Ошибка: анализ документов не вернул результатов"); } // 5. Формируем объединенный список идентификаторов файлов $fileIdCombined = implode(',', array_values($uploadedFileIds)); logMessage("Объединённый список идентификаторов файлов: " . $fileIdCombined); // 6. Формирование итогового контекста (prompt) для GPT // Подключаем файл aiassist/search_context.php, который формирует prompt с учетом результатов поиска по разным индексам. $finalPrompt = include 'aiassist/search_context.php'; logMessage("Итоговый prompt для GPT:\n" . $finalPrompt); // 7. Создаем новый тред для общения с GPT $threadId = createNewThread(); if (!$threadId) { logMessage("Ошибка создания треда"); die("Ошибка создания треда"); } // 9. Сохраняем результат анализа в ElasticSearch и отправляем его в CRM //********************************************************************************************************/ /* $saveResult = saveAnalysisToElasticSearch($id, $analysis, $filePathList); if ($saveResult) { logMessage("✅ Анализ успешно сохранён в ElasticSearch."); } else { logMessage("❌ Ошибка при сохранении анализа в ElasticSearch."); } */ //******************************************************************************************************/ // 10. эмбилдин // Получаем объединённый и очищенный текст для эмбеддинга: // 1. Анализируем текст обращения через GPT-4 //$parsedResponse = extractCaseDetailsWithGPT($queryText); // Проверяем, пришли ли все нужные данные $category = $parsedResponse['category'] ?? "Не определено"; $articles = $parsedResponse['articles'] ?? []; $claim_amount = $parsedResponse['claim_amount'] ?? "Не указана"; $facts = $parsedResponse['facts'] ?? "Нет данных"; $articlesList = is_array($articles) ? implode(" ", $articles) : "Не указаны"; $parsedResponse = extractCaseDetailsWithGPT($threadId, ASSISTANT_ID, $fileIdCombined, $combinedContent); if (!$parsedResponse || !is_array($parsedResponse)) { logMessage("❌ Ошибка: GPT-4 не вернул корректные данные."); die("Ошибка анализа обращения через GPT-4"); } logMessage("✅ GPT-4 вернул: Категория: $category, Статьи: " . json_encode($articles, JSON_UNESCAPED_UNICODE) . ", Сумма: $claim_amount, Факты: $facts"); logMessage("✅ GPT-4 вернул: Категория: $category, Статьи: " . json_encode($articles) . ", Сумма: $claim_amount, Факты: $facts"); if (!$parsedResponse) { logMessage("❌ Ошибка анализа обращения через GPT-4."); die("Ошибка анализа обращения через GPT-4"); } logMessage("✅ GPT-4 выделил данные: " . json_encode($parsedResponse, JSON_UNESCAPED_UNICODE)); $fullTextForEmbedding = buildFullTextForEmbedding($documents); //$fullTextForEmbedding = buildFullTextForEmbedding($queryText); logMessage("Полный текст для эмбеддинга (очищенный): " . $fullTextForEmbedding); // Затем передаем очищенный текст в функцию для получения эмбеддинга: $embedding = getTextEmbedding($fullTextForEmbedding); logMessage("Полученный эмбеддинг (первые 10 значений): " . json_encode(array_slice($embedding, 0, 10))); // 2. Генерируем эмбеддинг по ключевым фактам обращения $embedding = getTextEmbedding($parsedResponse['facts']); logMessage("✅ Создан эмбеддинг для поиска."); // 3. Делаем комбинированный поиск (BM25 + векторный) if (!isset($es) || !$es) { logMessage("❌ Ошибка: Подключение к Elasticsearch отсутствует!"); die(json_encode(["status" => "error", "message" => "Ошибка соединения с Elasticsearch."], JSON_UNESCAPED_UNICODE)); } $searchResults = searchSimilarCases([ 'category' => $parsedResponse['category'], 'article' => implode(" ", $parsedResponse['articles']), 'amount' => $parsedResponse['claim_amount'], 'facts' => $parsedResponse['facts'], 'embedding' => $embedding ]); logMessage("✅ Найдены похожие судебные решения: " . json_encode($searchResults, JSON_UNESCAPED_UNICODE)); logMessage("Обработка всех документов завершена."); exit; } else { logMessage("Ошибка: запрос должен быть POST"); die("Ошибка: запрос должен быть POST"); } /* ===================== Функции для работы с CRM и Vector Store ===================== */ //пилим предзапрос// $foundCases = []; foreach ($searchResults as $doc) { // Можно добавить проверку, что court_decision не пустой if (!empty($doc['court_decision'])) { $foundCases[] = [ 'case_id' => $doc['case_id'] ?? 'Неизвестный ID', 'court' => $doc['court'] ?? 'Неизвестный суд', 'court_decision' => $doc['court_decision'] ]; } } // 8. Отправляем итоговый prompt в GPT через ассистента (stream) - запрос второго анализа в GPT //******************************************************************************************************** */ $analysis = analyzeDocumentWithAssistantStream($threadId, $assistantId, $fileId, $content, $foundCases); if (!$analysis) { logMessage("❌ Ошибка анализа совокупного запроса"); die("Ошибка анализа совокупного запроса"); } //**********************************************************************************************************/ function extractCaseDetailsWithGPT($threadId, $assistantId, $fileId, $content) { logMessage("📌 Предварительный анализ обращения через GPT-4 (stream): thread_id=$threadId, fileId=$fileId"); $userMessage = "🔹 Разбери обращение и выдели ключевые параметры: 1️⃣ **Категория дела**: Определи, к какому типу спора относится это обращение (например, защита прав потребителей, нарушение договора, трудовой спор). 2️⃣ **Применимые статьи закона**: Выяви, какие нормы права могут быть применимы в данном случае (например, ГК РФ ст. 395, КоАП РФ). 3️⃣ **Основные обстоятельства дела**: Выдели важные факты из обращения и приложенных документов. 4️⃣ **Сумма требований (если указана)**: Если в обращении указана денежная сумма – извлеки её. 5️⃣ **Тип запрашиваемого документа**: Нужно ли составить иск, претензию, жалобу, ходатайство? 6️⃣ **Оценка вероятности выигрыша**: Исходя из предоставленных данных, оцени, какие шансы на положительное решение (в %). 🔹 **Формат ответа (JSON)**: { \"category\": \"\", \"articles\": [\"\"], \"facts\": \"\", \"claim_amount\": \"\", \"document_type\": \"\", \"win_probability\": \"\" } 📌 Файлы: " . $fileId . "\n 📌 Содержимое для анализа:\n" . $content; logMessage("📌 Формируем запрос к GPT-4: " . $userMessage); $payload = [ "assistant_id" => $assistantId, "thread" => [ "messages" => [ ["role" => "user", "content" => $userMessage] ] ], "stream" => true ]; $payloadJson = json_encode($payload, JSON_UNESCAPED_UNICODE); logMessage("🔹 Отправляем запрос в GPT-4 (Payload):\n" . $payloadJson); $finalMessage = ""; $curl = curl_init(); curl_setopt_array($curl, [ CURLOPT_URL => OPENAI_THREADS_API . "/runs", CURLOPT_RETURNTRANSFER => false, CURLOPT_POST => true, CURLOPT_POSTFIELDS => $payloadJson, CURLOPT_HTTPHEADER => [ 'Content-Type: application/json', 'Authorization: Bearer ' . OPENAI_API_KEY, 'OpenAI-Beta: assistants=v2' ], CURLOPT_WRITEFUNCTION => function($ch, $data) use (&$finalMessage) { $finalMessage .= $data; return strlen($data); } ]); curl_exec($curl); $curlError = curl_error($curl); if ($curlError) { logMessage("❌ Ошибка cURL в extractCaseDetailsWithGPT: " . $curlError); curl_close($curl); return null; } curl_close($curl); logMessage("📌 Сырой потоковый ответ (raw stream):\n" . $finalMessage); // Разбираем потоковый ответ $parsedMessage = ""; $lines = explode("\n", $finalMessage); foreach ($lines as $line) { $line = trim($line); if (strpos($line, "data: ") === 0) { $dataPart = substr($line, 6); if ($dataPart === "[DONE]") { break; } $json = json_decode($dataPart, true); logMessage("🔹 DEBUG: Распарсенный фрагмент: " . print_r($json, true)); if (is_array($json) && isset($json['delta']['content'])) { $contentPiece = ""; foreach ($json['delta']['content'] as $segment) { if (isset($segment['text']['value'])) { $contentPiece .= $segment['text']['value']; } } $parsedMessage .= $contentPiece; } } } if (empty(trim($parsedMessage))) { logMessage("❌ Парсинг не дал результата, используем сырой ответ."); $parsedMessage = $finalMessage; } logMessage("✅ Итоговый ответ от GPT-4 (stream):\n" . $parsedMessage); $structuredData = json_decode($parsedMessage, true); if (!$structuredData) { logMessage("❌ Ошибка парсинга JSON, возвращаем сырой ответ."); return ["error" => "Ошибка обработки данных"]; } return $structuredData; } // Функция для проверки, является ли документ судебным решением function isCourtDecision($filename, $content) { $keywords = ['решение суда', 'решение', 'судебное решение', 'постановление', 'определение', 'приказ']; foreach ($keywords as $keyword) { if (stripos($filename, $keyword) !== false || stripos($content, $keyword) !== false) { return true; } } return false; } function fetchDocumentData($pdo, $id) { logMessage("Получение данных документа из CRM по ID: $id"); $sql = " SELECT n.title, CASE WHEN a.storedname IS NOT NULL THEN CONCAT(a.path, a.attachmentsid, '_', a.storedname) ELSE CONCAT(a.path, a.attachmentsid, '_', a.name) END AS filepath, f.foldername AS folder_name, f.folderid AS folder_id FROM vtiger_senotesrel r LEFT JOIN vtiger_notes n ON n.notesid = r.notesid LEFT JOIN vtiger_crmentity e ON e.crmid = r.notesid LEFT JOIN vtiger_notescf ncf ON ncf.notesid = r.notesid LEFT JOIN vtiger_seattachmentsrel r2 ON r2.crmid = r.notesid LEFT JOIN vtiger_attachments a ON a.attachmentsid = r2.attachmentsid LEFT JOIN vtiger_attachmentsfolder f ON f.folderid = n.folderid WHERE r.crmid = ? AND e.deleted = 0 AND (a.type = 'application/pdf' OR a.type = 'application/octet-stream') "; try { $stmt = $pdo->prepare($sql); $stmt->execute([$id]); $documents = $stmt->fetchAll(PDO::FETCH_ASSOC); logMessage("Документы получены из CRM: " . json_encode($documents, JSON_UNESCAPED_UNICODE)); return $documents; } catch (PDOException $e) { logMessage("Ошибка при выполнении запроса к CRM: " . $e->getMessage()); return []; } } function createVectorStoreAndUploadFiles($filePaths) { logMessage("Создание Vector Store и загрузка файлов..."); $vectorStoreId = createVectorStore(); if (!$vectorStoreId) return null; $uploadedFiles = []; foreach ($filePaths as $filePath) { logMessage("Загрузка файла: $filePath"); if (!file_exists($filePath)) { logMessage("Ошибка: Файл не существует: $filePath"); continue; } $fileId = uploadFileToOpenAI($filePath); if (!$fileId) { logMessage("Ошибка загрузки файла: $filePath"); continue; } if (!addFileToVectorStore($vectorStoreId, $fileId)) { logMessage("Ошибка добавления файла в Vector Store: $filePath"); } else { logMessage("Файл успешно добавлен в Vector Store: $filePath"); $uploadedFiles[$filePath] = $fileId; } } return ['vectorStoreId' => $vectorStoreId, 'fileIds' => $uploadedFiles]; } function createVectorStore() { $curl = curl_init(); $payload = json_encode(['name' => 'Vector Store']); logMessage("Создание Vector Store. Отправляем payload: " . $payload); curl_setopt_array($curl, [ CURLOPT_URL => OPENAI_VECTOR_STORES_API, CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_POSTFIELDS => $payload, CURLOPT_HTTPHEADER => [ 'Content-Type: application/json', 'Authorization: Bearer ' . OPENAI_API_KEY, 'OpenAI-Beta: assistants=v2' ] ]); $response = curl_exec($curl); $httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE); $curlError = curl_error($curl); curl_close($curl); logMessage("Ответ OpenAI (создание Vector Store): HTTP $httpCode - " . $response); if ($curlError) { logMessage("Ошибка cURL при создании Vector Store: " . $curlError); return null; } $decoded = json_decode($response, true); if ($httpCode !== 200 || !isset($decoded['id'])) { logMessage("Ошибка при создании Vector Store: " . json_encode($decoded, JSON_UNESCAPED_UNICODE)); return null; } return $decoded['id']; } function uploadFileToOpenAI($filePath) { logMessage("Загрузка файла в OpenAI: $filePath"); $curl = curl_init(); curl_setopt_array($curl, [ CURLOPT_URL => OPENAI_FILES_API, CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_POSTFIELDS => [ 'file' => new CURLFile($filePath), 'purpose' => 'assistants' ], CURLOPT_HTTPHEADER => [ 'Authorization: Bearer ' . OPENAI_API_KEY, 'OpenAI-Beta: assistants=v2' ] ]); $response = curl_exec($curl); $httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE); $curlError = curl_error($curl); curl_close($curl); logMessage("Ответ OpenAI (загрузка файла): HTTP $httpCode - " . $response); if ($curlError) { logMessage("Ошибка cURL при загрузке файла: " . $curlError); return null; } $decoded = json_decode($response, true); if ($httpCode !== 200 || !isset($decoded['id'])) { logMessage("Ошибка при загрузке файла: " . json_encode($decoded, JSON_UNESCAPED_UNICODE)); return null; } return $decoded['id']; } function addFileToVectorStore($vectorStoreId, $fileId) { $curl = curl_init(); $payload = json_encode(['file_id' => $fileId]); logMessage("Добавление файла в Vector Store. Payload: " . $payload); curl_setopt_array($curl, [ CURLOPT_URL => OPENAI_VECTOR_STORES_API . "/$vectorStoreId/files", CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_POSTFIELDS => $payload, CURLOPT_HTTPHEADER => [ 'Content-Type: application/json', 'Authorization: Bearer ' . OPENAI_API_KEY, 'OpenAI-Beta: assistants=v2' ] ]); $response = curl_exec($curl); $httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE); $curlError = curl_error($curl); curl_close($curl); logMessage("Ответ OpenAI (добавление файла): HTTP $httpCode - " . $response); if ($curlError) { logMessage("Ошибка cURL при добавлении файла в Vector Store: " . $curlError); return false; } $decoded = json_decode($response, true); if ($httpCode !== 200 || !isset($decoded['id'])) { logMessage("Ошибка добавления файла: " . json_encode($decoded, JSON_UNESCAPED_UNICODE)); return false; } return true; } function updateAssistantWithVectorStore($vectorStoreId) { $data = [ 'tool_resources' => [ 'file_search' => [ 'vector_store_ids' => [$vectorStoreId] ] ] ]; $curl = curl_init(); $payload = json_encode($data); logMessage("Обновление ассистента. Payload: " . $payload); curl_setopt_array($curl, [ CURLOPT_URL => OPENAI_ASSISTANT_API . "/" . ASSISTANT_ID, CURLOPT_RETURNTRANSFER => true, CURLOPT_CUSTOMREQUEST => 'POST', CURLOPT_POSTFIELDS => $payload, CURLOPT_HTTPHEADER => [ 'Content-Type: application/json', 'Authorization: Bearer ' . OPENAI_API_KEY, 'OpenAI-Beta: assistants=v2' ] ]); $response = curl_exec($curl); $httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE); $curlError = curl_error($curl); curl_close($curl); logMessage("Ответ OpenAI (обновление ассистента): HTTP $httpCode - " . $response); if ($curlError) { logMessage("Ошибка обновления ассистента: " . $curlError); return false; } $decoded = json_decode($response, true); if ($httpCode !== 200 || !isset($decoded['id'])) { logMessage("Ошибка обновления ассистента: " . json_encode($decoded, JSON_UNESCAPED_UNICODE)); return false; } return true; } function extractDocumentContent($filePath) { // 1. Попытка извлечь текст через pdftotext $text = extractText($filePath); if (mb_strlen($text, 'UTF-8') >= 200) { logMessage("pdftotext дал достаточное количество символов."); return ['mode' => 'pdf', 'content' => $text]; } else { logMessage("pdftotext дал недостаточное количество символов. Переходим к обработке через изображения."); // 2. Конвертация PDF в изображения (JPG) $outputDir = sys_get_temp_dir() . '/pdf_images_' . md5($filePath); $images = convertPdfToImages($filePath, $outputDir); if (empty($images)) { logMessage("Ошибка: не удалось конвертировать PDF в изображения для $filePath"); return ['mode' => 'error', 'content' => '']; } // 3. Проверка каждого изображения на NSFW $allSafe = true; foreach ($images as $image) { $classification = classifyImage($image); $absImagePath = realpath($image); $unsafeProbability = isset($classification[$absImagePath]) ? ($classification[$absImagePath]['unsafe'] ?? 0) : 0; logMessage("DEBUG: Для изображения '$absImagePath' получено unsafeProbability = " . $unsafeProbability); if ($unsafeProbability > 0.8) { logMessage("DEBUG: Изображение '$absImagePath' не прошло NSFW проверку."); $allSafe = false; break; } } if (!$allSafe) { return ['mode' => 'nsfw', 'content' => 'Файл содержит трешконтент.']; } else { // 4. Если все изображения безопасны, запускаем OCR для извлечения текста $combinedOcrText = ""; foreach ($images as $image) { $ocrText = doOCR($image); if (!empty($ocrText)) { logMessage("DEBUG: OCR успешно извлечёк текст для изображения: " . $image); $combinedOcrText .= $ocrText . "\n"; } else { logMessage("DEBUG: OCR не смог извлечь текст для изображения: " . $image); } } if (empty(trim($combinedOcrText))) { logMessage("Ошибка: Не удалось извлечь текст посредством OCR для $filePath"); return ['mode' => 'error', 'content' => '']; } return ['mode' => 'ocr', 'content' => trim($combinedOcrText)]; } } } function analyzeDocuments($documents, $uploadedFileIds) { $combinedMessages = []; foreach ($documents as $doc) { if (empty($doc['filepath']) || strpos($doc['filepath'], '_') === 0) { logMessage("Неверный путь: " . json_encode($doc, JSON_UNESCAPED_UNICODE)); continue; } $normalizedPath = normalizeFilename($doc['filepath']); $messageBlock = "Документ: " . $doc['title'] . " (папка: " . $doc['folder_name'] . ")\n"; $extraction = extractDocumentContent($doc['filepath']); if (isset($doc['folder_name']) && mb_stripos($doc['folder_name'], 'судебные решения') !== false) { logMessage("📌 Найдено судебное решение в папке '{$doc['folder_name']}': " . $doc['title']); saveCourtDecisionToElastic($GLOBALS['caseId'], $doc['title'], $extraction['content']); $messageBlock .= "Тип: Судебное решение (сохранено отдельно).\n"; } else { if ($extraction['mode'] === 'pdf') { logMessage("DEBUG: Извлечение текста успешно для " . $doc['filepath'] . ". Передаём PDF файл."); $fileId = $uploadedFileIds[$doc['filepath']] ?? ''; $messageBlock .= "Тип: PDF (извлечение текста успешно, PDF передан для анализа).\nFileID: " . $fileId . "\n"; } elseif ($extraction['mode'] === 'ocr') { logMessage("DEBUG: Извлечение текста посредством OCR для " . $doc['filepath']); $messageBlock .= "OCR извлёк текст:\n" . $extraction['content'] . "\n"; } elseif ($extraction['mode'] === 'nsfw') { logMessage("DEBUG: Файл " . $doc['filepath'] . " не прошёл NSFW проверку."); $messageBlock .= "Ошибка: Файл не прошёл цензуру. Файл: " . $normalizedPath . "\n"; } else { $messageBlock .= "Ошибка: Не удалось извлечь текст. Файл: " . $normalizedPath . "\n"; } } $combinedMessages[] = $messageBlock; } return implode("\n-----------------\n", $combinedMessages); } /** * Собирает полный текст из всех документов, очищает его и возвращает. * * @param array $documents Массив документов (каждый должен содержать 'filepath'). * @return string Очищенный полный текст для эмбеддинга. */ function buildFullTextForEmbedding($documents) { $fullText = ""; foreach ($documents as $doc) { if (empty($doc['filepath'])) { logMessage("Пропущен документ без пути: " . json_encode($doc, JSON_UNESCAPED_UNICODE)); continue; } // Извлекаем текст из файла (может быть как через pdftotext, так и через OCR) $extraction = extractDocumentContent($doc['filepath']); if (!empty($extraction['content'])) { $fullText .= $extraction['content'] . "\n"; } else { logMessage("Нет извлеченного текста для файла: " . $doc['filepath']); } } // Очищаем полученный текст: удаляем управляющие символы, заменяем множественные пробелы на один и обрезаем крайние пробелы $cleanText = cleanTextForEmbedding($fullText); return $cleanText; } /** * Функция для очистки текста, предназначенного для векторизации. * * @param string $text Исходный текст. * @return string Очищенный текст. */ function cleanTextForEmbedding($text) { // Удаляем непечатаемые символы (например, управляющие символы) $text = preg_replace('/[\x00-\x1F\x7F]/u', ' ', $text); // Заменяем последовательности пробелов и переводов строк одним пробелом $text = preg_replace('/\s+/', ' ', $text); // Обрезаем крайние пробелы $text = trim($text); return $text; } /* Функции, связанные с отправкой в GPT, остаются, но не используются, так как вызовы закомментированы. Их можно оставить для будущего восстановления функциональности, либо удалить полностью. */ function createNewThread() { $curl = curl_init(); curl_setopt_array($curl, [ CURLOPT_URL => OPENAI_THREADS_API, CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_HTTPHEADER => [ 'Authorization: Bearer ' . OPENAI_API_KEY, 'OpenAI-Beta: assistants=v2' ] ]); $response = curl_exec($curl); $decoded = json_decode($response, true); curl_close($curl); if (isset($decoded['id'])) { logMessage("🔄 Создан новый thread_id: " . $decoded['id']); return $decoded['id']; } else { logMessage("❌ Ошибка создания нового треда: " . json_encode($decoded)); return null; } } function extractText($filePath) { $extension = strtolower(pathinfo($filePath, PATHINFO_EXTENSION)); if ($extension !== 'pdf') { return ''; } $outputFile = tempnam(sys_get_temp_dir(), 'txt_') . '.txt'; $command = "pdftotext " . escapeshellarg($filePath) . " " . escapeshellarg($outputFile); logMessage("Выполняем команду для извлечения текста: " . $command); exec($command, $output, $returnVar); if ($returnVar !== 0) { logMessage("Ошибка pdftotext: " . implode("\n", $output)); return ''; } if (!file_exists($outputFile)) { logMessage("Файл pdftotext не создан: $filePath"); return ''; } $text = file_get_contents($outputFile); unlink($outputFile); $cleanText = trim(preg_replace('/\pC+/u', '', $text)); if (mb_strlen($cleanText, 'UTF-8') < 200) { logMessage("Извлечённый текст для $filePath слишком короткий или не содержит полезной информации. Запуск OCR."); return ''; } $snippet = mb_substr($cleanText, 0, 500, 'UTF-8'); logMessage("Извлечённый текст для $filePath (первые 500 символов): " . $snippet); return $cleanText; } function doOCR($filePath) { logMessage("Запуск OCR для файла: $filePath"); $outputFile = tempnam(sys_get_temp_dir(), 'ocr_') . '.txt'; $command = "tesseract " . escapeshellarg($filePath) . " " . escapeshellarg($outputFile) . " -l rus --psm 6 --oem 1"; logMessage("Выполняем команду OCR: " . $command); exec($command, $output, $returnVar); if ($returnVar !== 0) { logMessage("Ошибка Tesseract: " . implode("\n", $output)); return ''; } if (!file_exists($outputFile . ".txt")) { logMessage("Файл OCR не создан: $filePath"); return ''; } $text = file_get_contents($outputFile . ".txt"); if (empty($text)) { logMessage("DEBUG: Tesseract вернул пустой результат для $filePath"); } unlink($outputFile . ".txt"); return $text; } function describeImageWithVision($filePath) { logMessage("Запуск описания изображения через Vision для файла: $filePath"); $imageData = base64_encode(file_get_contents($filePath)); $data = [ "model" => "gpt-4-vision-preview", "messages" => [ [ "role" => "user", "content" => [ [ "type" => "text", "text" => "Опиши это изображение подробно. Если это документ, прочитай и опиши его содержимое." ], [ "type" => "image_url", "image_url" => [ "url" => "data:image/jpeg;base64,$imageData" ] ] ] ] ], "max_tokens" => 500 ]; $curl = curl_init(); logMessage("Отправляем запрос на описание изображения. Payload: " . json_encode($data)); curl_setopt_array($curl, [ CURLOPT_URL => OPENAI_VISION_API, CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_POSTFIELDS => json_encode($data), CURLOPT_HTTPHEADER => [ 'Content-Type: application/json', 'Authorization: Bearer ' . OPENAI_API_KEY ] ]); $response = curl_exec($curl); $httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE); $curlError = curl_error($curl); curl_close($curl); logMessage("Ответ Vision (описание): HTTP $httpCode - " . $response); if ($curlError) { logMessage("Ошибка cURL в описании изображения: " . $curlError); return ''; } $decoded = json_decode($response, true); if (isset($decoded['choices'][0]['message']['content'])) { $desc = $decoded['choices'][0]['message']['content']; if (empty($desc)) { logMessage("DEBUG: Описание изображения пустое для $filePath"); } return $desc; } else { logMessage("Ошибка при получении описания изображения: " . json_encode($decoded)); return ''; } } function checkNSFWWithVision($filePath) { logMessage("NSFW-проверка через URL " . NSFW_MODERATION_API . " для файла: $filePath"); $curl = curl_init(); curl_setopt_array($curl, [ CURLOPT_URL => NSFW_MODERATION_API, CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_POSTFIELDS => [ 'file' => new CURLFile($filePath) ], CURLOPT_HTTPHEADER => [ 'Authorization: Bearer ' . OPENAI_API_KEY ] ]); $response = curl_exec($curl); $httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE); $curlError = curl_error($curl); curl_close($curl); logMessage("Ответ NSFW: HTTP $httpCode - " . $response); if ($curlError) { logMessage("Ошибка cURL при проверке NSFW: " . $curlError); return null; } $decoded = json_decode($response, true); if ($httpCode !== 200 || isset($decoded['detail'])) { logMessage("Ошибка анализа NSFW: " . json_encode($decoded, JSON_UNESCAPED_UNICODE)); return null; } return $decoded['nsfw'] ?? null; } function classifyImage($imagePath) { $absolutePath = realpath($imagePath); if (!$absolutePath) { logMessage("ERROR: Не удалось получить абсолютный путь для " . $imagePath); return []; } logMessage("DEBUG: Абсолютный путь для классификации: " . $absolutePath); $escapedPath = escapeshellarg($absolutePath); logMessage("DEBUG: Экранированный путь для классификации: " . $escapedPath); $command = "python3 -c \"import json; from nudenet import NudeClassifier; classifier = NudeClassifier(); print(json.dumps(classifier.classify($escapedPath)))\""; logMessage("DEBUG: Выполнение команды: " . $command); $output = shell_exec($command); logMessage("DEBUG: Вывод команды: " . $output); if ($output === null) { logMessage("ERROR: shell_exec вернул null при выполнении NudeClassifier"); return []; } return json_decode(trim($output), true); } function convertPdfToImages($pdfPath, $outputDir) { if (!file_exists($pdfPath)) { logMessage("Файл не существует: $pdfPath"); return []; } if (!file_exists($outputDir)) { mkdir($outputDir, 0777, true); logMessage("Создана директория для изображений: $outputDir"); } $imagePattern = $outputDir . '/page-%03d.jpg'; $command = "LC_ALL=en_US.UTF-8 convert -density 300 " . escapeshellarg($pdfPath) . " -quality 90 " . escapeshellarg($imagePattern); logMessage("Выполняем команду: " . $command); exec($command . " 2>&1", $output, $returnVar); logMessage("DEBUG: Вывод convert: " . implode("\n", $output)); if ($returnVar !== 0) { logMessage("Ошибка при конвертации PDF в изображения."); return []; } return glob($outputDir . '/*.jpg'); } function getKnowledgeBaseContext($filePath) { return "Статическая информация: нормы и законы РФ, судебные прецеденты..."; } function analyzeDocumentWithAssistantStream($threadId, $assistantId, $fileId, $content, $foundCases) { logMessage("🔹 Анализ документа через GPT-4 (stream) + судебные решения: thread_id=$threadId, fileId=$fileId"); // Формируем список найденных судебных решений $casesSummary = ""; foreach ($foundCases as $index => $case) { $caseId = $case["case_id"] ?? "Неизвестный ID"; $court = $case["court"] ?? "Неизвестный суд"; $decision = mb_substr($case["court_decision"] ?? "Текст отсутствует", 0, 1000, "UTF-8"); $casesSummary .= "📌 Дело #".($index + 1)." (ID: $caseId, Суд: $court):\n$decision\n\n"; } // Создаём промпт для GPT-4 $userMessage = << $assistantId, "thread" => [ "messages" => [ ["role" => "user", "content" => $userMessage] ] ], "stream" => true ]; $payloadJson = json_encode($payload, JSON_UNESCAPED_UNICODE); logMessage("📡 Отправка запроса в GPT-4: " . $payloadJson); $finalMessage = ""; $curl = curl_init(); curl_setopt_array($curl, [ CURLOPT_URL => OPENAI_THREADS_API . "/runs", CURLOPT_RETURNTRANSFER => false, CURLOPT_POST => true, CURLOPT_POSTFIELDS => $payloadJson, CURLOPT_HTTPHEADER => [ 'Content-Type: application/json', 'Authorization: Bearer ' . OPENAI_API_KEY, 'OpenAI-Beta: assistants=v2' ], CURLOPT_WRITEFUNCTION => function($ch, $data) use (&$finalMessage) { $finalMessage .= $data; return strlen($data); } ]); curl_exec($curl); $curlError = curl_error($curl); if ($curlError) { logMessage("❌ Ошибка cURL в analyzeDocumentWithAssistantStream: " . $curlError); curl_close($curl); return null; } curl_close($curl); logMessage("📥 Сырой потоковый ответ от GPT-4:\n" . $finalMessage); // Парсим потоковый ответ $parsedMessage = ""; $lines = explode("\n", $finalMessage); foreach ($lines as $line) { $line = trim($line); if (strpos($line, "data: ") === 0) { $dataPart = substr($line, 6); if ($dataPart === "[DONE]") { break; } $json = json_decode($dataPart, true); if (is_array($json) && isset($json['delta']['content'])) { $contentPiece = ""; foreach ($json['delta']['content'] as $segment) { if (isset($segment['text']['value'])) { $contentPiece .= $segment['text']['value']; } } $parsedMessage .= $contentPiece; } } } if (empty(trim($parsedMessage))) { logMessage("⚠️ Парсинг не дал результата, используем сырой ответ."); $parsedMessage = $finalMessage; } logMessage("✅ Итоговый анализ от GPT-4:\n" . $parsedMessage); return [ "status" => "complete", "content" => $parsedMessage ]; } function generateReport($allResults) { $report = "### Итоговый отчет\n\n"; foreach ($allResults as $result) { $report .= "**Результат анализа:**\n" . ($result['analysis']['content'] ?? 'Нет данных') . "\n"; $report .= "**Вердикт:** " . ($result['analysis']['moderationVerdict'] ?? 'Не определен') . "\n\n"; } return $report; } ?>