From ec1b5bdf5ff54fbe4bb5e04470f4976b430cdee1 Mon Sep 17 00:00:00 2001
From: Petr Mironychev <9195189+Palm1r@users.noreply.github.com>
Date: Wed, 17 Sep 2025 19:38:27 +0200
Subject: [PATCH] refactor: Remove non-streaming support (#229)
---
ChatView/ClientInterface.cpp | 6 +-
LLMClientInterface.cpp | 5 +-
QuickRefactorHandler.cpp | 3 +-
llmcore/CMakeLists.txt | 2 +
llmcore/DataBuffers.hpp | 39 ++++++
llmcore/Provider.cpp | 21 ++-
llmcore/Provider.hpp | 9 +-
llmcore/RequestType.hpp | 4 +
llmcore/SSEBuffer.cpp | 51 +++++++
llmcore/SSEBuffer.hpp | 42 ++++++
providers/ClaudeProvider.cpp | 61 ++++----
providers/ClaudeProvider.hpp | 3 -
providers/GoogleAIProvider.cpp | 210 ++++++----------------------
providers/GoogleAIProvider.hpp | 6 +-
providers/LMStudioProvider.cpp | 47 ++++---
providers/LMStudioProvider.hpp | 3 -
providers/LlamaCppProvider.cpp | 49 ++++---
providers/LlamaCppProvider.hpp | 3 -
providers/MistralAIProvider.cpp | 47 ++++---
providers/MistralAIProvider.hpp | 3 -
providers/OllamaProvider.cpp | 35 +++--
providers/OllamaProvider.hpp | 3 -
providers/OpenAICompatProvider.cpp | 49 +++----
providers/OpenAICompatProvider.hpp | 3 -
providers/OpenAIProvider.cpp | 47 ++++---
providers/OpenAIProvider.hpp | 3 -
providers/OpenRouterAIProvider.cpp | 46 +++---
providers/OpenRouterAIProvider.hpp | 4 -
settings/ChatAssistantSettings.cpp | 6 -
settings/ChatAssistantSettings.hpp | 1 -
settings/CodeCompletionSettings.cpp | 6 -
settings/CodeCompletionSettings.hpp | 1 -
settings/SettingsConstants.hpp | 2 -
33 files changed, 412 insertions(+), 408 deletions(-)
create mode 100644 llmcore/DataBuffers.hpp
create mode 100644 llmcore/SSEBuffer.cpp
create mode 100644 llmcore/SSEBuffer.hpp
diff --git a/ChatView/ClientInterface.cpp b/ChatView/ClientInterface.cpp
index 4e8268c..64cb974 100644
--- a/ChatView/ClientInterface.cpp
+++ b/ChatView/ClientInterface.cpp
@@ -98,8 +98,7 @@ void ClientInterface::sendMessage(
config.provider = provider;
config.promptTemplate = promptTemplate;
if (provider->providerID() == LLMCore::ProviderID::GoogleAI) {
- QString stream = chatAssistantSettings.stream() ? QString{"streamGenerateContent?alt=sse"}
- : QString{"generateContent?"};
+ QString stream = QString{"streamGenerateContent?alt=sse"};
config.url = QUrl(QString("%1/models/%2:%3")
.arg(
Settings::generalSettings().caUrl(),
@@ -109,8 +108,7 @@ void ClientInterface::sendMessage(
config.url
= QString("%1%2").arg(Settings::generalSettings().caUrl(), provider->chatEndpoint());
config.providerRequest
- = {{"model", Settings::generalSettings().caModel()},
- {"stream", chatAssistantSettings.stream()}};
+ = {{"model", Settings::generalSettings().caModel()}, {"stream", true}};
}
config.apiKey = provider->apiKey();
diff --git a/LLMClientInterface.cpp b/LLMClientInterface.cpp
index ab8f9ef..92fae6d 100644
--- a/LLMClientInterface.cpp
+++ b/LLMClientInterface.cpp
@@ -224,13 +224,12 @@ void LLMClientInterface::handleCompletion(const QJsonObject &request)
config.promptTemplate = promptTemplate;
// TODO refactor networking
if (provider->providerID() == LLMCore::ProviderID::GoogleAI) {
- QString stream = m_completeSettings.stream() ? QString{"streamGenerateContent?alt=sse"}
- : QString{"generateContent?"};
+ QString stream = QString{"streamGenerateContent?alt=sse"};
config.url = QUrl(QString("%1/models/%2:%3").arg(url, modelName, stream));
} else {
config.url = QUrl(
QString("%1%2").arg(url, endpoint(provider, promptTemplate->type(), isPreset1Active)));
- config.providerRequest = {{"model", modelName}, {"stream", m_completeSettings.stream()}};
+ config.providerRequest = {{"model", modelName}, {"stream", true}};
}
config.apiKey = provider->apiKey();
config.multiLineCompletion = m_completeSettings.multiLineCompletion();
diff --git a/QuickRefactorHandler.cpp b/QuickRefactorHandler.cpp
index 8bd1039..ae8cda0 100644
--- a/QuickRefactorHandler.cpp
+++ b/QuickRefactorHandler.cpp
@@ -138,8 +138,7 @@ void QuickRefactorHandler::prepareAndSendRequest(
config.provider = provider;
config.promptTemplate = promptTemplate;
config.url = QString("%1%2").arg(settings.caUrl(), provider->chatEndpoint());
- config.providerRequest
- = {{"model", settings.caModel()}, {"stream", Settings::chatAssistantSettings().stream()}};
+ config.providerRequest = {{"model", settings.caModel()}, {"stream", true}};
config.apiKey = provider->apiKey();
LLMCore::ContextData context = prepareContext(editor, range, instructions);
diff --git a/llmcore/CMakeLists.txt b/llmcore/CMakeLists.txt
index 66cba53..fdce389 100644
--- a/llmcore/CMakeLists.txt
+++ b/llmcore/CMakeLists.txt
@@ -15,6 +15,8 @@ add_library(LLMCore STATIC
ValidationUtils.hpp ValidationUtils.cpp
ProviderID.hpp
HttpClient.hpp HttpClient.cpp
+ DataBuffers.hpp
+ SSEBuffer.hpp SSEBuffer.cpp
)
target_link_libraries(LLMCore
diff --git a/llmcore/DataBuffers.hpp b/llmcore/DataBuffers.hpp
new file mode 100644
index 0000000..4ab5dee
--- /dev/null
+++ b/llmcore/DataBuffers.hpp
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2025 Petr Mironychev
+ *
+ * This file is part of QodeAssist.
+ *
+ * QodeAssist is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * QodeAssist is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with QodeAssist. If not, see .
+ */
+
+#pragma once
+
+#include "SSEBuffer.hpp"
+#include
+
+namespace QodeAssist::LLMCore {
+
+struct DataBuffers
+{
+ SSEBuffer rawStreamBuffer;
+ QString responseContent;
+
+ void clear()
+ {
+ rawStreamBuffer.clear();
+ responseContent.clear();
+ }
+};
+
+} // namespace QodeAssist::LLMCore
diff --git a/llmcore/Provider.cpp b/llmcore/Provider.cpp
index f9d8e0f..897875e 100644
--- a/llmcore/Provider.cpp
+++ b/llmcore/Provider.cpp
@@ -1,18 +1,31 @@
#include "Provider.hpp"
+#include
+
namespace QodeAssist::LLMCore {
Provider::Provider(QObject *parent)
: QObject(parent)
- , m_httpClient(std::make_unique())
+ , m_httpClient(new HttpClient(this))
{
- connect(m_httpClient.get(), &HttpClient::dataReceived, this, &Provider::onDataReceived);
- connect(m_httpClient.get(), &HttpClient::requestFinished, this, &Provider::onRequestFinished);
+ connect(m_httpClient, &HttpClient::dataReceived, this, &Provider::onDataReceived);
+ connect(m_httpClient, &HttpClient::requestFinished, this, &Provider::onRequestFinished);
}
HttpClient *Provider::httpClient() const
{
- return m_httpClient.get();
+ return m_httpClient;
+}
+
+QJsonObject Provider::parseEventLine(const QString &line)
+{
+ if (!line.startsWith("data: "))
+ return QJsonObject();
+
+ QString jsonStr = line.mid(6);
+
+ QJsonDocument doc = QJsonDocument::fromJson(jsonStr.toUtf8());
+ return doc.object();
}
} // namespace QodeAssist::LLMCore
diff --git a/llmcore/Provider.hpp b/llmcore/Provider.hpp
index 7207c47..5a213cc 100644
--- a/llmcore/Provider.hpp
+++ b/llmcore/Provider.hpp
@@ -25,6 +25,7 @@
#include
#include "ContextData.hpp"
+#include "DataBuffers.hpp"
#include "HttpClient.hpp"
#include "PromptTemplate.hpp"
#include "RequestType.hpp"
@@ -73,8 +74,14 @@ signals:
void fullResponseReceived(const QString &requestId, const QString &fullText);
void requestFailed(const QString &requestId, const QString &error);
+protected:
+ QJsonObject parseEventLine(const QString &line);
+
+ QHash m_dataBuffers;
+ QHash m_requestUrls;
+
private:
- std::unique_ptr m_httpClient;
+ HttpClient *m_httpClient;
};
} // namespace QodeAssist::LLMCore
diff --git a/llmcore/RequestType.hpp b/llmcore/RequestType.hpp
index 991eca7..ab77c54 100644
--- a/llmcore/RequestType.hpp
+++ b/llmcore/RequestType.hpp
@@ -17,9 +17,13 @@
* along with QodeAssist. If not, see .
*/
+#include
+
#pragma once
namespace QodeAssist::LLMCore {
enum RequestType { CodeCompletion, Chat, Embedding };
+
+using RequestID = QString;
}
diff --git a/llmcore/SSEBuffer.cpp b/llmcore/SSEBuffer.cpp
new file mode 100644
index 0000000..bc53c29
--- /dev/null
+++ b/llmcore/SSEBuffer.cpp
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2025 Petr Mironychev
+ *
+ * This file is part of QodeAssist.
+ *
+ * QodeAssist is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * QodeAssist is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with QodeAssist. If not, see .
+ */
+
+#include "SSEBuffer.hpp"
+
+namespace QodeAssist::LLMCore {
+
+QStringList SSEBuffer::processData(const QByteArray &data)
+{
+ m_buffer += QString::fromUtf8(data);
+
+ QStringList lines = m_buffer.split('\n');
+ m_buffer = lines.takeLast();
+
+ lines.removeAll(QString());
+
+ return lines;
+}
+
+void SSEBuffer::clear()
+{
+ m_buffer.clear();
+}
+
+QString SSEBuffer::currentBuffer() const
+{
+ return m_buffer;
+}
+
+bool SSEBuffer::hasIncompleteData() const
+{
+ return !m_buffer.isEmpty();
+}
+
+} // namespace QodeAssist::LLMCore
diff --git a/llmcore/SSEBuffer.hpp b/llmcore/SSEBuffer.hpp
new file mode 100644
index 0000000..1f05572
--- /dev/null
+++ b/llmcore/SSEBuffer.hpp
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2025 Petr Mironychev
+ *
+ * This file is part of QodeAssist.
+ *
+ * QodeAssist is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * QodeAssist is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with QodeAssist. If not, see .
+ */
+
+#pragma once
+
+#include
+#include
+
+namespace QodeAssist::LLMCore {
+
+class SSEBuffer
+{
+public:
+ SSEBuffer() = default;
+
+ QStringList processData(const QByteArray &data);
+
+ void clear();
+ QString currentBuffer() const;
+ bool hasIncompleteData() const;
+
+private:
+ QString m_buffer;
+};
+
+} // namespace QodeAssist::LLMCore
diff --git a/providers/ClaudeProvider.cpp b/providers/ClaudeProvider.cpp
index 4650f5b..ca444c9 100644
--- a/providers/ClaudeProvider.cpp
+++ b/providers/ClaudeProvider.cpp
@@ -174,6 +174,9 @@ LLMCore::ProviderID ClaudeProvider::providerID() const
void ClaudeProvider::sendRequest(
const QString &requestId, const QUrl &url, const QJsonObject &payload)
{
+ m_dataBuffers[requestId].clear();
+ m_requestUrls[requestId] = url;
+
QNetworkRequest networkRequest(url);
prepareNetworkRequest(networkRequest);
@@ -187,50 +190,49 @@ void ClaudeProvider::sendRequest(
void ClaudeProvider::onDataReceived(const QString &requestId, const QByteArray &data)
{
- QString &accumulatedResponse = m_accumulatedResponses[requestId];
+ LLMCore::DataBuffers &buffers = m_dataBuffers[requestId];
+ QStringList lines = buffers.rawStreamBuffer.processData(data);
+
QString tempResponse;
bool isComplete = false;
- QByteArrayList lines = data.split('\n');
- for (const QByteArray &line : lines) {
- QByteArray trimmedLine = line.trimmed();
- if (trimmedLine.isEmpty())
+ for (const QString &line : lines) {
+ QJsonObject responseObj = parseEventLine(line);
+ if (responseObj.isEmpty())
continue;
- if (!trimmedLine.startsWith("data:"))
- continue;
- trimmedLine = trimmedLine.mid(6);
-
- QJsonDocument jsonResponse = QJsonDocument::fromJson(trimmedLine);
- if (jsonResponse.isNull())
- continue;
-
- QJsonObject responseObj = jsonResponse.object();
QString eventType = responseObj["type"].toString();
- if (eventType == "message_delta") {
- if (responseObj.contains("delta")) {
- QJsonObject delta = responseObj["delta"].toObject();
- if (delta.contains("stop_reason")) {
- isComplete = true;
- }
- }
+ if (eventType == "message_start") {
+ QString messageId = responseObj["message"].toObject()["id"].toString();
+ LOG_MESSAGE(QString("Claude message started: %1").arg(messageId));
+
} else if (eventType == "content_block_delta") {
QJsonObject delta = responseObj["delta"].toObject();
if (delta["type"].toString() == "text_delta") {
tempResponse += delta["text"].toString();
}
+
+ } else if (eventType == "message_delta") {
+ QJsonObject delta = responseObj["delta"].toObject();
+ if (delta.contains("stop_reason")) {
+ isComplete = true;
+ QJsonObject usage = responseObj["usage"].toObject();
+ LOG_MESSAGE(QString("Tokens: input=%1, output=%2")
+ .arg(usage["input_tokens"].toInt())
+ .arg(usage["output_tokens"].toInt()));
+ }
}
}
if (!tempResponse.isEmpty()) {
- accumulatedResponse += tempResponse;
+ buffers.responseContent += tempResponse;
emit partialResponseReceived(requestId, tempResponse);
}
if (isComplete) {
- emit fullResponseReceived(requestId, accumulatedResponse);
- m_accumulatedResponses.remove(requestId);
+ emit fullResponseReceived(requestId, buffers.responseContent);
+ m_dataBuffers.remove(requestId);
}
}
@@ -240,15 +242,16 @@ void ClaudeProvider::onRequestFinished(const QString &requestId, bool success, c
LOG_MESSAGE(QString("ClaudeProvider request %1 failed: %2").arg(requestId, error));
emit requestFailed(requestId, error);
} else {
- if (m_accumulatedResponses.contains(requestId)) {
- const QString fullResponse = m_accumulatedResponses[requestId];
- if (!fullResponse.isEmpty()) {
- emit fullResponseReceived(requestId, fullResponse);
+ if (m_dataBuffers.contains(requestId)) {
+ const LLMCore::DataBuffers &buffers = m_dataBuffers[requestId];
+ if (!buffers.responseContent.isEmpty()) {
+ emit fullResponseReceived(requestId, buffers.responseContent);
}
}
}
- m_accumulatedResponses.remove(requestId);
+ m_dataBuffers.remove(requestId);
+ m_requestUrls.remove(requestId);
}
} // namespace QodeAssist::Providers
diff --git a/providers/ClaudeProvider.hpp b/providers/ClaudeProvider.hpp
index 465e7c1..686fdc4 100644
--- a/providers/ClaudeProvider.hpp
+++ b/providers/ClaudeProvider.hpp
@@ -47,9 +47,6 @@ public:
public slots:
void onDataReceived(const QString &requestId, const QByteArray &data) override;
void onRequestFinished(const QString &requestId, bool success, const QString &error) override;
-
-private:
- QHash m_accumulatedResponses;
};
} // namespace QodeAssist::Providers
diff --git a/providers/GoogleAIProvider.cpp b/providers/GoogleAIProvider.cpp
index 59794eb..09dadb6 100644
--- a/providers/GoogleAIProvider.cpp
+++ b/providers/GoogleAIProvider.cpp
@@ -172,6 +172,9 @@ LLMCore::ProviderID GoogleAIProvider::providerID() const
void GoogleAIProvider::sendRequest(
const QString &requestId, const QUrl &url, const QJsonObject &payload)
{
+ m_dataBuffers[requestId].clear();
+ m_requestUrls[requestId] = url;
+
QNetworkRequest networkRequest(url);
prepareNetworkRequest(networkRequest);
@@ -186,8 +189,6 @@ void GoogleAIProvider::sendRequest(
void GoogleAIProvider::onDataReceived(const QString &requestId, const QByteArray &data)
{
- QString &accumulatedResponse = m_accumulatedResponses[requestId];
-
if (data.isEmpty()) {
return;
}
@@ -205,204 +206,85 @@ void GoogleAIProvider::onDataReceived(const QString &requestId, const QByteArray
LOG_MESSAGE(fullError);
emit requestFailed(requestId, fullError);
- m_accumulatedResponses.remove(requestId);
+ m_dataBuffers.remove(requestId);
return;
}
}
- bool isDone = false;
-
- if (data.startsWith("data: ")) {
- isDone = handleStreamResponse(requestId, data, accumulatedResponse);
- } else {
- isDone = handleRegularResponse(requestId, data, accumulatedResponse);
- }
+ bool isDone = handleStreamResponse(requestId, data);
if (isDone) {
- emit fullResponseReceived(requestId, accumulatedResponse);
- m_accumulatedResponses.remove(requestId);
+ LLMCore::DataBuffers &buffers = m_dataBuffers[requestId];
+ emit fullResponseReceived(requestId, buffers.responseContent);
+ m_dataBuffers.remove(requestId);
}
}
void GoogleAIProvider::onRequestFinished(const QString &requestId, bool success, const QString &error)
{
if (!success) {
- QString detailedError = error;
-
- if (m_accumulatedResponses.contains(requestId)) {
- const QString response = m_accumulatedResponses[requestId];
- if (!response.isEmpty()) {
- QJsonParseError parseError;
- QJsonDocument doc = QJsonDocument::fromJson(response.toUtf8(), &parseError);
- if (!doc.isNull() && doc.isObject()) {
- QJsonObject obj = doc.object();
- if (obj.contains("error")) {
- QJsonObject errorObj = obj["error"].toObject();
- QString apiError = errorObj["message"].toString();
- int errorCode = errorObj["code"].toInt();
- detailedError
- = QString("Google AI API Error %1: %2").arg(errorCode).arg(apiError);
- }
- }
- }
- }
-
- LOG_MESSAGE(QString("GoogleAIProvider request %1 failed: %2").arg(requestId, detailedError));
- emit requestFailed(requestId, detailedError);
+ LOG_MESSAGE(QString("GoogleAIProvider request %1 failed: %2").arg(requestId, error));
+ emit requestFailed(requestId, error);
} else {
- if (m_accumulatedResponses.contains(requestId)) {
- const QString fullResponse = m_accumulatedResponses[requestId];
- if (!fullResponse.isEmpty()) {
- emit fullResponseReceived(requestId, fullResponse);
+ if (m_dataBuffers.contains(requestId)) {
+ const LLMCore::DataBuffers &buffers = m_dataBuffers[requestId];
+ if (!buffers.responseContent.isEmpty()) {
+ emit fullResponseReceived(requestId, buffers.responseContent);
}
}
}
- m_accumulatedResponses.remove(requestId);
+ m_dataBuffers.remove(requestId);
+ m_requestUrls.remove(requestId);
}
-bool GoogleAIProvider::handleStreamResponse(
- const QString &requestId, const QByteArray &data, QString &accumulatedResponse)
+bool GoogleAIProvider::handleStreamResponse(const QString &requestId, const QByteArray &data)
{
- QByteArrayList lines = data.split('\n');
+ LLMCore::DataBuffers &buffers = m_dataBuffers[requestId];
+ QStringList lines = buffers.rawStreamBuffer.processData(data);
+
bool isDone = false;
+ QString tempResponse;
- for (const QByteArray &line : lines) {
- QByteArray trimmedLine = line.trimmed();
- if (trimmedLine.isEmpty()) {
+ for (const QString &line : lines) {
+ if (line.trimmed().isEmpty()) {
continue;
}
- if (trimmedLine == "data: [DONE]") {
- isDone = true;
+ QJsonObject responseObj = parseEventLine(line);
+ if (responseObj.isEmpty())
continue;
- }
- if (trimmedLine.startsWith("data: ")) {
- QByteArray jsonData = trimmedLine.mid(6);
- QJsonParseError parseError;
- QJsonDocument doc = QJsonDocument::fromJson(jsonData, &parseError);
- if (doc.isNull() || !doc.isObject()) {
- if (parseError.error != QJsonParseError::NoError) {
- LOG_MESSAGE(QString("JSON parse error in GoogleAI stream: %1")
- .arg(parseError.errorString()));
- }
- continue;
- }
-
- QJsonObject responseObj = doc.object();
-
- if (responseObj.contains("error")) {
- QJsonObject error = responseObj["error"].toObject();
- QString errorMessage = error["message"].toString();
- int errorCode = error["code"].toInt();
- QString fullError
- = QString("Google AI Stream Error %1: %2").arg(errorCode).arg(errorMessage);
-
- LOG_MESSAGE(fullError);
- emit requestFailed(requestId, fullError);
- return true;
- }
-
- if (responseObj.contains("candidates")) {
- QJsonArray candidates = responseObj["candidates"].toArray();
- if (!candidates.isEmpty()) {
- QJsonObject candidate = candidates.first().toObject();
-
- if (candidate.contains("finishReason")
- && !candidate["finishReason"].toString().isEmpty()) {
- isDone = true;
- }
-
- if (candidate.contains("content")) {
- QJsonObject content = candidate["content"].toObject();
- if (content.contains("parts")) {
- QJsonArray parts = content["parts"].toArray();
- QString partialContent;
- for (const auto &part : parts) {
- QJsonObject partObj = part.toObject();
- if (partObj.contains("text")) {
- partialContent += partObj["text"].toString();
- }
- }
- if (!partialContent.isEmpty()) {
- accumulatedResponse += partialContent;
- emit partialResponseReceived(requestId, partialContent);
+ if (responseObj.contains("candidates")) {
+ QJsonArray candidates = responseObj["candidates"].toArray();
+ for (const QJsonValue &candidate : candidates) {
+ QJsonObject candidateObj = candidate.toObject();
+ if (candidateObj.contains("content")) {
+ QJsonObject content = candidateObj["content"].toObject();
+ if (content.contains("parts")) {
+ QJsonArray parts = content["parts"].toArray();
+ for (const QJsonValue &part : parts) {
+ QJsonObject partObj = part.toObject();
+ if (partObj.contains("text")) {
+ tempResponse += partObj["text"].toString();
}
}
}
}
+
+ if (candidateObj.contains("finishReason")) {
+ isDone = true;
+ }
}
}
}
+ if (!tempResponse.isEmpty()) {
+ buffers.responseContent += tempResponse;
+ emit partialResponseReceived(requestId, tempResponse);
+ }
+
return isDone;
}
-bool GoogleAIProvider::handleRegularResponse(
- const QString &requestId, const QByteArray &data, QString &accumulatedResponse)
-{
- QJsonParseError parseError;
- QJsonDocument doc = QJsonDocument::fromJson(data, &parseError);
- if (doc.isNull() || !doc.isObject()) {
- QString error
- = QString("Invalid JSON response from Google AI API: %1").arg(parseError.errorString());
- LOG_MESSAGE(error);
- emit requestFailed(requestId, error);
- return false;
- }
-
- QJsonObject response = doc.object();
-
- if (response.contains("error")) {
- QJsonObject error = response["error"].toObject();
- QString errorMessage = error["message"].toString();
- int errorCode = error["code"].toInt();
- QString fullError = QString("Google AI API Error %1: %2").arg(errorCode).arg(errorMessage);
-
- LOG_MESSAGE(fullError);
- emit requestFailed(requestId, fullError);
- return false;
- }
-
- if (!response.contains("candidates") || response["candidates"].toArray().isEmpty()) {
- QString error = "No candidates in Google AI response";
- LOG_MESSAGE(error);
- emit requestFailed(requestId, error);
- return false;
- }
-
- QJsonObject candidate = response["candidates"].toArray().first().toObject();
- if (!candidate.contains("content")) {
- QString error = "No content in Google AI response candidate";
- LOG_MESSAGE(error);
- emit requestFailed(requestId, error);
- return false;
- }
-
- QJsonObject content = candidate["content"].toObject();
- if (!content.contains("parts")) {
- QString error = "No parts in Google AI response content";
- LOG_MESSAGE(error);
- emit requestFailed(requestId, error);
- return false;
- }
-
- QJsonArray parts = content["parts"].toArray();
- QString responseContent;
- for (const auto &part : parts) {
- QJsonObject partObj = part.toObject();
- if (partObj.contains("text")) {
- responseContent += partObj["text"].toString();
- }
- }
-
- if (!responseContent.isEmpty()) {
- accumulatedResponse += responseContent;
- emit partialResponseReceived(requestId, responseContent);
- }
-
- return true;
-}
-
} // namespace QodeAssist::Providers
diff --git a/providers/GoogleAIProvider.hpp b/providers/GoogleAIProvider.hpp
index 5af70e9..a595489 100644
--- a/providers/GoogleAIProvider.hpp
+++ b/providers/GoogleAIProvider.hpp
@@ -49,11 +49,7 @@ public slots:
void onRequestFinished(const QString &requestId, bool success, const QString &error) override;
private:
- QHash m_accumulatedResponses;
- bool handleStreamResponse(
- const QString &requestId, const QByteArray &data, QString &accumulatedResponse);
- bool handleRegularResponse(
- const QString &requestId, const QByteArray &data, QString &accumulatedResponse);
+ bool handleStreamResponse(const QString &requestId, const QByteArray &data);
};
} // namespace QodeAssist::Providers
diff --git a/providers/LMStudioProvider.cpp b/providers/LMStudioProvider.cpp
index 27b4a68..bf867ab 100644
--- a/providers/LMStudioProvider.cpp
+++ b/providers/LMStudioProvider.cpp
@@ -125,6 +125,9 @@ LLMCore::ProviderID LMStudioProvider::providerID() const
void LMStudioProvider::sendRequest(
const QString &requestId, const QUrl &url, const QJsonObject &payload)
{
+ m_dataBuffers[requestId].clear();
+ m_requestUrls[requestId] = url;
+
QNetworkRequest networkRequest(url);
prepareNetworkRequest(networkRequest);
@@ -139,16 +142,17 @@ void LMStudioProvider::sendRequest(
void LMStudioProvider::onDataReceived(const QString &requestId, const QByteArray &data)
{
- QString &accumulatedResponse = m_accumulatedResponses[requestId];
+ LLMCore::DataBuffers &buffers = m_dataBuffers[requestId];
+ QStringList lines = buffers.rawStreamBuffer.processData(data);
if (data.isEmpty()) {
return;
}
bool isDone = false;
- QByteArrayList lines = data.split('\n');
+ QString tempResponse;
- for (const QByteArray &line : lines) {
+ for (const QString &line : lines) {
if (line.trimmed().isEmpty()) {
continue;
}
@@ -158,19 +162,11 @@ void LMStudioProvider::onDataReceived(const QString &requestId, const QByteArray
continue;
}
- QByteArray jsonData = line;
- if (line.startsWith("data: ")) {
- jsonData = line.mid(6);
- }
-
- QJsonParseError error;
- QJsonDocument doc = QJsonDocument::fromJson(jsonData, &error);
-
- if (doc.isNull()) {
+ QJsonObject responseObj = parseEventLine(line);
+ if (responseObj.isEmpty())
continue;
- }
- auto message = LLMCore::OpenAIMessage::fromJson(doc.object());
+ auto message = LLMCore::OpenAIMessage::fromJson(responseObj);
if (message.hasError()) {
LOG_MESSAGE("Error in LMStudio response: " + message.error);
continue;
@@ -178,8 +174,7 @@ void LMStudioProvider::onDataReceived(const QString &requestId, const QByteArray
QString content = message.getContent();
if (!content.isEmpty()) {
- accumulatedResponse += content;
- emit partialResponseReceived(requestId, content);
+ tempResponse += content;
}
if (message.isDone()) {
@@ -187,9 +182,14 @@ void LMStudioProvider::onDataReceived(const QString &requestId, const QByteArray
}
}
+ if (!tempResponse.isEmpty()) {
+ buffers.responseContent += tempResponse;
+ emit partialResponseReceived(requestId, tempResponse);
+ }
+
if (isDone) {
- emit fullResponseReceived(requestId, accumulatedResponse);
- m_accumulatedResponses.remove(requestId);
+ emit fullResponseReceived(requestId, buffers.responseContent);
+ m_dataBuffers.remove(requestId);
}
}
@@ -199,15 +199,16 @@ void LMStudioProvider::onRequestFinished(const QString &requestId, bool success,
LOG_MESSAGE(QString("LMStudioProvider request %1 failed: %2").arg(requestId, error));
emit requestFailed(requestId, error);
} else {
- if (m_accumulatedResponses.contains(requestId)) {
- const QString fullResponse = m_accumulatedResponses[requestId];
- if (!fullResponse.isEmpty()) {
- emit fullResponseReceived(requestId, fullResponse);
+ if (m_dataBuffers.contains(requestId)) {
+ const LLMCore::DataBuffers &buffers = m_dataBuffers[requestId];
+ if (!buffers.responseContent.isEmpty()) {
+ emit fullResponseReceived(requestId, buffers.responseContent);
}
}
}
- m_accumulatedResponses.remove(requestId);
+ m_dataBuffers.remove(requestId);
+ m_requestUrls.remove(requestId);
}
void QodeAssist::Providers::LMStudioProvider::prepareRequest(
diff --git a/providers/LMStudioProvider.hpp b/providers/LMStudioProvider.hpp
index d2c7900..62d096d 100644
--- a/providers/LMStudioProvider.hpp
+++ b/providers/LMStudioProvider.hpp
@@ -47,9 +47,6 @@ public:
public slots:
void onDataReceived(const QString &requestId, const QByteArray &data) override;
void onRequestFinished(const QString &requestId, bool success, const QString &error) override;
-
-private:
- QHash m_accumulatedResponses;
};
} // namespace QodeAssist::Providers
diff --git a/providers/LlamaCppProvider.cpp b/providers/LlamaCppProvider.cpp
index f6bdc00..3a415dd 100644
--- a/providers/LlamaCppProvider.cpp
+++ b/providers/LlamaCppProvider.cpp
@@ -151,6 +151,9 @@ LLMCore::ProviderID LlamaCppProvider::providerID() const
void LlamaCppProvider::sendRequest(
const QString &requestId, const QUrl &url, const QJsonObject &payload)
{
+ m_dataBuffers[requestId].clear();
+ m_requestUrls[requestId] = url;
+
QNetworkRequest networkRequest(url);
prepareNetworkRequest(networkRequest);
@@ -165,16 +168,17 @@ void LlamaCppProvider::sendRequest(
void LlamaCppProvider::onDataReceived(const QString &requestId, const QByteArray &data)
{
- QString &accumulatedResponse = m_accumulatedResponses[requestId];
+ LLMCore::DataBuffers &buffers = m_dataBuffers[requestId];
+ QStringList lines = buffers.rawStreamBuffer.processData(data);
if (data.isEmpty()) {
return;
}
bool isDone = data.contains("\"stop\":true") || data.contains("data: [DONE]");
+ QString tempResponse;
- QByteArrayList lines = data.split('\n');
- for (const QByteArray &line : lines) {
+ for (const QString &line : lines) {
if (line.trimmed().isEmpty()) {
continue;
}
@@ -184,25 +188,15 @@ void LlamaCppProvider::onDataReceived(const QString &requestId, const QByteArray
continue;
}
- QByteArray jsonData = line;
- if (line.startsWith("data: ")) {
- jsonData = line.mid(6);
- }
-
- QJsonParseError error;
- QJsonDocument doc = QJsonDocument::fromJson(jsonData, &error);
- if (doc.isNull()) {
+ QJsonObject obj = parseEventLine(line);
+ if (obj.isEmpty())
continue;
- }
-
- QJsonObject obj = doc.object();
QString content;
if (obj.contains("content")) {
content = obj["content"].toString();
if (!content.isEmpty()) {
- accumulatedResponse += content;
- emit partialResponseReceived(requestId, content);
+ tempResponse += content;
}
} else if (obj.contains("choices")) {
auto message = LLMCore::OpenAIMessage::fromJson(obj);
@@ -213,8 +207,7 @@ void LlamaCppProvider::onDataReceived(const QString &requestId, const QByteArray
content = message.getContent();
if (!content.isEmpty()) {
- accumulatedResponse += content;
- emit partialResponseReceived(requestId, content);
+ tempResponse += content;
}
if (message.isDone()) {
@@ -227,9 +220,14 @@ void LlamaCppProvider::onDataReceived(const QString &requestId, const QByteArray
}
}
+ if (!tempResponse.isEmpty()) {
+ buffers.responseContent += tempResponse;
+ emit partialResponseReceived(requestId, tempResponse);
+ }
+
if (isDone) {
- emit fullResponseReceived(requestId, accumulatedResponse);
- m_accumulatedResponses.remove(requestId);
+ emit fullResponseReceived(requestId, buffers.responseContent);
+ m_dataBuffers.remove(requestId);
}
}
@@ -239,15 +237,16 @@ void LlamaCppProvider::onRequestFinished(const QString &requestId, bool success,
LOG_MESSAGE(QString("LlamaCppProvider request %1 failed: %2").arg(requestId, error));
emit requestFailed(requestId, error);
} else {
- if (m_accumulatedResponses.contains(requestId)) {
- const QString fullResponse = m_accumulatedResponses[requestId];
- if (!fullResponse.isEmpty()) {
- emit fullResponseReceived(requestId, fullResponse);
+ if (m_dataBuffers.contains(requestId)) {
+ const LLMCore::DataBuffers &buffers = m_dataBuffers[requestId];
+ if (!buffers.responseContent.isEmpty()) {
+ emit fullResponseReceived(requestId, buffers.responseContent);
}
}
}
- m_accumulatedResponses.remove(requestId);
+ m_dataBuffers.remove(requestId);
+ m_requestUrls.remove(requestId);
}
} // namespace QodeAssist::Providers
diff --git a/providers/LlamaCppProvider.hpp b/providers/LlamaCppProvider.hpp
index 8c4425b..740454c 100644
--- a/providers/LlamaCppProvider.hpp
+++ b/providers/LlamaCppProvider.hpp
@@ -47,9 +47,6 @@ public:
public slots:
void onDataReceived(const QString &requestId, const QByteArray &data) override;
void onRequestFinished(const QString &requestId, bool success, const QString &error) override;
-
-private:
- QHash m_accumulatedResponses;
};
} // namespace QodeAssist::Providers
diff --git a/providers/MistralAIProvider.cpp b/providers/MistralAIProvider.cpp
index b9d3ab3..df766b0 100644
--- a/providers/MistralAIProvider.cpp
+++ b/providers/MistralAIProvider.cpp
@@ -128,6 +128,9 @@ LLMCore::ProviderID MistralAIProvider::providerID() const
void MistralAIProvider::sendRequest(
const QString &requestId, const QUrl &url, const QJsonObject &payload)
{
+ m_dataBuffers[requestId].clear();
+ m_requestUrls[requestId] = url;
+
QNetworkRequest networkRequest(url);
prepareNetworkRequest(networkRequest);
@@ -142,16 +145,17 @@ void MistralAIProvider::sendRequest(
void MistralAIProvider::onDataReceived(const QString &requestId, const QByteArray &data)
{
- QString &accumulatedResponse = m_accumulatedResponses[requestId];
+ LLMCore::DataBuffers &buffers = m_dataBuffers[requestId];
+ QStringList lines = buffers.rawStreamBuffer.processData(data);
if (data.isEmpty()) {
return;
}
bool isDone = false;
- QByteArrayList lines = data.split('\n');
+ QString tempResponse;
- for (const QByteArray &line : lines) {
+ for (const QString &line : lines) {
if (line.trimmed().isEmpty()) {
continue;
}
@@ -161,19 +165,11 @@ void MistralAIProvider::onDataReceived(const QString &requestId, const QByteArra
continue;
}
- QByteArray jsonData = line;
- if (line.startsWith("data: ")) {
- jsonData = line.mid(6);
- }
-
- QJsonParseError error;
- QJsonDocument doc = QJsonDocument::fromJson(jsonData, &error);
-
- if (doc.isNull()) {
+ QJsonObject responseObj = parseEventLine(line);
+ if (responseObj.isEmpty())
continue;
- }
- auto message = LLMCore::OpenAIMessage::fromJson(doc.object());
+ auto message = LLMCore::OpenAIMessage::fromJson(responseObj);
if (message.hasError()) {
LOG_MESSAGE("Error in MistralAI response: " + message.error);
continue;
@@ -181,8 +177,7 @@ void MistralAIProvider::onDataReceived(const QString &requestId, const QByteArra
QString content = message.getContent();
if (!content.isEmpty()) {
- accumulatedResponse += content;
- emit partialResponseReceived(requestId, content);
+ tempResponse += content;
}
if (message.isDone()) {
@@ -190,9 +185,14 @@ void MistralAIProvider::onDataReceived(const QString &requestId, const QByteArra
}
}
+ if (!tempResponse.isEmpty()) {
+ buffers.responseContent += tempResponse;
+ emit partialResponseReceived(requestId, tempResponse);
+ }
+
if (isDone) {
- emit fullResponseReceived(requestId, accumulatedResponse);
- m_accumulatedResponses.remove(requestId);
+ emit fullResponseReceived(requestId, buffers.responseContent);
+ m_dataBuffers.remove(requestId);
}
}
@@ -203,15 +203,16 @@ void MistralAIProvider::onRequestFinished(
LOG_MESSAGE(QString("MistralAIProvider request %1 failed: %2").arg(requestId, error));
emit requestFailed(requestId, error);
} else {
- if (m_accumulatedResponses.contains(requestId)) {
- const QString fullResponse = m_accumulatedResponses[requestId];
- if (!fullResponse.isEmpty()) {
- emit fullResponseReceived(requestId, fullResponse);
+ if (m_dataBuffers.contains(requestId)) {
+ const LLMCore::DataBuffers &buffers = m_dataBuffers[requestId];
+ if (!buffers.responseContent.isEmpty()) {
+ emit fullResponseReceived(requestId, buffers.responseContent);
}
}
}
- m_accumulatedResponses.remove(requestId);
+ m_dataBuffers.remove(requestId);
+ m_requestUrls.remove(requestId);
}
void MistralAIProvider::prepareRequest(
diff --git a/providers/MistralAIProvider.hpp b/providers/MistralAIProvider.hpp
index f338358..14cbd1b 100644
--- a/providers/MistralAIProvider.hpp
+++ b/providers/MistralAIProvider.hpp
@@ -47,9 +47,6 @@ public:
public slots:
void onDataReceived(const QString &requestId, const QByteArray &data) override;
void onRequestFinished(const QString &requestId, bool success, const QString &error) override;
-
-private:
- QHash m_accumulatedResponses;
};
} // namespace QodeAssist::Providers
diff --git a/providers/OllamaProvider.cpp b/providers/OllamaProvider.cpp
index d46dbe9..07fd75e 100644
--- a/providers/OllamaProvider.cpp
+++ b/providers/OllamaProvider.cpp
@@ -188,6 +188,9 @@ LLMCore::ProviderID OllamaProvider::providerID() const
void OllamaProvider::sendRequest(
const QString &requestId, const QUrl &url, const QJsonObject &payload)
{
+ m_dataBuffers[requestId].clear();
+ m_requestUrls[requestId] = url;
+
QNetworkRequest networkRequest(url);
prepareNetworkRequest(networkRequest);
@@ -201,22 +204,23 @@ void OllamaProvider::sendRequest(
void OllamaProvider::onDataReceived(const QString &requestId, const QByteArray &data)
{
- QString &accumulatedResponse = m_accumulatedResponses[requestId];
+ LLMCore::DataBuffers &buffers = m_dataBuffers[requestId];
+ QStringList lines = buffers.rawStreamBuffer.processData(data);
if (data.isEmpty()) {
return;
}
- QByteArrayList lines = data.split('\n');
bool isDone = false;
+ QString tempResponse;
- for (const QByteArray &line : lines) {
+ for (const QString &line : lines) {
if (line.trimmed().isEmpty()) {
continue;
}
QJsonParseError error;
- QJsonDocument doc = QJsonDocument::fromJson(line, &error);
+ QJsonDocument doc = QJsonDocument::fromJson(line.toUtf8(), &error);
if (doc.isNull()) {
continue;
}
@@ -238,8 +242,7 @@ void OllamaProvider::onDataReceived(const QString &requestId, const QByteArray &
}
if (!content.isEmpty()) {
- accumulatedResponse += content;
- emit partialResponseReceived(requestId, content);
+ tempResponse += content;
}
if (obj["done"].toBool()) {
@@ -247,9 +250,14 @@ void OllamaProvider::onDataReceived(const QString &requestId, const QByteArray &
}
}
+ if (!tempResponse.isEmpty()) {
+ buffers.responseContent += tempResponse;
+ emit partialResponseReceived(requestId, tempResponse);
+ }
+
if (isDone) {
- emit fullResponseReceived(requestId, accumulatedResponse);
- m_accumulatedResponses.remove(requestId);
+ emit fullResponseReceived(requestId, buffers.responseContent);
+ m_dataBuffers.remove(requestId);
}
}
@@ -259,15 +267,16 @@ void OllamaProvider::onRequestFinished(const QString &requestId, bool success, c
LOG_MESSAGE(QString("OllamaProvider request %1 failed: %2").arg(requestId, error));
emit requestFailed(requestId, error);
} else {
- if (m_accumulatedResponses.contains(requestId)) {
- const QString fullResponse = m_accumulatedResponses[requestId];
- if (!fullResponse.isEmpty()) {
- emit fullResponseReceived(requestId, fullResponse);
+ if (m_dataBuffers.contains(requestId)) {
+ const LLMCore::DataBuffers &buffers = m_dataBuffers[requestId];
+ if (!buffers.responseContent.isEmpty()) {
+ emit fullResponseReceived(requestId, buffers.responseContent);
}
}
}
- m_accumulatedResponses.remove(requestId);
+ m_dataBuffers.remove(requestId);
+ m_requestUrls.remove(requestId);
}
} // namespace QodeAssist::Providers
diff --git a/providers/OllamaProvider.hpp b/providers/OllamaProvider.hpp
index 3abd113..8bc7ae9 100644
--- a/providers/OllamaProvider.hpp
+++ b/providers/OllamaProvider.hpp
@@ -47,9 +47,6 @@ public:
public slots:
void onDataReceived(const QString &requestId, const QByteArray &data) override;
void onRequestFinished(const QString &requestId, bool success, const QString &error) override;
-
-private:
- QHash m_accumulatedResponses;
};
} // namespace QodeAssist::Providers
diff --git a/providers/OpenAICompatProvider.cpp b/providers/OpenAICompatProvider.cpp
index cd7ebc0..6fb85be 100644
--- a/providers/OpenAICompatProvider.cpp
+++ b/providers/OpenAICompatProvider.cpp
@@ -137,6 +137,9 @@ LLMCore::ProviderID OpenAICompatProvider::providerID() const
void OpenAICompatProvider::sendRequest(
const QString &requestId, const QUrl &url, const QJsonObject &payload)
{
+ m_dataBuffers[requestId].clear();
+ m_requestUrls[requestId] = url;
+
QNetworkRequest networkRequest(url);
prepareNetworkRequest(networkRequest);
@@ -151,16 +154,17 @@ void OpenAICompatProvider::sendRequest(
void OpenAICompatProvider::onDataReceived(const QString &requestId, const QByteArray &data)
{
- QString &accumulatedResponse = m_accumulatedResponses[requestId];
+ LLMCore::DataBuffers &buffers = m_dataBuffers[requestId];
+ QStringList lines = buffers.rawStreamBuffer.processData(data);
if (data.isEmpty()) {
return;
}
bool isDone = false;
- QByteArrayList lines = data.split('\n');
+ QString tempResponse;
- for (const QByteArray &line : lines) {
+ for (const QString &line : lines) {
if (line.trimmed().isEmpty()) {
continue;
}
@@ -170,19 +174,11 @@ void OpenAICompatProvider::onDataReceived(const QString &requestId, const QByteA
continue;
}
- QByteArray jsonData = line;
- if (line.startsWith("data: ")) {
- jsonData = line.mid(6);
- }
-
- QJsonParseError error;
- QJsonDocument doc = QJsonDocument::fromJson(jsonData, &error);
-
- if (doc.isNull()) {
+ QJsonObject responseObj = parseEventLine(line);
+ if (responseObj.isEmpty())
continue;
- }
- auto message = LLMCore::OpenAIMessage::fromJson(doc.object());
+ auto message = LLMCore::OpenAIMessage::fromJson(responseObj);
if (message.hasError()) {
LOG_MESSAGE("Error in OpenAI response: " + message.error);
continue;
@@ -190,8 +186,7 @@ void OpenAICompatProvider::onDataReceived(const QString &requestId, const QByteA
QString content = message.getContent();
if (!content.isEmpty()) {
- accumulatedResponse += content;
- emit partialResponseReceived(requestId, content);
+ tempResponse += content;
}
if (message.isDone()) {
@@ -199,9 +194,14 @@ void OpenAICompatProvider::onDataReceived(const QString &requestId, const QByteA
}
}
+ if (!tempResponse.isEmpty()) {
+ buffers.responseContent += tempResponse;
+ emit partialResponseReceived(requestId, tempResponse);
+ }
+
if (isDone) {
- emit fullResponseReceived(requestId, accumulatedResponse);
- m_accumulatedResponses.remove(requestId);
+ emit fullResponseReceived(requestId, buffers.responseContent);
+ m_dataBuffers.remove(requestId);
}
}
@@ -209,18 +209,19 @@ void OpenAICompatProvider::onRequestFinished(
const QString &requestId, bool success, const QString &error)
{
if (!success) {
- LOG_MESSAGE(QString("OpenAIProvider request %1 failed: %2").arg(requestId, error));
+ LOG_MESSAGE(QString("OpenAICompatProvider request %1 failed: %2").arg(requestId, error));
emit requestFailed(requestId, error);
} else {
- if (m_accumulatedResponses.contains(requestId)) {
- const QString fullResponse = m_accumulatedResponses[requestId];
- if (!fullResponse.isEmpty()) {
- emit fullResponseReceived(requestId, fullResponse);
+ if (m_dataBuffers.contains(requestId)) {
+ const LLMCore::DataBuffers &buffers = m_dataBuffers[requestId];
+ if (!buffers.responseContent.isEmpty()) {
+ emit fullResponseReceived(requestId, buffers.responseContent);
}
}
}
- m_accumulatedResponses.remove(requestId);
+ m_dataBuffers.remove(requestId);
+ m_requestUrls.remove(requestId);
}
} // namespace QodeAssist::Providers
diff --git a/providers/OpenAICompatProvider.hpp b/providers/OpenAICompatProvider.hpp
index 36c3cff..9ec2ade 100644
--- a/providers/OpenAICompatProvider.hpp
+++ b/providers/OpenAICompatProvider.hpp
@@ -47,9 +47,6 @@ public:
public slots:
void onDataReceived(const QString &requestId, const QByteArray &data) override;
void onRequestFinished(const QString &requestId, bool success, const QString &error) override;
-
-private:
- QHash m_accumulatedResponses;
};
} // namespace QodeAssist::Providers
diff --git a/providers/OpenAIProvider.cpp b/providers/OpenAIProvider.cpp
index f71aa37..7bde5ba 100644
--- a/providers/OpenAIProvider.cpp
+++ b/providers/OpenAIProvider.cpp
@@ -175,6 +175,9 @@ LLMCore::ProviderID OpenAIProvider::providerID() const
void OpenAIProvider::sendRequest(
const QString &requestId, const QUrl &url, const QJsonObject &payload)
{
+ m_dataBuffers[requestId].clear();
+ m_requestUrls[requestId] = url;
+
QNetworkRequest networkRequest(url);
prepareNetworkRequest(networkRequest);
@@ -188,16 +191,17 @@ void OpenAIProvider::sendRequest(
void OpenAIProvider::onDataReceived(const QString &requestId, const QByteArray &data)
{
- QString &accumulatedResponse = m_accumulatedResponses[requestId];
+ LLMCore::DataBuffers &buffers = m_dataBuffers[requestId];
+ QStringList lines = buffers.rawStreamBuffer.processData(data);
if (data.isEmpty()) {
return;
}
bool isDone = false;
- QByteArrayList lines = data.split('\n');
+ QString tempResponse;
- for (const QByteArray &line : lines) {
+ for (const QString &line : lines) {
if (line.trimmed().isEmpty()) {
continue;
}
@@ -207,19 +211,11 @@ void OpenAIProvider::onDataReceived(const QString &requestId, const QByteArray &
continue;
}
- QByteArray jsonData = line;
- if (line.startsWith("data: ")) {
- jsonData = line.mid(6);
- }
-
- QJsonParseError error;
- QJsonDocument doc = QJsonDocument::fromJson(jsonData, &error);
-
- if (doc.isNull()) {
+ QJsonObject responseObj = parseEventLine(line);
+ if (responseObj.isEmpty())
continue;
- }
- auto message = LLMCore::OpenAIMessage::fromJson(doc.object());
+ auto message = LLMCore::OpenAIMessage::fromJson(responseObj);
if (message.hasError()) {
LOG_MESSAGE("Error in OpenAI response: " + message.error);
continue;
@@ -227,8 +223,7 @@ void OpenAIProvider::onDataReceived(const QString &requestId, const QByteArray &
QString content = message.getContent();
if (!content.isEmpty()) {
- accumulatedResponse += content;
- emit partialResponseReceived(requestId, content);
+ tempResponse += content;
}
if (message.isDone()) {
@@ -236,9 +231,14 @@ void OpenAIProvider::onDataReceived(const QString &requestId, const QByteArray &
}
}
+ if (!tempResponse.isEmpty()) {
+ buffers.responseContent += tempResponse;
+ emit partialResponseReceived(requestId, tempResponse);
+ }
+
if (isDone) {
- emit fullResponseReceived(requestId, accumulatedResponse);
- m_accumulatedResponses.remove(requestId);
+ emit fullResponseReceived(requestId, buffers.responseContent);
+ m_dataBuffers.remove(requestId);
}
}
@@ -248,15 +248,16 @@ void OpenAIProvider::onRequestFinished(const QString &requestId, bool success, c
LOG_MESSAGE(QString("OpenAIProvider request %1 failed: %2").arg(requestId, error));
emit requestFailed(requestId, error);
} else {
- if (m_accumulatedResponses.contains(requestId)) {
- const QString fullResponse = m_accumulatedResponses[requestId];
- if (!fullResponse.isEmpty()) {
- emit fullResponseReceived(requestId, fullResponse);
+ if (m_dataBuffers.contains(requestId)) {
+ const LLMCore::DataBuffers &buffers = m_dataBuffers[requestId];
+ if (!buffers.responseContent.isEmpty()) {
+ emit fullResponseReceived(requestId, buffers.responseContent);
}
}
}
- m_accumulatedResponses.remove(requestId);
+ m_dataBuffers.remove(requestId);
+ m_requestUrls.remove(requestId);
}
} // namespace QodeAssist::Providers
diff --git a/providers/OpenAIProvider.hpp b/providers/OpenAIProvider.hpp
index 592300c..69919e0 100644
--- a/providers/OpenAIProvider.hpp
+++ b/providers/OpenAIProvider.hpp
@@ -47,9 +47,6 @@ public:
public slots:
void onDataReceived(const QString &requestId, const QByteArray &data) override;
void onRequestFinished(const QString &requestId, bool success, const QString &error) override;
-
-private:
- QHash m_accumulatedResponses;
};
} // namespace QodeAssist::Providers
diff --git a/providers/OpenRouterAIProvider.cpp b/providers/OpenRouterAIProvider.cpp
index e672af7..f0aecec 100644
--- a/providers/OpenRouterAIProvider.cpp
+++ b/providers/OpenRouterAIProvider.cpp
@@ -53,16 +53,17 @@ LLMCore::ProviderID OpenRouterProvider::providerID() const
void OpenRouterProvider::onDataReceived(const QString &requestId, const QByteArray &data)
{
- QString &accumulatedResponse = m_accumulatedResponses[requestId];
+ LLMCore::DataBuffers &buffers = m_dataBuffers[requestId];
+ QStringList lines = buffers.rawStreamBuffer.processData(data);
if (data.isEmpty()) {
return;
}
bool isDone = false;
- QByteArrayList lines = data.split('\n');
+ QString tempResponse;
- for (const QByteArray &line : lines) {
+ for (const QString &line : lines) {
if (line.trimmed().isEmpty() || line.contains("OPENROUTER PROCESSING")) {
continue;
}
@@ -72,28 +73,19 @@ void OpenRouterProvider::onDataReceived(const QString &requestId, const QByteArr
continue;
}
- QByteArray jsonData = line;
- if (line.startsWith("data: ")) {
- jsonData = line.mid(6);
- }
-
- QJsonParseError error;
- QJsonDocument doc = QJsonDocument::fromJson(jsonData, &error);
-
- if (doc.isNull()) {
+ QJsonObject responseObj = parseEventLine(line);
+ if (responseObj.isEmpty())
continue;
- }
- auto message = LLMCore::OpenAIMessage::fromJson(doc.object());
+ auto message = LLMCore::OpenAIMessage::fromJson(responseObj);
if (message.hasError()) {
- LOG_MESSAGE("Error in OpenAI response: " + message.error);
+ LOG_MESSAGE("Error in OpenRouter response: " + message.error);
continue;
}
QString content = message.getContent();
if (!content.isEmpty()) {
- accumulatedResponse += content;
- emit partialResponseReceived(requestId, content);
+ tempResponse += content;
}
if (message.isDone()) {
@@ -101,9 +93,14 @@ void OpenRouterProvider::onDataReceived(const QString &requestId, const QByteArr
}
}
+ if (!tempResponse.isEmpty()) {
+ buffers.responseContent += tempResponse;
+ emit partialResponseReceived(requestId, tempResponse);
+ }
+
if (isDone) {
- emit fullResponseReceived(requestId, accumulatedResponse);
- m_accumulatedResponses.remove(requestId);
+ emit fullResponseReceived(requestId, buffers.responseContent);
+ m_dataBuffers.remove(requestId);
}
}
@@ -114,15 +111,16 @@ void OpenRouterProvider::onRequestFinished(
LOG_MESSAGE(QString("OpenRouterProvider request %1 failed: %2").arg(requestId, error));
emit requestFailed(requestId, error);
} else {
- if (m_accumulatedResponses.contains(requestId)) {
- const QString fullResponse = m_accumulatedResponses[requestId];
- if (!fullResponse.isEmpty()) {
- emit fullResponseReceived(requestId, fullResponse);
+ if (m_dataBuffers.contains(requestId)) {
+ const LLMCore::DataBuffers &buffers = m_dataBuffers[requestId];
+ if (!buffers.responseContent.isEmpty()) {
+ emit fullResponseReceived(requestId, buffers.responseContent);
}
}
}
- m_accumulatedResponses.remove(requestId);
+ m_dataBuffers.remove(requestId);
+ m_requestUrls.remove(requestId);
}
} // namespace QodeAssist::Providers
diff --git a/providers/OpenRouterAIProvider.hpp b/providers/OpenRouterAIProvider.hpp
index 4bcfeec..2c064d7 100644
--- a/providers/OpenRouterAIProvider.hpp
+++ b/providers/OpenRouterAIProvider.hpp
@@ -19,7 +19,6 @@
#pragma once
-#include "llmcore/Provider.hpp"
#include "providers/OpenAICompatProvider.hpp"
namespace QodeAssist::Providers {
@@ -35,9 +34,6 @@ public:
public slots:
void onDataReceived(const QString &requestId, const QByteArray &data) override;
void onRequestFinished(const QString &requestId, bool success, const QString &error) override;
-
-private:
- QHash m_accumulatedResponses;
};
} // namespace QodeAssist::Providers
diff --git a/settings/ChatAssistantSettings.cpp b/settings/ChatAssistantSettings.cpp
index f0330c0..4d67ce9 100644
--- a/settings/ChatAssistantSettings.cpp
+++ b/settings/ChatAssistantSettings.cpp
@@ -56,10 +56,6 @@ ChatAssistantSettings::ChatAssistantSettings()
linkOpenFiles.setLabelText(Tr::tr("Sync open files with assistant by default"));
linkOpenFiles.setDefaultValue(false);
- stream.setSettingsKey(Constants::CA_STREAM);
- stream.setDefaultValue(true);
- stream.setLabelText(Tr::tr("Enable stream option"));
-
autosave.setSettingsKey(Constants::CA_AUTOSAVE);
autosave.setDefaultValue(true);
autosave.setLabelText(Tr::tr("Enable autosave when message received"));
@@ -251,7 +247,6 @@ ChatAssistantSettings::ChatAssistantSettings()
Group{title(Tr::tr("Chat Settings")),
Column{Row{chatTokensThreshold, Stretch{1}},
linkOpenFiles,
- stream,
autosave,
enableChatInBottomToolBar,
enableChatInNavigationPanel}},
@@ -294,7 +289,6 @@ void ChatAssistantSettings::resetSettingsToDefaults()
QMessageBox::Yes | QMessageBox::No);
if (reply == QMessageBox::Yes) {
- resetAspect(stream);
resetAspect(chatTokensThreshold);
resetAspect(temperature);
resetAspect(maxTokens);
diff --git a/settings/ChatAssistantSettings.hpp b/settings/ChatAssistantSettings.hpp
index 0ddec36..984cf8d 100644
--- a/settings/ChatAssistantSettings.hpp
+++ b/settings/ChatAssistantSettings.hpp
@@ -35,7 +35,6 @@ public:
// Chat settings
Utils::IntegerAspect chatTokensThreshold{this};
Utils::BoolAspect linkOpenFiles{this};
- Utils::BoolAspect stream{this};
Utils::BoolAspect autosave{this};
Utils::BoolAspect enableChatInBottomToolBar{this};
Utils::BoolAspect enableChatInNavigationPanel{this};
diff --git a/settings/CodeCompletionSettings.cpp b/settings/CodeCompletionSettings.cpp
index f9425e4..b0c1243 100644
--- a/settings/CodeCompletionSettings.cpp
+++ b/settings/CodeCompletionSettings.cpp
@@ -51,10 +51,6 @@ CodeCompletionSettings::CodeCompletionSettings()
multiLineCompletion.setDefaultValue(true);
multiLineCompletion.setLabelText(Tr::tr("Enable Multiline Completion"));
- stream.setSettingsKey(Constants::CC_STREAM);
- stream.setDefaultValue(true);
- stream.setLabelText(Tr::tr("Enable stream option"));
-
modelOutputHandler.setLabelText(Tr::tr("Text output proccessing mode:"));
modelOutputHandler.setSettingsKey(Constants::CC_MODEL_OUTPUT_HANDLER);
modelOutputHandler.setDisplayStyle(Utils::SelectionAspect::DisplayStyle::ComboBox);
@@ -303,7 +299,6 @@ CodeCompletionSettings::CodeCompletionSettings()
Column{autoCompletion,
Space{8},
multiLineCompletion,
- stream,
Row{modelOutputHandler, Stretch{1}},
Row{autoCompletionCharThreshold,
autoCompletionTypingInterval,
@@ -365,7 +360,6 @@ void CodeCompletionSettings::resetSettingsToDefaults()
if (reply == QMessageBox::Yes) {
resetAspect(autoCompletion);
resetAspect(multiLineCompletion);
- resetAspect(stream);
resetAspect(temperature);
resetAspect(maxTokens);
resetAspect(useTopP);
diff --git a/settings/CodeCompletionSettings.hpp b/settings/CodeCompletionSettings.hpp
index 0c4ca8d..ff56aad 100644
--- a/settings/CodeCompletionSettings.hpp
+++ b/settings/CodeCompletionSettings.hpp
@@ -35,7 +35,6 @@ public:
// Auto Completion Settings
Utils::BoolAspect autoCompletion{this};
Utils::BoolAspect multiLineCompletion{this};
- Utils::BoolAspect stream{this};
Utils::SelectionAspect modelOutputHandler{this};
Utils::IntegerAspect startSuggestionTimer{this};
diff --git a/settings/SettingsConstants.hpp b/settings/SettingsConstants.hpp
index 6f4618f..aaff040 100644
--- a/settings/SettingsConstants.hpp
+++ b/settings/SettingsConstants.hpp
@@ -75,12 +75,10 @@ const char СС_AUTO_COMPLETION_CHAR_THRESHOLD[] = "QodeAssist.autoCompletionCha
const char СС_AUTO_COMPLETION_TYPING_INTERVAL[] = "QodeAssist.autoCompletionTypingInterval";
const char MAX_FILE_THRESHOLD[] = "QodeAssist.maxFileThreshold";
const char CC_MULTILINE_COMPLETION[] = "QodeAssist.ccMultilineCompletion";
-const char CC_STREAM[] = "QodeAssist.ccStream";
const char CC_MODEL_OUTPUT_HANDLER[] = "QodeAssist.ccModelOutputHandler";
const char CUSTOM_JSON_TEMPLATE[] = "QodeAssist.customJsonTemplate";
const char CA_TOKENS_THRESHOLD[] = "QodeAssist.caTokensThreshold";
const char CA_LINK_OPEN_FILES[] = "QodeAssist.caLinkOpenFiles";
-const char CA_STREAM[] = "QodeAssist.caStream";
const char CA_AUTOSAVE[] = "QodeAssist.caAutosave";
const char CC_CUSTOM_LANGUAGES[] = "QodeAssist.ccCustomLanguages";