Files
crm.clientright.ru/aiassist2.php

627 lines
25 KiB
PHP
Raw Permalink Normal View History

<?php
// Настройки 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_NSFW_API = 'https://api.proxyapi.ru/openai/v1/vision';
//const OPENAI_NSFW_API = 'https://api.proxyapi.ru/openai/v1/chat/completions';
//const OPENAI_VISION_API = 'https://api.proxyapi.ru/openai/v1/vision';
const LOG_FILE = 'logs/scriptDS99.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 (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;
}