mirror of
https://github.com/Palm1r/QodeAssist.git
synced 2026-06-16 11:19:16 -04:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9a2ba08538 | ||
|
|
37084bec59 | ||
|
|
6910037e97 | ||
|
|
a72cdd85a4 | ||
|
|
31b4e73af5 | ||
|
|
088887c802 | ||
|
|
b7a9787cc3 | ||
|
|
e2e13f0f38 |
8
.github/workflows/build_cmake.yml
vendored
8
.github/workflows/build_cmake.yml
vendored
@@ -45,14 +45,14 @@ jobs:
|
|||||||
cc: "clang", cxx: "clang++"
|
cc: "clang", cxx: "clang++"
|
||||||
}
|
}
|
||||||
qt_config:
|
qt_config:
|
||||||
- {
|
|
||||||
qt_version: "6.9.2",
|
|
||||||
qt_creator_version: "17.0.2"
|
|
||||||
}
|
|
||||||
- {
|
- {
|
||||||
qt_version: "6.10.1",
|
qt_version: "6.10.1",
|
||||||
qt_creator_version: "18.0.2"
|
qt_creator_version: "18.0.2"
|
||||||
}
|
}
|
||||||
|
- {
|
||||||
|
qt_version: "6.10.2",
|
||||||
|
qt_creator_version: "19.0.0"
|
||||||
|
}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955
|
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955
|
||||||
|
|||||||
@@ -750,7 +750,7 @@ void ChatRootView::openRulesFolder()
|
|||||||
|
|
||||||
void ChatRootView::openSettings()
|
void ChatRootView::openSettings()
|
||||||
{
|
{
|
||||||
Core::ICore::showOptionsDialog(Constants::QODE_ASSIST_CHAT_ASSISTANT_SETTINGS_PAGE_ID);
|
Settings::showSettings(Constants::QODE_ASSIST_CHAT_ASSISTANT_SETTINGS_PAGE_ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChatRootView::openFileInEditor(const QString &filePath)
|
void ChatRootView::openFileInEditor(const QString &filePath)
|
||||||
@@ -1515,7 +1515,7 @@ QString ChatRootView::currentAgentRoleSystemPrompt() const
|
|||||||
|
|
||||||
void ChatRootView::openAgentRolesSettings()
|
void ChatRootView::openAgentRolesSettings()
|
||||||
{
|
{
|
||||||
Core::ICore::showOptionsDialog(Utils::Id("QodeAssist.AgentRoles"));
|
Settings::showSettings(Utils::Id("QodeAssist.AgentRoles"));
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChatRootView::compressCurrentChat()
|
void ChatRootView::compressCurrentChat()
|
||||||
|
|||||||
@@ -514,11 +514,8 @@ ChatRootView {
|
|||||||
|
|
||||||
sequences: ["Ctrl+Return", "Ctrl+Enter"]
|
sequences: ["Ctrl+Return", "Ctrl+Enter"]
|
||||||
context: Qt.WindowShortcut
|
context: Qt.WindowShortcut
|
||||||
onActivated: {
|
enabled: messageInput.activeFocus && !Qt.inputMethod.visible && !fileMentionPopup.visible
|
||||||
if (messageInput.activeFocus && !Qt.inputMethod.visible && !fileMentionPopup.visible) {
|
onActivated: root.sendChatMessage()
|
||||||
root.sendChatMessage()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearChat() {
|
function clearChat() {
|
||||||
|
|||||||
@@ -170,28 +170,26 @@ void ConfigurationManager::selectModel()
|
|||||||
: isQuickRefactor ? m_generalSettings.qrUrl.volatileValue()
|
: isQuickRefactor ? m_generalSettings.qrUrl.volatileValue()
|
||||||
: m_generalSettings.caUrl.volatileValue();
|
: m_generalSettings.caUrl.volatileValue();
|
||||||
|
|
||||||
auto &targetSettings = isCodeCompletion ? m_generalSettings.ccModel
|
auto *targetSettings = &(isCodeCompletion ? m_generalSettings.ccModel
|
||||||
: isPreset1 ? m_generalSettings.ccPreset1Model
|
: isPreset1 ? m_generalSettings.ccPreset1Model
|
||||||
: isQuickRefactor ? m_generalSettings.qrModel
|
: isQuickRefactor ? m_generalSettings.qrModel
|
||||||
: m_generalSettings.caModel;
|
: m_generalSettings.caModel);
|
||||||
|
|
||||||
if (auto provider = m_providersManager.getProviderByName(providerName)) {
|
if (auto provider = m_providersManager.getProviderByName(providerName)) {
|
||||||
if (!provider->supportsModelListing()) {
|
if (!provider->supportsModelListing()) {
|
||||||
m_generalSettings.showModelsNotSupportedDialog(targetSettings);
|
m_generalSettings.showModelsNotSupportedDialog(*targetSettings);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto modelList = provider->getInstalledModels(providerUrl);
|
provider->getInstalledModels(providerUrl)
|
||||||
|
.then(this, [this, targetSettings](const QList<QString> &modelList) {
|
||||||
if (modelList.isEmpty()) {
|
if (modelList.isEmpty()) {
|
||||||
m_generalSettings.showModelsNotFoundDialog(targetSettings);
|
m_generalSettings.showModelsNotFoundDialog(*targetSettings);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
m_generalSettings.showSelectionDialog(
|
||||||
QTimer::singleShot(0, &m_generalSettings, [this, modelList, &targetSettings]() {
|
modelList, *targetSettings, Tr::tr("Select LLM Model"), Tr::tr("Models:"));
|
||||||
m_generalSettings.showSelectionDialog(
|
});
|
||||||
modelList, targetSettings, Tr::tr("Select LLM Model"), Tr::tr("Models:"));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"Id" : "qodeassist",
|
"Id" : "qodeassist",
|
||||||
"Name" : "QodeAssist",
|
"Name" : "QodeAssist",
|
||||||
"Version" : "0.9.10",
|
"Version" : "0.9.11",
|
||||||
"CompatVersion" : "${IDE_VERSION}",
|
"CompatVersion" : "${IDE_VERSION}",
|
||||||
"Vendor" : "Petr Mironychev",
|
"Vendor" : "Petr Mironychev",
|
||||||
"VendorId" : "petrmironychev",
|
"VendorId" : "petrmironychev",
|
||||||
|
|||||||
@@ -21,7 +21,6 @@
|
|||||||
|
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QMutexLocker>
|
#include <QMutexLocker>
|
||||||
#include <QUuid>
|
|
||||||
|
|
||||||
#include <Logger.hpp>
|
#include <Logger.hpp>
|
||||||
|
|
||||||
@@ -30,9 +29,7 @@ namespace QodeAssist::LLMCore {
|
|||||||
HttpClient::HttpClient(QObject *parent)
|
HttpClient::HttpClient(QObject *parent)
|
||||||
: QObject(parent)
|
: QObject(parent)
|
||||||
, m_manager(new QNetworkAccessManager(this))
|
, m_manager(new QNetworkAccessManager(this))
|
||||||
{
|
{}
|
||||||
connect(this, &HttpClient::sendRequest, this, &HttpClient::onSendRequest);
|
|
||||||
}
|
|
||||||
|
|
||||||
HttpClient::~HttpClient()
|
HttpClient::~HttpClient()
|
||||||
{
|
{
|
||||||
@@ -44,156 +41,96 @@ HttpClient::~HttpClient()
|
|||||||
m_activeRequests.clear();
|
m_activeRequests.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpClient::onSendRequest(const HttpRequest &request)
|
QFuture<QByteArray> HttpClient::get(const QNetworkRequest &request)
|
||||||
{
|
{
|
||||||
QJsonDocument doc(request.payload);
|
LOG_MESSAGE(QString("HttpClient: GET %1").arg(request.url().toString()));
|
||||||
LOG_MESSAGE(QString("HttpClient: data: %1").arg(doc.toJson(QJsonDocument::Indented)));
|
|
||||||
|
|
||||||
QNetworkReply *reply
|
auto promise = std::make_shared<QPromise<QByteArray>>();
|
||||||
= m_manager->post(request.networkRequest, doc.toJson(QJsonDocument::Compact));
|
promise->start();
|
||||||
addActiveRequest(reply, request.requestId);
|
|
||||||
|
QNetworkReply *reply = m_manager->get(request);
|
||||||
|
setupNonStreamingReply(reply, promise);
|
||||||
|
|
||||||
|
return promise->future();
|
||||||
|
}
|
||||||
|
|
||||||
|
QFuture<QByteArray> HttpClient::post(const QNetworkRequest &request, const QJsonObject &payload)
|
||||||
|
{
|
||||||
|
QJsonDocument doc(payload);
|
||||||
|
LOG_MESSAGE(QString("HttpClient: POST %1, data: %2")
|
||||||
|
.arg(request.url().toString(), doc.toJson(QJsonDocument::Indented)));
|
||||||
|
|
||||||
|
auto promise = std::make_shared<QPromise<QByteArray>>();
|
||||||
|
promise->start();
|
||||||
|
|
||||||
|
QNetworkReply *reply = m_manager->post(request, doc.toJson(QJsonDocument::Compact));
|
||||||
|
setupNonStreamingReply(reply, promise);
|
||||||
|
|
||||||
|
return promise->future();
|
||||||
|
}
|
||||||
|
|
||||||
|
QFuture<QByteArray> HttpClient::del(const QNetworkRequest &request,
|
||||||
|
std::optional<QJsonObject> payload)
|
||||||
|
{
|
||||||
|
auto promise = std::make_shared<QPromise<QByteArray>>();
|
||||||
|
promise->start();
|
||||||
|
|
||||||
|
QNetworkReply *reply;
|
||||||
|
if (payload) {
|
||||||
|
QJsonDocument doc(*payload);
|
||||||
|
LOG_MESSAGE(QString("HttpClient: DELETE %1, data: %2")
|
||||||
|
.arg(request.url().toString(), doc.toJson(QJsonDocument::Indented)));
|
||||||
|
reply = m_manager->sendCustomRequest(request, "DELETE", doc.toJson(QJsonDocument::Compact));
|
||||||
|
} else {
|
||||||
|
LOG_MESSAGE(QString("HttpClient: DELETE %1").arg(request.url().toString()));
|
||||||
|
reply = m_manager->deleteResource(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
setupNonStreamingReply(reply, promise);
|
||||||
|
|
||||||
|
return promise->future();
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpClient::setupNonStreamingReply(QNetworkReply *reply,
|
||||||
|
std::shared_ptr<QPromise<QByteArray>> promise)
|
||||||
|
{
|
||||||
|
connect(reply, &QNetworkReply::finished, this, [this, reply, promise]() {
|
||||||
|
int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||||
|
QByteArray responseBody = reply->readAll();
|
||||||
|
QNetworkReply::NetworkError networkError = reply->error();
|
||||||
|
QString networkErrorString = reply->errorString();
|
||||||
|
|
||||||
|
reply->disconnect();
|
||||||
|
reply->deleteLater();
|
||||||
|
|
||||||
|
LOG_MESSAGE(
|
||||||
|
QString("HttpClient: Non-streaming request - HTTP Status: %1").arg(statusCode));
|
||||||
|
|
||||||
|
bool hasError = (networkError != QNetworkReply::NoError) || (statusCode >= 400);
|
||||||
|
if (hasError) {
|
||||||
|
QString errorMsg = parseErrorFromResponse(statusCode, responseBody, networkErrorString);
|
||||||
|
LOG_MESSAGE(QString("HttpClient: Non-streaming request - Error: %1").arg(errorMsg));
|
||||||
|
promise->setException(
|
||||||
|
std::make_exception_ptr(std::runtime_error(errorMsg.toStdString())));
|
||||||
|
} else {
|
||||||
|
promise->addResult(responseBody);
|
||||||
|
}
|
||||||
|
promise->finish();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpClient::postStreaming(const QString &requestId, const QNetworkRequest &request,
|
||||||
|
const QJsonObject &payload)
|
||||||
|
{
|
||||||
|
QJsonDocument doc(payload);
|
||||||
|
LOG_MESSAGE(QString("HttpClient: POST streaming %1, data: %2")
|
||||||
|
.arg(request.url().toString(), doc.toJson(QJsonDocument::Indented)));
|
||||||
|
|
||||||
|
QNetworkReply *reply = m_manager->post(request, doc.toJson(QJsonDocument::Compact));
|
||||||
|
addActiveRequest(reply, requestId);
|
||||||
|
|
||||||
connect(reply, &QNetworkReply::readyRead, this, &HttpClient::onReadyRead);
|
connect(reply, &QNetworkReply::readyRead, this, &HttpClient::onReadyRead);
|
||||||
connect(reply, &QNetworkReply::finished, this, &HttpClient::onFinished);
|
connect(reply, &QNetworkReply::finished, this, &HttpClient::onStreamingFinished);
|
||||||
}
|
|
||||||
|
|
||||||
void HttpClient::onReadyRead()
|
|
||||||
{
|
|
||||||
QNetworkReply *reply = qobject_cast<QNetworkReply *>(sender());
|
|
||||||
|
|
||||||
if (!reply || reply->isFinished())
|
|
||||||
return;
|
|
||||||
|
|
||||||
int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
|
||||||
if (statusCode >= 400) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString requestId;
|
|
||||||
{
|
|
||||||
QMutexLocker locker(&m_mutex);
|
|
||||||
bool found = false;
|
|
||||||
for (auto it = m_activeRequests.begin(); it != m_activeRequests.end(); ++it) {
|
|
||||||
if (it.value() == reply) {
|
|
||||||
requestId = it.key();
|
|
||||||
found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!found)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (requestId.isEmpty())
|
|
||||||
return;
|
|
||||||
|
|
||||||
QByteArray data = reply->readAll();
|
|
||||||
if (!data.isEmpty()) {
|
|
||||||
emit dataReceived(requestId, data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void HttpClient::onFinished()
|
|
||||||
{
|
|
||||||
QNetworkReply *reply = qobject_cast<QNetworkReply *>(sender());
|
|
||||||
if (!reply)
|
|
||||||
return;
|
|
||||||
|
|
||||||
int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
|
||||||
QByteArray responseBody = reply->readAll();
|
|
||||||
QNetworkReply::NetworkError networkError = reply->error();
|
|
||||||
QString networkErrorString = reply->errorString();
|
|
||||||
|
|
||||||
reply->disconnect();
|
|
||||||
|
|
||||||
QString requestId;
|
|
||||||
bool hasError = false;
|
|
||||||
QString errorMsg;
|
|
||||||
|
|
||||||
{
|
|
||||||
QMutexLocker locker(&m_mutex);
|
|
||||||
bool found = false;
|
|
||||||
for (auto it = m_activeRequests.begin(); it != m_activeRequests.end(); ++it) {
|
|
||||||
if (it.value() == reply) {
|
|
||||||
requestId = it.key();
|
|
||||||
m_activeRequests.erase(it);
|
|
||||||
found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!found) {
|
|
||||||
reply->deleteLater();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
hasError = (networkError != QNetworkReply::NoError) || (statusCode >= 400);
|
|
||||||
|
|
||||||
if (hasError) {
|
|
||||||
errorMsg = parseErrorFromResponse(statusCode, responseBody, networkErrorString);
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_MESSAGE(QString("HttpClient: Request %1 - HTTP Status: %2").arg(requestId).arg(statusCode));
|
|
||||||
|
|
||||||
if (!responseBody.isEmpty()) {
|
|
||||||
LOG_MESSAGE(QString("HttpClient: Request %1 - Response body (%2 bytes): %3")
|
|
||||||
.arg(requestId)
|
|
||||||
.arg(responseBody.size())
|
|
||||||
.arg(QString::fromUtf8(responseBody)));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasError) {
|
|
||||||
LOG_MESSAGE(QString("HttpClient: Request %1 - Error: %2").arg(requestId, errorMsg));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
reply->deleteLater();
|
|
||||||
|
|
||||||
if (!requestId.isEmpty()) {
|
|
||||||
emit requestFinished(requestId, !hasError, errorMsg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QString HttpClient::addActiveRequest(QNetworkReply *reply, const QString &requestId)
|
|
||||||
{
|
|
||||||
QMutexLocker locker(&m_mutex);
|
|
||||||
m_activeRequests[requestId] = reply;
|
|
||||||
LOG_MESSAGE(QString("HttpClient: Added active request: %1").arg(requestId));
|
|
||||||
return requestId;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString HttpClient::parseErrorFromResponse(
|
|
||||||
int statusCode, const QByteArray &responseBody, const QString &networkErrorString)
|
|
||||||
{
|
|
||||||
QString errorMsg;
|
|
||||||
|
|
||||||
if (!responseBody.isEmpty()) {
|
|
||||||
QJsonDocument errorDoc = QJsonDocument::fromJson(responseBody);
|
|
||||||
if (!errorDoc.isNull() && errorDoc.isObject()) {
|
|
||||||
QJsonObject errorObj = errorDoc.object();
|
|
||||||
if (errorObj.contains("error")) {
|
|
||||||
QJsonObject error = errorObj["error"].toObject();
|
|
||||||
QString message = error["message"].toString();
|
|
||||||
QString type = error["type"].toString();
|
|
||||||
QString code = error["code"].toString();
|
|
||||||
|
|
||||||
errorMsg = QString("HTTP %1: %2").arg(statusCode).arg(message);
|
|
||||||
if (!type.isEmpty())
|
|
||||||
errorMsg += QString(" (type: %1)").arg(type);
|
|
||||||
if (!code.isEmpty())
|
|
||||||
errorMsg += QString(" (code: %1)").arg(code);
|
|
||||||
} else {
|
|
||||||
errorMsg = QString("HTTP %1: %2").arg(statusCode).arg(QString::fromUtf8(responseBody));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
errorMsg = QString("HTTP %1: %2").arg(statusCode).arg(QString::fromUtf8(responseBody));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
errorMsg = QString("HTTP %1: %2").arg(statusCode).arg(networkErrorString);
|
|
||||||
}
|
|
||||||
|
|
||||||
return errorMsg;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpClient::cancelRequest(const QString &requestId)
|
void HttpClient::cancelRequest(const QString &requestId)
|
||||||
@@ -212,4 +149,128 @@ void HttpClient::cancelRequest(const QString &requestId)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void HttpClient::onReadyRead()
|
||||||
|
{
|
||||||
|
QNetworkReply *reply = qobject_cast<QNetworkReply *>(sender());
|
||||||
|
if (!reply || reply->isFinished())
|
||||||
|
return;
|
||||||
|
|
||||||
|
int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||||
|
if (statusCode >= 400)
|
||||||
|
return;
|
||||||
|
|
||||||
|
QString requestId = findRequestId(reply);
|
||||||
|
if (requestId.isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
QByteArray data = reply->readAll();
|
||||||
|
if (!data.isEmpty()) {
|
||||||
|
emit dataReceived(requestId, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpClient::onStreamingFinished()
|
||||||
|
{
|
||||||
|
QNetworkReply *reply = qobject_cast<QNetworkReply *>(sender());
|
||||||
|
if (!reply)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||||
|
QByteArray responseBody = reply->readAll();
|
||||||
|
QNetworkReply::NetworkError networkError = reply->error();
|
||||||
|
QString networkErrorString = reply->errorString();
|
||||||
|
|
||||||
|
reply->disconnect();
|
||||||
|
|
||||||
|
QString requestId;
|
||||||
|
std::optional<QString> error;
|
||||||
|
|
||||||
|
{
|
||||||
|
QMutexLocker locker(&m_mutex);
|
||||||
|
for (auto it = m_activeRequests.begin(); it != m_activeRequests.end(); ++it) {
|
||||||
|
if (it.value() == reply) {
|
||||||
|
requestId = it.key();
|
||||||
|
m_activeRequests.erase(it);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requestId.isEmpty()) {
|
||||||
|
reply->deleteLater();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hasError = (networkError != QNetworkReply::NoError) || (statusCode >= 400);
|
||||||
|
if (hasError) {
|
||||||
|
error = parseErrorFromResponse(statusCode, responseBody, networkErrorString);
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_MESSAGE(
|
||||||
|
QString("HttpClient: Request %1 - HTTP Status: %2").arg(requestId).arg(statusCode));
|
||||||
|
|
||||||
|
if (!responseBody.isEmpty()) {
|
||||||
|
LOG_MESSAGE(QString("HttpClient: Request %1 - Response body (%2 bytes): %3")
|
||||||
|
.arg(requestId)
|
||||||
|
.arg(responseBody.size())
|
||||||
|
.arg(QString::fromUtf8(responseBody)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
LOG_MESSAGE(QString("HttpClient: Request %1 - Error: %2").arg(requestId, *error));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reply->deleteLater();
|
||||||
|
|
||||||
|
if (!requestId.isEmpty()) {
|
||||||
|
emit requestFinished(requestId, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QString HttpClient::findRequestId(QNetworkReply *reply)
|
||||||
|
{
|
||||||
|
QMutexLocker locker(&m_mutex);
|
||||||
|
for (auto it = m_activeRequests.begin(); it != m_activeRequests.end(); ++it) {
|
||||||
|
if (it.value() == reply)
|
||||||
|
return it.key();
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpClient::addActiveRequest(QNetworkReply *reply, const QString &requestId)
|
||||||
|
{
|
||||||
|
QMutexLocker locker(&m_mutex);
|
||||||
|
m_activeRequests[requestId] = reply;
|
||||||
|
LOG_MESSAGE(QString("HttpClient: Added active request: %1").arg(requestId));
|
||||||
|
}
|
||||||
|
|
||||||
|
QString HttpClient::parseErrorFromResponse(
|
||||||
|
int statusCode, const QByteArray &responseBody, const QString &networkErrorString)
|
||||||
|
{
|
||||||
|
if (!responseBody.isEmpty()) {
|
||||||
|
QJsonDocument errorDoc = QJsonDocument::fromJson(responseBody);
|
||||||
|
if (!errorDoc.isNull() && errorDoc.isObject()) {
|
||||||
|
QJsonObject errorObj = errorDoc.object();
|
||||||
|
if (errorObj.contains("error")) {
|
||||||
|
QJsonObject error = errorObj["error"].toObject();
|
||||||
|
QString message = error["message"].toString();
|
||||||
|
QString type = error["type"].toString();
|
||||||
|
QString code = error["code"].toString();
|
||||||
|
|
||||||
|
QString errorMsg = QString("HTTP %1: %2").arg(statusCode).arg(message);
|
||||||
|
if (!type.isEmpty())
|
||||||
|
errorMsg += QString(" (type: %1)").arg(type);
|
||||||
|
if (!code.isEmpty())
|
||||||
|
errorMsg += QString(" (code: %1)").arg(code);
|
||||||
|
return errorMsg;
|
||||||
|
}
|
||||||
|
return QString("HTTP %1: %2")
|
||||||
|
.arg(statusCode)
|
||||||
|
.arg(QString::fromUtf8(responseBody));
|
||||||
|
}
|
||||||
|
return QString("HTTP %1: %2").arg(statusCode).arg(QString::fromUtf8(responseBody));
|
||||||
|
}
|
||||||
|
return QString("HTTP %1: %2").arg(statusCode).arg(networkErrorString);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace QodeAssist::LLMCore
|
} // namespace QodeAssist::LLMCore
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2025 Petr Mironychev
|
* Copyright (C) 2025 Petr Mironychev
|
||||||
*
|
*
|
||||||
* This file is part of QodeAssist.
|
* This file is part of QodeAssist.
|
||||||
@@ -19,24 +19,19 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
#include <QFuture>
|
||||||
#include <QHash>
|
#include <QHash>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QMap>
|
|
||||||
#include <QMutex>
|
#include <QMutex>
|
||||||
#include <QNetworkAccessManager>
|
#include <QNetworkAccessManager>
|
||||||
#include <QNetworkReply>
|
#include <QNetworkReply>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QUrl>
|
#include <QPromise>
|
||||||
|
|
||||||
namespace QodeAssist::LLMCore {
|
namespace QodeAssist::LLMCore {
|
||||||
|
|
||||||
struct HttpRequest
|
|
||||||
{
|
|
||||||
QNetworkRequest networkRequest;
|
|
||||||
QString requestId;
|
|
||||||
QJsonObject payload;
|
|
||||||
};
|
|
||||||
|
|
||||||
class HttpClient : public QObject
|
class HttpClient : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
@@ -45,21 +40,33 @@ public:
|
|||||||
HttpClient(QObject *parent = nullptr);
|
HttpClient(QObject *parent = nullptr);
|
||||||
~HttpClient();
|
~HttpClient();
|
||||||
|
|
||||||
|
// Non-streaming — return QFuture with full response
|
||||||
|
QFuture<QByteArray> get(const QNetworkRequest &request);
|
||||||
|
QFuture<QByteArray> post(const QNetworkRequest &request, const QJsonObject &payload);
|
||||||
|
QFuture<QByteArray> del(const QNetworkRequest &request,
|
||||||
|
std::optional<QJsonObject> payload = std::nullopt);
|
||||||
|
|
||||||
|
// Streaming — signal-based with requestId
|
||||||
|
void postStreaming(const QString &requestId, const QNetworkRequest &request,
|
||||||
|
const QJsonObject &payload);
|
||||||
|
|
||||||
void cancelRequest(const QString &requestId);
|
void cancelRequest(const QString &requestId);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void sendRequest(const QodeAssist::LLMCore::HttpRequest &request);
|
|
||||||
void dataReceived(const QString &requestId, const QByteArray &data);
|
void dataReceived(const QString &requestId, const QByteArray &data);
|
||||||
void requestFinished(const QString &requestId, bool success, const QString &error);
|
void requestFinished(const QString &requestId, std::optional<QString> error);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void onSendRequest(const QodeAssist::LLMCore::HttpRequest &request);
|
|
||||||
void onReadyRead();
|
void onReadyRead();
|
||||||
void onFinished();
|
void onStreamingFinished();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QString addActiveRequest(QNetworkReply *reply, const QString &requestId);
|
void setupNonStreamingReply(QNetworkReply *reply, std::shared_ptr<QPromise<QByteArray>> promise);
|
||||||
QString parseErrorFromResponse(int statusCode, const QByteArray &responseBody, const QString &networkErrorString);
|
|
||||||
|
QString findRequestId(QNetworkReply *reply);
|
||||||
|
void addActiveRequest(QNetworkReply *reply, const QString &requestId);
|
||||||
|
QString parseErrorFromResponse(int statusCode, const QByteArray &responseBody,
|
||||||
|
const QString &networkErrorString);
|
||||||
|
|
||||||
QNetworkAccessManager *m_manager;
|
QNetworkAccessManager *m_manager;
|
||||||
QHash<QString, QNetworkReply *> m_activeRequests;
|
QHash<QString, QNetworkReply *> m_activeRequests;
|
||||||
|
|||||||
@@ -19,6 +19,9 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
#include <QFuture>
|
||||||
#include <utils/environment.h>
|
#include <utils/environment.h>
|
||||||
#include <QNetworkRequest>
|
#include <QNetworkRequest>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
@@ -57,7 +60,7 @@ public:
|
|||||||
bool isToolsEnabled,
|
bool isToolsEnabled,
|
||||||
bool isThinkingEnabled)
|
bool isThinkingEnabled)
|
||||||
= 0;
|
= 0;
|
||||||
virtual QList<QString> getInstalledModels(const QString &url) = 0;
|
virtual QFuture<QList<QString>> getInstalledModels(const QString &url) = 0;
|
||||||
virtual QList<QString> validateRequest(const QJsonObject &request, TemplateType type) = 0;
|
virtual QList<QString> validateRequest(const QJsonObject &request, TemplateType type) = 0;
|
||||||
virtual QString apiKey() const = 0;
|
virtual QString apiKey() const = 0;
|
||||||
virtual void prepareNetworkRequest(QNetworkRequest &networkRequest) const = 0;
|
virtual void prepareNetworkRequest(QNetworkRequest &networkRequest) const = 0;
|
||||||
@@ -81,7 +84,7 @@ public slots:
|
|||||||
const QodeAssist::LLMCore::RequestID &requestId, const QByteArray &data)
|
const QodeAssist::LLMCore::RequestID &requestId, const QByteArray &data)
|
||||||
= 0;
|
= 0;
|
||||||
virtual void onRequestFinished(
|
virtual void onRequestFinished(
|
||||||
const QodeAssist::LLMCore::RequestID &requestId, bool success, const QString &error)
|
const QodeAssist::LLMCore::RequestID &requestId, std::optional<QString> error)
|
||||||
= 0;
|
= 0;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
|||||||
@@ -19,11 +19,9 @@
|
|||||||
|
|
||||||
#include "ClaudeProvider.hpp"
|
#include "ClaudeProvider.hpp"
|
||||||
|
|
||||||
#include <QEventLoop>
|
|
||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QNetworkReply>
|
|
||||||
#include <QUrlQuery>
|
#include <QUrlQuery>
|
||||||
|
|
||||||
#include "llmcore/ValidationUtils.hpp"
|
#include "llmcore/ValidationUtils.hpp"
|
||||||
@@ -142,11 +140,8 @@ void ClaudeProvider::prepareRequest(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<QString> ClaudeProvider::getInstalledModels(const QString &baseUrl)
|
QFuture<QList<QString>> ClaudeProvider::getInstalledModels(const QString &baseUrl)
|
||||||
{
|
{
|
||||||
QList<QString> models;
|
|
||||||
QNetworkAccessManager manager;
|
|
||||||
|
|
||||||
QUrl url(baseUrl + "/v1/models");
|
QUrl url(baseUrl + "/v1/models");
|
||||||
QUrlQuery query;
|
QUrlQuery query;
|
||||||
query.addQueryItem("limit", "1000");
|
query.addQueryItem("limit", "1000");
|
||||||
@@ -160,32 +155,24 @@ QList<QString> ClaudeProvider::getInstalledModels(const QString &baseUrl)
|
|||||||
request.setRawHeader("x-api-key", apiKey().toUtf8());
|
request.setRawHeader("x-api-key", apiKey().toUtf8());
|
||||||
}
|
}
|
||||||
|
|
||||||
QNetworkReply *reply = manager.get(request);
|
return httpClient()->get(request).then([](const QByteArray &data) {
|
||||||
QEventLoop loop;
|
QList<QString> models;
|
||||||
QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
|
QJsonObject jsonObject = QJsonDocument::fromJson(data).object();
|
||||||
loop.exec();
|
|
||||||
|
|
||||||
if (reply->error() == QNetworkReply::NoError) {
|
|
||||||
QByteArray responseData = reply->readAll();
|
|
||||||
QJsonDocument jsonResponse = QJsonDocument::fromJson(responseData);
|
|
||||||
QJsonObject jsonObject = jsonResponse.object();
|
|
||||||
|
|
||||||
if (jsonObject.contains("data")) {
|
if (jsonObject.contains("data")) {
|
||||||
QJsonArray modelArray = jsonObject["data"].toArray();
|
QJsonArray modelArray = jsonObject["data"].toArray();
|
||||||
for (const QJsonValue &value : modelArray) {
|
for (const QJsonValue &value : modelArray) {
|
||||||
QJsonObject modelObject = value.toObject();
|
QJsonObject modelObject = value.toObject();
|
||||||
if (modelObject.contains("id")) {
|
if (modelObject.contains("id")) {
|
||||||
QString modelId = modelObject["id"].toString();
|
models.append(modelObject["id"].toString());
|
||||||
models.append(modelId);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
return models;
|
||||||
LOG_MESSAGE(QString("Error fetching Claude models: %1").arg(reply->errorString()));
|
}).onFailed([](const std::exception &e) {
|
||||||
}
|
LOG_MESSAGE(QString("Error fetching Claude models: %1").arg(e.what()));
|
||||||
|
return QList<QString>{};
|
||||||
reply->deleteLater();
|
});
|
||||||
return models;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<QString> ClaudeProvider::validateRequest(const QJsonObject &request, LLMCore::TemplateType type)
|
QList<QString> ClaudeProvider::validateRequest(const QJsonObject &request, LLMCore::TemplateType type)
|
||||||
@@ -240,12 +227,9 @@ void ClaudeProvider::sendRequest(
|
|||||||
QNetworkRequest networkRequest(url);
|
QNetworkRequest networkRequest(url);
|
||||||
prepareNetworkRequest(networkRequest);
|
prepareNetworkRequest(networkRequest);
|
||||||
|
|
||||||
LLMCore::HttpRequest
|
|
||||||
request{.networkRequest = networkRequest, .requestId = requestId, .payload = payload};
|
|
||||||
|
|
||||||
LOG_MESSAGE(QString("ClaudeProvider: Sending request %1 to %2").arg(requestId, url.toString()));
|
LOG_MESSAGE(QString("ClaudeProvider: Sending request %1 to %2").arg(requestId, url.toString()));
|
||||||
|
|
||||||
emit httpClient()->sendRequest(request);
|
httpClient()->postStreaming(requestId, networkRequest, payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ClaudeProvider::supportsTools() const
|
bool ClaudeProvider::supportsTools() const
|
||||||
@@ -289,11 +273,11 @@ void ClaudeProvider::onDataReceived(
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ClaudeProvider::onRequestFinished(
|
void ClaudeProvider::onRequestFinished(
|
||||||
const QodeAssist::LLMCore::RequestID &requestId, bool success, const QString &error)
|
const QodeAssist::LLMCore::RequestID &requestId, std::optional<QString> error)
|
||||||
{
|
{
|
||||||
if (!success) {
|
if (error) {
|
||||||
LOG_MESSAGE(QString("ClaudeProvider request %1 failed: %2").arg(requestId, error));
|
LOG_MESSAGE(QString("ClaudeProvider request %1 failed: %2").arg(requestId, *error));
|
||||||
emit requestFailed(requestId, error);
|
emit requestFailed(requestId, *error);
|
||||||
cleanupRequest(requestId);
|
cleanupRequest(requestId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ public:
|
|||||||
LLMCore::RequestType type,
|
LLMCore::RequestType type,
|
||||||
bool isToolsEnabled,
|
bool isToolsEnabled,
|
||||||
bool isThinkingEnabled) override;
|
bool isThinkingEnabled) override;
|
||||||
QList<QString> getInstalledModels(const QString &url) override;
|
QFuture<QList<QString>> getInstalledModels(const QString &url) override;
|
||||||
QList<QString> validateRequest(const QJsonObject &request, LLMCore::TemplateType type) override;
|
QList<QString> validateRequest(const QJsonObject &request, LLMCore::TemplateType type) override;
|
||||||
QString apiKey() const override;
|
QString apiKey() const override;
|
||||||
void prepareNetworkRequest(QNetworkRequest &networkRequest) const override;
|
void prepareNetworkRequest(QNetworkRequest &networkRequest) const override;
|
||||||
@@ -65,8 +65,7 @@ public slots:
|
|||||||
const QodeAssist::LLMCore::RequestID &requestId, const QByteArray &data) override;
|
const QodeAssist::LLMCore::RequestID &requestId, const QByteArray &data) override;
|
||||||
void onRequestFinished(
|
void onRequestFinished(
|
||||||
const QodeAssist::LLMCore::RequestID &requestId,
|
const QodeAssist::LLMCore::RequestID &requestId,
|
||||||
bool success,
|
std::optional<QString> error) override;
|
||||||
const QString &error) override;
|
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void onToolExecutionComplete(
|
void onToolExecutionComplete(
|
||||||
|
|||||||
@@ -19,11 +19,9 @@
|
|||||||
|
|
||||||
#include "GoogleAIProvider.hpp"
|
#include "GoogleAIProvider.hpp"
|
||||||
|
|
||||||
#include <QEventLoop>
|
|
||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QNetworkReply>
|
|
||||||
#include <QtCore/qurlquery.h>
|
#include <QtCore/qurlquery.h>
|
||||||
|
|
||||||
#include "llmcore/ValidationUtils.hpp"
|
#include "llmcore/ValidationUtils.hpp"
|
||||||
@@ -156,29 +154,17 @@ void GoogleAIProvider::prepareRequest(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<QString> GoogleAIProvider::getInstalledModels(const QString &url)
|
QFuture<QList<QString>> GoogleAIProvider::getInstalledModels(const QString &url)
|
||||||
{
|
{
|
||||||
QList<QString> models;
|
|
||||||
|
|
||||||
QNetworkAccessManager manager;
|
|
||||||
QNetworkRequest request(QString("%1/models?key=%2").arg(url, apiKey()));
|
QNetworkRequest request(QString("%1/models?key=%2").arg(url, apiKey()));
|
||||||
|
|
||||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||||
|
|
||||||
QNetworkReply *reply = manager.get(request);
|
return httpClient()->get(request).then([](const QByteArray &data) {
|
||||||
QEventLoop loop;
|
QList<QString> models;
|
||||||
QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
|
QJsonObject jsonObject = QJsonDocument::fromJson(data).object();
|
||||||
loop.exec();
|
|
||||||
|
|
||||||
if (reply->error() == QNetworkReply::NoError) {
|
|
||||||
QByteArray responseData = reply->readAll();
|
|
||||||
QJsonDocument jsonResponse = QJsonDocument::fromJson(responseData);
|
|
||||||
QJsonObject jsonObject = jsonResponse.object();
|
|
||||||
|
|
||||||
if (jsonObject.contains("models")) {
|
if (jsonObject.contains("models")) {
|
||||||
QJsonArray modelArray = jsonObject["models"].toArray();
|
QJsonArray modelArray = jsonObject["models"].toArray();
|
||||||
models.clear();
|
|
||||||
|
|
||||||
for (const QJsonValue &value : modelArray) {
|
for (const QJsonValue &value : modelArray) {
|
||||||
QJsonObject modelObject = value.toObject();
|
QJsonObject modelObject = value.toObject();
|
||||||
if (modelObject.contains("name")) {
|
if (modelObject.contains("name")) {
|
||||||
@@ -190,12 +176,11 @@ QList<QString> GoogleAIProvider::getInstalledModels(const QString &url)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
return models;
|
||||||
LOG_MESSAGE(QString("Error fetching Google AI models: %1").arg(reply->errorString()));
|
}).onFailed([](const std::exception &e) {
|
||||||
}
|
LOG_MESSAGE(QString("Error fetching Google AI models: %1").arg(e.what()));
|
||||||
|
return QList<QString>{};
|
||||||
reply->deleteLater();
|
});
|
||||||
return models;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<QString> GoogleAIProvider::validateRequest(
|
QList<QString> GoogleAIProvider::validateRequest(
|
||||||
@@ -254,13 +239,10 @@ void GoogleAIProvider::sendRequest(
|
|||||||
QNetworkRequest networkRequest(url);
|
QNetworkRequest networkRequest(url);
|
||||||
prepareNetworkRequest(networkRequest);
|
prepareNetworkRequest(networkRequest);
|
||||||
|
|
||||||
LLMCore::HttpRequest
|
|
||||||
request{.networkRequest = networkRequest, .requestId = requestId, .payload = payload};
|
|
||||||
|
|
||||||
LOG_MESSAGE(
|
LOG_MESSAGE(
|
||||||
QString("GoogleAIProvider: Sending request %1 to %2").arg(requestId, url.toString()));
|
QString("GoogleAIProvider: Sending request %1 to %2").arg(requestId, url.toString()));
|
||||||
|
|
||||||
emit httpClient()->sendRequest(request);
|
httpClient()->postStreaming(requestId, networkRequest, payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GoogleAIProvider::supportsTools() const
|
bool GoogleAIProvider::supportsTools() const
|
||||||
@@ -327,11 +309,11 @@ void GoogleAIProvider::onDataReceived(
|
|||||||
}
|
}
|
||||||
|
|
||||||
void GoogleAIProvider::onRequestFinished(
|
void GoogleAIProvider::onRequestFinished(
|
||||||
const QodeAssist::LLMCore::RequestID &requestId, bool success, const QString &error)
|
const QodeAssist::LLMCore::RequestID &requestId, std::optional<QString> error)
|
||||||
{
|
{
|
||||||
if (!success) {
|
if (error) {
|
||||||
LOG_MESSAGE(QString("GoogleAIProvider request %1 failed: %2").arg(requestId, error));
|
LOG_MESSAGE(QString("GoogleAIProvider request %1 failed: %2").arg(requestId, *error));
|
||||||
emit requestFailed(requestId, error);
|
emit requestFailed(requestId, *error);
|
||||||
cleanupRequest(requestId);
|
cleanupRequest(requestId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ public:
|
|||||||
LLMCore::RequestType type,
|
LLMCore::RequestType type,
|
||||||
bool isToolsEnabled,
|
bool isToolsEnabled,
|
||||||
bool isThinkingEnabled) override;
|
bool isThinkingEnabled) override;
|
||||||
QList<QString> getInstalledModels(const QString &url) override;
|
QFuture<QList<QString>> getInstalledModels(const QString &url) override;
|
||||||
QList<QString> validateRequest(const QJsonObject &request, LLMCore::TemplateType type) override;
|
QList<QString> validateRequest(const QJsonObject &request, LLMCore::TemplateType type) override;
|
||||||
QString apiKey() const override;
|
QString apiKey() const override;
|
||||||
void prepareNetworkRequest(QNetworkRequest &networkRequest) const override;
|
void prepareNetworkRequest(QNetworkRequest &networkRequest) const override;
|
||||||
@@ -62,8 +62,7 @@ public slots:
|
|||||||
const QodeAssist::LLMCore::RequestID &requestId, const QByteArray &data) override;
|
const QodeAssist::LLMCore::RequestID &requestId, const QByteArray &data) override;
|
||||||
void onRequestFinished(
|
void onRequestFinished(
|
||||||
const QodeAssist::LLMCore::RequestID &requestId,
|
const QodeAssist::LLMCore::RequestID &requestId,
|
||||||
bool success,
|
std::optional<QString> error) override;
|
||||||
const QString &error) override;
|
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void onToolExecutionComplete(
|
void onToolExecutionComplete(
|
||||||
|
|||||||
@@ -27,11 +27,9 @@
|
|||||||
#include "settings/GeneralSettings.hpp"
|
#include "settings/GeneralSettings.hpp"
|
||||||
#include "settings/ProviderSettings.hpp"
|
#include "settings/ProviderSettings.hpp"
|
||||||
|
|
||||||
#include <QEventLoop>
|
|
||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QNetworkReply>
|
|
||||||
|
|
||||||
namespace QodeAssist::Providers {
|
namespace QodeAssist::Providers {
|
||||||
|
|
||||||
@@ -71,35 +69,24 @@ bool LMStudioProvider::supportsModelListing() const
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<QString> LMStudioProvider::getInstalledModels(const QString &url)
|
QFuture<QList<QString>> LMStudioProvider::getInstalledModels(const QString &url)
|
||||||
{
|
{
|
||||||
QList<QString> models;
|
|
||||||
QNetworkAccessManager manager;
|
|
||||||
QNetworkRequest request(QString("%1%2").arg(url, "/v1/models"));
|
QNetworkRequest request(QString("%1%2").arg(url, "/v1/models"));
|
||||||
|
|
||||||
QNetworkReply *reply = manager.get(request);
|
return httpClient()->get(request).then([](const QByteArray &data) {
|
||||||
|
QList<QString> models;
|
||||||
|
QJsonObject jsonObject = QJsonDocument::fromJson(data).object();
|
||||||
|
QJsonArray modelArray = jsonObject["data"].toArray();
|
||||||
|
|
||||||
QEventLoop loop;
|
for (const QJsonValue &value : modelArray) {
|
||||||
QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
|
QJsonObject modelObject = value.toObject();
|
||||||
loop.exec();
|
models.append(modelObject["id"].toString());
|
||||||
|
|
||||||
if (reply->error() == QNetworkReply::NoError) {
|
|
||||||
QByteArray responseData = reply->readAll();
|
|
||||||
QJsonDocument jsonResponse = QJsonDocument::fromJson(responseData);
|
|
||||||
QJsonObject jsonObject = jsonResponse.object();
|
|
||||||
QJsonArray modelArray = jsonObject["data"].toArray();
|
|
||||||
|
|
||||||
for (const QJsonValue &value : modelArray) {
|
|
||||||
QJsonObject modelObject = value.toObject();
|
|
||||||
QString modelId = modelObject["id"].toString();
|
|
||||||
models.append(modelId);
|
|
||||||
}
|
}
|
||||||
} else {
|
return models;
|
||||||
LOG_MESSAGE(QString("Error fetching LMStudio models: %1").arg(reply->errorString()));
|
}).onFailed([](const std::exception &e) {
|
||||||
}
|
LOG_MESSAGE(QString("Error fetching LMStudio models: %1").arg(e.what()));
|
||||||
|
return QList<QString>{};
|
||||||
reply->deleteLater();
|
});
|
||||||
return models;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<QString> LMStudioProvider::validateRequest(
|
QList<QString> LMStudioProvider::validateRequest(
|
||||||
@@ -149,13 +136,10 @@ void LMStudioProvider::sendRequest(
|
|||||||
QNetworkRequest networkRequest(url);
|
QNetworkRequest networkRequest(url);
|
||||||
prepareNetworkRequest(networkRequest);
|
prepareNetworkRequest(networkRequest);
|
||||||
|
|
||||||
LLMCore::HttpRequest
|
|
||||||
request{.networkRequest = networkRequest, .requestId = requestId, .payload = payload};
|
|
||||||
|
|
||||||
LOG_MESSAGE(
|
LOG_MESSAGE(
|
||||||
QString("LMStudioProvider: Sending request %1 to %2").arg(requestId, url.toString()));
|
QString("LMStudioProvider: Sending request %1 to %2").arg(requestId, url.toString()));
|
||||||
|
|
||||||
emit httpClient()->sendRequest(request);
|
httpClient()->postStreaming(requestId, networkRequest, payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool LMStudioProvider::supportsTools() const
|
bool LMStudioProvider::supportsTools() const
|
||||||
@@ -195,11 +179,11 @@ void LMStudioProvider::onDataReceived(
|
|||||||
}
|
}
|
||||||
|
|
||||||
void LMStudioProvider::onRequestFinished(
|
void LMStudioProvider::onRequestFinished(
|
||||||
const QodeAssist::LLMCore::RequestID &requestId, bool success, const QString &error)
|
const QodeAssist::LLMCore::RequestID &requestId, std::optional<QString> error)
|
||||||
{
|
{
|
||||||
if (!success) {
|
if (error) {
|
||||||
LOG_MESSAGE(QString("LMStudioProvider request %1 failed: %2").arg(requestId, error));
|
LOG_MESSAGE(QString("LMStudioProvider request %1 failed: %2").arg(requestId, *error));
|
||||||
emit requestFailed(requestId, error);
|
emit requestFailed(requestId, *error);
|
||||||
cleanupRequest(requestId);
|
cleanupRequest(requestId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ public:
|
|||||||
LLMCore::RequestType type,
|
LLMCore::RequestType type,
|
||||||
bool isToolsEnabled,
|
bool isToolsEnabled,
|
||||||
bool isThinkingEnabled) override;
|
bool isThinkingEnabled) override;
|
||||||
QList<QString> getInstalledModels(const QString &url) override;
|
QFuture<QList<QString>> getInstalledModels(const QString &url) override;
|
||||||
QList<QString> validateRequest(const QJsonObject &request, LLMCore::TemplateType type) override;
|
QList<QString> validateRequest(const QJsonObject &request, LLMCore::TemplateType type) override;
|
||||||
QString apiKey() const override;
|
QString apiKey() const override;
|
||||||
void prepareNetworkRequest(QNetworkRequest &networkRequest) const override;
|
void prepareNetworkRequest(QNetworkRequest &networkRequest) const override;
|
||||||
@@ -61,8 +61,7 @@ public slots:
|
|||||||
const QodeAssist::LLMCore::RequestID &requestId, const QByteArray &data) override;
|
const QodeAssist::LLMCore::RequestID &requestId, const QByteArray &data) override;
|
||||||
void onRequestFinished(
|
void onRequestFinished(
|
||||||
const QodeAssist::LLMCore::RequestID &requestId,
|
const QodeAssist::LLMCore::RequestID &requestId,
|
||||||
bool success,
|
std::optional<QString> error) override;
|
||||||
const QString &error) override;
|
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void onToolExecutionComplete(
|
void onToolExecutionComplete(
|
||||||
|
|||||||
@@ -26,11 +26,9 @@
|
|||||||
#include "settings/QuickRefactorSettings.hpp"
|
#include "settings/QuickRefactorSettings.hpp"
|
||||||
#include "settings/GeneralSettings.hpp"
|
#include "settings/GeneralSettings.hpp"
|
||||||
|
|
||||||
#include <QEventLoop>
|
|
||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QNetworkReply>
|
|
||||||
|
|
||||||
namespace QodeAssist::Providers {
|
namespace QodeAssist::Providers {
|
||||||
|
|
||||||
@@ -121,9 +119,9 @@ void LlamaCppProvider::prepareRequest(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<QString> LlamaCppProvider::getInstalledModels(const QString &url)
|
QFuture<QList<QString>> LlamaCppProvider::getInstalledModels(const QString &)
|
||||||
{
|
{
|
||||||
return {};
|
return QtFuture::makeReadyFuture(QList<QString>{});
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<QString> LlamaCppProvider::validateRequest(
|
QList<QString> LlamaCppProvider::validateRequest(
|
||||||
@@ -192,13 +190,10 @@ void LlamaCppProvider::sendRequest(
|
|||||||
QNetworkRequest networkRequest(url);
|
QNetworkRequest networkRequest(url);
|
||||||
prepareNetworkRequest(networkRequest);
|
prepareNetworkRequest(networkRequest);
|
||||||
|
|
||||||
LLMCore::HttpRequest
|
|
||||||
request{.networkRequest = networkRequest, .requestId = requestId, .payload = payload};
|
|
||||||
|
|
||||||
LOG_MESSAGE(
|
LOG_MESSAGE(
|
||||||
QString("LlamaCppProvider: Sending request %1 to %2").arg(requestId, url.toString()));
|
QString("LlamaCppProvider: Sending request %1 to %2").arg(requestId, url.toString()));
|
||||||
|
|
||||||
emit httpClient()->sendRequest(request);
|
httpClient()->postStreaming(requestId, networkRequest, payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool LlamaCppProvider::supportsTools() const
|
bool LlamaCppProvider::supportsTools() const
|
||||||
@@ -250,11 +245,11 @@ void LlamaCppProvider::onDataReceived(
|
|||||||
}
|
}
|
||||||
|
|
||||||
void LlamaCppProvider::onRequestFinished(
|
void LlamaCppProvider::onRequestFinished(
|
||||||
const QodeAssist::LLMCore::RequestID &requestId, bool success, const QString &error)
|
const QodeAssist::LLMCore::RequestID &requestId, std::optional<QString> error)
|
||||||
{
|
{
|
||||||
if (!success) {
|
if (error) {
|
||||||
LOG_MESSAGE(QString("LlamaCppProvider request %1 failed: %2").arg(requestId, error));
|
LOG_MESSAGE(QString("LlamaCppProvider request %1 failed: %2").arg(requestId, *error));
|
||||||
emit requestFailed(requestId, error);
|
emit requestFailed(requestId, *error);
|
||||||
cleanupRequest(requestId);
|
cleanupRequest(requestId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ public:
|
|||||||
LLMCore::RequestType type,
|
LLMCore::RequestType type,
|
||||||
bool isToolsEnabled,
|
bool isToolsEnabled,
|
||||||
bool isThinkingEnabled) override;
|
bool isThinkingEnabled) override;
|
||||||
QList<QString> getInstalledModels(const QString &url) override;
|
QFuture<QList<QString>> getInstalledModels(const QString &url) override;
|
||||||
QList<QString> validateRequest(const QJsonObject &request, LLMCore::TemplateType type) override;
|
QList<QString> validateRequest(const QJsonObject &request, LLMCore::TemplateType type) override;
|
||||||
QString apiKey() const override;
|
QString apiKey() const override;
|
||||||
void prepareNetworkRequest(QNetworkRequest &networkRequest) const override;
|
void prepareNetworkRequest(QNetworkRequest &networkRequest) const override;
|
||||||
@@ -61,8 +61,7 @@ public slots:
|
|||||||
const QodeAssist::LLMCore::RequestID &requestId, const QByteArray &data) override;
|
const QodeAssist::LLMCore::RequestID &requestId, const QByteArray &data) override;
|
||||||
void onRequestFinished(
|
void onRequestFinished(
|
||||||
const QodeAssist::LLMCore::RequestID &requestId,
|
const QodeAssist::LLMCore::RequestID &requestId,
|
||||||
bool success,
|
std::optional<QString> error) override;
|
||||||
const QString &error) override;
|
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void onToolExecutionComplete(
|
void onToolExecutionComplete(
|
||||||
|
|||||||
@@ -27,11 +27,9 @@
|
|||||||
#include "settings/GeneralSettings.hpp"
|
#include "settings/GeneralSettings.hpp"
|
||||||
#include "settings/ProviderSettings.hpp"
|
#include "settings/ProviderSettings.hpp"
|
||||||
|
|
||||||
#include <QEventLoop>
|
|
||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QNetworkReply>
|
|
||||||
|
|
||||||
namespace QodeAssist::Providers {
|
namespace QodeAssist::Providers {
|
||||||
|
|
||||||
@@ -71,43 +69,32 @@ bool MistralAIProvider::supportsModelListing() const
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<QString> MistralAIProvider::getInstalledModels(const QString &url)
|
QFuture<QList<QString>> MistralAIProvider::getInstalledModels(const QString &url)
|
||||||
{
|
{
|
||||||
QList<QString> models;
|
|
||||||
QNetworkAccessManager manager;
|
|
||||||
QNetworkRequest request(QString("%1/v1/models").arg(url));
|
QNetworkRequest request(QString("%1/v1/models").arg(url));
|
||||||
|
|
||||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||||
if (!apiKey().isEmpty()) {
|
if (!apiKey().isEmpty()) {
|
||||||
request.setRawHeader("Authorization", QString("Bearer %1").arg(apiKey()).toUtf8());
|
request.setRawHeader("Authorization", QString("Bearer %1").arg(apiKey()).toUtf8());
|
||||||
}
|
}
|
||||||
|
|
||||||
QNetworkReply *reply = manager.get(request);
|
return httpClient()->get(request).then([](const QByteArray &data) {
|
||||||
QEventLoop loop;
|
QList<QString> models;
|
||||||
QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
|
QJsonObject jsonObject = QJsonDocument::fromJson(data).object();
|
||||||
loop.exec();
|
|
||||||
|
|
||||||
if (reply->error() == QNetworkReply::NoError) {
|
|
||||||
QByteArray responseData = reply->readAll();
|
|
||||||
QJsonDocument jsonResponse = QJsonDocument::fromJson(responseData);
|
|
||||||
QJsonObject jsonObject = jsonResponse.object();
|
|
||||||
|
|
||||||
if (jsonObject.contains("data") && jsonObject["object"].toString() == "list") {
|
if (jsonObject.contains("data") && jsonObject["object"].toString() == "list") {
|
||||||
QJsonArray modelArray = jsonObject["data"].toArray();
|
QJsonArray modelArray = jsonObject["data"].toArray();
|
||||||
for (const QJsonValue &value : modelArray) {
|
for (const QJsonValue &value : modelArray) {
|
||||||
QJsonObject modelObject = value.toObject();
|
QJsonObject modelObject = value.toObject();
|
||||||
if (modelObject.contains("id")) {
|
if (modelObject.contains("id")) {
|
||||||
QString modelId = modelObject["id"].toString();
|
models.append(modelObject["id"].toString());
|
||||||
models.append(modelId);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
return models;
|
||||||
LOG_MESSAGE(QString("Error fetching Mistral AI models: %1").arg(reply->errorString()));
|
}).onFailed([](const std::exception &e) {
|
||||||
}
|
LOG_MESSAGE(QString("Error fetching Mistral AI models: %1").arg(e.what()));
|
||||||
|
return QList<QString>{};
|
||||||
reply->deleteLater();
|
});
|
||||||
return models;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<QString> MistralAIProvider::validateRequest(
|
QList<QString> MistralAIProvider::validateRequest(
|
||||||
@@ -170,13 +157,10 @@ void MistralAIProvider::sendRequest(
|
|||||||
QNetworkRequest networkRequest(url);
|
QNetworkRequest networkRequest(url);
|
||||||
prepareNetworkRequest(networkRequest);
|
prepareNetworkRequest(networkRequest);
|
||||||
|
|
||||||
LLMCore::HttpRequest
|
|
||||||
request{.networkRequest = networkRequest, .requestId = requestId, .payload = payload};
|
|
||||||
|
|
||||||
LOG_MESSAGE(
|
LOG_MESSAGE(
|
||||||
QString("MistralAIProvider: Sending request %1 to %2").arg(requestId, url.toString()));
|
QString("MistralAIProvider: Sending request %1 to %2").arg(requestId, url.toString()));
|
||||||
|
|
||||||
emit httpClient()->sendRequest(request);
|
httpClient()->postStreaming(requestId, networkRequest, payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MistralAIProvider::supportsTools() const
|
bool MistralAIProvider::supportsTools() const
|
||||||
@@ -216,11 +200,11 @@ void MistralAIProvider::onDataReceived(
|
|||||||
}
|
}
|
||||||
|
|
||||||
void MistralAIProvider::onRequestFinished(
|
void MistralAIProvider::onRequestFinished(
|
||||||
const QodeAssist::LLMCore::RequestID &requestId, bool success, const QString &error)
|
const QodeAssist::LLMCore::RequestID &requestId, std::optional<QString> error)
|
||||||
{
|
{
|
||||||
if (!success) {
|
if (error) {
|
||||||
LOG_MESSAGE(QString("MistralAIProvider request %1 failed: %2").arg(requestId, error));
|
LOG_MESSAGE(QString("MistralAIProvider request %1 failed: %2").arg(requestId, *error));
|
||||||
emit requestFailed(requestId, error);
|
emit requestFailed(requestId, *error);
|
||||||
cleanupRequest(requestId);
|
cleanupRequest(requestId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ public:
|
|||||||
LLMCore::RequestType type,
|
LLMCore::RequestType type,
|
||||||
bool isToolsEnabled,
|
bool isToolsEnabled,
|
||||||
bool isThinkingEnabled) override;
|
bool isThinkingEnabled) override;
|
||||||
QList<QString> getInstalledModels(const QString &url) override;
|
QFuture<QList<QString>> getInstalledModels(const QString &url) override;
|
||||||
QList<QString> validateRequest(const QJsonObject &request, LLMCore::TemplateType type) override;
|
QList<QString> validateRequest(const QJsonObject &request, LLMCore::TemplateType type) override;
|
||||||
QString apiKey() const override;
|
QString apiKey() const override;
|
||||||
void prepareNetworkRequest(QNetworkRequest &networkRequest) const override;
|
void prepareNetworkRequest(QNetworkRequest &networkRequest) const override;
|
||||||
@@ -61,8 +61,7 @@ public slots:
|
|||||||
const QodeAssist::LLMCore::RequestID &requestId, const QByteArray &data) override;
|
const QodeAssist::LLMCore::RequestID &requestId, const QByteArray &data) override;
|
||||||
void onRequestFinished(
|
void onRequestFinished(
|
||||||
const QodeAssist::LLMCore::RequestID &requestId,
|
const QodeAssist::LLMCore::RequestID &requestId,
|
||||||
bool success,
|
std::optional<QString> error) override;
|
||||||
const QString &error) override;
|
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void onToolExecutionComplete(
|
void onToolExecutionComplete(
|
||||||
|
|||||||
@@ -22,8 +22,6 @@
|
|||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QNetworkReply>
|
|
||||||
#include <QtCore/qeventloop.h>
|
|
||||||
|
|
||||||
#include "llmcore/ValidationUtils.hpp"
|
#include "llmcore/ValidationUtils.hpp"
|
||||||
#include "logger/Logger.hpp"
|
#include "logger/Logger.hpp"
|
||||||
@@ -147,35 +145,25 @@ void OllamaProvider::prepareRequest(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<QString> OllamaProvider::getInstalledModels(const QString &url)
|
QFuture<QList<QString>> OllamaProvider::getInstalledModels(const QString &url)
|
||||||
{
|
{
|
||||||
QList<QString> models;
|
|
||||||
QNetworkAccessManager manager;
|
|
||||||
QNetworkRequest request(QString("%1%2").arg(url, "/api/tags"));
|
QNetworkRequest request(QString("%1%2").arg(url, "/api/tags"));
|
||||||
prepareNetworkRequest(request);
|
prepareNetworkRequest(request);
|
||||||
QNetworkReply *reply = manager.get(request);
|
|
||||||
|
|
||||||
QEventLoop loop;
|
return httpClient()->get(request).then([](const QByteArray &data) {
|
||||||
QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
|
QList<QString> models;
|
||||||
loop.exec();
|
QJsonObject jsonObject = QJsonDocument::fromJson(data).object();
|
||||||
|
|
||||||
if (reply->error() == QNetworkReply::NoError) {
|
|
||||||
QByteArray responseData = reply->readAll();
|
|
||||||
QJsonDocument jsonResponse = QJsonDocument::fromJson(responseData);
|
|
||||||
QJsonObject jsonObject = jsonResponse.object();
|
|
||||||
QJsonArray modelArray = jsonObject["models"].toArray();
|
QJsonArray modelArray = jsonObject["models"].toArray();
|
||||||
|
|
||||||
for (const QJsonValue &value : modelArray) {
|
for (const QJsonValue &value : modelArray) {
|
||||||
QJsonObject modelObject = value.toObject();
|
QJsonObject modelObject = value.toObject();
|
||||||
QString modelName = modelObject["name"].toString();
|
models.append(modelObject["name"].toString());
|
||||||
models.append(modelName);
|
|
||||||
}
|
}
|
||||||
} else {
|
return models;
|
||||||
LOG_MESSAGE(QString("Error fetching models: %1").arg(reply->errorString()));
|
}).onFailed([](const std::exception &e) {
|
||||||
}
|
LOG_MESSAGE(QString("Error fetching models: %1").arg(e.what()));
|
||||||
|
return QList<QString>{};
|
||||||
reply->deleteLater();
|
});
|
||||||
return models;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<QString> OllamaProvider::validateRequest(const QJsonObject &request, LLMCore::TemplateType type)
|
QList<QString> OllamaProvider::validateRequest(const QJsonObject &request, LLMCore::TemplateType type)
|
||||||
@@ -248,12 +236,9 @@ void OllamaProvider::sendRequest(
|
|||||||
QNetworkRequest networkRequest(url);
|
QNetworkRequest networkRequest(url);
|
||||||
prepareNetworkRequest(networkRequest);
|
prepareNetworkRequest(networkRequest);
|
||||||
|
|
||||||
LLMCore::HttpRequest
|
|
||||||
request{.networkRequest = networkRequest, .requestId = requestId, .payload = payload};
|
|
||||||
|
|
||||||
LOG_MESSAGE(QString("OllamaProvider: Sending request %1 to %2").arg(requestId, url.toString()));
|
LOG_MESSAGE(QString("OllamaProvider: Sending request %1 to %2").arg(requestId, url.toString()));
|
||||||
|
|
||||||
emit httpClient()->sendRequest(request);
|
httpClient()->postStreaming(requestId, networkRequest, payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool OllamaProvider::supportsTools() const
|
bool OllamaProvider::supportsTools() const
|
||||||
@@ -312,11 +297,11 @@ void OllamaProvider::onDataReceived(
|
|||||||
}
|
}
|
||||||
|
|
||||||
void OllamaProvider::onRequestFinished(
|
void OllamaProvider::onRequestFinished(
|
||||||
const QodeAssist::LLMCore::RequestID &requestId, bool success, const QString &error)
|
const QodeAssist::LLMCore::RequestID &requestId, std::optional<QString> error)
|
||||||
{
|
{
|
||||||
if (!success) {
|
if (error) {
|
||||||
LOG_MESSAGE(QString("OllamaProvider request %1 failed: %2").arg(requestId, error));
|
LOG_MESSAGE(QString("OllamaProvider request %1 failed: %2").arg(requestId, *error));
|
||||||
emit requestFailed(requestId, error);
|
emit requestFailed(requestId, *error);
|
||||||
cleanupRequest(requestId);
|
cleanupRequest(requestId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ public:
|
|||||||
LLMCore::RequestType type,
|
LLMCore::RequestType type,
|
||||||
bool isToolsEnabled,
|
bool isToolsEnabled,
|
||||||
bool isThinkingEnabled) override;
|
bool isThinkingEnabled) override;
|
||||||
QList<QString> getInstalledModels(const QString &url) override;
|
QFuture<QList<QString>> getInstalledModels(const QString &url) override;
|
||||||
QList<QString> validateRequest(const QJsonObject &request, LLMCore::TemplateType type) override;
|
QList<QString> validateRequest(const QJsonObject &request, LLMCore::TemplateType type) override;
|
||||||
QString apiKey() const override;
|
QString apiKey() const override;
|
||||||
void prepareNetworkRequest(QNetworkRequest &networkRequest) const override;
|
void prepareNetworkRequest(QNetworkRequest &networkRequest) const override;
|
||||||
@@ -63,8 +63,7 @@ public slots:
|
|||||||
const QodeAssist::LLMCore::RequestID &requestId, const QByteArray &data) override;
|
const QodeAssist::LLMCore::RequestID &requestId, const QByteArray &data) override;
|
||||||
void onRequestFinished(
|
void onRequestFinished(
|
||||||
const QodeAssist::LLMCore::RequestID &requestId,
|
const QodeAssist::LLMCore::RequestID &requestId,
|
||||||
bool success,
|
std::optional<QString> error) override;
|
||||||
const QString &error) override;
|
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void onToolExecutionComplete(
|
void onToolExecutionComplete(
|
||||||
|
|||||||
@@ -122,9 +122,9 @@ void OpenAICompatProvider::prepareRequest(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<QString> OpenAICompatProvider::getInstalledModels(const QString &url)
|
QFuture<QList<QString>> OpenAICompatProvider::getInstalledModels(const QString &)
|
||||||
{
|
{
|
||||||
return QStringList();
|
return QtFuture::makeReadyFuture(QList<QString>{});
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<QString> OpenAICompatProvider::validateRequest(
|
QList<QString> OpenAICompatProvider::validateRequest(
|
||||||
@@ -178,13 +178,10 @@ void OpenAICompatProvider::sendRequest(
|
|||||||
QNetworkRequest networkRequest(url);
|
QNetworkRequest networkRequest(url);
|
||||||
prepareNetworkRequest(networkRequest);
|
prepareNetworkRequest(networkRequest);
|
||||||
|
|
||||||
LLMCore::HttpRequest
|
|
||||||
request{.networkRequest = networkRequest, .requestId = requestId, .payload = payload};
|
|
||||||
|
|
||||||
LOG_MESSAGE(
|
LOG_MESSAGE(
|
||||||
QString("OpenAICompatProvider: Sending request %1 to %2").arg(requestId, url.toString()));
|
QString("OpenAICompatProvider: Sending request %1 to %2").arg(requestId, url.toString()));
|
||||||
|
|
||||||
emit httpClient()->sendRequest(request);
|
httpClient()->postStreaming(requestId, networkRequest, payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool OpenAICompatProvider::supportsTools() const
|
bool OpenAICompatProvider::supportsTools() const
|
||||||
@@ -224,11 +221,11 @@ void OpenAICompatProvider::onDataReceived(
|
|||||||
}
|
}
|
||||||
|
|
||||||
void OpenAICompatProvider::onRequestFinished(
|
void OpenAICompatProvider::onRequestFinished(
|
||||||
const QodeAssist::LLMCore::RequestID &requestId, bool success, const QString &error)
|
const QodeAssist::LLMCore::RequestID &requestId, std::optional<QString> error)
|
||||||
{
|
{
|
||||||
if (!success) {
|
if (error) {
|
||||||
LOG_MESSAGE(QString("OpenAICompatProvider request %1 failed: %2").arg(requestId, error));
|
LOG_MESSAGE(QString("OpenAICompatProvider request %1 failed: %2").arg(requestId, *error));
|
||||||
emit requestFailed(requestId, error);
|
emit requestFailed(requestId, *error);
|
||||||
cleanupRequest(requestId);
|
cleanupRequest(requestId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ public:
|
|||||||
LLMCore::RequestType type,
|
LLMCore::RequestType type,
|
||||||
bool isToolsEnabled,
|
bool isToolsEnabled,
|
||||||
bool isThinkingEnabled) override;
|
bool isThinkingEnabled) override;
|
||||||
QList<QString> getInstalledModels(const QString &url) override;
|
QFuture<QList<QString>> getInstalledModels(const QString &url) override;
|
||||||
QList<QString> validateRequest(const QJsonObject &request, LLMCore::TemplateType type) override;
|
QList<QString> validateRequest(const QJsonObject &request, LLMCore::TemplateType type) override;
|
||||||
QString apiKey() const override;
|
QString apiKey() const override;
|
||||||
void prepareNetworkRequest(QNetworkRequest &networkRequest) const override;
|
void prepareNetworkRequest(QNetworkRequest &networkRequest) const override;
|
||||||
@@ -61,8 +61,7 @@ public slots:
|
|||||||
const QodeAssist::LLMCore::RequestID &requestId, const QByteArray &data) override;
|
const QodeAssist::LLMCore::RequestID &requestId, const QByteArray &data) override;
|
||||||
void onRequestFinished(
|
void onRequestFinished(
|
||||||
const QodeAssist::LLMCore::RequestID &requestId,
|
const QodeAssist::LLMCore::RequestID &requestId,
|
||||||
bool success,
|
std::optional<QString> error) override;
|
||||||
const QString &error) override;
|
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void onToolExecutionComplete(
|
void onToolExecutionComplete(
|
||||||
|
|||||||
@@ -27,11 +27,9 @@
|
|||||||
#include "settings/GeneralSettings.hpp"
|
#include "settings/GeneralSettings.hpp"
|
||||||
#include "settings/ProviderSettings.hpp"
|
#include "settings/ProviderSettings.hpp"
|
||||||
|
|
||||||
#include <QEventLoop>
|
|
||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QNetworkReply>
|
|
||||||
|
|
||||||
namespace QodeAssist::Providers {
|
namespace QodeAssist::Providers {
|
||||||
|
|
||||||
@@ -141,26 +139,17 @@ void OpenAIProvider::prepareRequest(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<QString> OpenAIProvider::getInstalledModels(const QString &url)
|
QFuture<QList<QString>> OpenAIProvider::getInstalledModels(const QString &url)
|
||||||
{
|
{
|
||||||
QList<QString> models;
|
|
||||||
QNetworkAccessManager manager;
|
|
||||||
QNetworkRequest request(QString("%1/v1/models").arg(url));
|
QNetworkRequest request(QString("%1/v1/models").arg(url));
|
||||||
|
|
||||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||||
if (!apiKey().isEmpty()) {
|
if (!apiKey().isEmpty()) {
|
||||||
request.setRawHeader("Authorization", QString("Bearer %1").arg(apiKey()).toUtf8());
|
request.setRawHeader("Authorization", QString("Bearer %1").arg(apiKey()).toUtf8());
|
||||||
}
|
}
|
||||||
|
|
||||||
QNetworkReply *reply = manager.get(request);
|
return httpClient()->get(request).then([](const QByteArray &data) {
|
||||||
QEventLoop loop;
|
QList<QString> models;
|
||||||
QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
|
QJsonObject jsonObject = QJsonDocument::fromJson(data).object();
|
||||||
loop.exec();
|
|
||||||
|
|
||||||
if (reply->error() == QNetworkReply::NoError) {
|
|
||||||
QByteArray responseData = reply->readAll();
|
|
||||||
QJsonDocument jsonResponse = QJsonDocument::fromJson(responseData);
|
|
||||||
QJsonObject jsonObject = jsonResponse.object();
|
|
||||||
|
|
||||||
if (jsonObject.contains("data")) {
|
if (jsonObject.contains("data")) {
|
||||||
QJsonArray modelArray = jsonObject["data"].toArray();
|
QJsonArray modelArray = jsonObject["data"].toArray();
|
||||||
@@ -176,12 +165,11 @@ QList<QString> OpenAIProvider::getInstalledModels(const QString &url)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
return models;
|
||||||
LOG_MESSAGE(QString("Error fetching OpenAI models: %1").arg(reply->errorString()));
|
}).onFailed([](const std::exception &e) {
|
||||||
}
|
LOG_MESSAGE(QString("Error fetching OpenAI models: %1").arg(e.what()));
|
||||||
|
return QList<QString>{};
|
||||||
reply->deleteLater();
|
});
|
||||||
return models;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<QString> OpenAIProvider::validateRequest(const QJsonObject &request, LLMCore::TemplateType type)
|
QList<QString> OpenAIProvider::validateRequest(const QJsonObject &request, LLMCore::TemplateType type)
|
||||||
@@ -235,12 +223,9 @@ void OpenAIProvider::sendRequest(
|
|||||||
QNetworkRequest networkRequest(url);
|
QNetworkRequest networkRequest(url);
|
||||||
prepareNetworkRequest(networkRequest);
|
prepareNetworkRequest(networkRequest);
|
||||||
|
|
||||||
LLMCore::HttpRequest
|
|
||||||
request{.networkRequest = networkRequest, .requestId = requestId, .payload = payload};
|
|
||||||
|
|
||||||
LOG_MESSAGE(QString("OpenAIProvider: Sending request %1 to %2").arg(requestId, url.toString()));
|
LOG_MESSAGE(QString("OpenAIProvider: Sending request %1 to %2").arg(requestId, url.toString()));
|
||||||
|
|
||||||
emit httpClient()->sendRequest(request);
|
httpClient()->postStreaming(requestId, networkRequest, payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool OpenAIProvider::supportsTools() const
|
bool OpenAIProvider::supportsTools() const
|
||||||
@@ -280,11 +265,11 @@ void OpenAIProvider::onDataReceived(
|
|||||||
}
|
}
|
||||||
|
|
||||||
void OpenAIProvider::onRequestFinished(
|
void OpenAIProvider::onRequestFinished(
|
||||||
const QodeAssist::LLMCore::RequestID &requestId, bool success, const QString &error)
|
const QodeAssist::LLMCore::RequestID &requestId, std::optional<QString> error)
|
||||||
{
|
{
|
||||||
if (!success) {
|
if (error) {
|
||||||
LOG_MESSAGE(QString("OpenAIProvider request %1 failed: %2").arg(requestId, error));
|
LOG_MESSAGE(QString("OpenAIProvider request %1 failed: %2").arg(requestId, *error));
|
||||||
emit requestFailed(requestId, error);
|
emit requestFailed(requestId, *error);
|
||||||
cleanupRequest(requestId);
|
cleanupRequest(requestId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ public:
|
|||||||
LLMCore::RequestType type,
|
LLMCore::RequestType type,
|
||||||
bool isToolsEnabled,
|
bool isToolsEnabled,
|
||||||
bool isThinkingEnabled) override;
|
bool isThinkingEnabled) override;
|
||||||
QList<QString> getInstalledModels(const QString &url) override;
|
QFuture<QList<QString>> getInstalledModels(const QString &url) override;
|
||||||
QList<QString> validateRequest(const QJsonObject &request, LLMCore::TemplateType type) override;
|
QList<QString> validateRequest(const QJsonObject &request, LLMCore::TemplateType type) override;
|
||||||
QString apiKey() const override;
|
QString apiKey() const override;
|
||||||
void prepareNetworkRequest(QNetworkRequest &networkRequest) const override;
|
void prepareNetworkRequest(QNetworkRequest &networkRequest) const override;
|
||||||
@@ -61,8 +61,7 @@ public slots:
|
|||||||
const QodeAssist::LLMCore::RequestID &requestId, const QByteArray &data) override;
|
const QodeAssist::LLMCore::RequestID &requestId, const QByteArray &data) override;
|
||||||
void onRequestFinished(
|
void onRequestFinished(
|
||||||
const QodeAssist::LLMCore::RequestID &requestId,
|
const QodeAssist::LLMCore::RequestID &requestId,
|
||||||
bool success,
|
std::optional<QString> error) override;
|
||||||
const QString &error) override;
|
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void onToolExecutionComplete(
|
void onToolExecutionComplete(
|
||||||
|
|||||||
@@ -28,11 +28,9 @@
|
|||||||
#include "settings/ProviderSettings.hpp"
|
#include "settings/ProviderSettings.hpp"
|
||||||
#include "settings/QuickRefactorSettings.hpp"
|
#include "settings/QuickRefactorSettings.hpp"
|
||||||
|
|
||||||
#include <QEventLoop>
|
|
||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QNetworkReply>
|
|
||||||
|
|
||||||
namespace QodeAssist::Providers {
|
namespace QodeAssist::Providers {
|
||||||
|
|
||||||
@@ -158,26 +156,17 @@ void OpenAIResponsesProvider::prepareRequest(
|
|||||||
request["stream"] = true;
|
request["stream"] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<QString> OpenAIResponsesProvider::getInstalledModels(const QString &url)
|
QFuture<QList<QString>> OpenAIResponsesProvider::getInstalledModels(const QString &url)
|
||||||
{
|
{
|
||||||
QList<QString> models;
|
|
||||||
QNetworkAccessManager manager;
|
|
||||||
QNetworkRequest request(QString("%1/v1/models").arg(url));
|
QNetworkRequest request(QString("%1/v1/models").arg(url));
|
||||||
|
|
||||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||||
if (!apiKey().isEmpty()) {
|
if (!apiKey().isEmpty()) {
|
||||||
request.setRawHeader("Authorization", QString("Bearer %1").arg(apiKey()).toUtf8());
|
request.setRawHeader("Authorization", QString("Bearer %1").arg(apiKey()).toUtf8());
|
||||||
}
|
}
|
||||||
|
|
||||||
QNetworkReply *reply = manager.get(request);
|
return httpClient()->get(request).then([](const QByteArray &data) {
|
||||||
QEventLoop loop;
|
QList<QString> models;
|
||||||
QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
|
const QJsonObject jsonObject = QJsonDocument::fromJson(data).object();
|
||||||
loop.exec();
|
|
||||||
|
|
||||||
if (reply->error() == QNetworkReply::NoError) {
|
|
||||||
const QByteArray responseData = reply->readAll();
|
|
||||||
const QJsonDocument jsonResponse = QJsonDocument::fromJson(responseData);
|
|
||||||
const QJsonObject jsonObject = jsonResponse.object();
|
|
||||||
|
|
||||||
if (jsonObject.contains("data")) {
|
if (jsonObject.contains("data")) {
|
||||||
const QJsonArray modelArray = jsonObject["data"].toArray();
|
const QJsonArray modelArray = jsonObject["data"].toArray();
|
||||||
@@ -200,12 +189,11 @@ QList<QString> OpenAIResponsesProvider::getInstalledModels(const QString &url)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
return models;
|
||||||
LOG_MESSAGE(QString("Error fetching OpenAI models: %1").arg(reply->errorString()));
|
}).onFailed([](const std::exception &e) {
|
||||||
}
|
LOG_MESSAGE(QString("Error fetching OpenAI models: %1").arg(e.what()));
|
||||||
|
return QList<QString>{};
|
||||||
reply->deleteLater();
|
});
|
||||||
return models;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<QString> OpenAIResponsesProvider::validateRequest(
|
QList<QString> OpenAIResponsesProvider::validateRequest(
|
||||||
@@ -280,10 +268,7 @@ void OpenAIResponsesProvider::sendRequest(
|
|||||||
QNetworkRequest networkRequest(url);
|
QNetworkRequest networkRequest(url);
|
||||||
prepareNetworkRequest(networkRequest);
|
prepareNetworkRequest(networkRequest);
|
||||||
|
|
||||||
LLMCore::HttpRequest
|
httpClient()->postStreaming(requestId, networkRequest, payload);
|
||||||
request{.networkRequest = networkRequest, .requestId = requestId, .payload = payload};
|
|
||||||
|
|
||||||
emit httpClient()->sendRequest(request);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool OpenAIResponsesProvider::supportsTools() const
|
bool OpenAIResponsesProvider::supportsTools() const
|
||||||
@@ -344,11 +329,11 @@ void OpenAIResponsesProvider::onDataReceived(
|
|||||||
}
|
}
|
||||||
|
|
||||||
void OpenAIResponsesProvider::onRequestFinished(
|
void OpenAIResponsesProvider::onRequestFinished(
|
||||||
const QodeAssist::LLMCore::RequestID &requestId, bool success, const QString &error)
|
const QodeAssist::LLMCore::RequestID &requestId, std::optional<QString> error)
|
||||||
{
|
{
|
||||||
if (!success) {
|
if (error) {
|
||||||
LOG_MESSAGE(QString("OpenAIResponses request %1 failed: %2").arg(requestId, error));
|
LOG_MESSAGE(QString("OpenAIResponses request %1 failed: %2").arg(requestId, *error));
|
||||||
emit requestFailed(requestId, error);
|
emit requestFailed(requestId, *error);
|
||||||
cleanupRequest(requestId);
|
cleanupRequest(requestId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ public:
|
|||||||
LLMCore::RequestType type,
|
LLMCore::RequestType type,
|
||||||
bool isToolsEnabled,
|
bool isToolsEnabled,
|
||||||
bool isThinkingEnabled) override;
|
bool isThinkingEnabled) override;
|
||||||
QList<QString> getInstalledModels(const QString &url) override;
|
QFuture<QList<QString>> getInstalledModels(const QString &url) override;
|
||||||
QList<QString> validateRequest(const QJsonObject &request, LLMCore::TemplateType type) override;
|
QList<QString> validateRequest(const QJsonObject &request, LLMCore::TemplateType type) override;
|
||||||
QString apiKey() const override;
|
QString apiKey() const override;
|
||||||
void prepareNetworkRequest(QNetworkRequest &networkRequest) const override;
|
void prepareNetworkRequest(QNetworkRequest &networkRequest) const override;
|
||||||
@@ -62,8 +62,7 @@ public slots:
|
|||||||
const QodeAssist::LLMCore::RequestID &requestId, const QByteArray &data) override;
|
const QodeAssist::LLMCore::RequestID &requestId, const QByteArray &data) override;
|
||||||
void onRequestFinished(
|
void onRequestFinished(
|
||||||
const QodeAssist::LLMCore::RequestID &requestId,
|
const QodeAssist::LLMCore::RequestID &requestId,
|
||||||
bool success,
|
std::optional<QString> error) override;
|
||||||
const QString &error) override;
|
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void onToolExecutionComplete(
|
void onToolExecutionComplete(
|
||||||
|
|||||||
@@ -29,21 +29,24 @@
|
|||||||
|
|
||||||
namespace QodeAssist::Settings {
|
namespace QodeAssist::Settings {
|
||||||
|
|
||||||
AgentRoleDialog::AgentRoleDialog(QWidget *parent)
|
AgentRoleDialog::AgentRoleDialog(Action action, QWidget *parent)
|
||||||
: QDialog(parent)
|
: QDialog{parent}
|
||||||
, m_editMode(false)
|
, m_action{action}
|
||||||
{
|
{
|
||||||
setWindowTitle(tr("Add Agent Role"));
|
auto getTitle = [](Action action) {
|
||||||
setupUI();
|
switch(action)
|
||||||
}
|
{
|
||||||
|
case Action::Add:
|
||||||
|
return tr("Add Agent Role");
|
||||||
|
case Action::Duplicate:
|
||||||
|
return tr("Duplicate Agent Role");
|
||||||
|
case Action::Edit:
|
||||||
|
return tr("Edit Agent Role");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
AgentRoleDialog::AgentRoleDialog(const AgentRole &role, bool editMode, QWidget *parent)
|
setWindowTitle(getTitle(action));
|
||||||
: QDialog(parent)
|
|
||||||
, m_editMode(editMode)
|
|
||||||
{
|
|
||||||
setWindowTitle(editMode ? tr("Edit Agent Role") : tr("Duplicate Agent Role"));
|
|
||||||
setupUI();
|
setupUI();
|
||||||
setRole(role);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AgentRoleDialog::setupUI()
|
void AgentRoleDialog::setupUI()
|
||||||
@@ -83,7 +86,7 @@ void AgentRoleDialog::setupUI()
|
|||||||
connect(m_idEdit, &QLineEdit::textChanged, this, &AgentRoleDialog::validateInput);
|
connect(m_idEdit, &QLineEdit::textChanged, this, &AgentRoleDialog::validateInput);
|
||||||
connect(m_systemPromptEdit, &QTextEdit::textChanged, this, &AgentRoleDialog::validateInput);
|
connect(m_systemPromptEdit, &QTextEdit::textChanged, this, &AgentRoleDialog::validateInput);
|
||||||
|
|
||||||
if (m_editMode) {
|
if (m_action == Action::Edit) {
|
||||||
m_idEdit->setEnabled(false);
|
m_idEdit->setEnabled(false);
|
||||||
m_idEdit->setToolTip(tr("ID cannot be changed for existing roles"));
|
m_idEdit->setToolTip(tr("ID cannot be changed for existing roles"));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,8 +34,18 @@ class AgentRoleDialog : public QDialog
|
|||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit AgentRoleDialog(QWidget *parent = nullptr);
|
enum class Action {
|
||||||
explicit AgentRoleDialog(const AgentRole &role, bool editMode = true, QWidget *parent = nullptr);
|
Add,
|
||||||
|
Duplicate,
|
||||||
|
Edit,
|
||||||
|
};
|
||||||
|
|
||||||
|
explicit AgentRoleDialog(Action action, QWidget *parent = nullptr);
|
||||||
|
explicit AgentRoleDialog(const AgentRole &role, Action action, QWidget *parent = nullptr)
|
||||||
|
: AgentRoleDialog{action, parent}
|
||||||
|
{
|
||||||
|
setRole(role);
|
||||||
|
}
|
||||||
|
|
||||||
AgentRole getRole() const;
|
AgentRole getRole() const;
|
||||||
void setRole(const AgentRole &role);
|
void setRole(const AgentRole &role);
|
||||||
@@ -49,7 +59,7 @@ private:
|
|||||||
QTextEdit *m_descriptionEdit = nullptr;
|
QTextEdit *m_descriptionEdit = nullptr;
|
||||||
QTextEdit *m_systemPromptEdit = nullptr;
|
QTextEdit *m_systemPromptEdit = nullptr;
|
||||||
QDialogButtonBox *m_buttonBox = nullptr;
|
QDialogButtonBox *m_buttonBox = nullptr;
|
||||||
bool m_editMode = false;
|
Action m_action;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace QodeAssist::Settings
|
} // namespace QodeAssist::Settings
|
||||||
|
|||||||
@@ -34,13 +34,6 @@
|
|||||||
|
|
||||||
namespace QodeAssist::Settings {
|
namespace QodeAssist::Settings {
|
||||||
|
|
||||||
AgentRolesWidget::AgentRolesWidget(QWidget *parent)
|
|
||||||
: QWidget(parent)
|
|
||||||
{
|
|
||||||
setupUI();
|
|
||||||
loadRoles();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AgentRolesWidget::setupUI()
|
void AgentRolesWidget::setupUI()
|
||||||
{
|
{
|
||||||
auto *mainLayout = new QVBoxLayout(this);
|
auto *mainLayout = new QVBoxLayout(this);
|
||||||
@@ -129,7 +122,7 @@ void AgentRolesWidget::updateButtons()
|
|||||||
|
|
||||||
void AgentRolesWidget::onAddRole()
|
void AgentRolesWidget::onAddRole()
|
||||||
{
|
{
|
||||||
AgentRoleDialog dialog(this);
|
AgentRoleDialog dialog{AgentRoleDialog::Action::Add, this};
|
||||||
if (dialog.exec() != QDialog::Accepted)
|
if (dialog.exec() != QDialog::Accepted)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -170,7 +163,7 @@ void AgentRolesWidget::onEditRole()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
AgentRoleDialog dialog(role, this);
|
AgentRoleDialog dialog{role, AgentRoleDialog::Action::Edit, this};
|
||||||
if (dialog.exec() != QDialog::Accepted)
|
if (dialog.exec() != QDialog::Accepted)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -203,7 +196,7 @@ void AgentRolesWidget::onDuplicateRole()
|
|||||||
role.id = baseId + QString::number(counter++);
|
role.id = baseId + QString::number(counter++);
|
||||||
}
|
}
|
||||||
|
|
||||||
AgentRoleDialog dialog(role, false, this);
|
AgentRoleDialog dialog{role, AgentRoleDialog::Action::Duplicate, this};
|
||||||
if (dialog.exec() != QDialog::Accepted)
|
if (dialog.exec() != QDialog::Accepted)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|||||||
@@ -19,19 +19,23 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <QWidget>
|
#include <coreplugin/dialogs/ioptionspage.h>
|
||||||
|
|
||||||
class QListWidget;
|
class QListWidget;
|
||||||
class QPushButton;
|
class QPushButton;
|
||||||
|
|
||||||
namespace QodeAssist::Settings {
|
namespace QodeAssist::Settings {
|
||||||
|
|
||||||
class AgentRolesWidget : public QWidget
|
class AgentRolesWidget : public Core::IOptionsPageWidget
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit AgentRolesWidget(QWidget *parent = nullptr);
|
explicit AgentRolesWidget()
|
||||||
|
{
|
||||||
|
setupUI();
|
||||||
|
loadRoles();
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void setupUI();
|
void setupUI();
|
||||||
|
|||||||
@@ -50,9 +50,9 @@ QVector<AIConfiguration> ConfigurationManager::getPredefinedConfigurations(
|
|||||||
|
|
||||||
AIConfiguration claudeOpus;
|
AIConfiguration claudeOpus;
|
||||||
claudeOpus.id = "preset_claude_opus";
|
claudeOpus.id = "preset_claude_opus";
|
||||||
claudeOpus.name = "Claude Opus 4.5";
|
claudeOpus.name = "Claude Opus 4.6";
|
||||||
claudeOpus.provider = "Claude";
|
claudeOpus.provider = "Claude";
|
||||||
claudeOpus.model = "claude-opus-4-5-20251101";
|
claudeOpus.model = "claude-opus-4-6";
|
||||||
claudeOpus.url = "https://api.anthropic.com";
|
claudeOpus.url = "https://api.anthropic.com";
|
||||||
claudeOpus.endpointMode = "Auto";
|
claudeOpus.endpointMode = "Auto";
|
||||||
claudeOpus.customEndpoint = "";
|
claudeOpus.customEndpoint = "";
|
||||||
@@ -62,9 +62,9 @@ QVector<AIConfiguration> ConfigurationManager::getPredefinedConfigurations(
|
|||||||
|
|
||||||
AIConfiguration claudeSonnet;
|
AIConfiguration claudeSonnet;
|
||||||
claudeSonnet.id = "preset_claude_sonnet";
|
claudeSonnet.id = "preset_claude_sonnet";
|
||||||
claudeSonnet.name = "Claude Sonnet 4.5";
|
claudeSonnet.name = "Claude Sonnet 4.6";
|
||||||
claudeSonnet.provider = "Claude";
|
claudeSonnet.provider = "Claude";
|
||||||
claudeSonnet.model = "claude-sonnet-4-5-20250929";
|
claudeSonnet.model = "claude-sonnet-4-6";
|
||||||
claudeSonnet.url = "https://api.anthropic.com";
|
claudeSonnet.url = "https://api.anthropic.com";
|
||||||
claudeSonnet.endpointMode = "Auto";
|
claudeSonnet.endpointMode = "Auto";
|
||||||
claudeSonnet.customEndpoint = "";
|
claudeSonnet.customEndpoint = "";
|
||||||
@@ -88,7 +88,7 @@ QVector<AIConfiguration> ConfigurationManager::getPredefinedConfigurations(
|
|||||||
codestral.id = "preset_codestral";
|
codestral.id = "preset_codestral";
|
||||||
codestral.name = "Codestral";
|
codestral.name = "Codestral";
|
||||||
codestral.provider = "Codestral";
|
codestral.provider = "Codestral";
|
||||||
codestral.model = "codestral-2501";
|
codestral.model = "codestral-latest";
|
||||||
codestral.url = "https://codestral.mistral.ai";
|
codestral.url = "https://codestral.mistral.ai";
|
||||||
codestral.endpointMode = "Auto";
|
codestral.endpointMode = "Auto";
|
||||||
codestral.customEndpoint = "";
|
codestral.customEndpoint = "";
|
||||||
@@ -120,22 +120,22 @@ QVector<AIConfiguration> ConfigurationManager::getPredefinedConfigurations(
|
|||||||
geminiFlash.type = type;
|
geminiFlash.type = type;
|
||||||
geminiFlash.isPredefined = true;
|
geminiFlash.isPredefined = true;
|
||||||
|
|
||||||
AIConfiguration gpt52codex;
|
AIConfiguration gpt;
|
||||||
gpt52codex.id = "preset_gpt52codex";
|
gpt.id = "preset_gpt";
|
||||||
gpt52codex.name = "gpt-5.2-codex";
|
gpt.name = "gpt-5.4";
|
||||||
gpt52codex.provider = "OpenAI Responses";
|
gpt.provider = "OpenAI Responses";
|
||||||
gpt52codex.model = "gpt-5.2-codex";
|
gpt.model = "gpt-5.4";
|
||||||
gpt52codex.url = "https://api.openai.com";
|
gpt.url = "https://api.openai.com";
|
||||||
gpt52codex.endpointMode = "Auto";
|
gpt.endpointMode = "Auto";
|
||||||
gpt52codex.customEndpoint = "";
|
gpt.customEndpoint = "";
|
||||||
gpt52codex.templateName = "OpenAI Responses";
|
gpt.templateName = "OpenAI Responses";
|
||||||
gpt52codex.type = type;
|
gpt.type = type;
|
||||||
gpt52codex.isPredefined = true;
|
gpt.isPredefined = true;
|
||||||
|
|
||||||
presets.append(claudeSonnet);
|
presets.append(claudeSonnet);
|
||||||
presets.append(claudeHaiku);
|
presets.append(claudeHaiku);
|
||||||
presets.append(claudeOpus);
|
presets.append(claudeOpus);
|
||||||
presets.append(gpt52codex);
|
presets.append(gpt);
|
||||||
presets.append(codestral);
|
presets.append(codestral);
|
||||||
presets.append(mistral);
|
presets.append(mistral);
|
||||||
presets.append(geminiFlash);
|
presets.append(geminiFlash);
|
||||||
|
|||||||
@@ -452,7 +452,7 @@ void GeneralSettings::showModelsNotFoundDialog(Utils::StringAspect &aspect)
|
|||||||
|
|
||||||
connect(configureApiKeyBtn, &QPushButton::clicked, &dialog, [&dialog]() {
|
connect(configureApiKeyBtn, &QPushButton::clicked, &dialog, [&dialog]() {
|
||||||
dialog.close();
|
dialog.close();
|
||||||
Core::ICore::showOptionsDialog(Constants::QODE_ASSIST_PROVIDER_SETTINGS_PAGE_ID);
|
Settings::showSettings(Constants::QODE_ASSIST_PROVIDER_SETTINGS_PAGE_ID);
|
||||||
});
|
});
|
||||||
|
|
||||||
dialog.buttonLayout()->addWidget(selectProviderBtn);
|
dialog.buttonLayout()->addWidget(selectProviderBtn);
|
||||||
@@ -609,7 +609,7 @@ void GeneralSettings::setupConnections()
|
|||||||
});
|
});
|
||||||
|
|
||||||
connect(&ccConfigureApiKey, &ButtonAspect::clicked, this, []() {
|
connect(&ccConfigureApiKey, &ButtonAspect::clicked, this, []() {
|
||||||
Core::ICore::showOptionsDialog(Constants::QODE_ASSIST_PROVIDER_SETTINGS_PAGE_ID);
|
Settings::showSettings(Constants::QODE_ASSIST_PROVIDER_SETTINGS_PAGE_ID);
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(&caPresetConfig, &Utils::SelectionAspect::volatileValueChanged, this, [this]() {
|
connect(&caPresetConfig, &Utils::SelectionAspect::volatileValueChanged, this, [this]() {
|
||||||
@@ -618,7 +618,7 @@ void GeneralSettings::setupConnections()
|
|||||||
});
|
});
|
||||||
|
|
||||||
connect(&caConfigureApiKey, &ButtonAspect::clicked, this, []() {
|
connect(&caConfigureApiKey, &ButtonAspect::clicked, this, []() {
|
||||||
Core::ICore::showOptionsDialog(Constants::QODE_ASSIST_PROVIDER_SETTINGS_PAGE_ID);
|
Settings::showSettings(Constants::QODE_ASSIST_PROVIDER_SETTINGS_PAGE_ID);
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(&qrPresetConfig, &Utils::SelectionAspect::volatileValueChanged, this, [this]() {
|
connect(&qrPresetConfig, &Utils::SelectionAspect::volatileValueChanged, this, [this]() {
|
||||||
@@ -627,7 +627,7 @@ void GeneralSettings::setupConnections()
|
|||||||
});
|
});
|
||||||
|
|
||||||
connect(&qrConfigureApiKey, &ButtonAspect::clicked, this, []() {
|
connect(&qrConfigureApiKey, &ButtonAspect::clicked, this, []() {
|
||||||
Core::ICore::showOptionsDialog(Constants::QODE_ASSIST_PROVIDER_SETTINGS_PAGE_ID);
|
Settings::showSettings(Constants::QODE_ASSIST_PROVIDER_SETTINGS_PAGE_ID);
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(&specifyPreset1, &Utils::BoolAspect::volatileValueChanged, this, [this]() {
|
connect(&specifyPreset1, &Utils::BoolAspect::volatileValueChanged, this, [this]() {
|
||||||
@@ -1046,5 +1046,29 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
const GeneralSettingsPage generalSettingsPage;
|
const GeneralSettingsPage generalSettingsPage;
|
||||||
|
/*!
|
||||||
|
\sa {Core::ICore::showOptionsDialog()}, {Core::ICore::showSettings()}
|
||||||
|
\note This function was added to fix Qt Creator API broken changes in v19.0.0-beta2 version
|
||||||
|
*/
|
||||||
|
void showSettings(const Utils::Id page)
|
||||||
|
{
|
||||||
|
#if QODEASSIST_QT_CREATOR_VERSION >= QT_VERSION_CHECK(18, 0, 83)
|
||||||
|
Core::ICore::showSettings(page);
|
||||||
|
#else
|
||||||
|
Core::ICore::showOptionsDialog(page);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
/*!
|
||||||
|
\sa {Core::ICore::showOptionsDialog()}, {Core::ICore::showSettings()}
|
||||||
|
\note This function was added to fix Qt Creator API broken changes in v19.0.0-beta2 version
|
||||||
|
*/
|
||||||
|
void showSettings(const Utils::Id page, Utils::Id item)
|
||||||
|
{
|
||||||
|
#if QODEASSIST_QT_CREATOR_VERSION >= QT_VERSION_CHECK(18, 0, 83)
|
||||||
|
Core::ICore::showSettings(page, item);
|
||||||
|
#else
|
||||||
|
Core::ICore::showOptionsDialog(page, item);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace QodeAssist::Settings
|
} // namespace QodeAssist::Settings
|
||||||
|
|||||||
@@ -187,4 +187,7 @@ private:
|
|||||||
|
|
||||||
GeneralSettings &generalSettings();
|
GeneralSettings &generalSettings();
|
||||||
|
|
||||||
|
void showSettings(const Utils::Id page);
|
||||||
|
void showSettings(const Utils::Id page, Utils::Id item);
|
||||||
|
|
||||||
} // namespace QodeAssist::Settings
|
} // namespace QodeAssist::Settings
|
||||||
|
|||||||
@@ -116,6 +116,7 @@ const char CA_ALLOWED_TERMINAL_COMMANDS[] = "QodeAssist.caAllowedTerminalCommand
|
|||||||
const char CA_ALLOWED_TERMINAL_COMMANDS_LINUX[] = "QodeAssist.caAllowedTerminalCommandsLinux";
|
const char CA_ALLOWED_TERMINAL_COMMANDS_LINUX[] = "QodeAssist.caAllowedTerminalCommandsLinux";
|
||||||
const char CA_ALLOWED_TERMINAL_COMMANDS_MACOS[] = "QodeAssist.caAllowedTerminalCommandsMacOS";
|
const char CA_ALLOWED_TERMINAL_COMMANDS_MACOS[] = "QodeAssist.caAllowedTerminalCommandsMacOS";
|
||||||
const char CA_ALLOWED_TERMINAL_COMMANDS_WINDOWS[] = "QodeAssist.caAllowedTerminalCommandsWindows";
|
const char CA_ALLOWED_TERMINAL_COMMANDS_WINDOWS[] = "QodeAssist.caAllowedTerminalCommandsWindows";
|
||||||
|
const char CA_TERMINAL_COMMAND_TIMEOUT[] = "QodeAssist.caTerminalCommandTimeout";
|
||||||
|
|
||||||
const char QODE_ASSIST_GENERAL_OPTIONS_ID[] = "QodeAssist.GeneralOptions";
|
const char QODE_ASSIST_GENERAL_OPTIONS_ID[] = "QodeAssist.GeneralOptions";
|
||||||
const char QODE_ASSIST_GENERAL_SETTINGS_PAGE_ID[] = "QodeAssist.1GeneralSettingsPageId";
|
const char QODE_ASSIST_GENERAL_SETTINGS_PAGE_ID[] = "QodeAssist.1GeneralSettingsPageId";
|
||||||
|
|||||||
@@ -128,6 +128,14 @@ ToolsSettings::ToolsSettings()
|
|||||||
allowedTerminalCommandsWindows.setDisplayStyle(Utils::StringAspect::LineEditDisplay);
|
allowedTerminalCommandsWindows.setDisplayStyle(Utils::StringAspect::LineEditDisplay);
|
||||||
allowedTerminalCommandsWindows.setDefaultValue("git, dir, type, findstr, where");
|
allowedTerminalCommandsWindows.setDefaultValue("git, dir, type, findstr, where");
|
||||||
|
|
||||||
|
terminalCommandTimeout.setSettingsKey(Constants::CA_TERMINAL_COMMAND_TIMEOUT);
|
||||||
|
terminalCommandTimeout.setLabelText(Tr::tr("Command Timeout (seconds)"));
|
||||||
|
terminalCommandTimeout.setToolTip(
|
||||||
|
Tr::tr("Maximum time in seconds to wait for a terminal command to complete. "
|
||||||
|
"Increase for long-running commands like builds."));
|
||||||
|
terminalCommandTimeout.setRange(5, 3600);
|
||||||
|
terminalCommandTimeout.setDefaultValue(30);
|
||||||
|
|
||||||
resetToDefaults.m_buttonText = Tr::tr("Reset Page to Defaults");
|
resetToDefaults.m_buttonText = Tr::tr("Reset Page to Defaults");
|
||||||
|
|
||||||
readSettings();
|
readSettings();
|
||||||
@@ -167,6 +175,7 @@ ToolsSettings::ToolsSettings()
|
|||||||
enableTerminalCommandTool,
|
enableTerminalCommandTool,
|
||||||
enableTodoTool,
|
enableTodoTool,
|
||||||
currentOsCommands,
|
currentOsCommands,
|
||||||
|
terminalCommandTimeout,
|
||||||
autoApplyFileEdits}},
|
autoApplyFileEdits}},
|
||||||
Stretch{1}};
|
Stretch{1}};
|
||||||
});
|
});
|
||||||
@@ -203,6 +212,7 @@ void ToolsSettings::resetSettingsToDefaults()
|
|||||||
resetAspect(allowedTerminalCommandsLinux);
|
resetAspect(allowedTerminalCommandsLinux);
|
||||||
resetAspect(allowedTerminalCommandsMacOS);
|
resetAspect(allowedTerminalCommandsMacOS);
|
||||||
resetAspect(allowedTerminalCommandsWindows);
|
resetAspect(allowedTerminalCommandsWindows);
|
||||||
|
resetAspect(terminalCommandTimeout);
|
||||||
writeSettings();
|
writeSettings();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ public:
|
|||||||
Utils::StringAspect allowedTerminalCommandsLinux{this};
|
Utils::StringAspect allowedTerminalCommandsLinux{this};
|
||||||
Utils::StringAspect allowedTerminalCommandsMacOS{this};
|
Utils::StringAspect allowedTerminalCommandsMacOS{this};
|
||||||
Utils::StringAspect allowedTerminalCommandsWindows{this};
|
Utils::StringAspect allowedTerminalCommandsWindows{this};
|
||||||
|
Utils::IntegerAspect terminalCommandTimeout{this};
|
||||||
Utils::BoolAspect autoApplyFileEdits{this};
|
Utils::BoolAspect autoApplyFileEdits{this};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
@@ -80,7 +80,10 @@ public:
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<QString> getInstalledModels(const QString &url) override { return {}; }
|
QFuture<QList<QString>> getInstalledModels(const QString &) override
|
||||||
|
{
|
||||||
|
return QtFuture::makeReadyFuture(QList<QString>{});
|
||||||
|
}
|
||||||
|
|
||||||
QStringList validateRequest(
|
QStringList validateRequest(
|
||||||
const QJsonObject &request, LLMCore::TemplateType templateType) override
|
const QJsonObject &request, LLMCore::TemplateType templateType) override
|
||||||
|
|||||||
@@ -32,6 +32,8 @@
|
|||||||
#include <QSharedPointer>
|
#include <QSharedPointer>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
|
||||||
namespace QodeAssist::Tools {
|
namespace QodeAssist::Tools {
|
||||||
|
|
||||||
ExecuteTerminalCommandTool::ExecuteTerminalCommandTool(QObject *parent)
|
ExecuteTerminalCommandTool::ExecuteTerminalCommandTool(QObject *parent)
|
||||||
@@ -188,54 +190,66 @@ QFuture<QString> ExecuteTerminalCommandTool::executeAsync(const QJsonObject &inp
|
|||||||
QFuture<QString> future = promise->future();
|
QFuture<QString> future = promise->future();
|
||||||
promise->start();
|
promise->start();
|
||||||
|
|
||||||
|
auto resolved = std::make_shared<std::atomic<bool>>(false);
|
||||||
|
|
||||||
QProcess *process = new QProcess();
|
QProcess *process = new QProcess();
|
||||||
process->setWorkingDirectory(workingDir);
|
process->setWorkingDirectory(workingDir);
|
||||||
process->setProcessChannelMode(QProcess::MergedChannels);
|
process->setProcessChannelMode(QProcess::MergedChannels);
|
||||||
|
|
||||||
process->setReadChannel(QProcess::StandardOutput);
|
process->setReadChannel(QProcess::StandardOutput);
|
||||||
|
|
||||||
|
const int timeoutMs = commandTimeoutMs();
|
||||||
|
|
||||||
QTimer *timeoutTimer = new QTimer();
|
QTimer *timeoutTimer = new QTimer();
|
||||||
timeoutTimer->setSingleShot(true);
|
timeoutTimer->setSingleShot(true);
|
||||||
timeoutTimer->setInterval(COMMAND_TIMEOUT_MS);
|
timeoutTimer->setInterval(timeoutMs);
|
||||||
|
|
||||||
auto outputSize = QSharedPointer<qint64>::create(0);
|
QObject::connect(timeoutTimer, &QTimer::timeout, [process, promise, resolved, command, args, timeoutTimer, timeoutMs]() {
|
||||||
|
if (*resolved)
|
||||||
|
return;
|
||||||
|
*resolved = true;
|
||||||
|
|
||||||
QObject::connect(timeoutTimer, &QTimer::timeout, [process, promise, command, args, timeoutTimer]() {
|
|
||||||
LOG_MESSAGE(QString("ExecuteTerminalCommandTool: Command '%1 %2' timed out after %3ms")
|
LOG_MESSAGE(QString("ExecuteTerminalCommandTool: Command '%1 %2' timed out after %3ms")
|
||||||
.arg(command)
|
.arg(command)
|
||||||
.arg(args)
|
.arg(args)
|
||||||
.arg(COMMAND_TIMEOUT_MS));
|
.arg(timeoutMs));
|
||||||
|
|
||||||
process->terminate();
|
process->terminate();
|
||||||
|
|
||||||
QTimer::singleShot(1000, process, [process]() {
|
QTimer::singleShot(1000, process, [process]() {
|
||||||
if (process->state() == QProcess::Running) {
|
if (process->state() != QProcess::NotRunning) {
|
||||||
LOG_MESSAGE("ExecuteTerminalCommandTool: Forcefully killing process after timeout");
|
LOG_MESSAGE("ExecuteTerminalCommandTool: Forcefully killing process after timeout");
|
||||||
process->kill();
|
process->kill();
|
||||||
}
|
}
|
||||||
|
process->deleteLater();
|
||||||
});
|
});
|
||||||
|
|
||||||
promise->addResult(QString("Error: Command '%1 %2' timed out after %3 seconds. "
|
promise->addResult(QString("Error: Command '%1 %2' timed out after %3 seconds. "
|
||||||
"The process has been terminated.")
|
"The process has been terminated.")
|
||||||
.arg(command)
|
.arg(command)
|
||||||
.arg(args.isEmpty() ? "" : args)
|
.arg(args.isEmpty() ? "" : args)
|
||||||
.arg(COMMAND_TIMEOUT_MS / 1000));
|
.arg(timeoutMs / 1000));
|
||||||
promise->finish();
|
promise->finish();
|
||||||
process->deleteLater();
|
|
||||||
timeoutTimer->deleteLater();
|
timeoutTimer->deleteLater();
|
||||||
});
|
});
|
||||||
|
|
||||||
QObject::connect(
|
QObject::connect(
|
||||||
process,
|
process,
|
||||||
QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
|
QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
|
||||||
[this, process, promise, command, args, timeoutTimer, outputSize](
|
[this, process, promise, resolved, command, args, timeoutTimer](
|
||||||
int exitCode, QProcess::ExitStatus exitStatus) {
|
int exitCode, QProcess::ExitStatus exitStatus) {
|
||||||
|
if (*resolved) {
|
||||||
|
process->deleteLater();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
*resolved = true;
|
||||||
|
|
||||||
timeoutTimer->stop();
|
timeoutTimer->stop();
|
||||||
timeoutTimer->deleteLater();
|
timeoutTimer->deleteLater();
|
||||||
|
|
||||||
const QByteArray rawOutput = process->readAll();
|
const QByteArray rawOutput = process->readAll();
|
||||||
*outputSize += rawOutput.size();
|
const qint64 outputSize = rawOutput.size();
|
||||||
const QString output = sanitizeOutput(QString::fromUtf8(rawOutput), *outputSize);
|
const QString output = sanitizeOutput(QString::fromUtf8(rawOutput), outputSize);
|
||||||
|
|
||||||
const QString fullCommand = args.isEmpty() ? command : QString("%1 %2").arg(command).arg(args);
|
const QString fullCommand = args.isEmpty() ? command : QString("%1 %2").arg(command).arg(args);
|
||||||
|
|
||||||
@@ -244,7 +258,7 @@ QFuture<QString> ExecuteTerminalCommandTool::executeAsync(const QJsonObject &inp
|
|||||||
LOG_MESSAGE(QString("ExecuteTerminalCommandTool: Command '%1' completed "
|
LOG_MESSAGE(QString("ExecuteTerminalCommandTool: Command '%1' completed "
|
||||||
"successfully (output size: %2 bytes)")
|
"successfully (output size: %2 bytes)")
|
||||||
.arg(fullCommand)
|
.arg(fullCommand)
|
||||||
.arg(*outputSize));
|
.arg(outputSize));
|
||||||
promise->addResult(
|
promise->addResult(
|
||||||
QString("Command '%1' executed successfully.\n\nOutput:\n%2")
|
QString("Command '%1' executed successfully.\n\nOutput:\n%2")
|
||||||
.arg(fullCommand)
|
.arg(fullCommand)
|
||||||
@@ -254,7 +268,7 @@ QFuture<QString> ExecuteTerminalCommandTool::executeAsync(const QJsonObject &inp
|
|||||||
"exit code %2 (output size: %3 bytes)")
|
"exit code %2 (output size: %3 bytes)")
|
||||||
.arg(fullCommand)
|
.arg(fullCommand)
|
||||||
.arg(exitCode)
|
.arg(exitCode)
|
||||||
.arg(*outputSize));
|
.arg(outputSize));
|
||||||
promise->addResult(
|
promise->addResult(
|
||||||
QString("Command '%1' failed with exit code %2.\n\nOutput:\n%3")
|
QString("Command '%1' failed with exit code %2.\n\nOutput:\n%3")
|
||||||
.arg(fullCommand)
|
.arg(fullCommand)
|
||||||
@@ -265,7 +279,7 @@ QFuture<QString> ExecuteTerminalCommandTool::executeAsync(const QJsonObject &inp
|
|||||||
LOG_MESSAGE(QString("ExecuteTerminalCommandTool: Command '%1' crashed or was "
|
LOG_MESSAGE(QString("ExecuteTerminalCommandTool: Command '%1' crashed or was "
|
||||||
"terminated (output size: %2 bytes)")
|
"terminated (output size: %2 bytes)")
|
||||||
.arg(fullCommand)
|
.arg(fullCommand)
|
||||||
.arg(*outputSize));
|
.arg(outputSize));
|
||||||
const QString error = process->errorString();
|
const QString error = process->errorString();
|
||||||
promise->addResult(
|
promise->addResult(
|
||||||
QString("Command '%1' crashed or was terminated.\n\nError: %2\n\nOutput:\n%3")
|
QString("Command '%1' crashed or was terminated.\n\nError: %2\n\nOutput:\n%3")
|
||||||
@@ -278,11 +292,13 @@ QFuture<QString> ExecuteTerminalCommandTool::executeAsync(const QJsonObject &inp
|
|||||||
process->deleteLater();
|
process->deleteLater();
|
||||||
});
|
});
|
||||||
|
|
||||||
QObject::connect(process, &QProcess::errorOccurred, [process, promise, command, args, timeoutTimer](
|
QObject::connect(process, &QProcess::errorOccurred, [process, promise, resolved, command, args, timeoutTimer](
|
||||||
QProcess::ProcessError error) {
|
QProcess::ProcessError error) {
|
||||||
if (promise->future().isFinished()) {
|
if (*resolved) {
|
||||||
|
process->deleteLater();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
*resolved = true;
|
||||||
|
|
||||||
timeoutTimer->stop();
|
timeoutTimer->stop();
|
||||||
timeoutTimer->deleteLater();
|
timeoutTimer->deleteLater();
|
||||||
@@ -292,7 +308,7 @@ QFuture<QString> ExecuteTerminalCommandTool::executeAsync(const QJsonObject &inp
|
|||||||
.arg(fullCommand)
|
.arg(fullCommand)
|
||||||
.arg(error)
|
.arg(error)
|
||||||
.arg(process->errorString()));
|
.arg(process->errorString()));
|
||||||
|
|
||||||
QString errorMessage;
|
QString errorMessage;
|
||||||
switch (error) {
|
switch (error) {
|
||||||
case QProcess::FailedToStart:
|
case QProcess::FailedToStart:
|
||||||
@@ -318,71 +334,46 @@ QFuture<QString> ExecuteTerminalCommandTool::executeAsync(const QJsonObject &inp
|
|||||||
.arg(process->errorString());
|
.arg(process->errorString());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
promise->addResult(QString("Error: %1").arg(errorMessage));
|
promise->addResult(QString("Error: %1").arg(errorMessage));
|
||||||
promise->finish();
|
promise->finish();
|
||||||
process->deleteLater();
|
process->deleteLater();
|
||||||
});
|
});
|
||||||
|
|
||||||
QString fullCommand = command;
|
QStringList argsList;
|
||||||
if (!args.isEmpty()) {
|
if (!args.isEmpty()) {
|
||||||
fullCommand += " " + args;
|
argsList = QProcess::splitCommand(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
static const QStringList windowsBuiltinCommands = {
|
static const QStringList windowsBuiltinCommands = {
|
||||||
"dir", "type", "del", "copy", "move", "ren", "rename",
|
"dir", "type", "del", "copy", "move", "ren", "rename",
|
||||||
"md", "mkdir", "rd", "rmdir", "cd", "chdir", "cls", "echo",
|
"md", "mkdir", "rd", "rmdir", "cd", "chdir", "cls", "echo",
|
||||||
"set", "path", "prompt", "ver", "vol", "date", "time"
|
"set", "path", "prompt", "ver", "vol", "date", "time"
|
||||||
};
|
};
|
||||||
|
|
||||||
const QString lowerCommand = command.toLower();
|
const QString lowerCommand = command.toLower();
|
||||||
const bool isBuiltin = windowsBuiltinCommands.contains(lowerCommand);
|
const bool isBuiltin = windowsBuiltinCommands.contains(lowerCommand);
|
||||||
|
|
||||||
if (isBuiltin) {
|
if (isBuiltin) {
|
||||||
LOG_MESSAGE(QString("ExecuteTerminalCommandTool: Executing Windows builtin command '%1' via cmd.exe")
|
LOG_MESSAGE(QString("ExecuteTerminalCommandTool: Executing Windows builtin command '%1' via cmd.exe")
|
||||||
.arg(command));
|
.arg(command));
|
||||||
process->start("cmd.exe", QStringList() << "/c" << fullCommand);
|
QStringList cmdArgs;
|
||||||
|
cmdArgs << "/c" << command;
|
||||||
|
cmdArgs.append(argsList);
|
||||||
|
process->start("cmd.exe", cmdArgs);
|
||||||
} else {
|
} else {
|
||||||
#endif
|
process->start(command, argsList);
|
||||||
QStringList splitCommand = QProcess::splitCommand(fullCommand);
|
|
||||||
if (splitCommand.isEmpty()) {
|
|
||||||
LOG_MESSAGE("ExecuteTerminalCommandTool: Failed to parse command");
|
|
||||||
promise->addResult(QString("Error: Failed to parse command '%1'").arg(fullCommand));
|
|
||||||
promise->finish();
|
|
||||||
process->deleteLater();
|
|
||||||
timeoutTimer->deleteLater();
|
|
||||||
return future;
|
|
||||||
}
|
|
||||||
|
|
||||||
const QString program = splitCommand.takeFirst();
|
|
||||||
process->start(program, splitCommand);
|
|
||||||
#ifdef Q_OS_WIN
|
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
process->start(command, argsList);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (!process->waitForStarted(PROCESS_START_TIMEOUT_MS)) {
|
|
||||||
LOG_MESSAGE(QString("ExecuteTerminalCommandTool: Failed to start command '%1' within %2ms")
|
|
||||||
.arg(fullCommand)
|
|
||||||
.arg(PROCESS_START_TIMEOUT_MS));
|
|
||||||
const QString errorString = process->errorString();
|
|
||||||
promise->addResult(QString("Error: Failed to start command '%1': %2\n\n"
|
|
||||||
"Possible reasons:\n"
|
|
||||||
"- Command not found in PATH\n"
|
|
||||||
"- Insufficient permissions\n"
|
|
||||||
"- Invalid command syntax")
|
|
||||||
.arg(fullCommand)
|
|
||||||
.arg(errorString));
|
|
||||||
promise->finish();
|
|
||||||
process->deleteLater();
|
|
||||||
timeoutTimer->deleteLater();
|
|
||||||
return future;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_MESSAGE(QString("ExecuteTerminalCommandTool: Process started successfully (PID: %1)")
|
|
||||||
.arg(process->processId()));
|
|
||||||
|
|
||||||
timeoutTimer->start();
|
timeoutTimer->start();
|
||||||
|
|
||||||
|
LOG_MESSAGE(QString("ExecuteTerminalCommandTool: Process start requested for '%1'")
|
||||||
|
.arg(command));
|
||||||
|
|
||||||
return future;
|
return future;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -414,19 +405,27 @@ bool ExecuteTerminalCommandTool::areArgumentsSafe(const QString &args) const
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for null bytes
|
||||||
|
if (args.contains(QChar('\0'))) {
|
||||||
|
LOG_MESSAGE("ExecuteTerminalCommandTool: Null byte found in args");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
static const QStringList dangerousPatterns = {
|
static const QStringList dangerousPatterns = {
|
||||||
";", // Command separator
|
";", // Command separator
|
||||||
"&&", // AND operator
|
"&", // Command separator / background execution
|
||||||
"||", // OR operator
|
|
||||||
"|", // Pipe operator
|
"|", // Pipe operator
|
||||||
">", // Output redirection
|
">", // Output redirection
|
||||||
">>", // Append redirection
|
|
||||||
"<", // Input redirection
|
"<", // Input redirection
|
||||||
"`", // Command substitution
|
"`", // Command substitution
|
||||||
"$(", // Command substitution
|
"$(", // Command substitution
|
||||||
"$()", // Command substitution
|
"${", // Variable expansion
|
||||||
"\\n", // Newline (could start new command)
|
"\n", // Newline (could start new command)
|
||||||
"\\r" // Carriage return
|
"\r", // Carriage return
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
"^", // Escape character in cmd.exe (can bypass other checks)
|
||||||
|
"%", // Environment variable expansion on Windows
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const QString &pattern : dangerousPatterns) {
|
for (const QString &pattern : dangerousPatterns) {
|
||||||
@@ -456,9 +455,6 @@ QString ExecuteTerminalCommandTool::sanitizeOutput(const QString &output, qint64
|
|||||||
|
|
||||||
QStringList ExecuteTerminalCommandTool::getAllowedCommands() const
|
QStringList ExecuteTerminalCommandTool::getAllowedCommands() const
|
||||||
{
|
{
|
||||||
static QString cachedCommandsStr;
|
|
||||||
static QStringList cachedCommands;
|
|
||||||
|
|
||||||
QString commandsStr;
|
QString commandsStr;
|
||||||
|
|
||||||
#ifdef Q_OS_LINUX
|
#ifdef Q_OS_LINUX
|
||||||
@@ -471,28 +467,27 @@ QStringList ExecuteTerminalCommandTool::getAllowedCommands() const
|
|||||||
commandsStr = Settings::toolsSettings().allowedTerminalCommandsLinux().trimmed(); // fallback
|
commandsStr = Settings::toolsSettings().allowedTerminalCommandsLinux().trimmed(); // fallback
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (commandsStr == cachedCommandsStr && !cachedCommands.isEmpty()) {
|
|
||||||
return cachedCommands;
|
|
||||||
}
|
|
||||||
|
|
||||||
cachedCommandsStr = commandsStr;
|
|
||||||
cachedCommands.clear();
|
|
||||||
|
|
||||||
if (commandsStr.isEmpty()) {
|
if (commandsStr.isEmpty()) {
|
||||||
return QStringList();
|
return QStringList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QStringList result;
|
||||||
const QStringList rawCommands = commandsStr.split(',', Qt::SkipEmptyParts);
|
const QStringList rawCommands = commandsStr.split(',', Qt::SkipEmptyParts);
|
||||||
cachedCommands.reserve(rawCommands.size());
|
result.reserve(rawCommands.size());
|
||||||
|
|
||||||
for (const QString &cmd : rawCommands) {
|
for (const QString &cmd : rawCommands) {
|
||||||
const QString trimmed = cmd.trimmed();
|
const QString trimmed = cmd.trimmed();
|
||||||
if (!trimmed.isEmpty()) {
|
if (!trimmed.isEmpty()) {
|
||||||
cachedCommands.append(trimmed);
|
result.append(trimmed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return cachedCommands;
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ExecuteTerminalCommandTool::commandTimeoutMs() const
|
||||||
|
{
|
||||||
|
return Settings::toolsSettings().terminalCommandTimeout() * 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString ExecuteTerminalCommandTool::getCommandDescription() const
|
QString ExecuteTerminalCommandTool::getCommandDescription() const
|
||||||
@@ -518,7 +513,7 @@ QString ExecuteTerminalCommandTool::getCommandDescription() const
|
|||||||
"Commands have a %2 second timeout. "
|
"Commands have a %2 second timeout. "
|
||||||
"Returns the command output (stdout and stderr) or an error message if the command fails.%3")
|
"Returns the command output (stdout and stderr) or an error message if the command fails.%3")
|
||||||
.arg(allowedList)
|
.arg(allowedList)
|
||||||
.arg(COMMAND_TIMEOUT_MS / 1000)
|
.arg(commandTimeoutMs() / 1000)
|
||||||
.arg(osInfo);
|
.arg(osInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -46,12 +46,12 @@ private:
|
|||||||
QString getCommandDescription() const;
|
QString getCommandDescription() const;
|
||||||
QString sanitizeOutput(const QString &output, qint64 maxSize) const;
|
QString sanitizeOutput(const QString &output, qint64 maxSize) const;
|
||||||
|
|
||||||
|
int commandTimeoutMs() const;
|
||||||
|
|
||||||
// Constants for production safety
|
// Constants for production safety
|
||||||
static constexpr int COMMAND_TIMEOUT_MS = 30000; // 30 seconds
|
|
||||||
static constexpr qint64 MAX_OUTPUT_SIZE = 10 * 1024 * 1024; // 10 MB
|
static constexpr qint64 MAX_OUTPUT_SIZE = 10 * 1024 * 1024; // 10 MB
|
||||||
static constexpr int MAX_COMMAND_LENGTH = 1024;
|
static constexpr int MAX_COMMAND_LENGTH = 1024;
|
||||||
static constexpr int MAX_ARGS_LENGTH = 4096;
|
static constexpr int MAX_ARGS_LENGTH = 4096;
|
||||||
static constexpr int PROCESS_START_TIMEOUT_MS = 3000;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace QodeAssist::Tools
|
} // namespace QodeAssist::Tools
|
||||||
|
|||||||
@@ -589,7 +589,7 @@ void QuickRefactorDialog::onOpenInstructionsFolder()
|
|||||||
|
|
||||||
void QuickRefactorDialog::onOpenSettings()
|
void QuickRefactorDialog::onOpenSettings()
|
||||||
{
|
{
|
||||||
Core::ICore::showOptionsDialog(Constants::QODE_ASSIST_QUICK_REFACTOR_SETTINGS_PAGE_ID);
|
Settings::showSettings(Constants::QODE_ASSIST_QUICK_REFACTOR_SETTINGS_PAGE_ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString QuickRefactorDialog::selectedConfiguration() const
|
QString QuickRefactorDialog::selectedConfiguration() const
|
||||||
|
|||||||
Reference in New Issue
Block a user