/* * Copyright (C) 2024-2025 Petr Mironychev * * This file is part of QodeAssist. * * QodeAssist is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * QodeAssist is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with QodeAssist. If not, see . */ #include "RequestHandler.hpp" #include "Logger.hpp" #include #include #include namespace QodeAssist::LLMCore { RequestHandler::RequestHandler(QObject *parent) : RequestHandlerBase(parent) , m_manager(new QNetworkAccessManager(this)) { connect( this, &RequestHandler::doSendRequest, this, &RequestHandler::sendLLMRequestInternal, Qt::QueuedConnection); connect( this, &RequestHandler::doCancelRequest, this, &RequestHandler::cancelRequestInternal, Qt::QueuedConnection); } RequestHandler::~RequestHandler() { for (auto reply : m_activeRequests) { reply->abort(); reply->deleteLater(); } m_activeRequests.clear(); m_accumulatedResponses.clear(); } void RequestHandler::sendLLMRequest(const LLMConfig &config, const QJsonObject &request) { emit doSendRequest(config, request); } bool RequestHandler::cancelRequest(const QString &id) { emit doCancelRequest(id); return true; } void RequestHandler::sendLLMRequestInternal(const LLMConfig &config, const QJsonObject &request) { LOG_MESSAGE(QString("Sending request to llm: \nurl: %1\nRequest body:\n%2") .arg( config.url.toString(), QString::fromUtf8( QJsonDocument(config.providerRequest).toJson(QJsonDocument::Indented)))); QNetworkRequest networkRequest(config.url); networkRequest.setTransferTimeout(300000); config.provider->prepareNetworkRequest(networkRequest); QNetworkReply *reply = m_manager->post(networkRequest, QJsonDocument(config.providerRequest).toJson()); if (!reply) { LOG_MESSAGE("Error: Failed to create network reply"); return; } QString requestId = request["id"].toString(); m_activeRequests[requestId] = reply; connect(reply, &QNetworkReply::readyRead, this, [this, reply, request, config]() { handleLLMResponse(reply, request, config); }); connect( reply, &QNetworkReply::finished, this, [this, reply, requestId]() { m_activeRequests.remove(requestId); if (reply->error() != QNetworkReply::NoError) { QString errorMessage = reply->errorString(); int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); LOG_MESSAGE( QString("Error details: %1\nStatus code: %2").arg(errorMessage).arg(statusCode)); emit requestFinished(requestId, false, errorMessage); } else { LOG_MESSAGE("Request finished successfully"); emit requestFinished(requestId, true, QString()); } reply->deleteLater(); }, Qt::QueuedConnection); } void RequestHandler::handleLLMResponse( QNetworkReply *reply, const QJsonObject &request, const LLMConfig &config) { QString &accumulatedResponse = m_accumulatedResponses[reply]; bool isComplete = config.provider->handleResponse(reply, accumulatedResponse); if (config.requestType == RequestType::CodeCompletion) { if (!config.multiLineCompletion && processSingleLineCompletion(reply, request, accumulatedResponse, config)) { return; } if (isComplete) { auto cleanedCompletion = removeStopWords(accumulatedResponse, config.promptTemplate->stopWords()); emit completionReceived(cleanedCompletion, request, true); } } else if (config.requestType == RequestType::Chat) { emit completionReceived(accumulatedResponse, request, isComplete); } if (isComplete) m_accumulatedResponses.remove(reply); } void RequestHandler::cancelRequestInternal(const QString &id) { QMutexLocker locker(&m_mutex); if (m_activeRequests.contains(id)) { QNetworkReply *reply = m_activeRequests[id]; reply->abort(); m_activeRequests.remove(id); m_accumulatedResponses.remove(reply); locker.unlock(); emit requestCancelled(id); } } bool RequestHandler::processSingleLineCompletion( QNetworkReply *reply, const QJsonObject &request, const QString &accumulatedResponse, const LLMConfig &config) { QString cleanedResponse = accumulatedResponse; int newlinePos = cleanedResponse.indexOf('\n'); if (newlinePos != -1) { QString singleLineCompletion = cleanedResponse.left(newlinePos).trimmed(); singleLineCompletion = removeStopWords(singleLineCompletion, config.promptTemplate->stopWords()); emit completionReceived(singleLineCompletion, request, true); m_accumulatedResponses.remove(reply); reply->abort(); return true; } return false; } QString RequestHandler::removeStopWords(const QStringView &completion, const QStringList &stopWords) { QString filteredCompletion = completion.toString(); for (const QString &stopWord : stopWords) { filteredCompletion = filteredCompletion.replace(stopWord, ""); } return filteredCompletion; } } // namespace QodeAssist::LLMCore