PDO::ERRMODE_EXCEPTION]); } catch (PDOException $e) { logMessage("Ошибка подключения к БД: " . $e->getMessage()); die("Ошибка подключения к БД"); } // Функция логирования function logMessage($message) { if (!is_dir('logs')) { mkdir('logs', 0777, true); } file_put_contents(LOG_FILE, date('Y-m-d H:i:s') . " - " . $message . "\n", FILE_APPEND | LOCK_EX); } // Основной скрипт if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'POST') { $input = json_decode(file_get_contents('php://input'), true); $id = $input['id'] ?? null; if (!$id) { logMessage("Ошибка: отсутствует ID документа"); die("Ошибка: отсутствует ID документа"); } logMessage("Начало обработки документа с ID: $id"); // Получаем документы из базы данных $documents = fetchDocumentData($pdo, $id); if (empty($documents)) { logMessage("Документы не найдены для ID: $id"); die("Документы не найдены для ID: $id"); } logMessage("Документы получены из БД: " . json_encode($documents, JSON_UNESCAPED_UNICODE)); // Извлекаем пути файлов $filePaths = array_column($documents, 'filepath'); // Создаем Vector Store и загружаем файлы, получая ассоциативный массив fileIds (путь => file_id) $uploadResult = createVectorStoreAndUploadFiles($filePaths); if (!$uploadResult) { logMessage("Ошибка создания Vector Store или загрузки файлов"); die("Ошибка создания Vector Store или загрузки файлов"); } $vectorStoreId = $uploadResult['vectorStoreId']; $uploadedFileIds = $uploadResult['fileIds']; // Обновляем ассистента с Vector Store if (!updateAssistantWithVectorStore($vectorStoreId)) { logMessage("Ошибка обновления ассистента с Vector Store"); die("Ошибка обновления ассистента"); } // Анализ документов – теперь передаем также $uploadedFileIds для связи файла с анализом $allResults = analyzeDocuments($documents, $uploadedFileIds); if (empty($allResults)) { logMessage("Ошибка: анализ документов не вернул результатов"); die("Ошибка: анализ документов не вернул результатов"); } // Формирование итогового отчета $report = generateReport($allResults); logMessage("Итоговый отчет:\n" . $report); echo $report; logMessage("Обработка всех документов завершена."); } else { logMessage("Ошибка: запрос должен быть POST"); die("Ошибка: запрос должен быть POST"); } // Функция для получения данных из базы Vtiger CRM 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 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') "; 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 []; } } // Функция для создания Vector Store и загрузки файлов // Теперь возвращается массив с ключами 'vectorStoreId' и 'fileIds' (mapping путь => file_id) function createVectorStoreAndUploadFiles($filePaths) { logMessage("Создание Vector Store и загрузка файлов..."); $vectorStoreId = createVectorStore(); if (!$vectorStoreId) { return null; } $uploadedFiles = []; // массив: путь => file_id 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]; } // Функция для создания Vector Store function createVectorStore() { $curl = curl_init(); curl_setopt_array($curl, [ CURLOPT_URL => OPENAI_VECTOR_STORES_API, CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_POSTFIELDS => json_encode(['name' => 'Vector Store']), 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); if ($curlError) { logMessage("Ошибка cURL при создании Vector Store: " . $curlError); return null; } logMessage("Ответ OpenAI (создание Vector Store): HTTP $httpCode - " . $response); $decodedResponse = json_decode($response, true); if ($httpCode !== 200 || !isset($decodedResponse['id'])) { logMessage("Ошибка при создании Vector Store: " . json_encode($decodedResponse, JSON_UNESCAPED_UNICODE)); return null; } return $decodedResponse['id']; } // Функция для загрузки файла в OpenAI 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); if ($curlError) { logMessage("Ошибка cURL при загрузке файла: " . $curlError); return null; } logMessage("Ответ OpenAI (загрузка файла): HTTP $httpCode - " . $response); $decodedResponse = json_decode($response, true); if ($httpCode !== 200 || !isset($decodedResponse['id'])) { logMessage("Ошибка при загрузке файла: " . json_encode($decodedResponse, JSON_UNESCAPED_UNICODE)); return null; } return $decodedResponse['id']; } // Функция для добавления файла в Vector Store function addFileToVectorStore($vectorStoreId, $fileId) { $curl = curl_init(); curl_setopt_array($curl, [ CURLOPT_URL => OPENAI_VECTOR_STORES_API . "/$vectorStoreId/files", CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_POSTFIELDS => json_encode(['file_id' => $fileId]), 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); if ($curlError) { logMessage("Ошибка cURL при добавлении файла в Vector Store: " . $curlError); return false; } logMessage("Ответ OpenAI (добавление файла в Vector Store): HTTP $httpCode - " . $response); $decodedResponse = json_decode($response, true); if ($httpCode !== 200 || !isset($decodedResponse['id'])) { logMessage("Ошибка при добавлении файла в Vector Store: " . json_encode($decodedResponse, JSON_UNESCAPED_UNICODE)); return false; } return true; } // Функция для обновления ассистента с Vector Store function updateAssistantWithVectorStore($vectorStoreId) { $updateData = [ 'tool_resources' => [ 'file_search' => [ 'vector_store_ids' => [$vectorStoreId] ] ] ]; $curl = curl_init(); curl_setopt_array($curl, [ CURLOPT_URL => OPENAI_ASSISTANT_API . "/" . ASSISTANT_ID, CURLOPT_RETURNTRANSFER => true, CURLOPT_CUSTOMREQUEST => 'POST', CURLOPT_POSTFIELDS => json_encode($updateData), 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); if ($curlError) { logMessage("Ошибка cURL при обновлении ассистента: " . $curlError); return false; } logMessage("Ответ OpenAI (обновление ассистента): HTTP $httpCode - " . $response); $decodedResponse = json_decode($response, true); if ($httpCode !== 200 || !isset($decodedResponse['id'])) { logMessage("Ошибка при обновлении ассистента: " . json_encode($decodedResponse, JSON_UNESCAPED_UNICODE)); return false; } return true; } // Функция для анализа документов // Теперь принимает второй параметр — массив fileIds function analyzeDocuments($documents, $uploadedFileIds) { $combinedFileIds = []; $allResults = []; // Собираем file_id для всех документов, прошедших NSFW-проверку foreach ($documents as $doc) { if (empty($doc['filepath']) || strpos($doc['filepath'], '_') === 0) { logMessage("Ошибка: Путь к файлу пуст или некорректен: " . json_encode($doc, JSON_UNESCAPED_UNICODE)); continue; } // Проверка на NSFW $isNSFW = checkNSFWWithOpenAI($doc['filepath']); if ($isNSFW === null) { $isNSFW = checkNSFWLocally($doc['filepath']); } if ($isNSFW) { logMessage("⚠️ Файл содержит NSFW-контент: " . $doc['filepath']); $allResults[] = [ 'document' => $doc['title'], 'status' => 'NSFW', 'message' => 'Файл содержит NSFW-контент и отправлен на ручную модерацию.' ]; continue; } // Получаем file_id из массива загруженных файлов $fileId = $uploadedFileIds[$doc['filepath']] ?? ''; if (!$fileId) { logMessage("Ошибка: Не найден file_id для файла " . $doc['filepath']); continue; } $combinedFileIds[] = $fileId; } // Если есть файлы для анализа, выполняем совокупный анализ if (!empty($combinedFileIds)) { $threadId = createThread(); if (!$threadId) { logMessage("Ошибка создания треда в OpenAI"); die("Ошибка создания треда в OpenAI"); } // Собираем строку из всех file_id $fileIdsString = implode(", ", $combinedFileIds); // Формируем сообщение для совокупного анализа // Можно также уточнить в сообщении, что анализ проводится по совокупности документов $analysis = analyzeDocumentWithAssistant($threadId, ASSISTANT_ID, $fileIdsString); if ($analysis) { logMessage("Анализ документов завершен: " . json_encode($analysis, JSON_UNESCAPED_UNICODE)); $allResults[] = [ 'document' => 'Совокупный анализ', 'status' => 'Анализ завершен', 'analysis' => $analysis ]; } else { logMessage("Ошибка анализа документов"); $allResults[] = [ 'document' => 'Совокупный анализ', 'status' => 'Ошибка анализа', 'message' => 'Не удалось проанализировать документы.' ]; } } return $allResults; } // Функция для создания треда function createThread() { $curl = curl_init(); curl_setopt_array($curl, [ CURLOPT_URL => OPENAI_THREADS_API, CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, 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); if ($curlError) { logMessage("Ошибка cURL при создании треда: " . $curlError); return null; } logMessage("Ответ OpenAI (создание треда): HTTP $httpCode - " . $response); $decodedResponse = json_decode($response, true); if ($httpCode !== 200 || !isset($decodedResponse['id'])) { logMessage("Ошибка при создании треда: " . json_encode($decodedResponse, JSON_UNESCAPED_UNICODE)); return null; } return $decodedResponse['id']; } // Функция для анализа документа с использованием ассистента // Теперь принимает третий параметр $fileId и передает его в сообщение function analyzeDocumentWithAssistant($threadId, $assistantId, $fileId) { logMessage("Анализ документа с использованием ассистента: thread_id=$threadId, assistant_id=$assistantId, fileId=$fileId"); $messageContent = "Проанализируй документ"; if (!empty($fileId)) { $messageContent .= " с файлом id: " . $fileId; } $messageData = [ 'role' => 'user', 'content' => $messageContent ]; $curl = curl_init(); curl_setopt_array($curl, [ CURLOPT_URL => OPENAI_THREADS_API . "/$threadId/messages", CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_POSTFIELDS => json_encode($messageData), 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); if ($curlError) { logMessage("Ошибка cURL при отправке сообщения в тред: " . $curlError); return null; } logMessage("Ответ OpenAI (сообщение в тред): HTTP $httpCode - " . $response); $decodedResponse = json_decode($response, true); if ($httpCode !== 200 || !isset($decodedResponse['id'])) { logMessage("Ошибка при отправке сообщения в тред: " . json_encode($decodedResponse, JSON_UNESCAPED_UNICODE)); return null; } // Запуск ассистента для обработки треда $runData = ['assistant_id' => $assistantId]; $curl = curl_init(); curl_setopt_array($curl, [ CURLOPT_URL => OPENAI_THREADS_API . "/$threadId/runs", CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_POSTFIELDS => json_encode($runData), 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); if ($curlError) { logMessage("Ошибка cURL при запуске ассистента: " . $curlError); return null; } logMessage("Ответ OpenAI (запуск ассистента): HTTP $httpCode - " . $response); $decodedResponse = json_decode($response, true); if ($httpCode !== 200 || !isset($decodedResponse['id'])) { logMessage("Ошибка при запуске ассистента: " . json_encode($decodedResponse, JSON_UNESCAPED_UNICODE)); return null; } $runId = $decodedResponse['id']; // Ожидание завершения обработки ассистентом do { sleep(2); $curl = curl_init(); curl_setopt_array($curl, [ CURLOPT_URL => OPENAI_THREADS_API . "/$threadId/runs/$runId", CURLOPT_RETURNTRANSFER => true, 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); if ($curlError) { logMessage("Ошибка cURL при проверке статуса запуска: " . $curlError); return null; } logMessage("Ответ OpenAI (статус запуска): HTTP $httpCode - " . $response); $decodedResponse = json_decode($response, true); $status = $decodedResponse['status'] ?? null; } while ($status === 'queued' || $status === 'in_progress'); if ($status !== 'completed') { logMessage("Ошибка: статус запуска ассистента - $status"); return null; } // Получение результата анализа $curl = curl_init(); curl_setopt_array($curl, [ CURLOPT_URL => OPENAI_THREADS_API . "/$threadId/messages", CURLOPT_RETURNTRANSFER => true, 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); if ($curlError) { logMessage("Ошибка cURL при получении сообщений: " . $curlError); return null; } logMessage("Ответ OpenAI (сообщения): HTTP $httpCode - " . $response); $decodedResponse = json_decode($response, true); if ($httpCode !== 200 || !isset($decodedResponse['data'])) { logMessage("Ошибка при получении сообщений: " . json_encode($decodedResponse, JSON_UNESCAPED_UNICODE)); return null; } logMessage("Результаты анализа: " . json_encode($decodedResponse['data'], JSON_UNESCAPED_UNICODE)); return $decodedResponse['data']; } // Функция для формирования итогового отчета function generateReport($allResults) { if (empty($allResults)) { logMessage("Ошибка: Нет данных для формирования отчета"); return "Ошибка: Нет данных для формирования отчета"; } $report = "### Итоговый отчет по документам\n\n"; foreach ($allResults as $result) { $report .= "**Документ:** " . $result['document'] . "\n"; $report .= "**Статус:** " . $result['status'] . "\n"; if (isset($result['analysis'])) { $report .= "**Анализ:** " . json_encode($result['analysis'], JSON_UNESCAPED_UNICODE) . "\n"; } else { $report .= "**Сообщение:** " . $result['message'] . "\n"; } $report .= "\n"; } return $report; } /* ===================== NSFW-фильтрация и OCR ===================== */ function checkNSFWWithOpenAI($filePath) { logMessage("Запуск NSFW-проверки через OpenAI для файла: $filePath"); $curl = curl_init(); curl_setopt_array($curl, [ CURLOPT_URL => OPENAI_NSFW_API, CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_POSTFIELDS => [ 'file' => new CURLFile($filePath), 'purpose' => 'nsfw' ], 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); if ($curlError) { logMessage("Ошибка cURL при проверке NSFW через OpenAI: " . $curlError); return null; } logMessage("Ответ OpenAI (NSFW-проверка): HTTP $httpCode - " . $response); $decodedResponse = json_decode($response, true); if ($httpCode === 200 && isset($decodedResponse['nsfw'])) { return $decodedResponse['nsfw']; } else { logMessage("Ошибка проверки NSFW через OpenAI: " . json_encode($decodedResponse, JSON_UNESCAPED_UNICODE)); return null; } } function checkNSFWLocally($filePath) { logMessage("Запуск локальной проверки NSFW для файла: $filePath"); $extension = strtolower(pathinfo($filePath, PATHINFO_EXTENSION)); $imageToCheck = $filePath; if ($extension === 'pdf') { $outputImage = tempnam(sys_get_temp_dir(), 'pdf_img_') . '.png'; $command = "convert -density 150 " . escapeshellarg($filePath) . "[0] -quality 90 " . escapeshellarg($outputImage); exec($command, $output, $returnVar); if ($returnVar !== 0) { logMessage("Ошибка конвертации PDF в изображение для NSFW проверки."); return null; } $imageToCheck = $outputImage; } logMessage("Локальная NSFW-проверка завершена: NSFW не обнаружен для файла: $imageToCheck"); return false; }