mirror of
https://github.com/Palm1r/QodeAssist.git
synced 2025-07-18 21:14:34 -04:00
Add basic chat widgets and functionality
This commit is contained in:
153
chat/ChatClientInterface.cpp
Normal file
153
chat/ChatClientInterface.cpp
Normal file
@ -0,0 +1,153 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "ChatClientInterface.hpp"
|
||||
#include "LLMProvidersManager.hpp"
|
||||
#include "PromptTemplateManager.hpp"
|
||||
#include "QodeAssistUtils.hpp"
|
||||
#include "settings/ContextSettings.hpp"
|
||||
#include "settings/GeneralSettings.hpp"
|
||||
#include "settings/PresetPromptsSettings.hpp"
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QUuid>
|
||||
|
||||
namespace QodeAssist::Chat {
|
||||
|
||||
ChatClientInterface::ChatClientInterface(QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_requestHandler(new LLMRequestHandler(this))
|
||||
{
|
||||
connect(m_requestHandler,
|
||||
&LLMRequestHandler::completionReceived,
|
||||
this,
|
||||
[this](const QString &completion, const QJsonObject &, bool isComplete) {
|
||||
handleLLMResponse(completion, isComplete);
|
||||
});
|
||||
|
||||
connect(m_requestHandler,
|
||||
&LLMRequestHandler::requestFinished,
|
||||
this,
|
||||
[this](const QString &, bool success, const QString &errorString) {
|
||||
if (!success) {
|
||||
emit errorOccurred(errorString);
|
||||
}
|
||||
});
|
||||
|
||||
// QJsonObject systemMessage;
|
||||
// systemMessage["role"] = "system";
|
||||
// systemMessage["content"] = "You are a helpful C++ and QML programming assistant.";
|
||||
// m_chatHistory.append(systemMessage);
|
||||
}
|
||||
|
||||
ChatClientInterface::~ChatClientInterface()
|
||||
{
|
||||
}
|
||||
|
||||
void ChatClientInterface::sendMessage(const QString &message)
|
||||
{
|
||||
logMessage("Sending message: " + message);
|
||||
logMessage("chatProvider " + Settings::generalSettings().chatLlmProviders.stringValue());
|
||||
logMessage("chatTemplate " + Settings::generalSettings().chatPrompts.stringValue());
|
||||
|
||||
auto chatTemplate = PromptTemplateManager::instance().getCurrentChatTemplate();
|
||||
auto chatProvider = LLMProvidersManager::instance().getCurrentChatProvider();
|
||||
|
||||
ContextData context;
|
||||
context.prefix = message;
|
||||
context.suffix = "";
|
||||
if (Settings::contextSettings().useSpecificInstructions())
|
||||
context.instriuctions = Settings::contextSettings().specificInstractions();
|
||||
|
||||
QJsonObject providerRequest;
|
||||
providerRequest["model"] = Settings::generalSettings().chatModelName();
|
||||
providerRequest["stream"] = true;
|
||||
|
||||
providerRequest["messages"] = m_chatHistory;
|
||||
|
||||
chatTemplate->prepareRequest(providerRequest, context);
|
||||
chatProvider->prepareRequest(providerRequest);
|
||||
|
||||
m_chatHistory = providerRequest["messages"].toArray();
|
||||
|
||||
LLMConfig config;
|
||||
config.requestType = RequestType::Chat;
|
||||
config.provider = chatProvider;
|
||||
config.promptTemplate = chatTemplate;
|
||||
config.url = QString("%1%2").arg(Settings::generalSettings().chatUrl(),
|
||||
Settings::generalSettings().chatEndPoint());
|
||||
config.providerRequest = providerRequest;
|
||||
|
||||
QJsonObject request;
|
||||
request["id"] = QUuid::createUuid().toString();
|
||||
|
||||
m_accumulatedResponse.clear();
|
||||
m_pendingMessage = message;
|
||||
m_requestHandler->sendLLMRequest(config, request);
|
||||
}
|
||||
|
||||
void ChatClientInterface::handleLLMResponse(const QString &response, bool isComplete)
|
||||
{
|
||||
m_accumulatedResponse += response;
|
||||
logMessage("Accumulated response: " + m_accumulatedResponse);
|
||||
|
||||
if (isComplete) {
|
||||
logMessage("Message completed. Final response: " + m_accumulatedResponse);
|
||||
emit messageReceived(m_accumulatedResponse.trimmed());
|
||||
|
||||
QJsonObject assistantMessage;
|
||||
assistantMessage["role"] = "assistant";
|
||||
assistantMessage["content"] = m_accumulatedResponse.trimmed();
|
||||
m_chatHistory.append(assistantMessage);
|
||||
|
||||
m_pendingMessage.clear();
|
||||
m_accumulatedResponse.clear();
|
||||
|
||||
trimChatHistory();
|
||||
}
|
||||
}
|
||||
|
||||
void ChatClientInterface::trimChatHistory()
|
||||
{
|
||||
int maxTokens = 4000;
|
||||
int totalTokens = 0;
|
||||
QJsonArray newHistory;
|
||||
|
||||
if (!m_chatHistory.isEmpty()
|
||||
&& m_chatHistory.first().toObject()["role"].toString() == "system") {
|
||||
newHistory.append(m_chatHistory.first());
|
||||
}
|
||||
|
||||
for (int i = m_chatHistory.size() - 1; i >= 0; --i) {
|
||||
QJsonObject message = m_chatHistory[i].toObject();
|
||||
int messageTokens = message["content"].toString().length() / 4;
|
||||
|
||||
if (totalTokens + messageTokens > maxTokens) {
|
||||
break;
|
||||
}
|
||||
|
||||
newHistory.prepend(message);
|
||||
totalTokens += messageTokens;
|
||||
}
|
||||
|
||||
m_chatHistory = newHistory;
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Chat
|
53
chat/ChatClientInterface.hpp
Normal file
53
chat/ChatClientInterface.hpp
Normal file
@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QtCore/qjsonarray.h>
|
||||
#include "QodeAssistData.hpp"
|
||||
#include "core/LLMRequestHandler.hpp"
|
||||
|
||||
namespace QodeAssist::Chat {
|
||||
|
||||
class ChatClientInterface : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ChatClientInterface(QObject *parent = nullptr);
|
||||
~ChatClientInterface();
|
||||
|
||||
void sendMessage(const QString &message);
|
||||
|
||||
signals:
|
||||
void messageReceived(const QString &message);
|
||||
void errorOccurred(const QString &error);
|
||||
|
||||
private:
|
||||
void handleLLMResponse(const QString &response, bool isComplete);
|
||||
void trimChatHistory();
|
||||
|
||||
LLMRequestHandler *m_requestHandler;
|
||||
QString m_accumulatedResponse;
|
||||
QString m_pendingMessage;
|
||||
QJsonArray m_chatHistory;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Chat
|
95
chat/ChatOutputPane.cpp
Normal file
95
chat/ChatOutputPane.cpp
Normal file
@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "ChatOutputPane.h"
|
||||
|
||||
#include "QodeAssisttr.h"
|
||||
|
||||
namespace QodeAssist::Chat {
|
||||
|
||||
ChatOutputPane::ChatOutputPane(QObject *parent)
|
||||
: Core::IOutputPane(parent)
|
||||
, m_chatWidget(new ChatWidget)
|
||||
{
|
||||
setId("QodeAssistChat");
|
||||
setDisplayName(Tr::tr("QodeAssist Chat"));
|
||||
setPriorityInStatusBar(-40);
|
||||
}
|
||||
|
||||
ChatOutputPane::~ChatOutputPane()
|
||||
{
|
||||
delete m_chatWidget;
|
||||
}
|
||||
|
||||
QWidget *ChatOutputPane::outputWidget(QWidget *)
|
||||
{
|
||||
return m_chatWidget;
|
||||
}
|
||||
|
||||
QList<QWidget *> ChatOutputPane::toolBarWidgets() const
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
void ChatOutputPane::clearContents()
|
||||
{
|
||||
m_chatWidget->clear();
|
||||
}
|
||||
|
||||
void ChatOutputPane::visibilityChanged(bool visible)
|
||||
{
|
||||
if (visible)
|
||||
m_chatWidget->scrollToBottom();
|
||||
}
|
||||
|
||||
void ChatOutputPane::setFocus()
|
||||
{
|
||||
m_chatWidget->setFocus();
|
||||
}
|
||||
|
||||
bool ChatOutputPane::hasFocus() const
|
||||
{
|
||||
return m_chatWidget->hasFocus();
|
||||
}
|
||||
|
||||
bool ChatOutputPane::canFocus() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ChatOutputPane::canNavigate() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ChatOutputPane::canNext() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ChatOutputPane::canPrevious() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void ChatOutputPane::goToNext() {}
|
||||
|
||||
void ChatOutputPane::goToPrev() {}
|
||||
|
||||
} // namespace QodeAssist::Chat
|
52
chat/ChatOutputPane.h
Normal file
52
chat/ChatOutputPane.h
Normal file
@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ChatWidget.h"
|
||||
#include <coreplugin/ioutputpane.h>
|
||||
|
||||
namespace QodeAssist::Chat {
|
||||
|
||||
class ChatOutputPane : public Core::IOutputPane
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ChatOutputPane(QObject *parent = nullptr);
|
||||
~ChatOutputPane() override;
|
||||
|
||||
QWidget *outputWidget(QWidget *parent) override;
|
||||
QList<QWidget *> toolBarWidgets() const override;
|
||||
void clearContents() override;
|
||||
void visibilityChanged(bool visible) override;
|
||||
void setFocus() override;
|
||||
bool hasFocus() const override;
|
||||
bool canFocus() const override;
|
||||
bool canNavigate() const override;
|
||||
bool canNext() const override;
|
||||
bool canPrevious() const override;
|
||||
void goToNext() override;
|
||||
void goToPrev() override;
|
||||
|
||||
private:
|
||||
ChatWidget *m_chatWidget;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Chat
|
150
chat/ChatWidget.cpp
Normal file
150
chat/ChatWidget.cpp
Normal file
@ -0,0 +1,150 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "ChatWidget.h"
|
||||
#include "QodeAssistUtils.hpp"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDateTime>
|
||||
#include <QScrollBar>
|
||||
#include <QVBoxLayout>
|
||||
#include <QtCore/qtimer.h>
|
||||
|
||||
namespace QodeAssist::Chat {
|
||||
|
||||
ChatWidget::ChatWidget(QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, m_showTimestamp(false)
|
||||
, m_chatClient(new ChatClientInterface(this))
|
||||
{
|
||||
setupUi();
|
||||
|
||||
connect(m_sendButton, &QPushButton::clicked, this, &ChatWidget::sendMessage);
|
||||
connect(m_messageInput, &QLineEdit::returnPressed, this, &ChatWidget::sendMessage);
|
||||
|
||||
connect(m_chatClient, &ChatClientInterface::messageReceived, this, &ChatWidget::receiveMessage);
|
||||
connect(m_chatClient, &ChatClientInterface::errorOccurred, this, &ChatWidget::handleError);
|
||||
|
||||
logMessage("ChatWidget initialized");
|
||||
}
|
||||
|
||||
void ChatWidget::setupUi()
|
||||
{
|
||||
m_chatDisplay = new QTextEdit(this);
|
||||
m_chatDisplay->setReadOnly(true);
|
||||
|
||||
m_messageInput = new QLineEdit(this);
|
||||
m_sendButton = new QPushButton("Send", this);
|
||||
|
||||
QHBoxLayout *inputLayout = new QHBoxLayout;
|
||||
inputLayout->addWidget(m_messageInput);
|
||||
inputLayout->addWidget(m_sendButton);
|
||||
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout(this);
|
||||
mainLayout->addWidget(m_chatDisplay);
|
||||
mainLayout->addLayout(inputLayout);
|
||||
|
||||
setLayout(mainLayout);
|
||||
}
|
||||
|
||||
void ChatWidget::sendMessage()
|
||||
{
|
||||
QString message = m_messageInput->text().trimmed();
|
||||
if (!message.isEmpty()) {
|
||||
logMessage("Sending message: " + message);
|
||||
addMessage(message, true);
|
||||
m_chatClient->sendMessage(message);
|
||||
m_messageInput->clear();
|
||||
addMessage("AI is typing...", false);
|
||||
}
|
||||
}
|
||||
|
||||
void ChatWidget::receiveMessage(const QString &message)
|
||||
{
|
||||
logMessage("Received message: " + message);
|
||||
updateLastAIMessage(message);
|
||||
}
|
||||
|
||||
void ChatWidget::receivePartialMessage(const QString &partialMessage)
|
||||
{
|
||||
logMessage("Received partial message: " + partialMessage);
|
||||
m_currentAIResponse += partialMessage;
|
||||
updateLastAIMessage(m_currentAIResponse);
|
||||
}
|
||||
|
||||
void ChatWidget::onMessageCompleted()
|
||||
{
|
||||
logMessage("Message completed. Final response: " + m_currentAIResponse);
|
||||
updateLastAIMessage(m_currentAIResponse);
|
||||
m_currentAIResponse.clear();
|
||||
scrollToBottom();
|
||||
}
|
||||
|
||||
void ChatWidget::handleError(const QString &error)
|
||||
{
|
||||
logMessage("Error occurred: " + error);
|
||||
addMessage("Error: " + error, false);
|
||||
}
|
||||
|
||||
void ChatWidget::addMessage(const QString &message, bool fromUser)
|
||||
{
|
||||
auto prefix = fromUser ? "You: " : "AI: ";
|
||||
QString timestamp = m_showTimestamp ? QDateTime::currentDateTime().toString("[hh:mm:ss] ") : "";
|
||||
QString fullMessage = timestamp + prefix + message;
|
||||
logMessage("Adding message to display: " + fullMessage);
|
||||
m_chatDisplay->append(fullMessage);
|
||||
scrollToBottom();
|
||||
}
|
||||
|
||||
void ChatWidget::updateLastAIMessage(const QString &message)
|
||||
{
|
||||
logMessage("Updating last AI message: " + message);
|
||||
QTextCursor cursor = m_chatDisplay->textCursor();
|
||||
cursor.movePosition(QTextCursor::End);
|
||||
cursor.movePosition(QTextCursor::StartOfBlock, QTextCursor::KeepAnchor);
|
||||
cursor.removeSelectedText();
|
||||
|
||||
QString timestamp = m_showTimestamp ? QDateTime::currentDateTime().toString("[hh:mm:ss] ") : "";
|
||||
cursor.insertText(timestamp + "AI: " + message);
|
||||
|
||||
cursor.movePosition(QTextCursor::End);
|
||||
m_chatDisplay->setTextCursor(cursor);
|
||||
|
||||
scrollToBottom();
|
||||
m_chatDisplay->repaint();
|
||||
}
|
||||
|
||||
void ChatWidget::clear()
|
||||
{
|
||||
m_chatDisplay->clear();
|
||||
m_currentAIResponse.clear();
|
||||
}
|
||||
|
||||
void ChatWidget::scrollToBottom()
|
||||
{
|
||||
QScrollBar *scrollBar = m_chatDisplay->verticalScrollBar();
|
||||
scrollBar->setValue(scrollBar->maximum());
|
||||
}
|
||||
|
||||
void ChatWidget::setShowTimestamp(bool show)
|
||||
{
|
||||
m_showTimestamp = show;
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Chat
|
62
chat/ChatWidget.h
Normal file
62
chat/ChatWidget.h
Normal file
@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QLineEdit>
|
||||
#include <QPushButton>
|
||||
#include <QTextEdit>
|
||||
#include <QWidget>
|
||||
|
||||
#include "ChatClientInterface.hpp"
|
||||
|
||||
namespace QodeAssist::Chat {
|
||||
|
||||
class ChatWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ChatWidget(QWidget *parent = nullptr);
|
||||
|
||||
void clear();
|
||||
void scrollToBottom();
|
||||
void setShowTimestamp(bool show);
|
||||
|
||||
void receiveMessage(const QString &message);
|
||||
private slots:
|
||||
void sendMessage();
|
||||
void receivePartialMessage(const QString &partialMessage);
|
||||
void onMessageCompleted();
|
||||
void handleError(const QString &error);
|
||||
|
||||
private:
|
||||
QTextEdit *m_chatDisplay;
|
||||
QLineEdit *m_messageInput;
|
||||
QPushButton *m_sendButton;
|
||||
bool m_showTimestamp;
|
||||
ChatClientInterface *m_chatClient;
|
||||
QString m_currentAIResponse;
|
||||
|
||||
void setupUi();
|
||||
void addMessage(const QString &message, bool fromUser = true);
|
||||
void updateLastAIMessage(const QString &message);
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Chat
|
Reference in New Issue
Block a user