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 checkPreviousAnalysis($id, $filePathList) {
// Заглушка: всегда возвращаем false, чтобы продолжить обработку
return false;
}*/
/*
function sendAnalysisToCRM($id, $analysis) {
// Заглушка для локального тестирования
logMessage("Отправка анализа в CRM (тестовая версия): " . json_encode($analysis, JSON_UNESCAPED_UNICODE));
}
*/
/*
function saveAnalysisToElasticSearch($id, $analysis, $filePathList) {
// Заглушка для локального тестирования
logMessage("Сохранение анализа в ElasticSearch (тестовая версия).");
return true;
}
*/
/*
function saveCourtDecisionToElastic($caseId, $title, $content) {
// Заглушка для сохранения судебных решений
logMessage("Сохранение судебного решения в ElasticSearch. Case ID: $caseId, Title: $title");
}
*/
/* ===================== Основной скрипт обработки ===================== */
/* ===================== Основной скрипт обработки ===================== */
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));
//--------------------------------------------------------------------------------------------------------------//
// Далее получаем объединённый и очищенный текст для эмбеддинга:
$queryText = $_POST['query_text'] ?? '';
//$fullTextForEmbedding = buildFullTextForEmbedding($documents); // для получения Embedding из всего текста обращения+документы
$fullTextForEmbedding = buildFullTextForEmbedding($queryText); // для получения Embedding только из текста обращения
logMessage("Полный текст для эмбеддинга (очищенный): " . $fullTextForEmbedding);
// Затем передаем очищенный текст в функцию для получения эмбеддинга:
$embedding = getTextEmbedding($fullTextForEmbedding);
logMessage("Полученный эмбеддинг (первые 10 значений): " . json_encode(array_slice($embedding, 0, 10)));
function cleanQueryText($text) {
// Удаляем непечатные символы (например, управляющие символы)
$text = preg_replace('/[\x00-\x1F\x7F]/u', ' ', $text);
// Заменяем множественные пробелы на один
$text = preg_replace('/\s+/', ' ', $text);
// Обрезаем пробелы в начале и конце строки
return trim($text);
}
$queryText = cleanQueryText($queryText);
//$queryText = "";
$searchResults = searchCases($queryText, $embedding); $resultTexts = parseSearchResults($searchResults);
//error_log("Текстовые данные из результатов поиска: " . print_r($resultTexts, true) . "\n", 3, __DIR__ . "/logs/search.log");
$resultTexts = parseSearchResults($searchResults);
foreach ($resultTexts as $doc) {
$caseId = $doc['case_id'] ?? 'не указан';
$courtDecision = $doc['court_decision'] ?? 'не указано';
error_log(date('Y-m-d H:i:s') . " case_id: $caseId, court_decision: $courtDecision\n", 3, __DIR__ . "/aiassist/logs/search.log");
}
//-------------------------------------------------------------------------------------------------------------//
// Создаем массив путей к файлам
$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("Ошибка обновления ассистента");
}
if (!function_exists('createVectorStoreAndUploadFiles')) {
function createVectorStoreAndUploadFiles($filePaths) {
logMessage("Заглушка: функция createVectorStoreAndUploadFiles отключена.");
return ['vectorStoreId' => null, 'fileIds' => []];
}
}
$uploadResult = createVectorStoreAndUploadFiles($filePathList);
if (!$uploadResult) {
logMessage("Ошибка создания Vector Store или загрузки файлов");
die("Ошибка создания Vector Store или загрузки файлов");
}
$vectorStoreId = $uploadResult['vectorStoreId'];
$uploadedFileIds = isset($uploadResult['fileIds']) ? $uploadResult['fileIds'] : [];
$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);
// Отключено: отправка документов и prompt в GPT.
// Следующие этапы закомментированы, чтобы не отправлять данные в GPT для анализа.
// 7. Создаем новый тред для общения с GPT
$threadId = createNewThread();
if (!$threadId) {
logMessage("Ошибка создания треда");
die("Ошибка создания треда");
}
// 8. Отправляем итоговый prompt в GPT через ассистента (stream)
$analysis = analyzeDocumentWithAssistantStream($threadId, ASSISTANT_ID, $fileIdCombined, $finalPrompt);
if (!$analysis) {
logMessage("❌ Ошибка анализа совокупного запроса");
die("Ошибка анализа совокупного запроса");
}
// 9. Сохраняем результат анализа в ElasticSearch и отправляем его в CRM
$saveResult = saveAnalysisToElasticSearch($id, $analysis, $filePathList);
if ($saveResult) {
logMessage("✅ Анализ успешно сохранён в ElasticSearch.");
} else {
logMessage("❌ Ошибка при сохранении анализа в ElasticSearch.");
}
sendAnalysisToCRM($id, $analysis);
logMessage("Обработка всех документов завершена.");
// Для тестирования локальной части выводим сформированный prompt и результат анализа документов
echo "Локальный анализ завершён.
";
echo "Итоговый prompt для GPT (отправка в GPT отключена):
";
echo nl2br(htmlspecialchars($finalPrompt));
exit;
} else {
logMessage("Ошибка: запрос должен быть POST");
die("Ошибка: запрос должен быть POST");
}
/* ===================== Функции для работы с CRM и Vector Store ===================== */
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 analyzeDocumentWithAssistantStream($threadId, $assistantId, $fileId, $content) {
logMessage("Анализ документа через ассистента (stream): thread_id=$threadId, fileId=$fileId");
$userMessage = "🔹 Отвечай по шаблону:
Если в запросе присутствуют файлы (например, PDF или изображения), извлеки из них текст для анализа, даже если содержимое уже передано отдельно.
Задача:
Проанализируй загруженные документы, выполнив следующие действия:
1️⃣ Список файлов и проверка соответствия названий
Перечисли все загруженные файлы, без указания технических идентификаторов и служебных слов.
Сравни название файлов с их содержимым. Если имеются расхождения, укажи это.
Если файл содержит изображение, сначала извлеки текст (если есть), затем проанализируй его содержание; если текста нет – опиши изображение.
Особое внимание удели NSFW-контенту: если присутствует nsfw_alert со значением true, укажи, что файл требует ручной проверки.
2️⃣ Краткий анализ спора
Определи истца (потребителя) и ответчика (компанию или ИП).
Проверь, совпадают ли лицо-отправитель и лицо, заключившее договор. Если нет – добавь, что требуется проверка.
Опиши суть спора и основные аргументы сторон.
3️⃣ Проверка на цензуру
Проверь документы на ненормативную лексику и нецензурные изображения.
4️⃣ Выдача итогового вердикта
Если все соответствует требованиям, укажи строго:
«Вердикт: Прошло модерацию.»
Если обнаружены проблемы (например, отсутствие извлечённого текста или расхождение данных), отметь, что требуется ручная проверка.
5️⃣ Характер спора
Дай краткую характеристику дела (например: «некачественная услуга», «невыполнение условий договора»).
6️⃣ Вероятность положительного решения спора
Укажи вероятность положительного решения спора в формате:
«Вероятность положительного решения спора: _____ %».
7️⃣ Чего не хватает/запросить у Ответчика
Перечисли, какие документы или сведения необходимо запросить у ответчика.
8️⃣ Чего не хватает/запросить у Истца
Укажи, каких дополнительных документов не хватает для полного отчёта.
📌 Отчёт должен быть структурированным, чётким и лаконичным, с указанием норм права РФ для регулирования спора.
Файлы: " . $fileId . "\n" .
"Содержимое для анализа:\n" . $content;
logMessage("Формируем текст запроса для ассистента (stream):\n" . $userMessage);
$payload = [
"assistant_id" => $assistantId,
"thread" => [
"messages" => [
["role" => "user", "content" => $userMessage]
]
],
"stream" => true
];
$payloadJson = json_encode($payload, JSON_UNESCAPED_UNICODE);
logMessage("Финальный запрос для GPT (Payload):\n" . $payloadJson);
// Вместо отправки запроса возвращаем сформированный пейлоуд для теста
return [
"status" => "complete",
"content" => "Заглушка: анализ в GPT отключён. Payload: " . $payloadJson,
"moderationVerdict" => "Не определён"
];
}
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 generateReport($allResults) {
$report = "### Итоговый отчет\n\n";
foreach ($allResults as $result) {
$report .= "**Результат анализа:**\n" . ($result['analysis']['content'] ?? 'Нет данных') . "\n";
$report .= "**Вердикт:** " . ($result['analysis']['moderationVerdict'] ?? 'Не определен') . "\n\n";
}
return $report;
}
?>