277 lines
11 KiB
PHP
Executable File
277 lines
11 KiB
PHP
Executable File
<?php
|
||
// aiassist/fileHandler.php
|
||
require_once 'logger.php';
|
||
|
||
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);
|
||
}
|
||
|
||
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 '';
|
||
}
|
||
logMessage("Извлечённый текст для $filePath (первые 500 символов): " . mb_substr($cleanText, 0, 500, 'UTF-8'));
|
||
return $cleanText;
|
||
}
|
||
|
||
function doOCR($filePath) {
|
||
logMessage("🔍 Запуск OCR. Сначала конвертируем PDF в изображения: $filePath");
|
||
|
||
// Создаем временную папку для изображений
|
||
$imageDir = sys_get_temp_dir() . "/ocr_images_" . uniqid();
|
||
$images = convertPdfToImages($filePath, $imageDir);
|
||
|
||
if (empty($images)) {
|
||
logMessage("❌ Ошибка: Не удалось преобразовать PDF в изображения");
|
||
return '';
|
||
}
|
||
|
||
logMessage("✅ PDF преобразован в " . count($images) . " изображений, запускаем OCR");
|
||
|
||
$outputText = '';
|
||
foreach ($images as $image) {
|
||
$text = runOCRonImage($image);
|
||
$outputText .= "\n" . $text;
|
||
}
|
||
|
||
logMessage("✅ OCR завершен. Извлеченный текст:\n" . mb_substr($outputText, 0, 500, 'UTF-8'));
|
||
return trim($outputText);
|
||
}
|
||
|
||
// Функция OCR для обработки каждого изображения
|
||
function runOCRonImage($imagePath) {
|
||
$outputFile = tempnam(sys_get_temp_dir(), 'ocr_') . '.txt';
|
||
$command = "tesseract " . escapeshellarg($imagePath) . " " . 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-файл не создан");
|
||
return '';
|
||
}
|
||
|
||
$text = file_get_contents($outputFile . ".txt");
|
||
unlink($outputFile . ".txt");
|
||
return $text;
|
||
}
|
||
|
||
|
||
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);
|
||
if ($returnVar !== 0) {
|
||
logMessage("Ошибка при конвертации PDF в изображения.");
|
||
return [];
|
||
}
|
||
return glob($outputDir . '/*.jpg');
|
||
}
|
||
/*
|
||
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 => [
|
||
"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("Ответ 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 checkNSFWWithVision($filePath) {
|
||
logMessage("NSFW-проверка через URL https://api.proxyapi.ru/v1/moderation для файла: $filePath");
|
||
|
||
$curl = curl_init();
|
||
curl_setopt_array($curl, [
|
||
// CURLOPT_URL => "https://api.proxyapi.ru/v1/moderation",
|
||
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['data'][0]['nsfw'] ?? null;
|
||
}
|
||
*/
|
||
function checkNSFWWithVision($filePath) {
|
||
logMessage("⚠️ NSFW-проверка отключена для файла: $filePath");
|
||
return null;
|
||
}
|
||
|
||
|
||
|
||
/**
|
||
* Формирует объединённое сообщение по документам.
|
||
* Если текст можно извлечь напрямую, то передаётся только ссылка (ID файла).
|
||
* Если не удалось извлечь текст, используется OCR, и включается полученный текст.
|
||
* Также производится проверка на цензуру.
|
||
*
|
||
* @param array $documents Массив документов (с полями 'filepath' и 'title').
|
||
* @param array $uploadedFileIds Ассоциативный массив с путями к файлам => их идентификаторами.
|
||
* @return string Объединённое сообщение для передачи в GPT.
|
||
*/
|
||
function analyzeDocuments($documents, $uploadedFileIds) {
|
||
$combinedMessages = [];
|
||
foreach ($documents as $doc) {
|
||
if (empty($doc['filepath'])) {
|
||
logMessage("Неверный путь: " . json_encode($doc, JSON_UNESCAPED_UNICODE));
|
||
continue;
|
||
}
|
||
// Получаем fileID, полученный при загрузке
|
||
$fileId = $uploadedFileIds[$doc['filepath']] ?? 'неизвестный_ID';
|
||
|
||
// Выполняем проверку на NSFW
|
||
$nsfw = checkNSFWWithVision($doc['filepath']);
|
||
if ($nsfw !== null && $nsfw > 0.8) {
|
||
$messageBlock = "Документ: " . $doc['title'] . "\n";
|
||
$messageBlock .= "Файл (ID: " . $fileId . ") не прошёл проверку цензуры.\n";
|
||
} else {
|
||
// Пытаемся извлечь текст напрямую
|
||
$text = extractText($doc['filepath']);
|
||
if (!empty($text)) {
|
||
// Если файл читаемый, передаём только ссылку (ID файла)
|
||
$messageBlock = "Документ: " . $doc['title'] . "\n";
|
||
$messageBlock .= "Файл передан с ID: " . $fileId . ". Текст не загружен, так как файл читаемый.\n";
|
||
} else {
|
||
// Если не удалось извлечь текст, пробуем OCR
|
||
$ocrText = doOCR($doc['filepath']);
|
||
if (!empty($ocrText)) {
|
||
$messageBlock = "Документ: " . $doc['title'] . "\n";
|
||
$messageBlock .= "Файл передан с ID: " . $fileId . ". OCR текст:\n" . $ocrText . "\n";
|
||
} else {
|
||
$messageBlock = "Документ: " . $doc['title'] . "\n";
|
||
$messageBlock .= "Файл передан с ID: " . $fileId . ". Не удалось извлечь текст.\n";
|
||
}
|
||
}
|
||
}
|
||
$combinedMessages[] = $messageBlock;
|
||
}
|
||
return implode("\n-----------------\n", $combinedMessages);
|
||
}
|
||
|
||
|
||
/**
|
||
* Создает Vector Store, загружает файлы и возвращает массив с идентификаторами.
|
||
*
|
||
* @param array $filePaths Массив путей к файлам.
|
||
* @return array|null Массив с ключами 'vectorStoreId' и 'fileIds' (ассоциативный массив [путь => fileID]), или null при ошибке.
|
||
*/
|
||
function createVectorStoreAndUploadFiles($filePaths) {
|
||
logMessage("Создание Vector Store и загрузка файлов...");
|
||
$vectorStoreId = createVectorStore();
|
||
if (!$vectorStoreId) {
|
||
logMessage("Ошибка: не удалось создать Vector Store.");
|
||
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];
|
||
}
|
||
|
||
?>
|