659 lines
22 KiB
C++
659 lines
22 KiB
C++
// neuralnetworkmanager.cpp
|
|
#include "neuralnetworkmanager.h"
|
|
#include <QNetworkRequest>
|
|
#include <QJsonDocument>
|
|
#include <QUrl>
|
|
#include <QDebug>
|
|
#include <QDateTime>
|
|
#include <QTimer>
|
|
#include <QSslCipher>
|
|
#include <QSslSocket>
|
|
#include <QFile>
|
|
#include <QElapsedTimer>
|
|
|
|
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<QSslCertificate> 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<QSslCertificate> 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::setCurrentNetworkType(NetworkType type)
|
|
{
|
|
QMutexLocker locker(&mutex);
|
|
m_currentNetworkType = type;
|
|
}
|
|
|
|
void NeuralNetworkManager::sendMessage(const QString &message)
|
|
{
|
|
// Используем текущий тип сети
|
|
sendMessage(message, m_currentNetworkType);
|
|
}
|
|
|
|
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<QSslError> &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<NetworkType>(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());
|
|
|
|
gigaChatAuth.tokenExpiry = 0;
|
|
emit errorOccurred(errorMsg);
|
|
reply->deleteLater();
|
|
return;
|
|
}
|
|
|
|
QJsonDocument doc = QJsonDocument::fromJson(data);
|
|
if (doc.isNull()) {
|
|
|
|
emit errorOccurred("Неверный формат ответа от GigaChat");
|
|
|
|
reply->deleteLater();
|
|
return;
|
|
}
|
|
|
|
QJsonObject json = doc.object();
|
|
|
|
if (json.contains("access_token")) {
|
|
m_tokenRetryCount = 0;
|
|
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();
|
|
|
|
// Очищаем очередь сообщений
|
|
|
|
emit errorOccurred("Ошибка GigaChat: " + errorMsg);
|
|
} else {
|
|
|
|
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 (m_tokenRetryCount >= MAX_TOKEN_RETRIES) {
|
|
qDebug() << "Превышено максимальное количество попыток получения токена";
|
|
m_tokenRetryCount = 0;
|
|
pendingMessages.clear(); // Только после превышения лимита очищаем очередь
|
|
emit errorOccurred("Не удалось получить токен GigaChat после нескольких попыток");
|
|
return;
|
|
}
|
|
|
|
m_tokenRetryCount++;
|
|
isProcessingGigaChatToken = true;
|
|
|
|
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<QSslError> &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;
|
|
}
|