Files
crm.clientright.ru/iacode10.php

867 lines
41 KiB
PHP
Executable File
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
error_reporting(E_ALL);
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
ini_set('log_errors', 1);
ini_set('error_log', 'aiassist/logs/php_errors.log');
// Подключаем модули (если они есть)
// Для локального тестирования, если этих модулей нет, можно использовать заглушки.
require_once 'aiassist/elastic.php'; // Функции для работы с ElasticSearch
require_once 'aiassist/crm_handler.php'; // Функции для связи с CRM
require_once 'aiassist/search.php'; // Функции для поиска (searchIndex, searchCases, parseSearchResults)
require_once 'aiassist/ner.php'; // Функции для NER (извлечение сущностей)
require_once 'aiassist/vectorizer.php'; // Функции для вычисления эмбеддингов (getTextEmbedding)
// Настройки OpenAI 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/openai/v1/chat/completions'; // для описания изображений
const NSFW_MODERATION_API = 'https://api.proxyapi.ru/v1/moderation/nsfw';
const LOG_FILE = 'aiassist/logs/testlog.log';
const ASSISTANT_ID = 'asst_suGt51aoepXUkJiC0t3vobeG';
const ASSISTANT_NAME = 'Clientright';
// Для корректной обработки кириллицы
setlocale(LC_ALL, 'ru_RU.UTF-8');
// Подключение к БД (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('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 "Локальный анализ завершён.<br><br>";
echo "<strong>Итоговый prompt для GPT (отправка в GPT отключена):</strong><br>";
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;
}
?>