diff --git a/QodeAssist.json.in b/QodeAssist.json.in index 73d1eeb..1aa26c3 100644 --- a/QodeAssist.json.in +++ b/QodeAssist.json.in @@ -1,6 +1,6 @@ { "Name" : "QodeAssist", - "Version" : "0.3.2", + "Version" : "0.3.3", "CompatVersion" : "${IDE_VERSION_COMPAT}", "Vendor" : "Petr Mironychev", "Copyright" : "(C) ${IDE_COPYRIGHT_YEAR} Petr Mironychev, (C) ${IDE_COPYRIGHT_YEAR} The Qt Company Ltd", diff --git a/chatview/ChatModel.cpp b/chatview/ChatModel.cpp index b669255..c5cf9f7 100644 --- a/chatview/ChatModel.cpp +++ b/chatview/ChatModel.cpp @@ -68,6 +68,28 @@ QHash ChatModel::roleNames() const return roles; } +void ChatModel::addMessage(const QString &content, ChatRole role, const QString &id) +{ + int tokenCount = estimateTokenCount(content); + + if (!m_messages.isEmpty() && !id.isEmpty() && m_messages.last().id == id) { + Message &lastMessage = m_messages.last(); + int oldTokenCount = lastMessage.tokenCount; + lastMessage.content = content; + lastMessage.tokenCount = tokenCount; + m_totalTokens += (tokenCount - oldTokenCount); + emit dataChanged(index(m_messages.size() - 1), index(m_messages.size() - 1)); + } else { + beginInsertRows(QModelIndex(), m_messages.size(), m_messages.size()); + m_messages.append({role, content, tokenCount, id}); + m_totalTokens += tokenCount; + endInsertRows(); + } + + trim(); + emit totalTokensChanged(); +} + QVector ChatModel::getChatHistory() const { return m_messages; @@ -92,17 +114,6 @@ int ChatModel::estimateTokenCount(const QString &text) const return text.length() / 4; } -void ChatModel::addMessage(const QString &content, ChatRole role) -{ - int tokenCount = estimateTokenCount(content); - beginInsertRows(QModelIndex(), m_messages.size(), m_messages.size()); - m_messages.append({role, content, tokenCount}); - m_totalTokens += tokenCount; - endInsertRows(); - trim(); - emit totalTokensChanged(); -} - void ChatModel::clear() { beginResetModel(); @@ -176,4 +187,9 @@ int ChatModel::tokensThreshold() const return settings.chatTokensThreshold(); } +QString ChatModel::lastMessageId() const +{ + return !m_messages.isEmpty() ? m_messages.last().id : ""; +} + } // namespace QodeAssist::Chat diff --git a/chatview/ChatModel.hpp b/chatview/ChatModel.hpp index b13630b..03343e1 100644 --- a/chatview/ChatModel.hpp +++ b/chatview/ChatModel.hpp @@ -46,6 +46,7 @@ public: ChatRole role; QString content; int tokenCount; + QString id; }; explicit ChatModel(QObject *parent = nullptr); @@ -54,7 +55,7 @@ public: QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; QHash roleNames() const override; - Q_INVOKABLE void addMessage(const QString &content, ChatRole role); + Q_INVOKABLE void addMessage(const QString &content, ChatRole role, const QString &id); Q_INVOKABLE void clear(); Q_INVOKABLE QList processMessageContent(const QString &content) const; @@ -65,6 +66,7 @@ public: int tokensThreshold() const; QString currentModel() const; + QString lastMessageId() const; signals: void totalTokensChanged(); diff --git a/chatview/ChatRootView.cpp b/chatview/ChatRootView.cpp index c04f01a..6450c51 100644 --- a/chatview/ChatRootView.cpp +++ b/chatview/ChatRootView.cpp @@ -60,6 +60,11 @@ void ChatRootView::copyToClipboard(const QString &text) QGuiApplication::clipboard()->setText(text); } +void ChatRootView::cancelRequest() +{ + m_clientInterface->cancelRequest(); +} + void ChatRootView::generateColors() { QColor baseColor = backgroundColor(); diff --git a/chatview/ChatRootView.hpp b/chatview/ChatRootView.hpp index 2f32ec6..f0d7272 100644 --- a/chatview/ChatRootView.hpp +++ b/chatview/ChatRootView.hpp @@ -52,6 +52,7 @@ public: public slots: void sendMessage(const QString &message) const; void copyToClipboard(const QString &text); + void cancelRequest(); signals: void chatModelChanged(); diff --git a/chatview/ClientInterface.cpp b/chatview/ClientInterface.cpp index 4b0b114..2d1c1b6 100644 --- a/chatview/ClientInterface.cpp +++ b/chatview/ClientInterface.cpp @@ -38,8 +38,8 @@ ClientInterface::ClientInterface(ChatModel *chatModel, QObject *parent) connect(m_requestHandler, &LLMCore::RequestHandler::completionReceived, this, - [this](const QString &completion, const QJsonObject &, bool isComplete) { - handleLLMResponse(completion, isComplete); + [this](const QString &completion, const QJsonObject &request, bool isComplete) { + handleLLMResponse(completion, request, isComplete); }); connect(m_requestHandler, @@ -56,6 +56,8 @@ ClientInterface::~ClientInterface() = default; void ClientInterface::sendMessage(const QString &message) { + cancelRequest(); + LOG_MESSAGE("Sending message: " + message); LOG_MESSAGE("chatProvider " + Settings::generalSettings().chatLlmProviders.stringValue()); LOG_MESSAGE("chatTemplate " + Settings::generalSettings().chatPrompts.stringValue()); @@ -74,6 +76,9 @@ void ClientInterface::sendMessage(const QString &message) providerRequest["stream"] = true; providerRequest["messages"] = m_chatModel->prepareMessagesForRequest(context); + if (!chatTemplate || !chatProvider) { + LOG_MESSAGE("Check settings, provider or template are not set"); + } chatTemplate->prepareRequest(providerRequest, context); chatProvider->prepareRequest(providerRequest, LLMCore::RequestType::Chat); @@ -89,28 +94,31 @@ void ClientInterface::sendMessage(const QString &message) QJsonObject request; request["id"] = QUuid::createUuid().toString(); - m_accumulatedResponse.clear(); - m_chatModel->addMessage(message, ChatModel::ChatRole::User); + m_chatModel->addMessage(message, ChatModel::ChatRole::User, ""); m_requestHandler->sendLLMRequest(config, request); } void ClientInterface::clearMessages() { m_chatModel->clear(); - m_accumulatedResponse.clear(); LOG_MESSAGE("Chat history cleared"); } -void ClientInterface::handleLLMResponse(const QString &response, bool isComplete) +void ClientInterface::cancelRequest() { - m_accumulatedResponse += response; + auto id = m_chatModel->lastMessageId(); + m_requestHandler->cancelRequest(id); +} + +void ClientInterface::handleLLMResponse(const QString &response, + const QJsonObject &request, + bool isComplete) +{ + QString messageId = request["id"].toString(); + m_chatModel->addMessage(response.trimmed(), ChatModel::ChatRole::Assistant, messageId); if (isComplete) { - LOG_MESSAGE("Message completed. Final response: " + m_accumulatedResponse); - emit messageReceived(m_accumulatedResponse.trimmed()); - - m_chatModel->addMessage(m_accumulatedResponse.trimmed(), ChatModel::ChatRole::Assistant); - m_accumulatedResponse.clear(); + LOG_MESSAGE("Message completed. Final response for message " + messageId + ": " + response); } } diff --git a/chatview/ClientInterface.hpp b/chatview/ClientInterface.hpp index 24863ce..4de4ea7 100644 --- a/chatview/ClientInterface.hpp +++ b/chatview/ClientInterface.hpp @@ -38,16 +38,15 @@ public: void sendMessage(const QString &message); void clearMessages(); + void cancelRequest(); signals: - void messageReceived(const QString &message); void errorOccurred(const QString &error); private: - void handleLLMResponse(const QString &response, bool isComplete); + void handleLLMResponse(const QString &response, const QJsonObject &request, bool isComplete); LLMCore::RequestHandler *m_requestHandler; - QString m_accumulatedResponse; ChatModel *m_chatModel; }; diff --git a/chatview/qml/RootItem.qml b/chatview/qml/RootItem.qml index 29556a7..805d747 100644 --- a/chatview/qml/RootItem.qml +++ b/chatview/qml/RootItem.qml @@ -121,6 +121,15 @@ ChatRootView { text: qsTr("Send") onClicked: sendChatMessage() } + + Button { + id: stopButton + + Layout.alignment: Qt.AlignBottom + text: qsTr("Stop") + onClicked: root.cancelRequest() + } + Button { id: clearButton diff --git a/llmcore/PromptTemplateManager.cpp b/llmcore/PromptTemplateManager.cpp index 5c10eb2..2eafc18 100644 --- a/llmcore/PromptTemplateManager.cpp +++ b/llmcore/PromptTemplateManager.cpp @@ -43,8 +43,8 @@ void PromptTemplateManager::setCurrentFimTemplate(const QString &name) PromptTemplate *PromptTemplateManager::getCurrentFimTemplate() { if (m_currentFimTemplate == nullptr) { - LOG_MESSAGE("Current fim provider is null"); - return nullptr; + LOG_MESSAGE("Current fim provider is null, return first"); + return m_fimTemplates.first(); } return m_currentFimTemplate; @@ -63,8 +63,10 @@ void PromptTemplateManager::setCurrentChatTemplate(const QString &name) PromptTemplate *PromptTemplateManager::getCurrentChatTemplate() { - if (m_currentChatTemplate == nullptr) - LOG_MESSAGE("Current chat provider is null"); + if (m_currentChatTemplate == nullptr) { + LOG_MESSAGE("Current chat provider is null, return first"); + return m_chatTemplates.first(); + } return m_currentChatTemplate; } diff --git a/llmcore/ProvidersManager.cpp b/llmcore/ProvidersManager.cpp index d60b42f..fdde562 100644 --- a/llmcore/ProvidersManager.cpp +++ b/llmcore/ProvidersManager.cpp @@ -56,8 +56,8 @@ Provider *ProvidersManager::setCurrentChatProvider(const QString &name) Provider *ProvidersManager::getCurrentFimProvider() { if (m_currentFimProvider == nullptr) { - LOG_MESSAGE("Current fim provider is null"); - return nullptr; + LOG_MESSAGE("Current fim provider is null, return first"); + return m_providers.first(); } return m_currentFimProvider; @@ -66,8 +66,8 @@ Provider *ProvidersManager::getCurrentFimProvider() Provider *ProvidersManager::getCurrentChatProvider() { if (m_currentChatProvider == nullptr) { - LOG_MESSAGE("Current chat provider is null"); - return nullptr; + LOG_MESSAGE("Current chat provider is null, return first"); + return m_providers.first(); } return m_currentChatProvider; diff --git a/llmcore/RequestHandler.cpp b/llmcore/RequestHandler.cpp index 99e19ca..1a7e5c6 100644 --- a/llmcore/RequestHandler.cpp +++ b/llmcore/RequestHandler.cpp @@ -80,22 +80,18 @@ void RequestHandler::handleLLMResponse(QNetworkReply *reply, && processSingleLineCompletion(reply, request, accumulatedResponse, config)) { return; } + + if (isComplete) { + auto cleanedCompletion = removeStopWords(accumulatedResponse, + config.promptTemplate->stopWords()); + emit completionReceived(cleanedCompletion, request, true); + } + } else if (config.requestType == RequestType::Chat) { + emit completionReceived(accumulatedResponse, request, isComplete); } - if (isComplete || reply->isFinished()) { - if (isComplete) { - if (config.requestType == RequestType::Fim) { - auto cleanedCompletion = removeStopWords(accumulatedResponse, - config.promptTemplate->stopWords()); - emit completionReceived(cleanedCompletion, request, true); - } else { - emit completionReceived(accumulatedResponse, request, true); - } - } else { - emit completionReceived(accumulatedResponse, request, false); - } + if (isComplete) m_accumulatedResponses.remove(reply); - } } bool RequestHandler::cancelRequest(const QString &id) diff --git a/settings/GeneralSettings.cpp b/settings/GeneralSettings.cpp index 8fc80ba..fc54e1c 100644 --- a/settings/GeneralSettings.cpp +++ b/settings/GeneralSettings.cpp @@ -134,7 +134,7 @@ GeneralSettings::GeneralSettings() chatTokensThreshold.setToolTip(Tr::tr("Maximum number of tokens in chat history. When " "exceeded, oldest messages will be removed.")); chatTokensThreshold.setRange(1000, 16000); - chatTokensThreshold.setDefaultValue(4000); + chatTokensThreshold.setDefaultValue(8000); loadProviders(); loadPrompts();