// neuralnetworkmanager.cpp #include "neuralnetworkmanager.h" #include #include #include #include #include #include #include #include #include #include NeuralNetworkManager::NeuralNetworkManager(QObject *parent) : QObject(parent) , networkManager(new QNetworkAccessManager(this)) { // Инициализируем SSL конфигурацию setupSSL(); // Инициализируем конфигурации сетей setupDefaultConfigs(); // Инициализируем GigaChat токен как невалидный gigaChatAuth.tokenExpiry = 0; // Подключаем обработчик сетевых ответов connect(networkManager, &QNetworkAccessManager::finished, this, &NeuralNetworkManager::handleNetworkReply); } NeuralNetworkManager::~NeuralNetworkManager() { // Очищаем очередь сообщений pendingMessages.clear(); } void NeuralNetworkManager::setupSSL() { // Настройка стандартной SSL конфигурации QSslConfiguration sslConfig = QSslConfiguration::defaultConfiguration(); // Устанавливаем протокол TLS 1.2 или новее sslConfig.setProtocol(QSsl::TlsV1_2OrLater); sslConfig.setPeerVerifyMode(QSslSocket::VerifyPeer); // Загружаем российские корневые сертификаты если доступны QStringList certPaths = { "/etc/ssl/certs/russian_trusted_root_ca.pem", ":/certs/russian_ca.pem", "./certs/russian_ca.pem" }; QList russianCerts; for (const QString &path : certPaths) { if (QFile::exists(path)) { QFile certFile(path); if (certFile.open(QIODevice::ReadOnly)) { QByteArray certData = certFile.readAll(); russianCerts += QSslCertificate::fromData(certData); certFile.close(); } } } if (!russianCerts.isEmpty()) { QList defaultCerts = sslConfig.caCertificates(); defaultCerts += russianCerts; sslConfig.setCaCertificates(defaultCerts); } // Устанавливаем как конфигурацию по умолчанию QSslConfiguration::setDefaultConfiguration(sslConfig); } void NeuralNetworkManager::setupSSLForGigaChat() { QSslConfiguration config = QSslConfiguration::defaultConfiguration(); // Критически важные настройки для GigaChat config.setProtocol(QSsl::TlsV1_2OrLater); config.setPeerVerifyMode(QSslSocket::VerifyPeer); // Дополнительные настройки для совместимости config.setSslOption(QSsl::SslOptionDisableSessionTickets, false); config.setSslOption(QSsl::SslOptionDisableCompression, false); config.setSslOption(QSsl::SslOptionDisableServerNameIndication, false); config.setPeerVerifyDepth(2); QSslConfiguration::setDefaultConfiguration(config); } void NeuralNetworkManager::setupDefaultConfigs() { // DeepSeek NetworkConfig deepseekConfig; deepseekConfig.baseUrl = "https://api.deepseek.com"; deepseekConfig.endpoint = "/chat/completions"; deepseekConfig.model = "deepseek-chat"; networkConfigs[DeepSeek] = deepseekConfig; // GigaChat NetworkConfig gigachatConfig; gigachatConfig.baseUrl = "https://gigachat.devices.sberbank.ru"; gigachatConfig.endpoint = "/api/v1/chat/completions"; gigachatConfig.model = "GigaChat"; networkConfigs[GigaChat] = gigachatConfig; // ChatGPT NetworkConfig chatgptConfig; chatgptConfig.baseUrl = "https://api.openai.com"; chatgptConfig.endpoint = "/v1/chat/completions"; chatgptConfig.model = "gpt-3.5-turbo"; networkConfigs[ChatGPT] = chatgptConfig; // Ollama NetworkConfig ollamaConfig; ollamaConfig.baseUrl = "http://localhost:11434"; ollamaConfig.endpoint = "/api/chat"; ollamaConfig.model = "zephyr:7b"; networkConfigs[Ollama] = ollamaConfig; } void NeuralNetworkManager::sendMessage(const QString &message, NetworkType networkType) { QMutexLocker locker(&mutex); if (!networkConfigs.contains(networkType)) { emit errorOccurred("Неизвестный тип нейросети"); return; } // Для GigaChat проверяем токен if (networkType == GigaChat) { qint64 currentTime = QDateTime::currentSecsSinceEpoch(); if (!isGigaChatTokenValid()) { // Добавляем сообщение в очередь PendingMessage pendingMsg; pendingMsg.message = message; pendingMsg.networkType = networkType; pendingMessages.append(pendingMsg); // Запрашиваем новый токен если еще не запрашиваем if (!isProcessingGigaChatToken) { requestGigaChatToken(); } return; } } // Если токен валиден, отправляем сообщение сразу sendMessageInternal(message, networkType); } void NeuralNetworkManager::sendMessageInternal(const QString &message, NetworkType networkType) { QElapsedTimer timer; timer.start(); QString fullMessage = currentPrefix.isEmpty() ? message : currentPrefix + " " + message; QNetworkRequest request = prepareRequest(networkType); QByteArray requestData = prepareRequestBody(networkType, fullMessage); // Устанавливаем таймаут // request.setTransferTimeout(30000); QNetworkReply *reply = networkManager->post(request, requestData); reply->setProperty("networkType", networkType); // Для GigaChat обрабатываем SSL ошибки if (networkType == GigaChat) { connect(reply, &QNetworkReply::sslErrors, this, [reply](const QList &errors) { for (const QSslError &error : errors) { } reply->ignoreSslErrors(); }); } } void NeuralNetworkManager::setPrefix(const QString &prefix) { QMutexLocker locker(&mutex); currentPrefix = prefix; } void NeuralNetworkManager::setApiKey(NetworkType networkType, const QString &apiKey) { QMutexLocker locker(&mutex); if (networkConfigs.contains(networkType)) { networkConfigs[networkType].apiKey = apiKey; } } void NeuralNetworkManager::setModel(NetworkType networkType, const QString &model) { QMutexLocker locker(&mutex); if (networkConfigs.contains(networkType)) { networkConfigs[networkType].model = model; } } void NeuralNetworkManager::setBaseUrl(NetworkType networkType, const QString &url) { QMutexLocker locker(&mutex); if (networkConfigs.contains(networkType)) { networkConfigs[networkType].baseUrl = url; } } void NeuralNetworkManager::setOllamaUrl(const QString &url) { setBaseUrl(Ollama, url); } void NeuralNetworkManager::setGigaChatCredentials(const QString &clientId, const QString &authorizationCode) { QMutexLocker locker(&mutex); gigaChatAuth.clientId = clientId; gigaChatAuth.authorizationCode = authorizationCode; } void NeuralNetworkManager::refreshGigaChatToken() { QMutexLocker locker(&mutex); if (!gigaChatAuth.clientId.isEmpty() && !gigaChatAuth.authorizationCode.isEmpty()) { requestGigaChatToken(); } } QNetworkRequest NeuralNetworkManager::prepareRequest(NetworkType type) { if (!networkConfigs.contains(type)) { qWarning() << "Попытка подготовить запрос для неизвестного типа:" << type; return QNetworkRequest(); } NetworkConfig config = networkConfigs[type]; // Проверяем и корректируем URL QString baseUrl = config.baseUrl; QString endpoint = config.endpoint; if (baseUrl.isEmpty()) { qWarning() << "Base URL пуст для типа" << type; return QNetworkRequest(); } // Убедимся, что baseUrl имеет схему if (!baseUrl.startsWith("http://") && !baseUrl.startsWith("https://")) { baseUrl = "https://" + baseUrl; } QString urlStr = baseUrl + endpoint; QUrl url(urlStr); if (!url.isValid()) { qWarning() << "Некорректный URL:" << urlStr; return QNetworkRequest(); } QNetworkRequest request(url); // ОСОБАЯ обработка для GigaChat if (type == GigaChat) { // SSL конфигурация для GigaChat setupSSLForGigaChat(); QSslConfiguration sslConfig = QSslConfiguration::defaultConfiguration(); request.setSslConfiguration(sslConfig); // Заголовки для GigaChat request.setRawHeader("Accept-Encoding", "gzip, deflate, br"); request.setRawHeader("Accept", "application/json"); request.setRawHeader("Content-Type", "application/json"); if (!gigaChatAuth.accessToken.isEmpty()) { QString authHeader = QString("Bearer %1").arg(gigaChatAuth.accessToken); request.setRawHeader("Authorization", authHeader.toUtf8()); } else { qWarning() << "Токен GigaChat отсутствует!"; } } else { // Общая настройка для других сервисов QSslConfiguration sslConfig = QSslConfiguration::defaultConfiguration(); sslConfig.setPeerVerifyMode(QSslSocket::VerifyPeer); request.setSslConfiguration(sslConfig); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); if (type == DeepSeek || type == ChatGPT) { if (!config.apiKey.isEmpty()) { request.setRawHeader("Authorization", QString("Bearer %1").arg(config.apiKey).toUtf8()); } } } // Общие заголовки request.setRawHeader("User-Agent", "TwitchBot/1.0 (Qt Network)"); request.setAttribute(QNetworkRequest::HTTP2AllowedAttribute, true); request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); return request; } QByteArray NeuralNetworkManager::prepareRequestBody(NetworkType type, const QString &message) { QJsonObject requestBody; QJsonArray messages; QJsonObject messageObj; messageObj["role"] = "user"; messageObj["content"] = message; messages.append(messageObj); if (type == Ollama) { // Формат запроса для Ollama requestBody["model"] = networkConfigs[type].model; requestBody["messages"] = messages; requestBody["stream"] = false; QJsonObject options; options["temperature"] = 0.7; options["num_predict"] = 200; requestBody["options"] = options; } else { // Формат запроса для остальных API (OpenAI-совместимый) requestBody["model"] = networkConfigs[type].model; requestBody["messages"] = messages; requestBody["max_tokens"] = 300; requestBody["temperature"] = 0.7; requestBody["stream"] = false; } return QJsonDocument(requestBody).toJson(); } void NeuralNetworkManager::handleNetworkReply(QNetworkReply *reply) { QElapsedTimer timer; timer.start(); // Определяем тип запроса bool isTokenRequest = reply->property("isTokenRequest").toBool(); QString url = reply->url().toString(); // Если это запрос токена GigaChat if (isTokenRequest || url.contains("oauth")) { handleGigaChatTokenReply(reply); reply->deleteLater(); return; } // Обработка обычных ответов от нейросетей NetworkType type = static_cast(reply->property("networkType").toInt()); if (reply->error() != QNetworkReply::NoError) { QString errorMsg = QString("Ошибка сети (%1): %2") .arg(type) .arg(reply->errorString()); // Для GigaChat проверяем, не истек ли токен if (type == GigaChat && (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 401 || reply->error() == QNetworkReply::AuthenticationRequiredError)) { gigaChatAuth.tokenExpiry = 0; // Помечаем токен как устаревший // Запрашиваем новый токен if (!isProcessingGigaChatToken) { requestGigaChatToken(); } } else { emit errorOccurred(errorMsg); } reply->deleteLater(); return; } QByteArray data = reply->readAll(); QString response = parseResponse(type, data); if (!response.isEmpty()) { QString truncatedResponse = truncateResponse(response); emit responseReceived(truncatedResponse); } else { emit errorOccurred("Не удалось получить ответ от нейросети"); } reply->deleteLater(); } void NeuralNetworkManager::handleGigaChatTokenReply(QNetworkReply *reply) { QMutexLocker locker(&mutex); QByteArray data = reply->readAll(); isProcessingGigaChatToken = false; if (reply->error() != QNetworkReply::NoError) { QString errorMsg = QString("Ошибка получения токена GigaChat: %1").arg(reply->errorString()); // Очищаем очередь сообщений если не удалось получить токен pendingMessages.clear(); emit errorOccurred(errorMsg); reply->deleteLater(); return; } QJsonDocument doc = QJsonDocument::fromJson(data); if (doc.isNull()) { pendingMessages.clear(); emit errorOccurred("Неверный формат ответа от GigaChat"); reply->deleteLater(); return; } QJsonObject json = doc.object(); if (json.contains("access_token")) { gigaChatAuth.accessToken = json["access_token"].toString(); int expiresIn = json.contains("expires_in") ? json["expires_in"].toInt(1800) : 1800; // Устанавливаем время истечения (текущее время + expiresIn секунд) qint64 currentTime = QDateTime::currentSecsSinceEpoch(); gigaChatAuth.tokenExpiry = currentTime + expiresIn - 300; // Вычитаем 5 минут для запаса emit gigaChatTokenRefreshed(); // Обрабатываем сообщения из очереди processPendingMessages(); } else if (json.contains("error")) { QString errorMsg = json["error"].toString(); // Очищаем очередь сообщений pendingMessages.clear(); emit errorOccurred("Ошибка GigaChat: " + errorMsg); } else { pendingMessages.clear(); emit errorOccurred("Неизвестный формат ответа от GigaChat"); } reply->deleteLater(); } bool NeuralNetworkManager::isGigaChatTokenValid() const { qint64 currentTime = QDateTime::currentSecsSinceEpoch(); // Проверяем есть ли токен и не истек ли он bool isValid = !gigaChatAuth.accessToken.isEmpty() && gigaChatAuth.tokenExpiry > currentTime; qDebug() << "Проверка токена GigaChat:" << "Токен:" << (!gigaChatAuth.accessToken.isEmpty() ? "есть" : "нет") << "Текущее время:" << currentTime << "Истекает:" << gigaChatAuth.tokenExpiry << "Валиден:" << (isValid ? "да" : "нет"); return isValid; } bool NeuralNetworkManager::shouldRefreshGigaChatToken() const { qint64 currentTime = QDateTime::currentSecsSinceEpoch(); // Проверяем, нужно ли обновить токен (осталось меньше 5 минут) bool shouldRefresh = gigaChatAuth.tokenExpiry > 0 && (gigaChatAuth.tokenExpiry - currentTime) < 300; qDebug() << "Нужно ли обновить токен:" << "Текущее время:" << currentTime << "Истекает:" << gigaChatAuth.tokenExpiry << "Осталось секунд:" << (gigaChatAuth.tokenExpiry - currentTime) << "Обновить:" << (shouldRefresh ? "да" : "нет"); return shouldRefresh; } void NeuralNetworkManager::requestGigaChatToken() { if (isProcessingGigaChatToken) { return; } if (gigaChatAuth.clientId.isEmpty() || gigaChatAuth.authorizationCode.isEmpty()) { emit errorOccurred("Не установлены учетные данные GigaChat"); // Очищаем очередь сообщений pendingMessages.clear(); return; } isProcessingGigaChatToken = true; QString tokenUrl = "https://ngw.devices.sberbank.ru:9443/api/v2/oauth"; QNetworkRequest request((QUrl(tokenUrl))); // Настройка SSL для GigaChat setupSSLForGigaChat(); QSslConfiguration sslConfig = QSslConfiguration::defaultConfiguration(); request.setSslConfiguration(sslConfig); // Заголовки request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); request.setRawHeader("Accept", "application/json"); request.setRawHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"); request.setRawHeader("RqUID", gigaChatAuth.clientId.toUtf8()); request.setRawHeader("Authorization", QString("Basic %1").arg(gigaChatAuth.authorizationCode).toUtf8()); // Тело запроса QByteArray postData = "scope=" + gigaChatScope.toUtf8(); QNetworkReply *reply = networkManager->post(request, postData); reply->setProperty("isTokenRequest", true); // Обработка SSL ошибок connect(reply, &QNetworkReply::sslErrors, this, [reply](const QList &errors) { reply->ignoreSslErrors(); }); } void NeuralNetworkManager::processPendingMessages() { while (!pendingMessages.isEmpty()) { PendingMessage pendingMsg = pendingMessages.takeFirst(); // Проверяем, что токен все еще валиден if (isGigaChatTokenValid()) { sendMessageInternal(pendingMsg.message, pendingMsg.networkType); } else { pendingMessages.prepend(pendingMsg); // Возвращаем сообщение в начало очереди break; } } } QString NeuralNetworkManager::parseResponse(NetworkType type, const QByteArray &data) { QJsonDocument doc = QJsonDocument::fromJson(data); if (doc.isNull()) { return QString(); } QJsonObject json = doc.object(); if (type == Ollama) { // Парсинг ответа Ollama if (json.contains("message")) { QJsonObject message = json["message"].toObject(); if (message.contains("content")) { return message["content"].toString().trimmed(); } } } else { // Парсинг ответа OpenAI-совместимых API if (json.contains("choices") && json["choices"].isArray()) { QJsonArray choices = json["choices"].toArray(); if (!choices.isEmpty()) { QJsonObject choice = choices[0].toObject(); if (choice.contains("message")) { QJsonObject message = choice["message"].toObject(); if (message.contains("content")) { return message["content"].toString().trimmed(); } } } } // Проверка на ошибки API if (json.contains("error")) { QJsonObject error = json["error"].toObject(); QString errorMsg = error.contains("message") ? error["message"].toString() : "Неизвестная ошибка API"; return QString(); } } return QString(); } QString NeuralNetworkManager::truncateResponse(const QString &response) { // Ограничиваем длину ответа 400 символами if (response.length() > 400) { QString truncated = response.left(400); // Пытаемся обрезать по последнему предложению int lastDot = truncated.lastIndexOf('.'); int lastExcl = truncated.lastIndexOf('!'); int lastQuest = truncated.lastIndexOf('?'); int lastBreak = qMax(lastDot, qMax(lastExcl, lastQuest)); if (lastBreak > 300) { truncated = truncated.left(lastBreak + 1); } else { truncated += "..."; } return truncated; } return response; }