Files
crm.clientright.ru/code4.php
Fedor ac7467f0b4 Major CRM updates: AI Assistant, Court Status API, S3 integration improvements, and extensive file storage system
- Added comprehensive AI Assistant system (aiassist/ directory):
  * Vector search and embedding capabilities
  * Typebot proxy integration
  * Elastic search functionality
  * Message classification and chat history
  * MCP proxy for external integrations

- Implemented Court Status API (GetCourtStatus.php):
  * Real-time court document status checking
  * Integration with external court systems
  * Comprehensive error handling and logging

- Enhanced S3 integration:
  * Improved file backup system with metadata
  * Batch processing capabilities
  * Enhanced error logging and recovery
  * Copy operations with URL fixing

- Added Telegram contact creation API
- Improved error logging across all modules
- Enhanced callback system for AI responses
- Extensive backup file storage with timestamps
- Updated documentation and README files

- File storage improvements:
  * Thousands of backup files with proper metadata
  * Fix operations for broken file references
  * Project-specific backup and recovery systems
  * Comprehensive file integrity checking

Total: 26,461+ files added/modified including AWS SDK, vendor dependencies, and extensive backup system.
2025-10-16 11:17:21 +03:00

719 lines
30 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
// Настройки OpenAI API и Vision API
const OPENAI_API_KEY = 'sk-GS24OxHQYfq8ErW5CRLoN5F1CfJPxNsY';
const OPENAI_ASSISTANT_API = 'https://api.proxyapi.ru/openai/v1/assistants';
const OPENAI_FILES_API = 'https://api.proxyapi.ru/openai/v1/files';
const OPENAI_THREADS_API = 'https://api.proxyapi.ru/openai/v1/threads';
const OPENAI_VECTOR_STORES_API = 'https://api.proxyapi.ru/openai/v1/vector_stores';
const OPENAI_VISION_API = 'https://api.proxyapi.ru/v1/chat/completions'; // Для анализа изображений и NSFW
const LOG_FILE = 'logs/scriptDS3.log';
// ID и имя ассистента
const ASSISTANT_ID = 'asst_suGt51aoepXUkJiC0t3vobeG';
const ASSISTANT_NAME = 'Clientright';
// Подключение к БД (Vtiger CRM)
$dsn = 'mysql:host=localhost;port=3306;dbname=ci20465_72new;charset=utf8mb4';
$user = 'ci20465_72new';
$password = 'EcY979Rn';
try {
$pdo = new PDO($dsn, $user, $password, [PDO::ATTR_ERRMODE => 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 ($_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");
// Получение данных из CRM
$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 и получение mapping (путь → file_id)
$uploadResult = createVectorStoreAndUploadFiles($filePaths);
if (!$uploadResult) {
logMessage("Ошибка создания Vector Store или загрузки файлов");
die("Ошибка создания Vector Store или загрузки файлов");
}
$vectorStoreId = $uploadResult['vectorStoreId'];
$uploadedFileIds = $uploadResult['fileIds'];
// Обновление ассистента с указанием векторного хранилища
if (!updateAssistantWithVectorStore($vectorStoreId)) {
logMessage("Ошибка обновления ассистента с Vector Store");
die("Ошибка обновления ассистента");
}
// Анализ документов с учетом NSFW, OCR, Vision и знаний из базы
$allResults = analyzeDocuments($documents, $uploadedFileIds);
if (empty($allResults)) {
logMessage("Ошибка: анализ документов не вернул результатов");
die("Ошибка: анализ документов не вернул результатов");
}
// Формирование итогового отчета
$report = generateReport($allResults);
logMessage("Итоговый отчет:\n" . $report);
echo $report;
logMessage("Обработка всех документов завершена.");
} 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
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 [];
}
}
function createVectorStoreAndUploadFiles($filePaths) {
logMessage("Создание Vector Store и загрузка файлов...");
$vectorStoreId = createVectorStore();
if (!$vectorStoreId) return null;
$uploadedFiles = []; // mapping: путь → 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];
}
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);
$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);
if ($curlError) {
logMessage("Ошибка cURL при загрузке файла: " . $curlError);
return null;
}
logMessage("Ответ OpenAI (загрузка файла): HTTP $httpCode - " . $response);
$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();
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 (добавление файла): HTTP $httpCode - " . $response);
$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();
curl_setopt_array($curl, [
CURLOPT_URL => OPENAI_ASSISTANT_API . "/" . ASSISTANT_ID,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_POSTFIELDS => json_encode($data),
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("Ошибка обновления ассистента: " . $curlError);
return false;
}
logMessage("Ответ OpenAI (обновление ассистента): HTTP $httpCode - " . $response);
$decoded = json_decode($response, true);
if ($httpCode !== 200 || !isset($decoded['id'])) {
logMessage("Ошибка обновления ассистента: " . json_encode($decoded, JSON_UNESCAPED_UNICODE));
return false;
}
return true;
}
/* ===================== Логика анализа документов ===================== */
/**
* Функция analyzeDocuments:
* Проверяет документ на NSFW через Vision.
* Если NSFW найден, помечает для ручной модерации.
* Иначе пытается извлечь текст (сначала с помощью встроенного извлечения, затем через OCR).
* Если текста нет, вызывает Vision для описания изображения.
* Получает контекст из базы знаний и отправляет данные в OpenAI Assistants для финального анализа.
*/
function analyzeDocuments($documents, $uploadedFileIds) {
$results = [];
foreach ($documents as $doc) {
if (empty($doc['filepath']) || strpos($doc['filepath'], '_') === 0) {
logMessage("Неверный путь: " . json_encode($doc, JSON_UNESCAPED_UNICODE));
continue;
}
// 1. Проверка NSFW через Vision API
$isNSFW = checkNSFWWithVision($doc['filepath']);
if ($isNSFW === null) {
$isNSFW = checkNSFWLocally($doc['filepath']);
}
if ($isNSFW) {
logMessage("NSFW обнаружен: " . $doc['filepath']);
$results[] = [
'document' => $doc['title'],
'status' => 'NSFW',
'message' => 'Файл содержит NSFW-контент и отправлен на ручную модерацию.'
];
continue;
}
// 2. Попытка извлечь текст напрямую (например, pdftotext)
$extractedText = extractText($doc['filepath']);
if (empty($extractedText)) {
// Если прямое извлечение не дало результата, запускаем OCR
$extractedText = doOCR($doc['filepath']);
}
// 3. Если текста все равно нет используем Vision для описания изображения
if (empty($extractedText)) {
$extractedText = describeImageWithVision($doc['filepath']);
}
// 4. Получаем контекст из базы знаний (stub-функция)
$knowledgeContext = getKnowledgeBaseContext($doc['filepath']);
// 5. Объединяем извлеченный текст и контекст
$finalContent = $extractedText . "\n" . $knowledgeContext;
// 6. Получаем file_id для привязки (если имеется)
$fileId = $uploadedFileIds[$doc['filepath']] ?? '';
// 7. Анализируем документ через ассистента
$threadId = createThread();
if (!$threadId) {
logMessage("Ошибка создания треда для " . $doc['filepath']);
continue;
}
$analysis = analyzeDocumentWithAssistant($threadId, ASSISTANT_ID, $fileId, $finalContent);
if ($analysis) {
logMessage("Анализ завершен: " . json_encode($analysis, JSON_UNESCAPED_UNICODE));
$results[] = [
'document' => $doc['title'],
'status' => 'Анализ завершен',
'analysis' => $analysis
];
} else {
logMessage("Ошибка анализа " . $doc['filepath']);
$results[] = [
'document' => $doc['title'],
'status' => 'Ошибка анализа',
'message' => 'Не удалось проанализировать документ.'
];
}
}
return $results;
}
/**
* Функция extractText пытается извлечь текст напрямую (например, через pdftotext).
* Если извлечение не удалось, возвращает пустую строку.
*/
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);
exec($command, $output, $returnVar);
if ($returnVar !== 0 || !file_exists($outputFile)) {
logMessage("Ошибка извлечения текста из PDF: $filePath");
return '';
}
$text = file_get_contents($outputFile);
unlink($outputFile);
return $text;
}
/**
* Функция doOCR использует локальную OCR-систему (например, Tesseract).
*/
function doOCR($filePath) {
logMessage("Запуск OCR для файла: $filePath");
$outputFile = tempnam(sys_get_temp_dir(), 'ocr_') . '.txt';
$command = "tesseract " . escapeshellarg($filePath) . " " . escapeshellarg($outputFile) . " -l rus";
exec($command, $output, $returnVar);
if ($returnVar !== 0 || !file_exists($outputFile . ".txt")) {
logMessage("Ошибка OCR для файла: $filePath");
return '';
}
$text = file_get_contents($outputFile . ".txt");
unlink($outputFile . ".txt");
return $text;
}
/**
* Функция describeImageWithVision вызывает OpenAI Vision для получения описания изображения.
*/
function describeImageWithVision($filePath) {
logMessage("Запуск описания изображения через Vision для файла: $filePath");
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => OPENAI_VISION_API . "/describe",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => [
'file' => new CURLFile($filePath)
],
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . OPENAI_API_KEY,
'OpenAI-Beta: vision'
]
]);
$response = curl_exec($curl);
$httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
$curlError = curl_error($curl);
curl_close($curl);
if ($curlError) {
logMessage("Ошибка cURL в описании изображения: " . $curlError);
return '';
}
logMessage("Ответ Vision (описание): HTTP $httpCode - " . $response);
$decoded = json_decode($response, true);
return $decoded['description'] ?? '';
}
/**
* Функция checkNSFWWithVision использует OpenAI Vision для анализа NSFW-контента.
*/
function checkNSFWWithVision($filePath) {
logMessage("NSFW-проверка через стандартный Vision endpoint для файла: $filePath");
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => OPENAI_VISION_API . "/analyze",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => [
'file' => new CURLFile($filePath)
],
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . OPENAI_API_KEY,
'OpenAI-Beta: vision'
]
]);
$response = curl_exec($curl);
$httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
$curlError = curl_error($curl);
curl_close($curl);
if ($curlError) {
logMessage("Ошибка cURL при проверке NSFW через Vision: " . $curlError);
return null;
}
logMessage("Ответ Vision (анализ): HTTP $httpCode - " . $response);
$decoded = json_decode($response, true);
if ($httpCode === 200 && isset($decoded['nsfw'])) {
return $decoded['nsfw']; // предполагается булевое значение
}
logMessage("Ошибка анализа NSFW через стандартный Vision endpoint: " . json_encode($decoded, JSON_UNESCAPED_UNICODE));
return null;
}
function classifyImage($imagePath) {
$absolutePath = realpath($imagePath);
if (!$absolutePath) {
logMessage("ERROR: Не удалось получить абсолютный путь для " . $imagePath);
return [];
}
logMessage("DEBUG: Абсолютный путь для классификации: " . $absolutePath);
$escapedPath = escapeshellarg($absolutePath);
logMessage("DEBUG: Экранированный путь для классификации: " . $escapedPath);
// Выполнение команды Python для запуска NudeClassifier
$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 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;
}
// Используем функцию classifyImage для анализа изображения
$classification = classifyImage($imageToCheck);
if (empty($classification)) {
logMessage("DEBUG: Нет данных проверки NSFW для изображения '$imageToCheck'.");
return false;
}
$absImagePath = realpath($imageToCheck);
if (isset($classification[$absImagePath])) {
$unsafeProbability = $classification[$absImagePath]['unsafe'] ?? 0;
logMessage("DEBUG: Для изображения '$absImagePath' получено unsafeProbability = " . $unsafeProbability);
if ($unsafeProbability > 0.8) {
logMessage("⚠️ Обнаружено NSFW-изображение: $absImagePath (unsafe = " . $unsafeProbability . ")");
return true;
} else {
logMessage("DEBUG: unsafeProbability для '$absImagePath' ниже порогового значения (0.8).");
}
} else {
logMessage("DEBUG: Классификатор не вернул данные для '$absImagePath'.");
}
return false;
}
/**
* Stub-функция для получения контекста из базы знаний.
*/
function getKnowledgeBaseContext($filePath) {
// Здесь можно реализовать поиск в базе знаний.
// Пока возвращаем статическую информацию.
return "Статическая информация: нормы и законы РФ, судебные прецеденты...";
}
/**
* Функция analyzeDocumentWithAssistant отправляет финальный запрос ассистенту.
* Дополнительно передаёт извлечённый текст и контекст.
*/
function analyzeDocumentWithAssistant($threadId, $assistantId, $fileId, $content) {
logMessage("Анализ документа: thread_id=$threadId, fileId=$fileId");
$messageContent = "Проанализируй документ";
if (!empty($fileId)) {
$messageContent .= " (file_id: $fileId)";
}
$messageContent .= ". Содержимое для анализа:\n" . $content;
$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("Ошибка отправки сообщения: " . $curlError);
return null;
}
logMessage("Ответ (сообщение): HTTP $httpCode - " . $response);
$decoded = json_decode($response, true);
if ($httpCode !== 200 || !isset($decoded['id'])) {
logMessage("Ошибка отправки сообщения: " . json_encode($decoded, 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("Ошибка запуска ассистента: " . $curlError);
return null;
}
logMessage("Ответ (запуск ассистента): HTTP $httpCode - " . $response);
$decodedRun = json_decode($response, true);
if ($httpCode !== 200 || !isset($decodedRun['id'])) {
logMessage("Ошибка запуска ассистента: " . json_encode($decodedRun, JSON_UNESCAPED_UNICODE));
return null;
}
$runId = $decodedRun['id'];
// Ожидание завершения обработки ассистентом с максимальным таймаутом
$maxIterations = 30; // максимум 30 итераций (примерно 60 секунд)
$iterations = 0;
do {
sleep(2);
$iterations++;
$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("Ошибка проверки статуса: " . $curlError);
return null;
}
logMessage("Статус запуска (итерация $iterations): HTTP $httpCode - " . $response);
$decodedStatus = json_decode($response, true);
$status = $decodedStatus['status'] ?? null;
if ($iterations >= $maxIterations) {
logMessage("Превышен максимальный таймаут ожидания завершения работы ассистента.");
return 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("Ошибка получения результата: " . $curlError);
return null;
}
logMessage("Ответ (сообщения): HTTP $httpCode - " . $response);
$decodedMessages = json_decode($response, true);
if ($httpCode !== 200 || !isset($decodedMessages['data'])) {
logMessage("Ошибка получения сообщений: " . json_encode($decodedMessages, JSON_UNESCAPED_UNICODE));
return null;
}
logMessage("Результаты анализа: " . json_encode($decodedMessages['data'], JSON_UNESCAPED_UNICODE));
return $decodedMessages['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 .= "**Анализ:**\n";
if (isset($result['analysis']['nsfw_status'])) {
$report .= "- NSFW статус: " . ($result['analysis']['nsfw_status'] ? "Обнаружен" : "Не обнаружен") . "\n";
}
if (isset($result['analysis']['image_description'])) {
$report .= "- Описание изображения: " . $result['analysis']['image_description'] . "\n";
}
if (isset($result['analysis']['text_content'])) {
$report .= "- Извлеченный текст: " . (strlen($result['analysis']['text_content']) > 100 ?
substr($result['analysis']['text_content'], 0, 100) . "..." :
$result['analysis']['text_content']) . "\n";
}
if (isset($result['analysis']['ai_analysis'])) {
$report .= "- AI анализ: " . $result['analysis']['ai_analysis'] . "\n";
}
} else {
$report .= "**Сообщение:** " . $result['message'] . "\n";
}
$report .= "\n";
}
return $report;
}