0) { $chunk = mb_substr($text, 0, $maxTokens * 4, 'UTF-8'); $chunks[] = trim($chunk); $text = mb_substr($text, $maxTokens * 4, null, 'UTF-8'); } return $chunks; } function logMessage($message) { $message = mb_convert_encoding($message, 'UTF-8', 'auto'); file_put_contents(LOG_FILE, date('Y-m-d H:i:s') . " - " . $message . "\n", FILE_APPEND | LOCK_EX); } function transliterate($text) { $cyr = ['а','б','в','г','д','е','ё','ж','з','и','й','к','л','м','н','о','п','р','с','т','у','ф','х','ц','ч','ш','щ','ъ','ы','ь','э','ю','я']; $lat = ['a','b','v','g','d','e','yo','zh','z','i','y','k','l','m','n','o','p','r','s','t','u','f','h','ts','ch','sh','shch','','y','','e','yu','ya']; return str_replace($cyr, $lat, mb_strtolower($text)); } function classifyImage($imagePath) { $absolutePath = realpath($imagePath); if (!$absolutePath) { logMessage("ERROR: Не удалось получить абсолютный путь для " . $imagePath); return []; } logMessage("DEBUG: Абсолютный путь для классификации: " . $absolutePath); $escapedPath = escapeshellarg($absolutePath); logMessage("DEBUG: Экранированный путь для классификации: " . $escapedPath); // Используем json.dumps для корректного JSON-вывода $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 renameFileForProcessing($filePath, $targetFolder = "scanpdf") { $pathInfo = pathinfo($filePath); $newName = transliterate($pathInfo['filename']) . "_" . uniqid() . "." . $pathInfo['extension']; $newPath = rtrim($targetFolder, "/") . "/" . $newName; if (copy($filePath, $newPath)) { logMessage("Файл переименован: $filePath -> $newPath"); return $newPath; } else { logMessage("Ошибка при копировании файла: $filePath"); return null; } } function extractTextFromPDF($pdfPath) { $text = shell_exec("pdftotext -layout -enc UTF-8 " . escapeshellarg($pdfPath) . " -"); $text = mb_convert_encoding($text, 'UTF-8', 'auto'); logMessage("DEBUG: Извлеченный текст: " . substr($text, 0, 500)); return trim($text); } function convertPdfToImages($pdfPath, $outputDir) { if (!file_exists($outputDir)) { mkdir($outputDir, 0777, true); logMessage("Создана директория для изображений: $outputDir"); } $imagePattern = $outputDir . '/page-%03d.jpg'; $command = "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 deleteFolderAndContents($folderPath) { if (is_dir($folderPath)) { $files = array_diff(scandir($folderPath), array('.', '..')); foreach ($files as $file) { $filePath = $folderPath . DIRECTORY_SEPARATOR . $file; if (is_dir($filePath)) { deleteFolderAndContents($filePath); } else { unlink($filePath); } } rmdir($folderPath); logMessage("Удалена подпапка и её содержимое: $folderPath"); } } // Подключение к БД $dbconfig = [ 'db_server' => 'localhost', 'db_port' => '3306', 'db_username' => 'ci20465_72new', 'db_password' => 'EcY979Rn', 'db_name' => 'ci20465_72new' ]; $conn = new mysqli( $dbconfig['db_server'], $dbconfig['db_username'], $dbconfig['db_password'], $dbconfig['db_name'], $dbconfig['db_port'] ); if ($conn->connect_error) { logMessage("Ошибка подключения к БД: " . $conn->connect_error); die(json_encode(["status" => "error", "message" => "Ошибка подключения к БД."])); } $conn->set_charset("utf8mb4"); $id = $_POST['id'] ?? null; if (!$id || !is_numeric($id)) { logMessage("Ошибка: Некорректный ID."); die(json_encode(["status" => "error", "message" => "Некорректный 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 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_seattachmentsrel r2 ON r2.crmid = r.notesid LEFT JOIN vtiger_attachments a ON a.attachmentsid = r2.attachmentsid WHERE r.crmid = ? AND e.deleted = 0 AND (a.type = 'application/pdf' OR a.type = 'application/octet-stream') "; $stmt = $conn->prepare($sql); $stmt->bind_param("i", $id); $stmt->execute(); $result = $stmt->get_result(); if ($result->num_rows == 0) { logMessage("Ошибка: Данные не найдены в БД."); die(json_encode(["status" => "error", "message" => "Нет данных."])); } // Создаем уникальную рабочую папку для текущего запуска $uniqueFolder = "scanpdf/" . uniqid('run_', true); if (!file_exists($uniqueFolder)) { mkdir($uniqueFolder, 0777, true); logMessage("Создана подпапка для работы: $uniqueFolder"); } $files_data = []; $maxTokens = 128000; // Лимит контекста GPT-4-Turbo $usedTokens = 0; $splitDocuments = []; $files_data = []; while ($row = $result->fetch_assoc()) { $title = $row["title"]; $filePath = $row["filepath"]; logMessage("Обрабатываем файл: " . $filePath); $tempFilePath = renameFileForProcessing($filePath, $uniqueFolder); if (!$tempFilePath) continue; $text = extractTextFromPDF($tempFilePath); $data = ["title" => $title]; if (!empty(trim($text)) && mb_strlen(trim($text), 'UTF-8') >= 100) { logMessage("DEBUG: Текст извлечён из $title: " . substr($text, 0, 500)); $data["text"] = $text; } else { logMessage("PDF '$title' пуст или содержит слишком мало текста, конвертируем в изображения."); $outputDir = $uniqueFolder . "/pdf_images_" . uniqid(); $images = convertPdfToImages($tempFilePath, $outputDir); if (!empty($images)) { logMessage("DEBUG: Изображения созданы из $title: " . implode(", ", $images)); $recognizedText = ""; $data["nsfw_alert"] = false; foreach ($images as $imagePath) { logMessage("DEBUG: Отправляем изображение '$imagePath' на проверку NSFW."); $classification = classifyImage($imagePath); if ($classification === null) { logMessage("DEBUG: Классификатор вернул null для '$imagePath'. NSFW-метка установлена."); $data["nsfw_alert"] = true; } elseif (isset($classification[$imagePath]) && $classification[$imagePath]['unsafe'] > 0.8) { logMessage("⚠️ Обнаружено NSFW-изображение: $imagePath (unsafe = " . $classification[$imagePath]['unsafe'] . ")"); $data["nsfw_alert"] = true; } $ocrText = shell_exec("tesseract " . escapeshellarg($imagePath) . " stdout -l rus+eng --oem 1"); if (!empty(trim($ocrText))) { $recognizedText .= trim($ocrText) . "\n"; } } if ($data["nsfw_alert"]) { logMessage("DEBUG: NSFW-контент найден, передаем изображения вместо текста."); unset($data["text"]); $data["images"] = $images; } else { if (!empty(trim($recognizedText)) && mb_strlen(trim($recognizedText), 'UTF-8') >= 100) { logMessage("DEBUG: OCR-текст извлечён: " . substr($recognizedText, 0, 500)); $data["text"] = $recognizedText; } else { logMessage("PDF '$title' остаётся без читаемого текста, передаем изображения."); $data["images"] = $images; } } } else { logMessage("Ошибка: PDF пуст, OCR не дал результатов, изображения не созданы."); } } // 🔹 Разбиваем текст на части, если слишком длинный if (isset($data["text"]) && !empty(trim($data["text"]))) { $tokens = countTokens($data["text"]); logMessage("DEBUG: Документ '$title' содержит $tokens токенов."); if ($tokens > 4000) { logMessage("⚠️ Документ '$title' слишком длинный ($tokens токенов), он будет разбит на части."); } if ($tokens + $usedTokens > $maxTokens) { logMessage("⚠️ Превышен лимит токенов, часть данных не будет отправлена."); break; } if ($tokens > 4000) { logMessage("⚠️ Документ '$title' слишком длинный, разбиваем на части."); $chunks = splitText($data["text"]); foreach ($chunks as $chunk) { $splitDocuments[] = ["title" => $title, "text" => $chunk, "nsfw_alert" => $data["nsfw_alert"]]; } } else { $splitDocuments[] = $data; } $usedTokens += $tokens; } else { $splitDocuments[] = $data; } // 🔹 Добавляем данные в $files_data[] if (!empty($splitDocuments)) { foreach ($splitDocuments as $splitDoc) { $files_data[] = $splitDoc; } } else { $files_data[] = $data; } // 🔹 Логируем NSFW-обнаружение if (isset($data["nsfw_alert"]) && $data["nsfw_alert"] === true) { logMessage("DEBUG: Обнаружен NSFW-контент в документе '$title'."); } // 🔹 Проверяем, прошла ли модерация if (isset($data["nsfw_alert"]) && $data["nsfw_alert"] === true) { $data["moderation_passed"] = false; } else { $data["moderation_passed"] = !empty(trim($data["text"])); } } /* while ($row = $result->fetch_assoc()) { $title = $row["title"]; $filePath = $row["filepath"]; logMessage("Обрабатываем файл: " . $filePath); $tempFilePath = renameFileForProcessing($filePath, $uniqueFolder); if (!$tempFilePath) continue; $text = extractTextFromPDF($tempFilePath); $data = ["title" => $title]; if (!empty(trim($text)) && mb_strlen(trim($text), 'UTF-8') >= 100) { logMessage("DEBUG: Текст извлечён из $title: " . substr($text, 0, 500)); $data["text"] = $text; } else { logMessage("PDF '$title' пуст или содержит слишком мало текста, конвертируем в изображения."); $outputDir = $uniqueFolder . "/pdf_images_" . uniqid(); $images = convertPdfToImages($tempFilePath, $outputDir); if (!empty($images)) { logMessage("DEBUG: Изображения созданы из $title: " . implode(", ", $images)); $recognizedText = ""; $data["nsfw_alert"] = false; // Инициализируем флаг NSFW foreach ($images as $imagePath) { logMessage("DEBUG: Отправляем изображение '$imagePath' на проверку NSFW."); $absImagePath = realpath($imagePath); logMessage("DEBUG: Абсолютный путь для проверки NSFW: " . $absImagePath); $classification = classifyImage($imagePath); if ($classification === null) { logMessage("DEBUG: Классификатор вернул null для изображения '$absImagePath'. Устанавливаем nsfw_alert в true."); $data["nsfw_alert"] = true; } elseif (isset($classification[$absImagePath])) { $unsafeProbability = $classification[$absImagePath]['unsafe']; logMessage("DEBUG: Для изображения '$absImagePath' получено unsafeProbability = " . $unsafeProbability); if ($unsafeProbability > 0.8) { logMessage("⚠️ Обнаружено NSFW-изображение: $absImagePath (unsafe = " . $unsafeProbability . ")"); $data["nsfw_alert"] = true; } else { logMessage("DEBUG: unsafeProbability для '$absImagePath' ниже порогового значения (0.8). Значение = " . $unsafeProbability); } } else { logMessage("DEBUG: Нет данных проверки NSFW для изображения '$absImagePath'."); } $ocrText = shell_exec("tesseract " . escapeshellarg($imagePath) . " stdout -l rus+eng --oem 1 --psm 3"); // $ocrText = shell_exec("tesseract " . escapeshellarg($imagePath) . " stdout -l rus+eng --oem 1"); if (!empty(trim($ocrText))) { $recognizedText .= trim($ocrText) . "\n"; } } // Если обнаружен NSFW-контент, отдаем изображения вместо OCR-текста if (isset($data["nsfw_alert"]) && $data["nsfw_alert"] === true) { logMessage("DEBUG: Обнаружен NSFW-контент, поэтому вместо OCR-текста передаем изображения."); unset($data["text"]); $data["images"] = $images; } else { if (!empty(trim($recognizedText)) && mb_strlen(trim($recognizedText), 'UTF-8') >= 100) { logMessage("DEBUG: OCR-текст (LSTM) извлечён из изображений $title: " . substr($recognizedText, 0, 500)); $data["text"] = $recognizedText; } else { logMessage("PDF '$title' остаётся без читаемого текста, отправляем изображения в GPT."); $data["images"] = $images; } } } else { logMessage("Ошибка: PDF пуст, OCR не дал результатов, изображения не созданы."); } } // Добавляем новое поле moderation_passed: // Если NSFW обнаружен, модерация не пройдена, иначе, если присутствует текст, считаем, что модерация пройдена. if (isset($data["nsfw_alert"]) && $data["nsfw_alert"] === true) { $data["moderation_passed"] = false; } else { if (isset($data["text"]) && !empty(trim($data["text"]))) { $data["moderation_passed"] = true; } else { $data["moderation_passed"] = false; } } $files_data[] = $data; if (isset($data["nsfw_alert"]) && $data["nsfw_alert"] === true) { logMessage("DEBUG: Обнаружен NSFW-контент в документе '$title'."); } } */ logMessage("DEBUG: JSON, отправляемый в OpenAI: " . json_encode($files_data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)); $task = '**🔹 Инструкция для анализа документов** **Задача:** Проанализируй загруженные документы и подготовь детальный отчет. Отвечай **строго по структуре**, не пропуская важные данные. --- ## **1️⃣ Список файлов и проверка соответствия названий** - Перечисли **все загруженные файлы**. - Проверь, соответствуют ли их названия содержимому. - Если файл содержит изображение: - **Попробуй извлечь текст** и проанализируй его. - Если текст плохо читается или частично распознан – укажи это. - Если текста нет – **опиши, что изображено**. - Обрати внимание на **NSFW-контент** (если `nsfw_alert: true`). - Проверь такие файлы **более внимательно**. - Если контент сомнительный, напиши: **"ТРЭШ-контент, требуется ручная проверка"**. --- ## **2️⃣ Краткий анализ спора** - Определи **истца** (потребителя) и **ответчика** (организацию, на которую подана жалоба). - Опиши **суть спора**: - Что произошло? - Какая проблема заявлена? - Какое требование выдвигает истец? - Укажи **основные аргументы сторон**: - **Позиция истца:** - **Возможные возражения ответчика:** --- ## **3️⃣ Проверка на цензуру** - Проанализируй текст на **ненормативную лексику** и **оскорбления**. - Проверь изображения на **нецензурный контент, запрещенную символику, сцены насилия**. - Если обнаружены нарушения, **укажи, какие именно**. - Если требуется ручная проверка, **отметь приоритетные файлы**. --- # **4️⃣ Выдача итогового вердикта** - Если всё соответствует, укажи: \"Вердикт: Прошло модерацию.\" - Если имеются несоответствия или потенциальные проблемы, укажи, что требуется ручная проверка. --- ## **5️⃣ Характер спора** Определи, к какой категории относится спор. Примеры: - **"Характер спора: некачественный товар"** - **"Характер спора: товар не привезли"** - **"Характер спора: отказ в возврате денег"** - **"Характер спора: некачественная услуга"** --- ## **6️⃣ Вероятность решения в пользу истца** **Вероятность положительного решения спора:** _**__%**_ --- ## **7️⃣ Чего не хватает для полного анализа?** - Перечисли **документы или данные**, которые нужно запросить дополнительно. - Используй формат: **"Запросить: ____"**. --- ### **📌 Важно** - Отчет должен быть **структурированным, четким и лаконичным**. - Если требуется ручная проверка, **укажи, какие файлы нужно проверить в первую очередь**. - Укажи, **какими нормами права РФ** регулируется данный спор (например, Закон РФ «О защите прав потребителей», ГК РФ и т.д.).'; logMessage("DEBUG: Начинаем формирование JSON для отправки в GPT-4 Turbo."); // 🔹 Формируем массив сообщений перед кодированием в JSON $messages = [ ["role" => "system", "content" => "Ты юридический аналитик. Проанализируй материалы согласно инструкции."], ["role" => "user", "content" => $task] ]; foreach ($splitDocuments as $doc) { $messages[] = ["role" => "user", "content" => json_encode($doc, JSON_UNESCAPED_UNICODE)]; } // 🔹 Кодируем JSON корректно (теперь $body создается после массива $messages) $body = json_encode([ "model" => "gpt-4-turbo", //"model" => "gpt-4", "messages" => $messages, "temperature" => 0.7, "max_completion_tokens" => 4000 ], JSON_UNESCAPED_UNICODE); logMessage("DEBUG: Итоговый JSON перед отправкой: " . $body); logMessage("DEBUG: Общая сумма токенов перед отправкой: " . $usedTokens); if ($usedTokens > 128000) { logMessage("⚠️ Превышен лимит токенов, отправка будет урезана!"); } // 🔹 Отправка запроса в OpenAI API $curl = curl_init(); curl_setopt_array($curl, [ CURLOPT_URL => OPENAI_API_URL, CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_POSTFIELDS => $body, CURLOPT_HTTPHEADER => [ 'Content-Type: application/json', 'Authorization: Bearer ' . OPENAI_API_KEY ] ]); // 🚀 ОТКЛЮЧАЕМ реальную отправку в GPT // $response = curl_exec($curl); /* ДЛЯ ТЕСТА!!!!! $http_code = 200; // Симулируем успешный HTTP-код $curl_error = null; $response = '{"choices":[{"message":{"content":"Вердикт: Прошло модерацию."}}]}'; // Фейковый ответ GPT curl_close($curl); */ $response = curl_exec($curl); $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE); $curl_error = curl_error($curl); curl_close($curl); // 🔹 Логируем ответ API logMessage("DEBUG: HTTP-код ответа от API: " . $http_code); logMessage("DEBUG: Ошибка CURL (если есть): " . ($curl_error ?: "Нет ошибок")); logMessage("DEBUG: Ответ от GPT-4-Turbo: " . $response); // 🔹 Проверяем, не пустой ли ответ от API if ($response === false || empty($response)) { logMessage("Ошибка: пустой ответ от GPT-4 Turbo."); die(json_encode(["status" => "error", "message" => "Ошибка: пустой ответ от GPT-4 Turbo."])); } // 🔹 Проверяем, пришел ли корректный JSON $gptAnalysis = json_decode($response, true); if ($gptAnalysis === null) { logMessage("Ошибка: JSON-декодирование не удалось."); die(json_encode(["status" => "error", "message" => "Ошибка: JSON-декодирование не удалось."])); } // 🔹 Проверяем, есть ли в ответе 'choices' if (!isset($gptAnalysis['choices'][0]['message']['content'])) { logMessage("Ошибка: в JSON-ответе отсутствует ключ 'choices'."); die(json_encode(["status" => "error", "message" => "Ошибка: в JSON-ответе отсутствует ключ 'choices'."])); } // 🔹 Получаем текстовый ответ от GPT $content = $gptAnalysis['choices'][0]['message']['content'] ?? null; if (!$content) { logMessage("Ошибка: контент не найден в ответе от GPT."); die(json_encode(["status" => "error", "message" => "Ошибка: контент не найден в ответе от GPT."])); } //logMessage("DEBUG: Извлеченный контент: " . $content); // Извлекаем модерационный вердикт из ответа GPT. // Ищем строку, начинающуюся с "Вердикт:" и берем все, что идет после неё. $moderationVerdict = ""; // Попробуем найти строку с вердиктом if (preg_match('/Вердикт:\s*(Прошло модерацию|Не прошло модерацию)/ui', $content, $matches)) { $moderationVerdict = trim($matches[1]); // Получаем сам текст вердикта } // Логируем извлеченный вердикт модерации logMessage("DEBUG: Извлеченный вердикт модерации: " . ($moderationVerdict ?: "Не найден")); $final_output = [ "status" => "complete", "content" => $content, "moderationVerdict" => $moderationVerdict, "files_data" => $files_data ]; logMessage("DEBUG: Извлеченный контент: " . $content); logMessage("DEBUG: Извлеченный вердикт модерации: " . $moderationVerdict); echo json_encode($final_output, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); //echo json_encode(["status" => "complete", "content" => $content], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); logMessage("Отправка запроса в GPT завершена."); deleteFolderAndContents($uniqueFolder); ?>