diff --git a/CMakeLists.txt b/CMakeLists.txt index 54337e3..ed9f02f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,7 @@ cmake_minimum_required(VERSION 3.16) +list(APPEND CMAKE_PREFIX_PATH "/Users/palm1r/Qt/Qt Creator.app/Contents/Resources/lib/cmake/QtCreator") + project(QodeAssist) set(CMAKE_AUTOMOC ON) @@ -12,6 +14,9 @@ set(CMAKE_CXX_EXTENSIONS OFF) find_package(QtCreator REQUIRED COMPONENTS Core) find_package(Qt6 COMPONENTS Core Gui Widgets Network REQUIRED) +add_subdirectory(llmcore) +add_subdirectory(settings) +add_subdirectory(logger) add_subdirectory(chatview) add_qtc_plugin(QodeAssist @@ -32,39 +37,25 @@ add_qtc_plugin(QodeAssist QodeAssistConstants.hpp QodeAssisttr.h LLMClientInterface.hpp LLMClientInterface.cpp - PromptTemplateManager.hpp PromptTemplateManager.cpp - templates/PromptTemplate.hpp - templates/CodeLlamaFimTemplate.hpp - templates/StarCoder2Template.hpp - templates/DeepSeekCoderV2.hpp - templates/CustomTemplate.hpp - templates/DeepSeekCoderChatTemplate.hpp - templates/CodeLlamaInstruct.hpp - providers/LLMProvider.hpp + templates/CodeLlamaFim.hpp + templates/StarCoder2Fim.hpp + templates/DeepSeekCoderFim.hpp + templates/CustomFimTemplate.hpp + templates/DeepSeekCoderChat.hpp + templates/CodeLlamaChat.hpp + templates/QwenChat.hpp providers/OllamaProvider.hpp providers/OllamaProvider.cpp providers/LMStudioProvider.hpp providers/LMStudioProvider.cpp providers/OpenAICompatProvider.hpp providers/OpenAICompatProvider.cpp - LLMProvidersManager.hpp LLMProvidersManager.cpp QodeAssist.qrc LSPCompletion.hpp LLMSuggestion.hpp LLMSuggestion.cpp QodeAssistClient.hpp QodeAssistClient.cpp - QodeAssistUtils.hpp DocumentContextReader.hpp DocumentContextReader.cpp - QodeAssistData.hpp utils/CounterTooltip.hpp utils/CounterTooltip.cpp - settings/GeneralSettings.hpp settings/GeneralSettings.cpp - settings/ContextSettings.hpp settings/ContextSettings.cpp - settings/CustomPromptSettings.hpp settings/CustomPromptSettings.cpp - settings/PresetPromptsSettings.hpp settings/PresetPromptsSettings.cpp - settings/SettingsUtils.hpp core/ChangesManager.h core/ChangesManager.cpp - core/LLMRequestHandler.hpp core/LLMRequestHandler.cpp - core/LLMRequestConfig.hpp - chat/ChatWidget.h chat/ChatWidget.cpp chat/ChatOutputPane.h chat/ChatOutputPane.cpp - chat/ChatClientInterface.hpp chat/ChatClientInterface.cpp chat/NavigationPanel.hpp chat/NavigationPanel.cpp ) -target_link_libraries(QodeAssist PUBLIC QodeAssistChatViewplugin) +target_link_libraries(QodeAssist PRIVATE QodeAssistChatViewplugin) diff --git a/DocumentContextReader.cpp b/DocumentContextReader.cpp index 5bbde27..7ac0189 100644 --- a/DocumentContextReader.cpp +++ b/DocumentContextReader.cpp @@ -135,13 +135,6 @@ QString DocumentContextReader::getLanguageAndFileInfo() const .arg(language, mimeType, filePath, fileExtension); } -QString DocumentContextReader::getSpecificInstructions() const -{ - QString specificInstruction = Settings::contextSettings().specificInstractions().arg( - LanguageServerProtocol::TextDocumentItem::mimeTypeToLanguageId(m_textDocument->mimeType())); - return QString("%1").arg(specificInstruction); -} - CopyrightInfo DocumentContextReader::findCopyright() { CopyrightInfo result = {-1, -1, false}; @@ -210,7 +203,7 @@ CopyrightInfo DocumentContextReader::copyrightInfo() const return m_copyrightInfo; } -ContextData DocumentContextReader::prepareContext(int lineNumber, int cursorPosition) const +LLMCore::ContextData DocumentContextReader::prepareContext(int lineNumber, int cursorPosition) const { QString contextBefore = getContextBefore(lineNumber, cursorPosition); QString contextAfter = getContextAfter(lineNumber, cursorPosition); diff --git a/DocumentContextReader.hpp b/DocumentContextReader.hpp index a3f9336..1a234b3 100644 --- a/DocumentContextReader.hpp +++ b/DocumentContextReader.hpp @@ -22,7 +22,8 @@ #include #include -#include "QodeAssistData.hpp" +// #include "QodeAssistData.hpp" +#include namespace QodeAssist { @@ -44,13 +45,12 @@ public: QString readWholeFileBefore(int lineNumber, int cursorPosition) const; QString readWholeFileAfter(int lineNumber, int cursorPosition) const; QString getLanguageAndFileInfo() const; - QString getSpecificInstructions() const; CopyrightInfo findCopyright(); QString getContextBetween(int startLine, int endLine, int cursorPosition) const; CopyrightInfo copyrightInfo() const; - ContextData prepareContext(int lineNumber, int cursorPosition) const; + LLMCore::ContextData prepareContext(int lineNumber, int cursorPosition) const; private: QString getContextBefore(int lineNumber, int cursorPosition) const; diff --git a/LLMClientInterface.cpp b/LLMClientInterface.cpp index b9b0230..0360725 100644 --- a/LLMClientInterface.cpp +++ b/LLMClientInterface.cpp @@ -23,13 +23,13 @@ #include #include +#include #include #include "DocumentContextReader.hpp" -#include "LLMProvidersManager.hpp" -#include "PromptTemplateManager.hpp" -#include "QodeAssistUtils.hpp" -#include "core/LLMRequestConfig.hpp" +#include "logger/Logger.hpp" +#include "llmcore/PromptTemplateManager.hpp" +#include "llmcore/ProvidersManager.hpp" #include "settings/ContextSettings.hpp" #include "settings/GeneralSettings.hpp" @@ -39,7 +39,7 @@ LLMClientInterface::LLMClientInterface() : m_requestHandler(this) { connect(&m_requestHandler, - &LLMRequestHandler::completionReceived, + &LLMCore::RequestHandler::completionReceived, this, &LLMClientInterface::sendCompletionToClient); } @@ -80,7 +80,7 @@ void LLMClientInterface::sendData(const QByteArray &data) } else if (method == "exit") { // TODO make exit handler } else { - logMessage(QString("Unknown method: %1").arg(method)); + LOG_MESSAGE(QString("Unknown method: %1").arg(method)); } } @@ -88,9 +88,9 @@ void LLMClientInterface::handleCancelRequest(const QJsonObject &request) { QString id = request["params"].toObject()["id"].toString(); if (m_requestHandler.cancelRequest(id)) { - logMessage(QString("Request %1 cancelled successfully").arg(id)); + LOG_MESSAGE(QString("Request %1 cancelled successfully").arg(id)); } else { - logMessage(QString("Request %1 not found").arg(id)); + LOG_MESSAGE(QString("Request %1 not found").arg(id)); } } @@ -148,10 +148,10 @@ void LLMClientInterface::handleCompletion(const QJsonObject &request) { auto updatedContext = prepareContext(request); - LLMConfig config; - config.requestType = RequestType::Fim; - config.provider = LLMProvidersManager::instance().getCurrentFimProvider(); - config.promptTemplate = PromptTemplateManager::instance().getCurrentFimTemplate(); + LLMCore::LLMConfig config; + config.requestType = LLMCore::RequestType::Fim; + config.provider = LLMCore::ProvidersManager::instance().getCurrentFimProvider(); + config.promptTemplate = LLMCore::PromptTemplateManager::instance().getCurrentFimTemplate(); config.url = QUrl(QString("%1%2").arg(Settings::generalSettings().url(), Settings::generalSettings().endPoint())); @@ -159,18 +159,19 @@ void LLMClientInterface::handleCompletion(const QJsonObject &request) {"stream", true}, {"stop", QJsonArray::fromStringList(config.promptTemplate->stopWords())}}; + config.multiLineCompletion = Settings::generalSettings().multiLineCompletion(); - if (Settings::contextSettings().useSpecificInstructions()) - config.providerRequest["system"] = Settings::contextSettings().specificInstractions(); + if (Settings::contextSettings().useSystemPrompt()) + config.providerRequest["system"] = Settings::contextSettings().systemPrompt(); config.promptTemplate->prepareRequest(config.providerRequest, updatedContext); - config.provider->prepareRequest(config.providerRequest); + config.provider->prepareRequest(config.providerRequest, LLMCore::RequestType::Fim); m_requestHandler.sendLLMRequest(config, request); } -ContextData LLMClientInterface::prepareContext(const QJsonObject &request, - const QStringView &accumulatedCompletion) +LLMCore::ContextData LLMClientInterface::prepareContext(const QJsonObject &request, + const QStringView &accumulatedCompletion) { QJsonObject params = request["params"].toObject(); QJsonObject doc = params["doc"].toObject(); @@ -182,8 +183,8 @@ ContextData LLMClientInterface::prepareContext(const QJsonObject &request, filePath); if (!textDocument) { - logMessage("Error: Document is not available for" + filePath.toString()); - return ContextData{}; + LOG_MESSAGE("Error: Document is not available for" + filePath.toString()); + return LLMCore::ContextData{}; } int cursorPosition = position["character"].toInt(); @@ -218,11 +219,11 @@ void LLMClientInterface::sendCompletionToClient(const QString &completion, result[LanguageServerProtocol::isIncompleteKey] = !isComplete; response[LanguageServerProtocol::resultKey] = result; - logMessage( + LOG_MESSAGE( QString("Completions: \n%1") .arg(QString::fromUtf8(QJsonDocument(completions).toJson(QJsonDocument::Indented)))); - logMessage(QString("Full response: \n%1") + LOG_MESSAGE(QString("Full response: \n%1") .arg(QString::fromUtf8(QJsonDocument(response).toJson(QJsonDocument::Indented)))); QString requestId = request["id"].toString(); @@ -250,7 +251,7 @@ void LLMClientInterface::logPerformance(const QString &requestId, const QString &operation, qint64 elapsedMs) { - logMessage(QString("Performance: %1 %2 took %3 ms").arg(requestId, operation).arg(elapsedMs)); + LOG_MESSAGE(QString("Performance: %1 %2 took %3 ms").arg(requestId, operation).arg(elapsedMs)); } void LLMClientInterface::parseCurrentMessage() {} diff --git a/LLMClientInterface.hpp b/LLMClientInterface.hpp index 2e5ce43..48dfb9c 100644 --- a/LLMClientInterface.hpp +++ b/LLMClientInterface.hpp @@ -22,8 +22,10 @@ #include #include -#include "QodeAssistData.hpp" -#include "core/LLMRequestHandler.hpp" +// #include "QodeAssistData.hpp" +// #include "core/LLMRequestHandler.hpp" +#include +#include class QNetworkReply; class QNetworkAccessManager; @@ -58,10 +60,10 @@ private: void handleExit(const QJsonObject &request); void handleCancelRequest(const QJsonObject &request); - ContextData prepareContext(const QJsonObject &request, - const QStringView &accumulatedCompletion = QString{}); + LLMCore::ContextData prepareContext(const QJsonObject &request, + const QStringView &accumulatedCompletion = QString{}); - LLMRequestHandler m_requestHandler; + LLMCore::RequestHandler m_requestHandler; QElapsedTimer m_completionTimer; QMap m_requestStartTimes; diff --git a/QodeAssistConstants.hpp b/QodeAssistConstants.hpp index dd4c88d..04c6202 100644 --- a/QodeAssistConstants.hpp +++ b/QodeAssistConstants.hpp @@ -24,60 +24,6 @@ namespace QodeAssist::Constants { const char ACTION_ID[] = "QodeAssist.Action"; const char MENU_ID[] = "QodeAssist.Menu"; -// settings -const char ENABLE_QODE_ASSIST[] = "QodeAssist.enableQodeAssist"; -const char ENABLE_AUTO_COMPLETE[] = "QodeAssist.enableAutoComplete"; -const char ENABLE_LOGGING[] = "QodeAssist.enableLogging"; -const char LLM_PROVIDERS[] = "QodeAssist.llmProviders"; -const char URL[] = "QodeAssist.url"; -const char END_POINT[] = "QodeAssist.endPoint"; -const char MODEL_NAME[] = "QodeAssist.modelName"; -const char SELECT_MODELS[] = "QodeAssist.selectModels"; -const char FIM_PROMPTS[] = "QodeAssist.fimPrompts"; -const char TEMPERATURE[] = "QodeAssist.temperature"; -const char MAX_TOKENS[] = "QodeAssist.maxTokens"; -const char READ_FULL_FILE[] = "QodeAssist.readFullFile"; -const char READ_STRINGS_BEFORE_CURSOR[] = "QodeAssist.readStringsBeforeCursor"; -const char READ_STRINGS_AFTER_CURSOR[] = "QodeAssist.readStringsAfterCursor"; -const char USE_TOP_P[] = "QodeAssist.useTopP"; -const char TOP_P[] = "QodeAssist.topP"; -const char USE_TOP_K[] = "QodeAssist.useTopK"; -const char TOP_K[] = "QodeAssist.topK"; -const char USE_PRESENCE_PENALTY[] = "QodeAssist.usePresencePenalty"; -const char PRESENCE_PENALTY[] = "QodeAssist.presencePenalty"; -const char USE_FREQUENCY_PENALTY[] = "QodeAssist.useFrequencyPenalty"; -const char FREQUENCY_PENALTY[] = "QodeAssist.frequencyPenalty"; -const char PROVIDER_PATHS[] = "QodeAssist.providerPaths"; -const char START_SUGGESTION_TIMER[] = "QodeAssist.startSuggestionTimer"; -const char AUTO_COMPLETION_CHAR_THRESHOLD[] = "QodeAssist.autoCompletionCharThreshold"; -const char AUTO_COMPLETION_TYPING_INTERVAL[] = "QodeAssist.autoCompletionTypingInterval"; -const char MAX_FILE_THRESHOLD[] = "QodeAssist.maxFileThreshold"; -const char OLLAMA_LIVETIME[] = "QodeAssist.ollamaLivetime"; -const char SYSTEM_PROMPT[] = "QodeAssist.systemPrompt"; -const char MULTILINE_COMPLETION[] = "QodeAssist.multilineCompletion"; -const char API_KEY[] = "QodeAssist.apiKey"; -const char USE_SYSTEM_PROMPT[] = "QodeAssist.useSystemPrompt"; -const char USE_FILE_PATH_IN_CONTEXT[] = "QodeAssist.useFilePathInContext"; -const char CUSTOM_JSON_TEMPLATE[] = "QodeAssist.customJsonTemplate"; -const char USE_PROJECT_CHANGES_CACHE[] = "QodeAssist.useProjectChangesCache"; -const char MAX_CHANGES_CACHE_SIZE[] = "QodeAssist.maxChangesCacheSize"; -const char CHAT_LLM_PROVIDERS[] = "QodeAssist.chatLlmProviders"; -const char CHAT_URL[] = "QodeAssist.chatUrl"; -const char CHAT_END_POINT[] = "QodeAssist.chatEndPoint"; -const char CHAT_MODEL_NAME[] = "QodeAssist.chatModelName"; -const char CHAT_SELECT_MODELS[] = "QodeAssist.chatSelectModels"; -const char CHAT_PROMPTS[] = "QodeAssist.chatPrompts"; - -const char QODE_ASSIST_GENERAL_OPTIONS_ID[] = "QodeAssist.GeneralOptions"; -const char QODE_ASSIST_GENERAL_SETTINGS_PAGE_ID[] = "QodeAssist.1GeneralSettingsPageId"; -const char QODE_ASSIST_CONTEXT_SETTINGS_PAGE_ID[] = "QodeAssist.2ContextSettingsPageId"; -const char QODE_ASSIST_PRESET_PROMPTS_SETTINGS_PAGE_ID[] - = "QodeAssist.3PresetPromptsSettingsPageId"; -const char QODE_ASSIST_CUSTOM_PROMPT_SETTINGS_PAGE_ID[] = "QodeAssist.4CustomPromptSettingsPageId"; - -const char QODE_ASSIST_GENERAL_OPTIONS_CATEGORY[] = "QodeAssist.Category"; -const char QODE_ASSIST_GENERAL_OPTIONS_DISPLAY_CATEGORY[] = "Qode Assist"; - const char QODE_ASSIST_REQUEST_SUGGESTION[] = "QodeAssist.RequestSuggestion"; } // namespace QodeAssist::Constants diff --git a/QodeAssistUtils.hpp b/QodeAssistUtils.hpp deleted file mode 100644 index 1087f36..0000000 --- a/QodeAssistUtils.hpp +++ /dev/null @@ -1,107 +0,0 @@ -/* - * 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 . - */ - -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace QodeAssist { - -inline bool &loggingEnabled() -{ - static bool enabled = false; - return enabled; -} - -inline void setLoggingEnabled(bool enable) -{ - loggingEnabled() = enable; -} - -inline void logMessage(const QString &message, bool silent = true) -{ - if (!loggingEnabled()) - return; - - const QString prefixedMessage = QLatin1String("[Qode Assist] ") + message; - if (silent) { - Core::MessageManager::writeSilently(prefixedMessage); - } else { - Core::MessageManager::writeFlashing(prefixedMessage); - } -} - -inline void logMessages(const QStringList &messages, bool silent = true) -{ - if (!loggingEnabled()) - return; - - QStringList prefixedMessages; - qDebug() << prefixedMessages; - - for (const QString &message : messages) { - prefixedMessages << (QLatin1String("[Qode Assist] ") + message); - } - if (silent) { - Core::MessageManager::writeSilently(prefixedMessages); - } else { - Core::MessageManager::writeFlashing(prefixedMessages); - } -} - -inline bool pingUrl(const QUrl &url, int timeout = 5000) -{ - if (!url.isValid()) { - return false; - } - - QNetworkAccessManager manager; - QNetworkRequest request(url); - request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, true); - - QScopedPointer reply(manager.get(request)); - - QTimer timer; - timer.setSingleShot(true); - - QEventLoop loop; - QObject::connect(reply.data(), &QNetworkReply::finished, &loop, &QEventLoop::quit); - QObject::connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit); - - timer.start(timeout); - loop.exec(); - - if (timer.isActive()) { - timer.stop(); - return (reply->error() == QNetworkReply::NoError); - } else { - QObject::disconnect(reply.data(), &QNetworkReply::finished, &loop, &QEventLoop::quit); - reply->abort(); - return false; - } -} - -} // namespace QodeAssist diff --git a/chat/ChatClientInterface.cpp b/chat/ChatClientInterface.cpp deleted file mode 100644 index de6808f..0000000 --- a/chat/ChatClientInterface.cpp +++ /dev/null @@ -1,192 +0,0 @@ -/* - * 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 . - */ - -#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 -#include -#include - -namespace QodeAssist::Chat { - -int ChatHistory::estimateTokenCount(const QString &text) const -{ - return text.length() / 4; -} - -void ChatHistory::addMessage(ChatMessage::Role role, const QString &content) -{ - int tokenCount = estimateTokenCount(content); - m_messages.append({role, content, tokenCount}); - m_totalTokens += tokenCount; - trim(); -} -void ChatHistory::clear() -{ - m_messages.clear(); - m_totalTokens = 0; -} - -QVector ChatHistory::getMessages() const -{ - return m_messages; -} - -QString ChatHistory::getSystemPrompt() const -{ - return m_systemPrompt; -} - -void ChatHistory::setSystemPrompt(const QString &prompt) -{ - m_systemPrompt = prompt; -} - -void ChatHistory::trim() -{ - while (m_messages.size() > MAX_HISTORY_SIZE || m_totalTokens > MAX_TOKENS) { - if (!m_messages.isEmpty()) { - m_totalTokens -= m_messages.first().tokenCount; - m_messages.removeFirst(); - } else { - break; - } - } -} - -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); - } - }); - - m_chatHistory.setSystemPrompt("You are a helpful C++ and QML programming assistant."); -} - -ChatClientInterface::~ChatClientInterface() = default; - -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"] = prepareMessagesForRequest(); - - chatTemplate->prepareRequest(providerRequest, context); - chatProvider->prepareRequest(providerRequest); - - 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_chatHistory.addMessage(ChatMessage::Role::User, message); - m_requestHandler->sendLLMRequest(config, request); -} - -void ChatClientInterface::clearMessages() -{ - m_chatHistory.clear(); - m_accumulatedResponse.clear(); - logMessage("Chat history cleared"); -} - -QVector ChatClientInterface::getChatHistory() const -{ - return m_chatHistory.getMessages(); -} - -void ChatClientInterface::handleLLMResponse(const QString &response, bool isComplete) -{ - m_accumulatedResponse += response; - - if (isComplete) { - logMessage("Message completed. Final response: " + m_accumulatedResponse); - emit messageReceived(m_accumulatedResponse.trimmed()); - - m_chatHistory.addMessage(ChatMessage::Role::Assistant, m_accumulatedResponse.trimmed()); - m_accumulatedResponse.clear(); - } -} - -QJsonArray ChatClientInterface::prepareMessagesForRequest() const -{ - QJsonArray messages; - - messages.append(QJsonObject{{"role", "system"}, {"content", m_chatHistory.getSystemPrompt()}}); - - for (const auto &message : m_chatHistory.getMessages()) { - QString role; - switch (message.role) { - case ChatMessage::Role::User: - role = "user"; - break; - case ChatMessage::Role::Assistant: - role = "assistant"; - break; - default: - continue; - } - messages.append(QJsonObject{{"role", role}, {"content", message.content}}); - } - - return messages; -} - -} // namespace QodeAssist::Chat diff --git a/chat/ChatOutputPane.h b/chat/ChatOutputPane.h index 12e8267..ff8fb92 100644 --- a/chat/ChatOutputPane.h +++ b/chat/ChatOutputPane.h @@ -19,7 +19,7 @@ #pragma once -#include "ChatWidget.h" +#include "chatview/ChatWidget.hpp" #include namespace QodeAssist::Chat { diff --git a/chat/ChatWidget.cpp b/chat/ChatWidget.cpp deleted file mode 100644 index b99dbad..0000000 --- a/chat/ChatWidget.cpp +++ /dev/null @@ -1,147 +0,0 @@ -/* - * 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 . - */ - -#include "ChatWidget.h" -#include "QodeAssistUtils.hpp" - -#include -#include -#include -#include -#include - -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) -{ - updateLastAIMessage(message); -} - -void ChatWidget::receivePartialMessage(const QString &partialMessage) -{ - logMessage("Received partial message: " + partialMessage); - m_currentAIResponse += partialMessage; - updateLastAIMessage(m_currentAIResponse); -} - -void ChatWidget::onMessageCompleted() -{ - 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; - m_chatDisplay->append(fullMessage); - scrollToBottom(); -} - -void ChatWidget::updateLastAIMessage(const QString &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(); - m_chatClient->clearMessages(); -} - -void ChatWidget::scrollToBottom() -{ - QScrollBar *scrollBar = m_chatDisplay->verticalScrollBar(); - scrollBar->setValue(scrollBar->maximum()); -} - -void ChatWidget::setShowTimestamp(bool show) -{ - m_showTimestamp = show; -} - -} // namespace QodeAssist::Chat diff --git a/chat/ChatWidget.h b/chat/ChatWidget.h deleted file mode 100644 index cef4f64..0000000 --- a/chat/ChatWidget.h +++ /dev/null @@ -1,63 +0,0 @@ -/* - * 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 . - */ - -#pragma once - -#include -#include -#include -#include - -#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 diff --git a/chat/NavigationPanel.cpp b/chat/NavigationPanel.cpp index 19b9211..d57eee7 100644 --- a/chat/NavigationPanel.cpp +++ b/chat/NavigationPanel.cpp @@ -19,7 +19,7 @@ #include "NavigationPanel.hpp" -#include "chatview/BaseChatWidget.hpp" +#include "chatview/ChatWidget.hpp" namespace QodeAssist::Chat { @@ -37,7 +37,7 @@ NavigationPanel::~NavigationPanel() Core::NavigationView NavigationPanel::createWidget() { Core::NavigationView view; - view.widget = new BaseChatWidget; + view.widget = new ChatWidget; return view; } diff --git a/chatview/CMakeLists.txt b/chatview/CMakeLists.txt index cdba3b7..f2c1f99 100644 --- a/chatview/CMakeLists.txt +++ b/chatview/CMakeLists.txt @@ -1,24 +1,33 @@ qt_add_library(QodeAssistChatView STATIC) -find_package(Qt6 COMPONENTS Core Widgets Quick QuickWidgets Network REQUIRED) - qt_add_qml_module(QodeAssistChatView URI ChatView VERSION 1.0 QML_FILES qml/RootItem.qml + qml/ChatItem.qml + qml/Badge.qml + qml/dialog/CodeBlock.qml + qml/dialog/TextBlock.qml SOURCES - BaseChatWidget.hpp BaseChatWidget.cpp + ChatWidget.hpp ChatWidget.cpp ChatModel.hpp ChatModel.cpp ChatRootView.hpp ChatRootView.cpp + ClientInterface.hpp ClientInterface.cpp + MessagePart.hpp + ChatUtils.h ChatUtils.cpp ) target_link_libraries(QodeAssistChatView - PRIVATE + PUBLIC Qt::Widgets Qt::Quick Qt::QuickWidgets Qt::Network + QtCreator::Core + QtCreator::Utils + LLMCore + QodeAssistSettings ) target_include_directories(QodeAssistChatView diff --git a/chatview/ChatModel.cpp b/chatview/ChatModel.cpp index 67eda66..b669255 100644 --- a/chatview/ChatModel.cpp +++ b/chatview/ChatModel.cpp @@ -18,12 +18,24 @@ */ #include "ChatModel.hpp" +#include +#include +#include + +#include "GeneralSettings.hpp" namespace QodeAssist::Chat { ChatModel::ChatModel(QObject *parent) : QAbstractListModel(parent) + , m_totalTokens(0) { + auto &settings = Settings::generalSettings(); + + connect(&settings.chatTokensThreshold, + &Utils::BaseAspect::changed, + this, + &ChatModel::tokensThresholdChanged); } int ChatModel::rowCount(const QModelIndex &parent) const @@ -40,8 +52,9 @@ QVariant ChatModel::data(const QModelIndex &index, int role) const switch (static_cast(role)) { case Roles::RoleType: return QVariant::fromValue(message.role); - case Roles::Content: + case Roles::Content: { return message.content; + } default: return QVariant(); } @@ -55,18 +68,112 @@ QHash ChatModel::roleNames() const return roles; } +QVector ChatModel::getChatHistory() const +{ + return m_messages; +} + +void ChatModel::trim() +{ + while (m_totalTokens > tokensThreshold()) { + if (!m_messages.isEmpty()) { + m_totalTokens -= m_messages.first().tokenCount; + beginRemoveRows(QModelIndex(), 0, 0); + m_messages.removeFirst(); + endRemoveRows(); + } else { + break; + } + } +} + +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}); + m_messages.append({role, content, tokenCount}); + m_totalTokens += tokenCount; endInsertRows(); + trim(); + emit totalTokensChanged(); } void ChatModel::clear() { beginResetModel(); m_messages.clear(); + m_totalTokens = 0; endResetModel(); + emit totalTokensChanged(); +} + +QList ChatModel::processMessageContent(const QString &content) const +{ + QList parts; + QRegularExpression codeBlockRegex("```(\\w*)\\n?([\\s\\S]*?)```"); + int lastIndex = 0; + auto blockMatches = codeBlockRegex.globalMatch(content); + + while (blockMatches.hasNext()) { + auto match = blockMatches.next(); + if (match.capturedStart() > lastIndex) { + QString textBetween = content.mid(lastIndex, match.capturedStart() - lastIndex).trimmed(); + if (!textBetween.isEmpty()) { + parts.append({MessagePart::Text, textBetween, ""}); + } + } + parts.append({MessagePart::Code, match.captured(2).trimmed(), match.captured(1)}); + lastIndex = match.capturedEnd(); + } + + if (lastIndex < content.length()) { + QString remainingText = content.mid(lastIndex).trimmed(); + if (!remainingText.isEmpty()) { + parts.append({MessagePart::Text, remainingText, ""}); + } + } + + return parts; +} + +QJsonArray ChatModel::prepareMessagesForRequest(LLMCore::ContextData context) const +{ + QJsonArray messages; + + messages.append(QJsonObject{{"role", "system"}, {"content", context.systemPrompt}}); + + for (const auto &message : m_messages) { + QString role; + switch (message.role) { + case ChatRole::User: + role = "user"; + break; + case ChatRole::Assistant: + role = "assistant"; + break; + default: + continue; + } + messages.append(QJsonObject{{"role", role}, {"content", message.content}}); + } + + return messages; +} + +int ChatModel::totalTokens() const +{ + return m_totalTokens; +} + +int ChatModel::tokensThreshold() const +{ + auto &settings = Settings::generalSettings(); + return settings.chatTokensThreshold(); } } // namespace QodeAssist::Chat diff --git a/chatview/ChatModel.hpp b/chatview/ChatModel.hpp index 2058d34..b13630b 100644 --- a/chatview/ChatModel.hpp +++ b/chatview/ChatModel.hpp @@ -19,25 +19,35 @@ #pragma once +#include "ContextData.hpp" +#include "MessagePart.hpp" + #include +#include +#include namespace QodeAssist::Chat { -enum class ChatRole { System, User, Assistant }; - -struct Message -{ - ChatRole role; - QString content; -}; - class ChatModel : public QAbstractListModel { Q_OBJECT + Q_PROPERTY(int totalTokens READ totalTokens NOTIFY totalTokensChanged FINAL) + Q_PROPERTY(int tokensThreshold READ tokensThreshold NOTIFY tokensThresholdChanged FINAL) + QML_ELEMENT public: enum Roles { RoleType = Qt::UserRole, Content }; + enum ChatRole { System, User, Assistant }; + Q_ENUM(ChatRole) + + struct Message + { + ChatRole role; + QString content; + int tokenCount; + }; + explicit ChatModel(QObject *parent = nullptr); int rowCount(const QModelIndex &parent = QModelIndex()) const override; @@ -46,11 +56,28 @@ public: Q_INVOKABLE void addMessage(const QString &content, ChatRole role); Q_INVOKABLE void clear(); + Q_INVOKABLE QList processMessageContent(const QString &content) const; + + QVector getChatHistory() const; + QJsonArray prepareMessagesForRequest(LLMCore::ContextData context) const; + + int totalTokens() const; + int tokensThreshold() const; + + QString currentModel() const; + +signals: + void totalTokensChanged(); + void tokensThresholdChanged(); private: + void trim(); + int estimateTokenCount(const QString &text) const; + QVector m_messages; + int m_totalTokens = 0; }; } // namespace QodeAssist::Chat - -Q_DECLARE_METATYPE(QodeAssist::Chat::ChatRole) +Q_DECLARE_METATYPE(QodeAssist::Chat::ChatModel::Message) +Q_DECLARE_METATYPE(QodeAssist::Chat::MessagePart) diff --git a/chatview/ChatRootView.cpp b/chatview/ChatRootView.cpp index c98f1f4..c04f01a 100644 --- a/chatview/ChatRootView.cpp +++ b/chatview/ChatRootView.cpp @@ -18,17 +18,110 @@ */ #include "ChatRootView.hpp" +#include +#include +#include + +#include "GeneralSettings.hpp" namespace QodeAssist::Chat { ChatRootView::ChatRootView(QQuickItem *parent) : QQuickItem(parent) , m_chatModel(new ChatModel(this)) -{} + , m_clientInterface(new ClientInterface(m_chatModel, this)) +{ + auto &settings = Settings::generalSettings(); + + connect(&settings.chatModelName, + &Utils::BaseAspect::changed, + this, + &ChatRootView::currentTemplateChanged); + generateColors(); +} ChatModel *ChatRootView::chatModel() const { return m_chatModel; } +QColor ChatRootView::backgroundColor() const +{ + return Utils::creatorColor(Utils::Theme::BackgroundColorNormal); +} + +void ChatRootView::sendMessage(const QString &message) const +{ + m_clientInterface->sendMessage(message); +} + +void ChatRootView::copyToClipboard(const QString &text) +{ + QGuiApplication::clipboard()->setText(text); +} + +void ChatRootView::generateColors() +{ + QColor baseColor = backgroundColor(); + bool isDarkTheme = baseColor.lightness() < 128; + + if (isDarkTheme) { + m_primaryColor = generateColor(baseColor, 0.1, 1.2, 1.4); + m_secondaryColor = generateColor(baseColor, -0.1, 1.1, 1.2); + m_codeColor = generateColor(baseColor, 0.05, 0.8, 1.1); + } else { + m_primaryColor = generateColor(baseColor, 0.05, 1.05, 1.1); + m_secondaryColor = generateColor(baseColor, -0.05, 1.1, 1.2); + m_codeColor = generateColor(baseColor, 0.02, 0.95, 1.05); + } +} + +QColor ChatRootView::generateColor(const QColor &baseColor, + float hueShift, + float saturationMod, + float lightnessMod) +{ + float h, s, l, a; + baseColor.getHslF(&h, &s, &l, &a); + bool isDarkTheme = l < 0.5; + + h = fmod(h + hueShift + 1.0, 1.0); + + s = qBound(0.0f, s * saturationMod, 1.0f); + + if (isDarkTheme) { + l = qBound(0.0f, l * lightnessMod, 1.0f); + } else { + l = qBound(0.0f, l / lightnessMod, 1.0f); + } + + h = qBound(0.0f, h, 1.0f); + s = qBound(0.0f, s, 1.0f); + l = qBound(0.0f, l, 1.0f); + a = qBound(0.0f, a, 1.0f); + + return QColor::fromHslF(h, s, l, a); +} + +QString ChatRootView::currentTemplate() const +{ + auto &settings = Settings::generalSettings(); + return settings.chatModelName(); +} + +QColor ChatRootView::primaryColor() const +{ + return m_primaryColor; +} + +QColor ChatRootView::secondaryColor() const +{ + return m_secondaryColor; +} + +QColor ChatRootView::codeColor() const +{ + return m_codeColor; +} + } // namespace QodeAssist::Chat diff --git a/chatview/ChatRootView.hpp b/chatview/ChatRootView.hpp index 9f83ed7..2f32ec6 100644 --- a/chatview/ChatRootView.hpp +++ b/chatview/ChatRootView.hpp @@ -22,25 +22,54 @@ #include #include "ChatModel.hpp" +#include "ClientInterface.hpp" namespace QodeAssist::Chat { class ChatRootView : public QQuickItem { - Q_OBJECT + Q_OBJECT + Q_PROPERTY(ChatModel *chatModel READ chatModel NOTIFY chatModelChanged FINAL) + Q_PROPERTY(QString currentTemplate READ currentTemplate NOTIFY currentTemplateChanged FINAL) + Q_PROPERTY(QColor backgroundColor READ backgroundColor CONSTANT FINAL) + Q_PROPERTY(QColor primaryColor READ primaryColor CONSTANT FINAL) + Q_PROPERTY(QColor secondaryColor READ secondaryColor CONSTANT FINAL) + Q_PROPERTY(QColor codeColor READ codeColor CONSTANT FINAL) QML_ELEMENT - Q_PROPERTY(ChatModel *chatModel READ chatModel NOTIFY chatModelChanged FINAL) public: ChatRootView(QQuickItem *parent = nullptr); ChatModel *chatModel() const; + QString currentTemplate() const; + + QColor backgroundColor() const; + QColor primaryColor() const; + QColor secondaryColor() const; + + QColor codeColor() const; + +public slots: + void sendMessage(const QString &message) const; + void copyToClipboard(const QString &text); signals: void chatModelChanged(); + void currentTemplateChanged(); private: - ChatModel *m_chatModel = nullptr; + void generateColors(); + QColor generateColor(const QColor &baseColor, + float hueShift, + float saturationMod, + float lightnessMod); + + ChatModel *m_chatModel; + ClientInterface *m_clientInterface; + QString m_currentTemplate; + QColor m_primaryColor; + QColor m_secondaryColor; + QColor m_codeColor; }; } // namespace QodeAssist::Chat diff --git a/chatview/ChatUtils.cpp b/chatview/ChatUtils.cpp new file mode 100644 index 0000000..2b41f56 --- /dev/null +++ b/chatview/ChatUtils.cpp @@ -0,0 +1,14 @@ +#include "ChatUtils.h" + +#include +#include + +namespace QodeAssist::Chat { + +void ChatUtils::copyToClipboard(const QString &text) +{ + qDebug() << "call clipboard" << text; + QGuiApplication::clipboard()->setText(text); +} + +} // namespace QodeAssist::Chat diff --git a/chatview/ChatUtils.h b/chatview/ChatUtils.h new file mode 100644 index 0000000..fd2c3a6 --- /dev/null +++ b/chatview/ChatUtils.h @@ -0,0 +1,21 @@ +#pragma once + +#include +#include + +namespace QodeAssist::Chat { +// Q_NAMESPACE + +class ChatUtils : public QObject +{ + Q_OBJECT + QML_NAMED_ELEMENT(ChatUtils) + +public: + explicit ChatUtils(QObject *parent = nullptr) + : QObject(parent) {}; + + Q_INVOKABLE void copyToClipboard(const QString &text); +}; + +} // namespace QodeAssist::Chat diff --git a/chatview/BaseChatWidget.cpp b/chatview/ChatWidget.cpp similarity index 76% rename from chatview/BaseChatWidget.cpp rename to chatview/ChatWidget.cpp index bb16c20..7963e5a 100644 --- a/chatview/BaseChatWidget.cpp +++ b/chatview/ChatWidget.cpp @@ -17,19 +17,27 @@ * along with QodeAssist. If not, see . */ -#include "BaseChatWidget.hpp" +#include "ChatWidget.hpp" -#include #include +#include namespace QodeAssist::Chat { -BaseChatWidget::BaseChatWidget(QWidget *parent) : QQuickWidget(parent) +ChatWidget::ChatWidget(QWidget *parent) + : QQuickWidget(parent) { setSource(QUrl("qrc:/ChatView/qml/RootItem.qml")); setResizeMode(QQuickWidget::SizeRootObjectToView); - - engine()->rootContext()->setContextObject(this); } +void ChatWidget::clear() +{ + QMetaObject::invokeMethod(rootObject(), "clearChat"); +} + +void ChatWidget::scrollToBottom() +{ + QMetaObject::invokeMethod(rootObject(), "scrollToBottom"); +} } diff --git a/chatview/BaseChatWidget.hpp b/chatview/ChatWidget.hpp similarity index 78% rename from chatview/BaseChatWidget.hpp rename to chatview/ChatWidget.hpp index e255672..e7a3c9d 100644 --- a/chatview/BaseChatWidget.hpp +++ b/chatview/ChatWidget.hpp @@ -23,13 +23,19 @@ namespace QodeAssist::Chat { -class BaseChatWidget : public QQuickWidget +class ChatWidget : public QQuickWidget { Q_OBJECT public: - explicit BaseChatWidget(QWidget *parent = nullptr); - ~BaseChatWidget() = default; + explicit ChatWidget(QWidget *parent = nullptr); + ~ChatWidget() = default; + + Q_INVOKABLE void clear(); + Q_INVOKABLE void scrollToBottom(); + +signals: + void clearPressed(); }; } diff --git a/chatview/ClientInterface.cpp b/chatview/ClientInterface.cpp new file mode 100644 index 0000000..4b0b114 --- /dev/null +++ b/chatview/ClientInterface.cpp @@ -0,0 +1,117 @@ +/* + * 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 . + */ + +#include "ClientInterface.hpp" +#include "ContextSettings.hpp" +#include "GeneralSettings.hpp" +#include "Logger.hpp" +#include "PromptTemplateManager.hpp" +#include "ProvidersManager.hpp" + +#include +#include +#include + +namespace QodeAssist::Chat { + +ClientInterface::ClientInterface(ChatModel *chatModel, QObject *parent) + : QObject(parent) + , m_requestHandler(new LLMCore::RequestHandler(this)) + , m_chatModel(chatModel) +{ + connect(m_requestHandler, + &LLMCore::RequestHandler::completionReceived, + this, + [this](const QString &completion, const QJsonObject &, bool isComplete) { + handleLLMResponse(completion, isComplete); + }); + + connect(m_requestHandler, + &LLMCore::RequestHandler::requestFinished, + this, + [this](const QString &, bool success, const QString &errorString) { + if (!success) { + emit errorOccurred(errorString); + } + }); +} + +ClientInterface::~ClientInterface() = default; + +void ClientInterface::sendMessage(const QString &message) +{ + LOG_MESSAGE("Sending message: " + message); + LOG_MESSAGE("chatProvider " + Settings::generalSettings().chatLlmProviders.stringValue()); + LOG_MESSAGE("chatTemplate " + Settings::generalSettings().chatPrompts.stringValue()); + + auto chatTemplate = LLMCore::PromptTemplateManager::instance().getCurrentChatTemplate(); + auto chatProvider = LLMCore::ProvidersManager::instance().getCurrentChatProvider(); + + LLMCore::ContextData context; + context.prefix = message; + context.suffix = ""; + if (Settings::contextSettings().useChatSystemPrompt()) + context.systemPrompt = Settings::contextSettings().chatSystemPrompt(); + + QJsonObject providerRequest; + providerRequest["model"] = Settings::generalSettings().chatModelName(); + providerRequest["stream"] = true; + providerRequest["messages"] = m_chatModel->prepareMessagesForRequest(context); + + chatTemplate->prepareRequest(providerRequest, context); + chatProvider->prepareRequest(providerRequest, LLMCore::RequestType::Chat); + + LLMCore::LLMConfig config; + config.requestType = LLMCore::RequestType::Chat; + config.provider = chatProvider; + config.promptTemplate = chatTemplate; + config.url = QString("%1%2").arg(Settings::generalSettings().chatUrl(), + Settings::generalSettings().chatEndPoint()); + config.providerRequest = providerRequest; + config.multiLineCompletion = Settings::generalSettings().multiLineCompletion(); + + QJsonObject request; + request["id"] = QUuid::createUuid().toString(); + + m_accumulatedResponse.clear(); + 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) +{ + m_accumulatedResponse += response; + + 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(); + } +} + +} // namespace QodeAssist::Chat diff --git a/chat/ChatClientInterface.hpp b/chatview/ClientInterface.hpp similarity index 54% rename from chat/ChatClientInterface.hpp rename to chatview/ClientInterface.hpp index 5bb3d71..24863ce 100644 --- a/chat/ChatClientInterface.hpp +++ b/chatview/ClientInterface.hpp @@ -22,50 +22,22 @@ #include #include #include -#include "QodeAssistData.hpp" -#include "core/LLMRequestHandler.hpp" + +#include "ChatModel.hpp" +#include "RequestHandler.hpp" namespace QodeAssist::Chat { -struct ChatMessage -{ - enum class Role { System, User, Assistant }; - Role role; - QString content; - int tokenCount; -}; - -class ChatHistory -{ -public: - void addMessage(ChatMessage::Role role, const QString &content); - void clear(); - QVector getMessages() const; - QString getSystemPrompt() const; - void setSystemPrompt(const QString &prompt); - void trim(); - -private: - QVector m_messages; - QString m_systemPrompt; - int m_totalTokens = 0; - static const int MAX_HISTORY_SIZE = 50; - static const int MAX_TOKENS = 4000; - - int estimateTokenCount(const QString &text) const; -}; - -class ChatClientInterface : public QObject +class ClientInterface : public QObject { Q_OBJECT public: - explicit ChatClientInterface(QObject *parent = nullptr); - ~ChatClientInterface(); + explicit ClientInterface(ChatModel *chatModel, QObject *parent = nullptr); + ~ClientInterface(); void sendMessage(const QString &message); void clearMessages(); - QVector getChatHistory() const; signals: void messageReceived(const QString &message); @@ -73,11 +45,10 @@ signals: private: void handleLLMResponse(const QString &response, bool isComplete); - QJsonArray prepareMessagesForRequest() const; - LLMRequestHandler *m_requestHandler; + LLMCore::RequestHandler *m_requestHandler; QString m_accumulatedResponse; - ChatHistory m_chatHistory; + ChatModel *m_chatModel; }; } // namespace QodeAssist::Chat diff --git a/chatview/MessagePart.hpp b/chatview/MessagePart.hpp new file mode 100644 index 0000000..46c4a64 --- /dev/null +++ b/chatview/MessagePart.hpp @@ -0,0 +1,51 @@ +/* + * 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 . + */ + +#pragma once + +#include +#include + +namespace QodeAssist::Chat { +Q_NAMESPACE + +class MessagePart +{ + Q_GADGET + Q_PROPERTY(PartType type MEMBER type CONSTANT FINAL) + Q_PROPERTY(QString text MEMBER text CONSTANT FINAL) + Q_PROPERTY(QString language MEMBER language CONSTANT FINAL) + QML_VALUE_TYPE(messagePart) +public: + enum PartType { Code, Text }; + Q_ENUM(PartType) + + PartType type; + QString text; + QString language; +}; + +class MessagePartType : public MessagePart +{ + Q_GADGET +}; + +QML_NAMED_ELEMENT(MessagePart) +QML_FOREIGN_NAMESPACE(QodeAssist::Chat::MessagePartType) +} // namespace QodeAssist::Chat diff --git a/chatview/qml/Badge.qml b/chatview/qml/Badge.qml new file mode 100644 index 0000000..b227376 --- /dev/null +++ b/chatview/qml/Badge.qml @@ -0,0 +1,40 @@ +/* + * 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 . + */ + +import QtQuick + +Rectangle { + id: root + + property alias text: badgeText.text + property alias fontColor: badgeText.color + + width: badgeText.implicitWidth + radius + height: badgeText.implicitHeight + 6 + color: "lightgreen" + radius: height / 2 + border.width: 1 + border.color: "gray" + + Text { + id: badgeText + + anchors.centerIn: parent + } +} diff --git a/chatview/qml/ChatItem.qml b/chatview/qml/ChatItem.qml new file mode 100644 index 0000000..457f306 --- /dev/null +++ b/chatview/qml/ChatItem.qml @@ -0,0 +1,91 @@ +/* + * 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 . + */ + +import QtQuick +import QtQuick.Controls +import ChatView +import "./dialog" + +Rectangle { + id: root + + property alias msgModel: msgCreator.model + property color fontColor + property color codeBgColor + property color selectionColor + + height: msgColumn.height + radius: 8 + + Column { + id: msgColumn + + anchors.verticalCenter: parent.verticalCenter + width: parent.width + spacing: 5 + + Repeater { + id: msgCreator + delegate: Loader { + property var itemData: modelData + + width: parent.width + sourceComponent: { + switch(modelData.type) { + case MessagePart.Text: return textComponent; + case MessagePart.Code: return codeBlockComponent; + default: return textComponent; + } + } + } + } + } + + Component { + id: textComponent + + TextBlock { + height: implicitHeight + 10 + verticalAlignment: Text.AlignVCenter + leftPadding: 10 + text: itemData.text + color: fontColor + selectionColor: root.selectionColor + } + } + + + Component { + id: codeBlockComponent + + CodeBlock { + anchors { + left: parent.left + leftMargin: 10 + right: parent.right + rightMargin: 10 + } + + code: itemData.text + language: itemData.language + color: root.codeBgColor + selectionColor: root.selectionColor + } + } +} diff --git a/chatview/qml/RootItem.qml b/chatview/qml/RootItem.qml index f166a45..4a41fae 100644 --- a/chatview/qml/RootItem.qml +++ b/chatview/qml/RootItem.qml @@ -1,4 +1,26 @@ +/* + * 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 . + */ + import QtQuick +import QtQuick.Controls +import QtQuick.Controls.Basic as QQC +import QtQuick.Layouts import ChatView ChatRootView { @@ -6,8 +28,134 @@ ChatRootView { Rectangle { id: bg - anchors.fill: parent - color: "gray" + color: root.backgroundColor + } + + ColumnLayout { + anchors { + fill: parent + } + spacing: 10 + + ListView { + id: chatListView + + Layout.fillWidth: true + Layout.fillHeight: true + leftMargin: 5 + model: root.chatModel + clip: true + spacing: 10 + boundsBehavior: Flickable.StopAtBounds + cacheBuffer: 2000 + + delegate: ChatItem { + width: ListView.view.width - scroll.width + msgModel: root.chatModel.processMessageContent(model.content) + color: model.roleType === ChatModel.User ? root.primaryColor : root.secondaryColor + fontColor: root.primaryColor.hslLightness > 0.5 ? "black" : "white" + codeBgColor: root.codeColor + selectionColor: root.primaryColor.hslLightness > 0.5 ? Qt.darker(root.primaryColor, 1.5) + : Qt.lighter(root.primaryColor, 1.5) + + } + + header: Item { + width: ListView.view.width - scroll.width + height: 30 + } + + ScrollBar.vertical: ScrollBar { + id: scroll + } + + onCountChanged: { + scrollToBottom() + } + + onContentHeightChanged: { + if (atYEnd) { + scrollToBottom() + } + } + } + + RowLayout { + Layout.fillWidth: true + spacing: 5 + + QQC.TextField { + id: messageInput + + Layout.fillWidth: true + Layout.minimumWidth: 60 + Layout.minimumHeight: 30 + rightInset: -(parent.width - sendButton.width - clearButton.width) + placeholderText: qsTr("Type your message here...") + placeholderTextColor: "#888" + color: root.primaryColor.hslLightness > 0.5 ? "black" : "white" + background: Rectangle { + radius: 2 + color: root.primaryColor + border.color: root.primaryColor.hslLightness > 0.5 ? Qt.lighter(root.primaryColor, 1.5) + : Qt.darker(root.primaryColor, 1.5) + border.width: 1 + } + + onAccepted: sendButton.clicked() + } + + Button { + id: sendButton + + Layout.alignment: Qt.AlignVCenter + Layout.minimumHeight: 30 + text: qsTr("Send") + onClicked: { + if (messageInput.text.trim() !== "") { + root.sendMessage(messageInput.text); + messageInput.text = "" + scrollToBottom() + } + } + } + Button { + id: clearButton + + Layout.alignment: Qt.AlignVCenter + Layout.minimumHeight: 30 + text: qsTr("Clear") + onClicked: clearChat() + } + } + } + + Row { + id: bar + + layoutDirection: Qt.RightToLeft + + anchors { + left: parent.left + leftMargin: 5 + right: parent.right + rightMargin: scroll.width + } + spacing: 10 + + Badge { + text: "%1/%2".arg(root.chatModel.totalTokens).arg(root.chatModel.tokensThreshold) + color: root.codeColor + fontColor: root.primaryColor.hslLightness > 0.5 ? "black" : "white" + } + } + + function clearChat() { + root.chatModel.clear() + } + + function scrollToBottom() { + Qt.callLater(chatListView.positionViewAtEnd) } } diff --git a/chatview/qml/dialog/CodeBlock.qml b/chatview/qml/dialog/CodeBlock.qml new file mode 100644 index 0000000..581815c --- /dev/null +++ b/chatview/qml/dialog/CodeBlock.qml @@ -0,0 +1,86 @@ +/* + * 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 . + */ + +import QtQuick +import QtQuick.Controls +import ChatView + +Rectangle { + id: root + + property string code: "" + property string language: "" + property color selectionColor + + border.color: root.color.hslLightness > 0.5 ? Qt.darker(root.color, 1.3) + : Qt.lighter(root.color, 1.3) + border.width: 2 + radius: 4 + + implicitWidth: parent.width + implicitHeight: codeText.implicitHeight + 20 + + ChatUtils { + id: utils + } + + TextEdit { + id: codeText + + anchors.fill: parent + anchors.margins: 10 + text: root.code + readOnly: true + selectByMouse: true + font.family: "monospace" + color: parent.color.hslLightness > 0.5 ? "black" : "white" + wrapMode: Text.WordWrap + selectionColor: root.selectionColor + } + + TextEdit { + anchors.top: parent.top + anchors.right: parent.right + anchors.margins: 5 + readOnly: true + selectByMouse: true + text: root.language + color: root.color.hslLightness > 0.5 ? Qt.darker(root.color, 1.1) + : Qt.lighter(root.color, 1.1) + font.pointSize: 8 + } + + Button { + anchors.top: parent.top + anchors.right: parent.right + anchors.margins: 5 + text: "Copy" + onClicked: { + utils.copyToClipboard(root.code) + text = qsTr("Copied") + copyTimer.start() + } + + Timer { + id: copyTimer + interval: 2000 + onTriggered: parent.text = qsTr("Copy") + } + } +} diff --git a/chatview/qml/dialog/TextBlock.qml b/chatview/qml/dialog/TextBlock.qml new file mode 100644 index 0000000..626b24c --- /dev/null +++ b/chatview/qml/dialog/TextBlock.qml @@ -0,0 +1,29 @@ +/* + * 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 . + */ + +import QtQuick + +TextEdit { + id: root + + readOnly: true + selectByMouse: true + wrapMode: Text.WordWrap + textFormat: Text.StyledText +} diff --git a/core/ChangesManager.cpp b/core/ChangesManager.cpp index 3fdb0d4..ae5a4ca 100644 --- a/core/ChangesManager.cpp +++ b/core/ChangesManager.cpp @@ -18,7 +18,7 @@ */ #include "ChangesManager.h" -#include "QodeAssistUtils.hpp" +#include "logger/Logger.hpp" #include "settings/ContextSettings.hpp" namespace QodeAssist { diff --git a/llmcore/CMakeLists.txt b/llmcore/CMakeLists.txt new file mode 100644 index 0000000..5f7915e --- /dev/null +++ b/llmcore/CMakeLists.txt @@ -0,0 +1,23 @@ +add_library(LLMCore STATIC + RequestType.hpp + Provider.hpp + ProvidersManager.hpp ProvidersManager.cpp + ContextData.hpp + PromptTemplate.hpp + PromptTemplateManager.hpp PromptTemplateManager.cpp + RequestConfig.hpp + RequestHandler.hpp RequestHandler.cpp +) + +target_link_libraries(LLMCore + PUBLIC + Qt::Core + Qt::Network + QtCreator::Core + QtCreator::Utils + QtCreator::ExtensionSystem + PRIVATE + QodeAssistLogger +) + +target_include_directories(LLMCore PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/QodeAssistData.hpp b/llmcore/ContextData.hpp similarity index 89% rename from QodeAssistData.hpp rename to llmcore/ContextData.hpp index 3a8c76e..6df13d3 100644 --- a/QodeAssistData.hpp +++ b/llmcore/ContextData.hpp @@ -21,13 +21,13 @@ #include -namespace QodeAssist { +namespace QodeAssist::LLMCore { struct ContextData { QString prefix; QString suffix; - QString instriuctions; + QString systemPrompt; }; -} // namespace QodeAssist +} // namespace QodeAssist::LLMCore diff --git a/templates/PromptTemplate.hpp b/llmcore/PromptTemplate.hpp similarity index 92% rename from templates/PromptTemplate.hpp rename to llmcore/PromptTemplate.hpp index 0dee78e..3952810 100644 --- a/templates/PromptTemplate.hpp +++ b/llmcore/PromptTemplate.hpp @@ -23,9 +23,9 @@ #include #include -#include "QodeAssistData.hpp" +#include "ContextData.hpp" -namespace QodeAssist::Templates { +namespace QodeAssist::LLMCore { enum class TemplateType { Chat, Fim }; @@ -39,4 +39,4 @@ public: virtual QStringList stopWords() const = 0; virtual void prepareRequest(QJsonObject &request, const ContextData &context) const = 0; }; -} // namespace QodeAssist::Templates +} // namespace QodeAssist::LLMCore diff --git a/PromptTemplateManager.cpp b/llmcore/PromptTemplateManager.cpp similarity index 76% rename from PromptTemplateManager.cpp rename to llmcore/PromptTemplateManager.cpp index 559f014..5c10eb2 100644 --- a/PromptTemplateManager.cpp +++ b/llmcore/PromptTemplateManager.cpp @@ -19,9 +19,9 @@ #include "PromptTemplateManager.hpp" -#include "QodeAssistUtils.hpp" +#include "Logger.hpp" -namespace QodeAssist { +namespace QodeAssist::LLMCore { PromptTemplateManager &PromptTemplateManager::instance() { @@ -31,19 +31,19 @@ PromptTemplateManager &PromptTemplateManager::instance() void PromptTemplateManager::setCurrentFimTemplate(const QString &name) { - logMessage("Setting current FIM provider to: " + name); + LOG_MESSAGE("Setting current FIM provider to: " + name); if (!m_fimTemplates.contains(name) || m_fimTemplates[name] == nullptr) { - logMessage("Error to set current FIM template" + name); + LOG_MESSAGE("Error to set current FIM template" + name); return; } m_currentFimTemplate = m_fimTemplates[name]; } -Templates::PromptTemplate *PromptTemplateManager::getCurrentFimTemplate() +PromptTemplate *PromptTemplateManager::getCurrentFimTemplate() { if (m_currentFimTemplate == nullptr) { - logMessage("Current fim provider is null"); + LOG_MESSAGE("Current fim provider is null"); return nullptr; } @@ -52,19 +52,19 @@ Templates::PromptTemplate *PromptTemplateManager::getCurrentFimTemplate() void PromptTemplateManager::setCurrentChatTemplate(const QString &name) { - logMessage("Setting current chat provider to: " + name); + LOG_MESSAGE("Setting current chat provider to: " + name); if (!m_chatTemplates.contains(name) || m_chatTemplates[name] == nullptr) { - logMessage("Error to set current chat template" + name); + LOG_MESSAGE("Error to set current chat template" + name); return; } m_currentChatTemplate = m_chatTemplates[name]; } -Templates::PromptTemplate *PromptTemplateManager::getCurrentChatTemplate() +PromptTemplate *PromptTemplateManager::getCurrentChatTemplate() { if (m_currentChatTemplate == nullptr) - logMessage("Current chat provider is null"); + LOG_MESSAGE("Current chat provider is null"); return m_currentChatTemplate; } @@ -85,4 +85,4 @@ PromptTemplateManager::~PromptTemplateManager() qDeleteAll(m_chatTemplates); } -} // namespace QodeAssist +} // namespace QodeAssist::LLMCore diff --git a/PromptTemplateManager.hpp b/llmcore/PromptTemplateManager.hpp similarity index 70% rename from PromptTemplateManager.hpp rename to llmcore/PromptTemplateManager.hpp index 47347d4..5c2c3bf 100644 --- a/PromptTemplateManager.hpp +++ b/llmcore/PromptTemplateManager.hpp @@ -22,9 +22,9 @@ #include #include -#include "templates/PromptTemplate.hpp" +#include "PromptTemplate.hpp" -namespace QodeAssist { +namespace QodeAssist::LLMCore { class PromptTemplateManager { @@ -35,22 +35,22 @@ public: template void registerTemplate() { - static_assert(std::is_base_of::value, + static_assert(std::is_base_of::value, "T must inherit from PromptTemplate"); T *template_ptr = new T(); QString name = template_ptr->name(); - if (template_ptr->type() == Templates::TemplateType::Fim) { + if (template_ptr->type() == TemplateType::Fim) { m_fimTemplates[name] = template_ptr; - } else if (template_ptr->type() == Templates::TemplateType::Chat) { + } else if (template_ptr->type() == TemplateType::Chat) { m_chatTemplates[name] = template_ptr; } } void setCurrentFimTemplate(const QString &name); - Templates::PromptTemplate *getCurrentFimTemplate(); + PromptTemplate *getCurrentFimTemplate(); void setCurrentChatTemplate(const QString &name); - Templates::PromptTemplate *getCurrentChatTemplate(); + PromptTemplate *getCurrentChatTemplate(); QStringList fimTemplatesNames() const; QStringList chatTemplatesNames() const; @@ -60,10 +60,10 @@ private: PromptTemplateManager(const PromptTemplateManager &) = delete; PromptTemplateManager &operator=(const PromptTemplateManager &) = delete; - QMap m_fimTemplates; - QMap m_chatTemplates; - Templates::PromptTemplate *m_currentFimTemplate; - Templates::PromptTemplate *m_currentChatTemplate; + QMap m_fimTemplates; + QMap m_chatTemplates; + PromptTemplate *m_currentFimTemplate; + PromptTemplate *m_currentChatTemplate; }; -} // namespace QodeAssist +} // namespace QodeAssist::LLMCore diff --git a/providers/LLMProvider.hpp b/llmcore/Provider.hpp similarity index 84% rename from providers/LLMProvider.hpp rename to llmcore/Provider.hpp index 27286b4..e66f941 100644 --- a/providers/LLMProvider.hpp +++ b/llmcore/Provider.hpp @@ -20,26 +20,27 @@ #pragma once #include +#include "RequestType.hpp" #include class QNetworkReply; class QJsonObject; -namespace QodeAssist::Providers { +namespace QodeAssist::LLMCore { -class LLMProvider +class Provider { public: - virtual ~LLMProvider() = default; + virtual ~Provider() = default; virtual QString name() const = 0; virtual QString url() const = 0; virtual QString completionEndpoint() const = 0; virtual QString chatEndpoint() const = 0; - virtual void prepareRequest(QJsonObject &request) = 0; + virtual void prepareRequest(QJsonObject &request, RequestType type) = 0; virtual bool handleResponse(QNetworkReply *reply, QString &accumulatedResponse) = 0; virtual QList getInstalledModels(const Utils::Environment &env, const QString &url) = 0; }; -} // namespace QodeAssist::Providers +} // namespace QodeAssist::LLMCore diff --git a/LLMProvidersManager.cpp b/llmcore/ProvidersManager.cpp similarity index 58% rename from LLMProvidersManager.cpp rename to llmcore/ProvidersManager.cpp index ebbaad1..d60b42f 100644 --- a/LLMProvidersManager.cpp +++ b/llmcore/ProvidersManager.cpp @@ -17,23 +17,23 @@ * along with QodeAssist. If not, see . */ -#include "LLMProvidersManager.hpp" +#include "ProvidersManager.hpp" +#include "Logger.hpp" +#include -#include "QodeAssistUtils.hpp" +namespace QodeAssist::LLMCore { -namespace QodeAssist { - -LLMProvidersManager &LLMProvidersManager::instance() +ProvidersManager &ProvidersManager::instance() { - static LLMProvidersManager instance; + static ProvidersManager instance; return instance; } -Providers::LLMProvider *LLMProvidersManager::setCurrentFimProvider(const QString &name) +Provider *ProvidersManager::setCurrentFimProvider(const QString &name) { - logMessage("Setting current FIM provider to: " + name); + LOG_MESSAGE("Setting current FIM provider to: " + name); if (!m_providers.contains(name)) { - logMessage("Can't find provider with name: " + name); + LOG_MESSAGE("Can't find provider with name: " + name); return nullptr; } @@ -41,11 +41,11 @@ Providers::LLMProvider *LLMProvidersManager::setCurrentFimProvider(const QString return m_currentFimProvider; } -Providers::LLMProvider *LLMProvidersManager::setCurrentChatProvider(const QString &name) +Provider *ProvidersManager::setCurrentChatProvider(const QString &name) { - logMessage("Setting current chat provider to: " + name); + LOG_MESSAGE("Setting current chat provider to: " + name); if (!m_providers.contains(name)) { - logMessage("Can't find chat provider with name: " + name); + LOG_MESSAGE("Can't find chat provider with name: " + name); return nullptr; } @@ -53,34 +53,34 @@ Providers::LLMProvider *LLMProvidersManager::setCurrentChatProvider(const QStrin return m_currentChatProvider; } -Providers::LLMProvider *LLMProvidersManager::getCurrentFimProvider() +Provider *ProvidersManager::getCurrentFimProvider() { if (m_currentFimProvider == nullptr) { - logMessage("Current fim provider is null"); + LOG_MESSAGE("Current fim provider is null"); return nullptr; } return m_currentFimProvider; } -Providers::LLMProvider *LLMProvidersManager::getCurrentChatProvider() +Provider *ProvidersManager::getCurrentChatProvider() { if (m_currentChatProvider == nullptr) { - logMessage("Current chat provider is null"); + LOG_MESSAGE("Current chat provider is null"); return nullptr; } return m_currentChatProvider; } -QStringList LLMProvidersManager::providersNames() const +QStringList ProvidersManager::providersNames() const { return m_providers.keys(); } -LLMProvidersManager::~LLMProvidersManager() +ProvidersManager::~ProvidersManager() { qDeleteAll(m_providers); } -} // namespace QodeAssist +} // namespace QodeAssist::LLMCore diff --git a/LLMProvidersManager.hpp b/llmcore/ProvidersManager.hpp similarity index 52% rename from LLMProvidersManager.hpp rename to llmcore/ProvidersManager.hpp index b6f3305..03eabd2 100644 --- a/LLMProvidersManager.hpp +++ b/llmcore/ProvidersManager.hpp @@ -21,42 +21,41 @@ #include -#include "providers/LLMProvider.hpp" +#include "Provider.hpp" -namespace QodeAssist { +namespace QodeAssist::LLMCore { -class LLMProvidersManager +class ProvidersManager { public: - static LLMProvidersManager &instance(); - ~LLMProvidersManager(); + static ProvidersManager &instance(); + ~ProvidersManager(); template void registerProvider() { - static_assert(std::is_base_of::value, - "T must inherit from LLMProvider"); + static_assert(std::is_base_of::value, "T must inherit from Provider"); T *provider = new T(); QString name = provider->name(); m_providers[name] = provider; } - Providers::LLMProvider *setCurrentFimProvider(const QString &name); - Providers::LLMProvider *setCurrentChatProvider(const QString &name); + Provider *setCurrentFimProvider(const QString &name); + Provider *setCurrentChatProvider(const QString &name); - Providers::LLMProvider *getCurrentFimProvider(); - Providers::LLMProvider *getCurrentChatProvider(); + Provider *getCurrentFimProvider(); + Provider *getCurrentChatProvider(); QStringList providersNames() const; private: - LLMProvidersManager() = default; - LLMProvidersManager(const LLMProvidersManager &) = delete; - LLMProvidersManager &operator=(const LLMProvidersManager &) = delete; + ProvidersManager() = default; + ProvidersManager(const ProvidersManager &) = delete; + ProvidersManager &operator=(const ProvidersManager &) = delete; - QMap m_providers; - Providers::LLMProvider *m_currentFimProvider = nullptr; - Providers::LLMProvider *m_currentChatProvider = nullptr; + QMap m_providers; + Provider *m_currentFimProvider = nullptr; + Provider *m_currentChatProvider = nullptr; }; -} // namespace QodeAssist +} // namespace QodeAssist::LLMCore diff --git a/core/LLMRequestConfig.hpp b/llmcore/RequestConfig.hpp similarity index 77% rename from core/LLMRequestConfig.hpp rename to llmcore/RequestConfig.hpp index c2fe73b..d456fed 100644 --- a/core/LLMRequestConfig.hpp +++ b/llmcore/RequestConfig.hpp @@ -21,20 +21,20 @@ #include #include -#include "providers/LLMProvider.hpp" -#include "templates/PromptTemplate.hpp" +#include "PromptTemplate.hpp" +#include "Provider.hpp" +#include "RequestType.hpp" -namespace QodeAssist { - -enum class RequestType { Fim, Chat }; +namespace QodeAssist::LLMCore { struct LLMConfig { QUrl url; - Providers::LLMProvider *provider; - Templates::PromptTemplate *promptTemplate; + Provider *provider; + PromptTemplate *promptTemplate; QJsonObject providerRequest; RequestType requestType; + bool multiLineCompletion; }; -} // namespace QodeAssist +} // namespace QodeAssist::LLMCore diff --git a/core/LLMRequestHandler.cpp b/llmcore/RequestHandler.cpp similarity index 73% rename from core/LLMRequestHandler.cpp rename to llmcore/RequestHandler.cpp index c05b8bb..99e19ca 100644 --- a/core/LLMRequestHandler.cpp +++ b/llmcore/RequestHandler.cpp @@ -17,24 +17,22 @@ * along with QodeAssist. If not, see . */ -#include "LLMRequestHandler.hpp" -#include "LLMProvidersManager.hpp" -#include "QodeAssistUtils.hpp" -#include "settings/GeneralSettings.hpp" +#include "RequestHandler.hpp" +#include "Logger.hpp" #include #include -namespace QodeAssist { +namespace QodeAssist::LLMCore { -LLMRequestHandler::LLMRequestHandler(QObject *parent) +RequestHandler::RequestHandler(QObject *parent) : QObject(parent) , m_manager(new QNetworkAccessManager(this)) {} -void LLMRequestHandler::sendLLMRequest(const LLMConfig &config, const QJsonObject &request) +void RequestHandler::sendLLMRequest(const LLMConfig &config, const QJsonObject &request) { - logMessage(QString("Sending request to llm: \nurl: %1\nRequest body:\n%2") + LOG_MESSAGE(QString("Sending request to llm: \nurl: %1\nRequest body:\n%2") .arg(config.url.toString(), QString::fromUtf8( QJsonDocument(config.providerRequest).toJson(QJsonDocument::Indented)))); @@ -45,7 +43,7 @@ void LLMRequestHandler::sendLLMRequest(const LLMConfig &config, const QJsonObjec QNetworkReply *reply = m_manager->post(networkRequest, QJsonDocument(config.providerRequest).toJson()); if (!reply) { - logMessage("Error: Failed to create network reply"); + LOG_MESSAGE("Error: Failed to create network reply"); return; } @@ -60,25 +58,25 @@ void LLMRequestHandler::sendLLMRequest(const LLMConfig &config, const QJsonObjec reply->deleteLater(); m_activeRequests.remove(requestId); if (reply->error() != QNetworkReply::NoError) { - logMessage(QString("Error in QodeAssist request: %1").arg(reply->errorString())); + LOG_MESSAGE(QString("Error in QodeAssist request: %1").arg(reply->errorString())); emit requestFinished(requestId, false, reply->errorString()); } else { - logMessage("Request finished successfully"); + LOG_MESSAGE("Request finished successfully"); emit requestFinished(requestId, true, QString()); } }); } -void LLMRequestHandler::handleLLMResponse(QNetworkReply *reply, - const QJsonObject &request, - const LLMConfig &config) +void RequestHandler::handleLLMResponse(QNetworkReply *reply, + const QJsonObject &request, + const LLMConfig &config) { QString &accumulatedResponse = m_accumulatedResponses[reply]; bool isComplete = config.provider->handleResponse(reply, accumulatedResponse); if (config.requestType == RequestType::Fim) { - if (!Settings::generalSettings().multiLineCompletion() + if (!config.multiLineCompletion && processSingleLineCompletion(reply, request, accumulatedResponse, config)) { return; } @@ -100,7 +98,7 @@ void LLMRequestHandler::handleLLMResponse(QNetworkReply *reply, } } -bool LLMRequestHandler::cancelRequest(const QString &id) +bool RequestHandler::cancelRequest(const QString &id) { if (m_activeRequests.contains(id)) { QNetworkReply *reply = m_activeRequests[id]; @@ -113,8 +111,8 @@ bool LLMRequestHandler::cancelRequest(const QString &id) return false; } -void LLMRequestHandler::prepareNetworkRequest(QNetworkRequest &networkRequest, - const QJsonObject &providerRequest) +void RequestHandler::prepareNetworkRequest(QNetworkRequest &networkRequest, + const QJsonObject &providerRequest) { networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); @@ -124,10 +122,10 @@ void LLMRequestHandler::prepareNetworkRequest(QNetworkRequest &networkRequest, } } -bool LLMRequestHandler::processSingleLineCompletion(QNetworkReply *reply, - const QJsonObject &request, - const QString &accumulatedResponse, - const LLMConfig &config) +bool RequestHandler::processSingleLineCompletion(QNetworkReply *reply, + const QJsonObject &request, + const QString &accumulatedResponse, + const LLMConfig &config) { int newlinePos = accumulatedResponse.indexOf('\n'); @@ -145,8 +143,7 @@ bool LLMRequestHandler::processSingleLineCompletion(QNetworkReply *reply, return false; } -QString LLMRequestHandler::removeStopWords(const QStringView &completion, - const QStringList &stopWords) +QString RequestHandler::removeStopWords(const QStringView &completion, const QStringList &stopWords) { QString filteredCompletion = completion.toString(); @@ -157,4 +154,4 @@ QString LLMRequestHandler::removeStopWords(const QStringView &completion, return filteredCompletion; } -} // namespace QodeAssist +} // namespace QodeAssist::LLMCore diff --git a/core/LLMRequestHandler.hpp b/llmcore/RequestHandler.hpp similarity index 90% rename from core/LLMRequestHandler.hpp rename to llmcore/RequestHandler.hpp index aadd590..d53a910 100644 --- a/core/LLMRequestHandler.hpp +++ b/llmcore/RequestHandler.hpp @@ -23,19 +23,18 @@ #include #include -#include "QodeAssistData.hpp" -#include "core/LLMRequestConfig.hpp" +#include "RequestConfig.hpp" class QNetworkReply; -namespace QodeAssist { +namespace QodeAssist::LLMCore { -class LLMRequestHandler : public QObject +class RequestHandler : public QObject { Q_OBJECT public: - explicit LLMRequestHandler(QObject *parent = nullptr); + explicit RequestHandler(QObject *parent = nullptr); void sendLLMRequest(const LLMConfig &config, const QJsonObject &request); void handleLLMResponse(QNetworkReply *reply, @@ -61,4 +60,4 @@ private: QString removeStopWords(const QStringView &completion, const QStringList &stopWords); }; -} // namespace QodeAssist +} // namespace QodeAssist::LLMCore diff --git a/llmcore/RequestType.hpp b/llmcore/RequestType.hpp new file mode 100644 index 0000000..80f64d5 --- /dev/null +++ b/llmcore/RequestType.hpp @@ -0,0 +1,6 @@ +#pragma once + +namespace QodeAssist::LLMCore { + +enum RequestType { Fim, Chat }; +} diff --git a/logger/CMakeLists.txt b/logger/CMakeLists.txt new file mode 100644 index 0000000..34fff4c --- /dev/null +++ b/logger/CMakeLists.txt @@ -0,0 +1,14 @@ +add_library(QodeAssistLogger STATIC + Logger.cpp + Logger.hpp +) + +target_link_libraries(QodeAssistLogger + PUBLIC + Qt::Core + QtCreator::Core +) + +target_include_directories(QodeAssistLogger + PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} +) diff --git a/logger/Logger.cpp b/logger/Logger.cpp new file mode 100644 index 0000000..acd0915 --- /dev/null +++ b/logger/Logger.cpp @@ -0,0 +1,55 @@ +#include "Logger.hpp" +#include + +namespace QodeAssist { + +Logger &Logger::instance() +{ + static Logger instance; + return instance; +} + +Logger::Logger() + : m_loggingEnabled(false) +{} + +void Logger::setLoggingEnabled(bool enable) +{ + m_loggingEnabled = enable; +} + +bool Logger::isLoggingEnabled() const +{ + return m_loggingEnabled; +} + +void Logger::log(const QString &message, bool silent) +{ + if (!m_loggingEnabled) + return; + + const QString prefixedMessage = QLatin1String("[Qode Assist] ") + message; + if (silent) { + Core::MessageManager::writeSilently(prefixedMessage); + } else { + Core::MessageManager::writeFlashing(prefixedMessage); + } +} + +void Logger::logMessages(const QStringList &messages, bool silent) +{ + if (!m_loggingEnabled) + return; + + QStringList prefixedMessages; + for (const QString &message : messages) { + prefixedMessages << (QLatin1String("[Qode Assist] ") + message); + } + + if (silent) { + Core::MessageManager::writeSilently(prefixedMessages); + } else { + Core::MessageManager::writeFlashing(prefixedMessages); + } +} +} // namespace QodeAssist diff --git a/logger/Logger.hpp b/logger/Logger.hpp new file mode 100644 index 0000000..4d6e4cb --- /dev/null +++ b/logger/Logger.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include +#include + +namespace QodeAssist { + +class Logger : public QObject +{ + Q_OBJECT + +public: + static Logger &instance(); + + void setLoggingEnabled(bool enable); + bool isLoggingEnabled() const; + + void log(const QString &message, bool silent = true); + void logMessages(const QStringList &messages, bool silent = true); + +private: + Logger(); + ~Logger() = default; + Logger(const Logger &) = delete; + Logger &operator=(const Logger &) = delete; + + bool m_loggingEnabled; +}; + +#define LOG_MESSAGE(msg) QodeAssist::Logger::instance().log(msg) +#define LOG_MESSAGES(msgs) QodeAssist::Logger::instance().logMessages(msgs) + +} // namespace QodeAssist diff --git a/providers/LMStudioProvider.cpp b/providers/LMStudioProvider.cpp index 67651b2..edcd832 100644 --- a/providers/LMStudioProvider.cpp +++ b/providers/LMStudioProvider.cpp @@ -25,7 +25,7 @@ #include #include -#include "QodeAssistUtils.hpp" +#include "logger/Logger.hpp" #include "settings/PresetPromptsSettings.hpp" namespace QodeAssist::Providers { @@ -52,9 +52,11 @@ QString LMStudioProvider::chatEndpoint() const return "/v1/chat/completions"; } -void LMStudioProvider::prepareRequest(QJsonObject &request) +void LMStudioProvider::prepareRequest(QJsonObject &request, LLMCore::RequestType type) { - auto &settings = Settings::presetPromptsSettings(); + auto &promptSettings = Settings::presetPromptsSettings(); + auto settings = promptSettings.getSettings(type); + QJsonArray messages; if (request.contains("system")) { @@ -72,16 +74,16 @@ void LMStudioProvider::prepareRequest(QJsonObject &request) request["messages"] = std::move(messages); } - request["max_tokens"] = settings.maxTokens(); - request["temperature"] = settings.temperature(); - if (settings.useTopP()) - request["top_p"] = settings.topP(); - if (settings.useTopK()) - request["top_k"] = settings.topK(); - if (settings.useFrequencyPenalty()) - request["frequency_penalty"] = settings.frequencyPenalty(); - if (settings.usePresencePenalty()) - request["presence_penalty"] = settings.presencePenalty(); + request["max_tokens"] = settings.maxTokens; + request["temperature"] = settings.temperature; + if (settings.useTopP) + request["top_p"] = settings.topP; + if (settings.useTopK) + request["top_k"] = settings.topK; + if (settings.useFrequencyPenalty) + request["frequency_penalty"] = settings.frequencyPenalty; + if (settings.usePresencePenalty) + request["presence_penalty"] = settings.presencePenalty; } bool LMStudioProvider::handleResponse(QNetworkReply *reply, QString &accumulatedResponse) @@ -150,7 +152,7 @@ QList LMStudioProvider::getInstalledModels(const Utils::Environment &en models.append(modelId); } } else { - logMessage(QString("Error fetching models: %1").arg(reply->errorString())); + LOG_MESSAGE(QString("Error fetching models: %1").arg(reply->errorString())); } reply->deleteLater(); diff --git a/providers/LMStudioProvider.hpp b/providers/LMStudioProvider.hpp index 0bcf56b..42964ab 100644 --- a/providers/LMStudioProvider.hpp +++ b/providers/LMStudioProvider.hpp @@ -19,11 +19,11 @@ #pragma once -#include "LLMProvider.hpp" +#include "llmcore/Provider.hpp" namespace QodeAssist::Providers { -class LMStudioProvider : public LLMProvider +class LMStudioProvider : public LLMCore::Provider { public: LMStudioProvider(); @@ -32,7 +32,7 @@ public: QString url() const override; QString completionEndpoint() const override; QString chatEndpoint() const override; - void prepareRequest(QJsonObject &request) override; + void prepareRequest(QJsonObject &request, LLMCore::RequestType type) override; bool handleResponse(QNetworkReply *reply, QString &accumulatedResponse) override; QList getInstalledModels(const Utils::Environment &env, const QString &url) override; }; diff --git a/providers/OllamaProvider.cpp b/providers/OllamaProvider.cpp index 5daf0d2..dd92033 100644 --- a/providers/OllamaProvider.cpp +++ b/providers/OllamaProvider.cpp @@ -25,8 +25,8 @@ #include #include -#include "PromptTemplateManager.hpp" -#include "QodeAssistUtils.hpp" +#include "llmcore/PromptTemplateManager.hpp" +#include "logger/Logger.hpp" #include "settings/PresetPromptsSettings.hpp" namespace QodeAssist::Providers { @@ -53,23 +53,24 @@ QString OllamaProvider::chatEndpoint() const return "/api/chat"; } -void OllamaProvider::prepareRequest(QJsonObject &request) +void OllamaProvider::prepareRequest(QJsonObject &request, LLMCore::RequestType type) { - auto &settings = Settings::presetPromptsSettings(); + auto &promptSettings = Settings::presetPromptsSettings(); + auto settings = promptSettings.getSettings(type); QJsonObject options; - options["num_predict"] = settings.maxTokens(); - options["temperature"] = settings.temperature(); - if (settings.useTopP()) - options["top_p"] = settings.topP(); - if (settings.useTopK()) - options["top_k"] = settings.topK(); - if (settings.useFrequencyPenalty()) - options["frequency_penalty"] = settings.frequencyPenalty(); - if (settings.usePresencePenalty()) - options["presence_penalty"] = settings.presencePenalty(); + options["num_predict"] = settings.maxTokens; + options["temperature"] = settings.temperature; + if (settings.useTopP) + options["top_p"] = settings.topP; + if (settings.useTopK) + options["top_k"] = settings.topK; + if (settings.useFrequencyPenalty) + options["frequency_penalty"] = settings.frequencyPenalty; + if (settings.usePresencePenalty) + options["presence_penalty"] = settings.presencePenalty; request["options"] = options; - request["keep_alive"] = settings.ollamaLivetime(); + request["keep_alive"] = settings.ollamaLivetime; } bool OllamaProvider::handleResponse(QNetworkReply *reply, QString &accumulatedResponse) @@ -85,7 +86,7 @@ bool OllamaProvider::handleResponse(QNetworkReply *reply, QString &accumulatedRe QJsonDocument doc = QJsonDocument::fromJson(line); if (doc.isNull()) { - logMessage("Invalid JSON response from Ollama: " + QString::fromUtf8(line)); + LOG_MESSAGE("Invalid JSON response from Ollama: " + QString::fromUtf8(line)); continue; } @@ -93,7 +94,7 @@ bool OllamaProvider::handleResponse(QNetworkReply *reply, QString &accumulatedRe if (responseObj.contains("error")) { QString errorMessage = responseObj["error"].toString(); - logMessage("Error in Ollama response: " + errorMessage); + LOG_MESSAGE("Error in Ollama response: " + errorMessage); return false; } @@ -111,7 +112,7 @@ bool OllamaProvider::handleResponse(QNetworkReply *reply, QString &accumulatedRe } } } else { - logMessage("Unknown endpoint: " + endpoint); + LOG_MESSAGE("Unknown endpoint: " + endpoint); } if (responseObj.contains("done") && responseObj["done"].toBool()) { @@ -146,7 +147,7 @@ QList OllamaProvider::getInstalledModels(const Utils::Environment &env, models.append(modelName); } } else { - logMessage(QString("Error fetching models: %1").arg(reply->errorString())); + LOG_MESSAGE(QString("Error fetching models: %1").arg(reply->errorString())); } reply->deleteLater(); diff --git a/providers/OllamaProvider.hpp b/providers/OllamaProvider.hpp index f4290e4..145d475 100644 --- a/providers/OllamaProvider.hpp +++ b/providers/OllamaProvider.hpp @@ -19,11 +19,11 @@ #pragma once -#include "LLMProvider.hpp" +#include "llmcore/Provider.hpp" namespace QodeAssist::Providers { -class OllamaProvider : public LLMProvider +class OllamaProvider : public LLMCore::Provider { public: OllamaProvider(); @@ -32,7 +32,7 @@ public: QString url() const override; QString completionEndpoint() const override; QString chatEndpoint() const override; - void prepareRequest(QJsonObject &request) override; + void prepareRequest(QJsonObject &request, LLMCore::RequestType type) override; bool handleResponse(QNetworkReply *reply, QString &accumulatedResponse) override; QList getInstalledModels(const Utils::Environment &env, const QString &url) override; }; diff --git a/providers/OpenAICompatProvider.cpp b/providers/OpenAICompatProvider.cpp index 8d322f6..d277015 100644 --- a/providers/OpenAICompatProvider.cpp +++ b/providers/OpenAICompatProvider.cpp @@ -50,9 +50,10 @@ QString OpenAICompatProvider::chatEndpoint() const return "/v1/chat/completions"; } -void OpenAICompatProvider::prepareRequest(QJsonObject &request) +void OpenAICompatProvider::prepareRequest(QJsonObject &request, LLMCore::RequestType type) { - auto &settings = Settings::presetPromptsSettings(); + auto &promptSettings = Settings::presetPromptsSettings(); + auto settings = promptSettings.getSettings(type); QJsonArray messages; if (request.contains("system")) { @@ -70,18 +71,18 @@ void OpenAICompatProvider::prepareRequest(QJsonObject &request) request["messages"] = std::move(messages); } - request["max_tokens"] = settings.maxTokens(); - request["temperature"] = settings.temperature(); - if (settings.useTopP()) - request["top_p"] = settings.topP(); - if (settings.useTopK()) - request["top_k"] = settings.topK(); - if (settings.useFrequencyPenalty()) - request["frequency_penalty"] = settings.frequencyPenalty(); - if (settings.usePresencePenalty()) - request["presence_penalty"] = settings.presencePenalty(); + request["max_tokens"] = settings.maxTokens; + request["temperature"] = settings.temperature; + if (settings.useTopP) + request["top_p"] = settings.topP; + if (settings.useTopK) + request["top_k"] = settings.topK; + if (settings.useFrequencyPenalty) + request["frequency_penalty"] = settings.frequencyPenalty; + if (settings.usePresencePenalty) + request["presence_penalty"] = settings.presencePenalty; - const QString &apiKey = settings.apiKey.value(); + const QString &apiKey = settings.apiKey; if (!apiKey.isEmpty()) { request["api_key"] = apiKey; } diff --git a/providers/OpenAICompatProvider.hpp b/providers/OpenAICompatProvider.hpp index 4d7b8ec..bd23c73 100644 --- a/providers/OpenAICompatProvider.hpp +++ b/providers/OpenAICompatProvider.hpp @@ -19,11 +19,11 @@ #pragma once -#include "LLMProvider.hpp" +#include "llmcore/Provider.hpp" namespace QodeAssist::Providers { -class OpenAICompatProvider : public LLMProvider +class OpenAICompatProvider : public LLMCore::Provider { public: OpenAICompatProvider(); @@ -32,7 +32,7 @@ public: QString url() const override; QString completionEndpoint() const override; QString chatEndpoint() const override; - void prepareRequest(QJsonObject &request) override; + void prepareRequest(QJsonObject &request, LLMCore::RequestType type) override; bool handleResponse(QNetworkReply *reply, QString &accumulatedResponse) override; QList getInstalledModels(const Utils::Environment &env, const QString &url) override; }; diff --git a/qodeassist.cpp b/qodeassist.cpp index 87ea06d..77e37f6 100644 --- a/qodeassist.cpp +++ b/qodeassist.cpp @@ -39,21 +39,22 @@ #include #include -#include "LLMProvidersManager.hpp" -#include "PromptTemplateManager.hpp" #include "QodeAssistClient.hpp" #include "chat/ChatOutputPane.h" +#include "chat/NavigationPanel.hpp" +#include "llmcore/PromptTemplateManager.hpp" +#include "llmcore/ProvidersManager.hpp" #include "providers/LMStudioProvider.hpp" #include "providers/OllamaProvider.hpp" #include "providers/OpenAICompatProvider.hpp" -#include "settings/GeneralSettings.hpp" -#include "templates/CodeLlamaFimTemplate.hpp" -#include "templates/CodeLlamaInstruct.hpp" -#include "templates/CustomTemplate.hpp" -#include "templates/DeepSeekCoderChatTemplate.hpp" -#include "templates/DeepSeekCoderV2.hpp" -#include "templates/StarCoder2Template.hpp" +#include "templates/CodeLlamaChat.hpp" +#include "templates/CodeLlamaFim.hpp" +#include "templates/CustomFimTemplate.hpp" +#include "templates/DeepSeekCoderChat.hpp" +#include "templates/DeepSeekCoderFim.hpp" +#include "templates/QwenChat.hpp" +#include "templates/StarCoder2Fim.hpp" using namespace Utils; using namespace Core; @@ -73,23 +74,26 @@ public: ~QodeAssistPlugin() final { - + delete m_qodeAssistClient; + delete m_chatOutputPane; + delete m_navigationPanel; } void initialize() final { - auto &providerManager = LLMProvidersManager::instance(); + auto &providerManager = LLMCore::ProvidersManager::instance(); providerManager.registerProvider(); providerManager.registerProvider(); providerManager.registerProvider(); - auto &templateManager = PromptTemplateManager::instance(); - templateManager.registerTemplate(); - templateManager.registerTemplate(); - templateManager.registerTemplate(); + auto &templateManager = LLMCore::PromptTemplateManager::instance(); + templateManager.registerTemplate(); + templateManager.registerTemplate(); + templateManager.registerTemplate(); templateManager.registerTemplate(); - templateManager.registerTemplate(); - templateManager.registerTemplate(); + templateManager.registerTemplate(); + templateManager.registerTemplate(); + templateManager.registerTemplate(); Utils::Icon QCODEASSIST_ICON( {{":/resources/images/qoderassist-icon.png", Utils::Theme::IconsBaseColor}}); @@ -116,6 +120,7 @@ public: StatusBarManager::addStatusBarWidget(toggleButton, StatusBarManager::RightCorner); m_chatOutputPane = new Chat::ChatOutputPane(this); + m_navigationPanel = new Chat::NavigationPanel(); } void extensionsInitialized() final @@ -124,9 +129,9 @@ public: void restartClient() { - LanguageClient::LanguageClientManager::shutdownClient(m_qodeAssistClient.get()); + LanguageClient::LanguageClientManager::shutdownClient(m_qodeAssistClient); - m_qodeAssistClient.reset(new QodeAssistClient()); + m_qodeAssistClient = new QodeAssistClient(); } bool delayedInitialize() final @@ -140,7 +145,7 @@ public: { if (!m_qodeAssistClient) return SynchronousShutdown; - connect(m_qodeAssistClient.get(), + connect(m_qodeAssistClient, &QObject::destroyed, this, &IPlugin::asynchronousShutdownFinished); @@ -148,8 +153,9 @@ public: } private: - QScopedPointer m_qodeAssistClient; + QPointer m_qodeAssistClient; QPointer m_chatOutputPane; + QPointer m_navigationPanel; }; } // namespace QodeAssist::Internal diff --git a/settings/Assisttr.h b/settings/Assisttr.h new file mode 100644 index 0000000..cfaa0de --- /dev/null +++ b/settings/Assisttr.h @@ -0,0 +1,31 @@ +/* + * 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 . + */ + +#pragma once + +#include + +namespace QodeAssist { + +struct Tr +{ + Q_DECLARE_TR_FUNCTIONS(QtC::QodeAssist) +}; + +} // namespace QodeAssist diff --git a/settings/CMakeLists.txt b/settings/CMakeLists.txt new file mode 100644 index 0000000..e885f4b --- /dev/null +++ b/settings/CMakeLists.txt @@ -0,0 +1,20 @@ +add_library(QodeAssistSettings STATIC + GeneralSettings.hpp GeneralSettings.cpp + ContextSettings.hpp ContextSettings.cpp + CustomPromptSettings.hpp CustomPromptSettings.cpp + PresetPromptsSettings.hpp PresetPromptsSettings.cpp + SettingsUtils.hpp + SettingsConstants.hpp +) + +target_link_libraries(QodeAssistSettings + PUBLIC + Qt::Core + Qt::Network + QtCreator::Core + QtCreator::Utils + QodeAssistLogger + PRIVATE + LLMCore +) +target_include_directories(QodeAssistSettings PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/settings/ContextSettings.cpp b/settings/ContextSettings.cpp index d14aed9..b18f757 100644 --- a/settings/ContextSettings.cpp +++ b/settings/ContextSettings.cpp @@ -24,8 +24,7 @@ #include #include -#include "QodeAssistConstants.hpp" -#include "QodeAssisttr.h" +#include "SettingsConstants.hpp" namespace QodeAssist::Settings { ContextSettings &contextSettings() @@ -58,17 +57,37 @@ ContextSettings::ContextSettings() useFilePathInContext.setDefaultValue(false); useFilePathInContext.setLabelText(Tr::tr("Use File Path in Context")); - useSpecificInstructions.setSettingsKey(Constants::USE_SYSTEM_PROMPT); - useSpecificInstructions.setDefaultValue(true); - useSpecificInstructions.setLabelText(Tr::tr("Use System Prompt")); + useSystemPrompt.setSettingsKey(Constants::USE_SYSTEM_PROMPT); + useSystemPrompt.setDefaultValue(true); + useSystemPrompt.setLabelText(Tr::tr("Use System Prompt")); - specificInstractions.setSettingsKey(Constants::SYSTEM_PROMPT); - specificInstractions.setDisplayStyle(Utils::StringAspect::TextEditDisplay); - specificInstractions.setLabelText( - Tr::tr("Instructions: Please keep %1 for languge name, warning, it shouldn't too big")); - specificInstractions.setDefaultValue( - "You are an expert %1 code completion AI." - "CRITICAL: Please provide minimal the best possible code completion suggestions.\n"); + systemPrompt.setSettingsKey(Constants::SYSTEM_PROMPT); + systemPrompt.setDisplayStyle(Utils::StringAspect::TextEditDisplay); + systemPrompt.setDefaultValue( + "You are an expert C++, Qt, and QML code completion AI. Your task is to provide accurate " + "and " + "contextually appropriate code suggestions. Focus on completing the code in a way that " + "follows best practices, is efficient, and matches the surrounding code style. Prioritize " + "Qt and QML-specific completions when appropriate. Avoid adding comments or explanations " + "in your completions."); + + useChatSystemPrompt.setSettingsKey(Constants::USE_CHAT_SYSTEM_PROMPT); + useChatSystemPrompt.setDefaultValue(true); + useChatSystemPrompt.setLabelText(Tr::tr("Use System Prompt for chat")); + + chatSystemPrompt.setSettingsKey(Constants::CHAT_SYSTEM_PROMPT); + chatSystemPrompt.setDisplayStyle(Utils::StringAspect::TextEditDisplay); + chatSystemPrompt.setDefaultValue( + "You are an advanced AI assistant specializing in C++, Qt, and QML development. Your role " + "is " + "to provide helpful, accurate, and detailed responses to questions about coding, " + "debugging, " + "and best practices in these technologies. Offer clear explanations, code examples when " + "appropriate, and guidance on Qt Creator usage. Always prioritize officially recommended " + "Qt " + "and C++ practices. If you're unsure about something, state it clearly and suggest where " + "the " + "user might find more information."); resetToDefaults.m_buttonText = Tr::tr("Reset Page to Defaults"); @@ -85,21 +104,26 @@ ContextSettings::ContextSettings() readStringsAfterCursor.setEnabled(!readFullFile()); readStringsBeforeCursor.setEnabled(!readFullFile()); - specificInstractions.setEnabled(useSpecificInstructions()); + systemPrompt.setEnabled(useSystemPrompt()); setupConnection(); setLayouter([this]() { using namespace Layouting; - return Column{Row{readFullFile, Stretch{1}, resetToDefaults}, - Row{readStringsBeforeCursor, Stretch{1}}, - Row{readStringsAfterCursor, Stretch{1}}, - useFilePathInContext, - useSpecificInstructions, - specificInstractions, - useProjectChangesCache, - Row{maxChangesCacheSize, Stretch{1}}, - Stretch{1}}; + return Column{Row{Stretch{1}, resetToDefaults}, + Group{title(Tr::tr("AI Suggestions Context")), + Column{Row{readFullFile, Stretch{1}}, + Row{readStringsBeforeCursor, Stretch{1}}, + Row{readStringsAfterCursor, Stretch{1}}, + useFilePathInContext, + useSystemPrompt, + systemPrompt, + useProjectChangesCache, + Row{maxChangesCacheSize, Stretch{1}}, + Stretch{1}}}, + Space{16}, + Group{title(Tr::tr("AI Chat Context")), + Column{useChatSystemPrompt, chatSystemPrompt}}}; }); } @@ -109,8 +133,8 @@ void ContextSettings::setupConnection() readStringsAfterCursor.setEnabled(!readFullFile.volatileValue()); readStringsBeforeCursor.setEnabled(!readFullFile.volatileValue()); }); - connect(&useSpecificInstructions, &Utils::BoolAspect::volatileValueChanged, this, [this]() { - specificInstractions.setEnabled(useSpecificInstructions.volatileValue()); + connect(&useSystemPrompt, &Utils::BoolAspect::volatileValueChanged, this, [this]() { + systemPrompt.setEnabled(useSystemPrompt.volatileValue()); }); connect(&resetToDefaults, &ButtonAspect::clicked, this, &ContextSettings::resetPageToDefaults); } @@ -129,8 +153,10 @@ void ContextSettings::resetPageToDefaults() resetAspect(readStringsBeforeCursor); resetAspect(readStringsAfterCursor); resetAspect(useFilePathInContext); - resetAspect(useSpecificInstructions); - resetAspect(specificInstractions); + resetAspect(useSystemPrompt); + resetAspect(systemPrompt); + resetAspect(useChatSystemPrompt); + resetAspect(chatSystemPrompt); } } diff --git a/settings/ContextSettings.hpp b/settings/ContextSettings.hpp index dbe491b..83c8630 100644 --- a/settings/ContextSettings.hpp +++ b/settings/ContextSettings.hpp @@ -34,11 +34,13 @@ public: Utils::IntegerAspect readStringsBeforeCursor{this}; Utils::IntegerAspect readStringsAfterCursor{this}; - Utils::StringAspect specificInstractions{this}; - Utils::BoolAspect useSpecificInstructions{this}; + Utils::BoolAspect useSystemPrompt{this}; + Utils::StringAspect systemPrompt{this}; Utils::BoolAspect useFilePathInContext{this}; Utils::BoolAspect useProjectChangesCache{this}; Utils::IntegerAspect maxChangesCacheSize{this}; + Utils::BoolAspect useChatSystemPrompt{this}; + Utils::StringAspect chatSystemPrompt{this}; ButtonAspect resetToDefaults{this}; diff --git a/settings/CustomPromptSettings.cpp b/settings/CustomPromptSettings.cpp index 4361efc..c2b2f12 100644 --- a/settings/CustomPromptSettings.cpp +++ b/settings/CustomPromptSettings.cpp @@ -26,8 +26,7 @@ #include #include -#include "QodeAssistConstants.hpp" -#include "QodeAssisttr.h" +#include "SettingsConstants.hpp" namespace QodeAssist::Settings { @@ -85,12 +84,13 @@ CustomPromptSettings::CustomPromptSettings() setLayouter([this]() { using namespace Layouting; - return Column{Row{customJsonLabel, Stretch{1}, resetToDefaults}, - Row{customJsonTemplate, - Column{saveCustomTemplateButton, - loadCustomTemplateButton, - customJsonLegend, - Stretch{1}}}}; + return Column{Group{title(Tr::tr("Custom prompt for FIM model")), + Column{Row{customJsonLabel, Stretch{1}, resetToDefaults}, + Row{customJsonTemplate, + Column{saveCustomTemplateButton, + loadCustomTemplateButton, + customJsonLegend, + Stretch{1}}}}}}; }); } diff --git a/settings/CustomPromptSettings.hpp b/settings/CustomPromptSettings.hpp index 7ff0515..18780ac 100644 --- a/settings/CustomPromptSettings.hpp +++ b/settings/CustomPromptSettings.hpp @@ -19,7 +19,7 @@ #pragma once -#include "settings/SettingsUtils.hpp" +#include "SettingsUtils.hpp" #include namespace QodeAssist::Settings { diff --git a/settings/GeneralSettings.cpp b/settings/GeneralSettings.cpp index b06e78b..8fc80ba 100644 --- a/settings/GeneralSettings.cpp +++ b/settings/GeneralSettings.cpp @@ -26,11 +26,12 @@ #include #include -#include "LLMProvidersManager.hpp" +#include "Logger.hpp" #include "PromptTemplateManager.hpp" -#include "QodeAssistConstants.hpp" -#include "QodeAssistUtils.hpp" -#include "QodeAssisttr.h" +#include "Provider.hpp" +#include "ProvidersManager.hpp" +#include "SettingsConstants.hpp" +#include "SettingsUtils.hpp" namespace QodeAssist::Settings { @@ -128,27 +129,34 @@ GeneralSettings::GeneralSettings() chatPrompts.setSettingsKey(Constants::CHAT_PROMPTS); chatPrompts.setDisplayStyle(Utils::SelectionAspect::DisplayStyle::ComboBox); + chatTokensThreshold.setSettingsKey(Constants::CHAT_TOKENS_THRESHOLD); + chatTokensThreshold.setLabelText(Tr::tr("Chat History Token Limit")); + 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); + loadProviders(); loadPrompts(); llmProviders.setDefaultValue(llmProviders.indexForDisplay("Ollama")); chatLlmProviders.setDefaultValue(chatLlmProviders.indexForDisplay("Ollama")); - fimPrompts.setDefaultValue(fimPrompts.indexForDisplay("CodeLLama FIM")); - chatPrompts.setDefaultValue(chatPrompts.indexForDisplay("CodeLLama Chat")); - - readSettings(); + fimPrompts.setDefaultValue(fimPrompts.indexForDisplay("CodeLlama FIM")); + chatPrompts.setDefaultValue(chatPrompts.indexForDisplay("CodeLlama Chat")); auto fimProviderName = llmProviders.displayForIndex(llmProviders.value()); setCurrentFimProvider(fimProviderName); auto chatProviderName = chatLlmProviders.displayForIndex(chatLlmProviders.value()); setCurrentChatProvider(chatProviderName); - auto nameFimPromts = fimPrompts.displayForIndex(fimPrompts.value()); - PromptTemplateManager::instance().setCurrentFimTemplate(nameFimPromts); - auto nameChatPromts = chatPrompts.displayForIndex(chatPrompts.value()); - PromptTemplateManager::instance().setCurrentChatTemplate(nameChatPromts); + readSettings(); - setLoggingEnabled(enableLogging()); + auto nameFimPromts = fimPrompts.displayForIndex(fimPrompts.value()); + LLMCore::PromptTemplateManager::instance().setCurrentFimTemplate(nameFimPromts); + auto nameChatPromts = chatPrompts.displayForIndex(chatPrompts.value()); + LLMCore::PromptTemplateManager::instance().setCurrentChatTemplate(nameChatPromts); + + Logger::instance().setLoggingEnabled(enableLogging()); setupConnections(); @@ -157,26 +165,26 @@ GeneralSettings::GeneralSettings() auto rootLayout = Column{Row{enableQodeAssist, Stretch{1}, resetToDefaults}, - enableAutoComplete, - multiLineCompletion, - Row{autoCompletionCharThreshold, - autoCompletionTypingInterval, - startSuggestionTimer, - Stretch{1}}, - Space{8}, - enableLogging, + Row{enableLogging, Stretch{1}}, Space{8}, Group{title(Tr::tr("AI Suggestions")), - Column{Row{llmProviders, Stretch{1}}, + Column{enableAutoComplete, + multiLineCompletion, + Row{autoCompletionCharThreshold, + autoCompletionTypingInterval, + startSuggestionTimer, + Stretch{1}}, + Row{llmProviders, Stretch{1}}, Row{url, endPoint, fimUrlIndicator}, Row{selectModels, modelName, fimModelIndicator}, Row{fimPrompts, Stretch{1}}}}, Space{16}, - Group{title(Tr::tr("AI Chat(experimental)")), + Group{title(Tr::tr("AI Chat")), Column{Row{chatLlmProviders, Stretch{1}}, Row{chatUrl, chatEndPoint, chatUrlIndicator}, Row{chatSelectModels, chatModelName, chatModelIndicator}, - Row{chatPrompts, Stretch{1}}}}, + Row{chatPrompts, Stretch{1}}, + Row{chatTokensThreshold, Stretch{1}}}}, Stretch{1}}; return rootLayout; }); @@ -199,24 +207,26 @@ void GeneralSettings::setupConnections() connect(&fimPrompts, &Utils::SelectionAspect::volatileValueChanged, this, [this]() { int index = fimPrompts.volatileValue(); - PromptTemplateManager::instance().setCurrentFimTemplate(fimPrompts.displayForIndex(index)); + LLMCore::PromptTemplateManager::instance().setCurrentFimTemplate( + fimPrompts.displayForIndex(index)); }); connect(&chatPrompts, &Utils::SelectionAspect::volatileValueChanged, this, [this]() { int index = chatPrompts.volatileValue(); - PromptTemplateManager::instance().setCurrentChatTemplate(chatPrompts.displayForIndex(index)); + LLMCore::PromptTemplateManager::instance().setCurrentChatTemplate( + chatPrompts.displayForIndex(index)); }); connect(&selectModels, &ButtonAspect::clicked, this, [this]() { - auto *provider = LLMProvidersManager::instance().getCurrentFimProvider(); + auto *provider = LLMCore::ProvidersManager::instance().getCurrentFimProvider(); showModelSelectionDialog(&modelName, provider); }); connect(&chatSelectModels, &ButtonAspect::clicked, this, [this]() { - auto *provider = LLMProvidersManager::instance().getCurrentChatProvider(); + auto *provider = LLMCore::ProvidersManager::instance().getCurrentChatProvider(); showModelSelectionDialog(&chatModelName, provider); }); connect(&enableLogging, &Utils::BoolAspect::volatileValueChanged, this, [this]() { - setLoggingEnabled(enableLogging.volatileValue()); + Logger::instance().setLoggingEnabled(enableLogging.volatileValue()); }); connect(&resetToDefaults, &ButtonAspect::clicked, this, &GeneralSettings::resetPageToDefaults); @@ -239,7 +249,7 @@ void GeneralSettings::setupConnections() } void GeneralSettings::showModelSelectionDialog(Utils::StringAspect *modelNameObj, - Providers::LLMProvider *provider) + LLMCore::Provider *provider) { Utils::Environment env = Utils::Environment::systemEnvironment(); QString providerUrl = (modelNameObj == &modelName) ? url() : chatUrl(); @@ -282,6 +292,7 @@ void GeneralSettings::resetPageToDefaults() resetAspect(chatLlmProviders); resetAspect(fimPrompts); resetAspect(chatPrompts); + resetAspect(chatTokensThreshold); } modelName.setVolatileValue(""); @@ -301,12 +312,12 @@ void GeneralSettings::updateStatusIndicators() bool fimPingSuccessful = false; if (fimUrlValid) { QUrl pingUrl(url.volatileValue()); - fimPingSuccessful = QodeAssist::pingUrl(pingUrl); + fimPingSuccessful = Settings::pingUrl(pingUrl); } bool chatPingSuccessful = false; if (chatUrlValid) { QUrl pingUrl(chatUrl.volatileValue()); - chatPingSuccessful = QodeAssist::pingUrl(pingUrl); + chatPingSuccessful = Settings::pingUrl(pingUrl); } setIndicatorStatus(fimModelIndicator, @@ -339,7 +350,7 @@ void GeneralSettings::setIndicatorStatus(Utils::StringAspect &indicator, void GeneralSettings::setCurrentFimProvider(const QString &name) { - const auto provider = LLMProvidersManager::instance().setCurrentFimProvider(name); + const auto provider = LLMCore::ProvidersManager::instance().setCurrentFimProvider(name); if (!provider) return; @@ -349,7 +360,7 @@ void GeneralSettings::setCurrentFimProvider(const QString &name) void GeneralSettings::setCurrentChatProvider(const QString &name) { - const auto provider = LLMProvidersManager::instance().setCurrentChatProvider(name); + const auto provider = LLMCore::ProvidersManager::instance().setCurrentChatProvider(name); if (!provider) return; @@ -359,7 +370,7 @@ void GeneralSettings::setCurrentChatProvider(const QString &name) void GeneralSettings::loadProviders() { - for (const auto &name : LLMProvidersManager::instance().providersNames()) { + for (const auto &name : LLMCore::ProvidersManager::instance().providersNames()) { llmProviders.addOption(name); chatLlmProviders.addOption(name); } @@ -367,10 +378,10 @@ void GeneralSettings::loadProviders() void GeneralSettings::loadPrompts() { - for (const auto &name : PromptTemplateManager::instance().fimTemplatesNames()) { + for (const auto &name : LLMCore::PromptTemplateManager::instance().fimTemplatesNames()) { fimPrompts.addOption(name); } - for (const auto &name : PromptTemplateManager::instance().chatTemplatesNames()) { + for (const auto &name : LLMCore::PromptTemplateManager::instance().chatTemplatesNames()) { chatPrompts.addOption(name); } } diff --git a/settings/GeneralSettings.hpp b/settings/GeneralSettings.hpp index 804d609..392eca8 100644 --- a/settings/GeneralSettings.hpp +++ b/settings/GeneralSettings.hpp @@ -21,9 +21,11 @@ #include -#include "providers/LLMProvider.hpp" -#include "settings/SettingsUtils.hpp" +#include "SettingsUtils.hpp" +namespace QodeAssist::LLMCore { +class Provider; +} namespace QodeAssist::Settings { class GeneralSettings : public Utils::AspectContainer @@ -61,10 +63,11 @@ public: Utils::StringAspect chatModelIndicator{this}; Utils::StringAspect chatUrlIndicator{this}; + Utils::IntegerAspect chatTokensThreshold{this}; + private: void setupConnections(); - void showModelSelectionDialog(Utils::StringAspect *modelNameObj, - Providers::LLMProvider *provider); + void showModelSelectionDialog(Utils::StringAspect *modelNameObj, LLMCore::Provider *provider); void resetPageToDefaults(); void updateStatusIndicators(); diff --git a/settings/PresetPromptsSettings.cpp b/settings/PresetPromptsSettings.cpp index 897f555..cc6af09 100644 --- a/settings/PresetPromptsSettings.cpp +++ b/settings/PresetPromptsSettings.cpp @@ -24,8 +24,8 @@ #include #include -#include "QodeAssistConstants.hpp" -#include "QodeAssisttr.h" +#include "RequestType.hpp" +#include "SettingsConstants.hpp" namespace QodeAssist::Settings { @@ -41,63 +41,121 @@ PresetPromptsSettings::PresetPromptsSettings() setDisplayName(Tr::tr("Preset Prompts Params")); - temperature.setSettingsKey(Constants::TEMPERATURE); - temperature.setLabelText(Tr::tr("Temperature:")); - temperature.setDefaultValue(0.2); - temperature.setRange(0.0, 10.0); - temperature.setSingleStep(0.1); + fimTemperature.setSettingsKey(Constants::FIM_TEMPERATURE); + fimTemperature.setLabelText(Tr::tr("Temperature:")); + fimTemperature.setDefaultValue(0.2); + fimTemperature.setRange(0.0, 10.0); + fimTemperature.setSingleStep(0.1); - ollamaLivetime.setSettingsKey(Constants::OLLAMA_LIVETIME); - ollamaLivetime.setLabelText( + chatTemperature.setSettingsKey(Constants::CHAT_TEMPERATURE); + chatTemperature.setLabelText(Tr::tr("Temperature:")); + chatTemperature.setDefaultValue(0.5); + chatTemperature.setRange(0.0, 10.0); + chatTemperature.setSingleStep(0.1); + + fimOllamaLivetime.setSettingsKey(Constants::FIM_OLLAMA_LIVETIME); + fimOllamaLivetime.setLabelText( Tr::tr("Time to suspend Ollama after completion request (in minutes), " "Only Ollama, -1 to disable")); - ollamaLivetime.setDefaultValue("5m"); - ollamaLivetime.setDisplayStyle(Utils::StringAspect::LineEditDisplay); + fimOllamaLivetime.setDefaultValue("5m"); + fimOllamaLivetime.setDisplayStyle(Utils::StringAspect::LineEditDisplay); - maxTokens.setSettingsKey(Constants::MAX_TOKENS); - maxTokens.setLabelText(Tr::tr("Max Tokens")); - maxTokens.setRange(-1, 10000); - maxTokens.setDefaultValue(150); + chatOllamaLivetime.setSettingsKey(Constants::CHAT_OLLAMA_LIVETIME); + chatOllamaLivetime.setLabelText( + Tr::tr("Time to suspend Ollama after completion request (in minutes), " + "Only Ollama, -1 to disable")); + chatOllamaLivetime.setDefaultValue("5m"); + chatOllamaLivetime.setDisplayStyle(Utils::StringAspect::LineEditDisplay); - useTopP.setSettingsKey(Constants::USE_TOP_P); - useTopP.setDefaultValue(false); + fimMaxTokens.setSettingsKey(Constants::FIM_MAX_TOKENS); + fimMaxTokens.setLabelText(Tr::tr("Max Tokens")); + fimMaxTokens.setRange(-1, 10000); + fimMaxTokens.setDefaultValue(50); - topP.setSettingsKey(Constants::TOP_P); - topP.setLabelText(Tr::tr("use top_p")); - topP.setDefaultValue(0.9); - topP.setRange(0.0, 1.0); - topP.setSingleStep(0.1); + chatMaxTokens.setSettingsKey(Constants::CHAT_MAX_TOKENS); + chatMaxTokens.setLabelText(Tr::tr("Max Tokens")); + chatMaxTokens.setRange(-1, 10000); + chatMaxTokens.setDefaultValue(2000); - useTopK.setSettingsKey(Constants::USE_TOP_K); - useTopK.setDefaultValue(false); + fimUseTopP.setSettingsKey(Constants::FIM_USE_TOP_P); + fimUseTopP.setDefaultValue(false); - topK.setSettingsKey(Constants::TOP_K); - topK.setLabelText(Tr::tr("use top_k")); - topK.setDefaultValue(50); - topK.setRange(1, 1000); + fimTopP.setSettingsKey(Constants::FIM_TOP_P); + fimTopP.setLabelText(Tr::tr("use top_p")); + fimTopP.setDefaultValue(0.9); + fimTopP.setRange(0.0, 1.0); + fimTopP.setSingleStep(0.1); - usePresencePenalty.setSettingsKey(Constants::USE_PRESENCE_PENALTY); - usePresencePenalty.setDefaultValue(false); + chatUseTopP.setSettingsKey(Constants::CHAT_USE_TOP_P); + chatUseTopP.setDefaultValue(false); - presencePenalty.setSettingsKey(Constants::PRESENCE_PENALTY); - presencePenalty.setLabelText(Tr::tr("use presence_penalty")); - presencePenalty.setDefaultValue(0.0); - presencePenalty.setRange(-2.0, 2.0); - presencePenalty.setSingleStep(0.1); + chatTopP.setSettingsKey(Constants::CHAT_TOP_P); + chatTopP.setLabelText(Tr::tr("use top_p")); + chatTopP.setDefaultValue(0.9); + chatTopP.setRange(0.0, 1.0); + chatTopP.setSingleStep(0.1); - useFrequencyPenalty.setSettingsKey(Constants::USE_FREQUENCY_PENALTY); - useFrequencyPenalty.setDefaultValue(false); + fimUseTopK.setSettingsKey(Constants::FIM_USE_TOP_K); + fimUseTopK.setDefaultValue(false); - frequencyPenalty.setSettingsKey(Constants::FREQUENCY_PENALTY); - frequencyPenalty.setLabelText(Tr::tr("use frequency_penalty")); - frequencyPenalty.setDefaultValue(0.0); - frequencyPenalty.setRange(-2.0, 2.0); - frequencyPenalty.setSingleStep(0.1); + fimTopK.setSettingsKey(Constants::FIM_TOP_K); + fimTopK.setLabelText(Tr::tr("use top_k")); + fimTopK.setDefaultValue(50); + fimTopK.setRange(1, 1000); - apiKey.setSettingsKey(Constants::API_KEY); - apiKey.setLabelText(Tr::tr("API Key:")); - apiKey.setDisplayStyle(Utils::StringAspect::LineEditDisplay); - apiKey.setPlaceHolderText(Tr::tr("Enter your API key here")); + chatUseTopK.setSettingsKey(Constants::CHAT_USE_TOP_K); + chatUseTopK.setDefaultValue(false); + + chatTopK.setSettingsKey(Constants::CHAT_TOP_K); + chatTopK.setLabelText(Tr::tr("use top_k")); + chatTopK.setDefaultValue(50); + chatTopK.setRange(1, 1000); + + fimUsePresencePenalty.setSettingsKey(Constants::FIM_USE_PRESENCE_PENALTY); + fimUsePresencePenalty.setDefaultValue(false); + + fimPresencePenalty.setSettingsKey(Constants::FIM_PRESENCE_PENALTY); + fimPresencePenalty.setLabelText(Tr::tr("use presence_penalty")); + fimPresencePenalty.setDefaultValue(0.0); + fimPresencePenalty.setRange(-2.0, 2.0); + fimPresencePenalty.setSingleStep(0.1); + + chatUsePresencePenalty.setSettingsKey(Constants::CHAT_USE_PRESENCE_PENALTY); + chatUsePresencePenalty.setDefaultValue(false); + + chatPresencePenalty.setSettingsKey(Constants::CHAT_PRESENCE_PENALTY); + chatPresencePenalty.setLabelText(Tr::tr("use presence_penalty")); + chatPresencePenalty.setDefaultValue(0.0); + chatPresencePenalty.setRange(-2.0, 2.0); + chatPresencePenalty.setSingleStep(0.1); + + fimUseFrequencyPenalty.setSettingsKey(Constants::FIM_USE_FREQUENCY_PENALTY); + fimUseFrequencyPenalty.setDefaultValue(false); + + fimFrequencyPenalty.setSettingsKey(Constants::FIM_FREQUENCY_PENALTY); + fimFrequencyPenalty.setLabelText(Tr::tr("use frequency_penalty")); + fimFrequencyPenalty.setDefaultValue(0.0); + fimFrequencyPenalty.setRange(-2.0, 2.0); + fimFrequencyPenalty.setSingleStep(0.1); + + chatUseFrequencyPenalty.setSettingsKey(Constants::CHAT_USE_FREQUENCY_PENALTY); + chatUseFrequencyPenalty.setDefaultValue(false); + + chatFrequencyPenalty.setSettingsKey(Constants::CHAT_FREQUENCY_PENALTY); + chatFrequencyPenalty.setLabelText(Tr::tr("use frequency_penalty")); + chatFrequencyPenalty.setDefaultValue(0.0); + chatFrequencyPenalty.setRange(-2.0, 2.0); + chatFrequencyPenalty.setSingleStep(0.1); + + fimApiKey.setSettingsKey(Constants::FIM_API_KEY); + fimApiKey.setLabelText(Tr::tr("API Key:")); + fimApiKey.setDisplayStyle(Utils::StringAspect::LineEditDisplay); + fimApiKey.setPlaceHolderText(Tr::tr("Enter your API key here")); + + chatApiKey.setSettingsKey(Constants::CHAT_API_KEY); + chatApiKey.setLabelText(Tr::tr("API Key:")); + chatApiKey.setDisplayStyle(Utils::StringAspect::LineEditDisplay); + chatApiKey.setPlaceHolderText(Tr::tr("Enter your API key here")); resetToDefaults.m_buttonText = Tr::tr("Reset Page to Defaults"); @@ -107,17 +165,64 @@ PresetPromptsSettings::PresetPromptsSettings() setLayouter([this]() { using namespace Layouting; - return Column{Row{temperature, Stretch{1}, resetToDefaults}, - Row{maxTokens, Stretch{1}}, - Row{useTopP, topP, Stretch{1}}, - Row{useTopK, topK, Stretch{1}}, - Row{usePresencePenalty, presencePenalty, Stretch{1}}, - Row{useFrequencyPenalty, frequencyPenalty, Stretch{1}}, - apiKey, + return Column{Row{Stretch{1}, resetToDefaults}, + Group{title(Tr::tr("Prompt settings for FIM")), + Column{Row{fimTemperature, Stretch{1}}, + Row{fimMaxTokens, Stretch{1}}, + Row{fimUseTopP, fimTopP, Stretch{1}}, + Row{fimUseTopK, fimTopK, Stretch{1}}, + Row{fimUsePresencePenalty, fimPresencePenalty, Stretch{1}}, + Row{fimUseFrequencyPenalty, fimFrequencyPenalty, Stretch{1}}, + Row{fimOllamaLivetime, Stretch{1}}, + fimApiKey}}, + Space{16}, + Group{title(Tr::tr("Prompt settings for Chat")), + Column{Row{chatTemperature, Stretch{1}}, + Row{chatMaxTokens, Stretch{1}}, + Row{chatUseTopP, chatTopP, Stretch{1}}, + Row{chatUseTopK, chatTopK, Stretch{1}}, + Row{fimUsePresencePenalty, fimPresencePenalty, Stretch{1}}, + Row{fimUseFrequencyPenalty, fimFrequencyPenalty, Stretch{1}}, + Row{chatOllamaLivetime, Stretch{1}}, + chatApiKey}}, Stretch{1}}; }); } +PresetPromptsSettings::PromptSettings PresetPromptsSettings::getSettings(int type) const +{ + auto reqtype = static_cast(type); + PromptSettings settings; + if (reqtype == LLMCore::RequestType::Fim) { + settings.temperature = fimTemperature(); + settings.maxTokens = fimMaxTokens(); + settings.useTopP = fimUseTopP(); + settings.topP = fimTopP(); + settings.useTopK = fimUseTopK(); + settings.topK = fimTopK(); + settings.usePresencePenalty = fimUsePresencePenalty(); + settings.presencePenalty = fimPresencePenalty(); + settings.useFrequencyPenalty = fimUseFrequencyPenalty(); + settings.frequencyPenalty = fimFrequencyPenalty(); + settings.ollamaLivetime = fimOllamaLivetime(); + settings.apiKey = fimApiKey(); + } else if (reqtype == LLMCore::RequestType::Chat) { + settings.temperature = chatTemperature(); + settings.maxTokens = chatMaxTokens(); + settings.useTopP = chatUseTopP(); + settings.topP = chatTopP(); + settings.useTopK = chatUseTopK(); + settings.topK = chatTopK(); + settings.usePresencePenalty = chatUsePresencePenalty(); + settings.presencePenalty = chatPresencePenalty(); + settings.useFrequencyPenalty = chatUseFrequencyPenalty(); + settings.frequencyPenalty = chatFrequencyPenalty(); + settings.ollamaLivetime = chatOllamaLivetime(); + settings.apiKey = chatApiKey(); + } + return settings; +} + void PresetPromptsSettings::setupConnections() { connect(&resetToDefaults, @@ -136,17 +241,28 @@ void PresetPromptsSettings::resetSettingsToDefaults() QMessageBox::Yes | QMessageBox::No); if (reply == QMessageBox::Yes) { - resetAspect(temperature); - resetAspect(maxTokens); - resetAspect(ollamaLivetime); - resetAspect(useTopP); - resetAspect(topP); - resetAspect(useTopK); - resetAspect(topK); - resetAspect(usePresencePenalty); - resetAspect(presencePenalty); - resetAspect(useFrequencyPenalty); - resetAspect(frequencyPenalty); + resetAspect(fimTemperature); + resetAspect(fimMaxTokens); + resetAspect(fimOllamaLivetime); + resetAspect(fimUseTopP); + resetAspect(fimTopP); + resetAspect(fimUseTopK); + resetAspect(fimTopK); + resetAspect(fimUsePresencePenalty); + resetAspect(fimPresencePenalty); + resetAspect(fimUseFrequencyPenalty); + resetAspect(fimFrequencyPenalty); + resetAspect(chatTemperature); + resetAspect(chatMaxTokens); + resetAspect(chatUseTopP); + resetAspect(chatTopP); + resetAspect(chatUseTopK); + resetAspect(chatTopK); + resetAspect(chatUsePresencePenalty); + resetAspect(chatPresencePenalty); + resetAspect(chatUseFrequencyPenalty); + resetAspect(chatFrequencyPenalty); + resetAspect(chatOllamaLivetime); } } diff --git a/settings/PresetPromptsSettings.hpp b/settings/PresetPromptsSettings.hpp index b953c4b..bf0386a 100644 --- a/settings/PresetPromptsSettings.hpp +++ b/settings/PresetPromptsSettings.hpp @@ -19,7 +19,7 @@ #pragma once -#include "settings/SettingsUtils.hpp" +#include "SettingsUtils.hpp" #include namespace QodeAssist::Settings { @@ -27,28 +27,63 @@ namespace QodeAssist::Settings { class PresetPromptsSettings : public Utils::AspectContainer { public: + struct PromptSettings + { + double temperature; + int maxTokens; + bool useTopP; + double topP; + bool useTopK; + int topK; + bool usePresencePenalty; + double presencePenalty; + bool useFrequencyPenalty; + double frequencyPenalty; + QString ollamaLivetime; + QString apiKey; + }; + PresetPromptsSettings(); - Utils::DoubleAspect temperature{this}; - Utils::IntegerAspect maxTokens{this}; + Utils::DoubleAspect fimTemperature{this}; + Utils::IntegerAspect fimMaxTokens{this}; - Utils::BoolAspect useTopP{this}; - Utils::DoubleAspect topP{this}; + Utils::DoubleAspect chatTemperature{this}; + Utils::IntegerAspect chatMaxTokens{this}; - Utils::BoolAspect useTopK{this}; - Utils::IntegerAspect topK{this}; + Utils::BoolAspect fimUseTopP{this}; + Utils::DoubleAspect fimTopP{this}; - Utils::BoolAspect usePresencePenalty{this}; - Utils::DoubleAspect presencePenalty{this}; + Utils::BoolAspect chatUseTopP{this}; + Utils::DoubleAspect chatTopP{this}; - Utils::BoolAspect useFrequencyPenalty{this}; - Utils::DoubleAspect frequencyPenalty{this}; + Utils::BoolAspect fimUseTopK{this}; + Utils::IntegerAspect fimTopK{this}; - Utils::StringAspect ollamaLivetime{this}; - Utils::StringAspect apiKey{this}; + Utils::BoolAspect chatUseTopK{this}; + Utils::IntegerAspect chatTopK{this}; + + Utils::BoolAspect fimUsePresencePenalty{this}; + Utils::DoubleAspect fimPresencePenalty{this}; + + Utils::BoolAspect chatUsePresencePenalty{this}; + Utils::DoubleAspect chatPresencePenalty{this}; + + Utils::BoolAspect fimUseFrequencyPenalty{this}; + Utils::DoubleAspect fimFrequencyPenalty{this}; + + Utils::BoolAspect chatUseFrequencyPenalty{this}; + Utils::DoubleAspect chatFrequencyPenalty{this}; + + Utils::StringAspect fimOllamaLivetime{this}; + Utils::StringAspect chatOllamaLivetime{this}; + Utils::StringAspect fimApiKey{this}; + Utils::StringAspect chatApiKey{this}; ButtonAspect resetToDefaults{this}; + PromptSettings getSettings(int type) const; + private: void setupConnections(); void resetSettingsToDefaults(); diff --git a/settings/SettingsConstants.hpp b/settings/SettingsConstants.hpp new file mode 100644 index 0000000..0f7d573 --- /dev/null +++ b/settings/SettingsConstants.hpp @@ -0,0 +1,102 @@ +/* + * 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 . + */ + +#pragma once + +namespace QodeAssist::Constants { + +const char ACTION_ID[] = "QodeAssist.Action"; +const char MENU_ID[] = "QodeAssist.Menu"; + +// settings +const char ENABLE_QODE_ASSIST[] = "QodeAssist.enableQodeAssist"; +const char ENABLE_AUTO_COMPLETE[] = "QodeAssist.enableAutoComplete"; +const char ENABLE_LOGGING[] = "QodeAssist.enableLogging"; +const char LLM_PROVIDERS[] = "QodeAssist.llmProviders"; +const char URL[] = "QodeAssist.url"; +const char END_POINT[] = "QodeAssist.endPoint"; +const char MODEL_NAME[] = "QodeAssist.modelName"; +const char SELECT_MODELS[] = "QodeAssist.selectModels"; +const char FIM_PROMPTS[] = "QodeAssist.fimPrompts"; +const char PROVIDER_PATHS[] = "QodeAssist.providerPaths"; +const char START_SUGGESTION_TIMER[] = "QodeAssist.startSuggestionTimer"; +const char AUTO_COMPLETION_CHAR_THRESHOLD[] = "QodeAssist.autoCompletionCharThreshold"; +const char AUTO_COMPLETION_TYPING_INTERVAL[] = "QodeAssist.autoCompletionTypingInterval"; +const char MAX_FILE_THRESHOLD[] = "QodeAssist.maxFileThreshold"; +const char MULTILINE_COMPLETION[] = "QodeAssist.multilineCompletion"; +const char CUSTOM_JSON_TEMPLATE[] = "QodeAssist.customJsonTemplate"; +const char CHAT_LLM_PROVIDERS[] = "QodeAssist.chatLlmProviders"; +const char CHAT_URL[] = "QodeAssist.chatUrl"; +const char CHAT_END_POINT[] = "QodeAssist.chatEndPoint"; +const char CHAT_MODEL_NAME[] = "QodeAssist.chatModelName"; +const char CHAT_SELECT_MODELS[] = "QodeAssist.chatSelectModels"; +const char CHAT_PROMPTS[] = "QodeAssist.chatPrompts"; +const char CHAT_TOKENS_THRESHOLD[] = "QodeAssist.chatTokensThreshold"; + +const char QODE_ASSIST_GENERAL_OPTIONS_ID[] = "QodeAssist.GeneralOptions"; +const char QODE_ASSIST_GENERAL_SETTINGS_PAGE_ID[] = "QodeAssist.1GeneralSettingsPageId"; +const char QODE_ASSIST_CONTEXT_SETTINGS_PAGE_ID[] = "QodeAssist.2ContextSettingsPageId"; +const char QODE_ASSIST_PRESET_PROMPTS_SETTINGS_PAGE_ID[] + = "QodeAssist.3PresetPromptsSettingsPageId"; +const char QODE_ASSIST_CUSTOM_PROMPT_SETTINGS_PAGE_ID[] = "QodeAssist.4CustomPromptSettingsPageId"; + +const char QODE_ASSIST_GENERAL_OPTIONS_CATEGORY[] = "QodeAssist.Category"; +const char QODE_ASSIST_GENERAL_OPTIONS_DISPLAY_CATEGORY[] = "Qode Assist"; + +const char QODE_ASSIST_REQUEST_SUGGESTION[] = "QodeAssist.RequestSuggestion"; + +// context settings +const char READ_FULL_FILE[] = "QodeAssist.readFullFile"; +const char READ_STRINGS_BEFORE_CURSOR[] = "QodeAssist.readStringsBeforeCursor"; +const char READ_STRINGS_AFTER_CURSOR[] = "QodeAssist.readStringsAfterCursor"; +const char USE_SYSTEM_PROMPT[] = "QodeAssist.useSystemPrompt"; +const char USE_FILE_PATH_IN_CONTEXT[] = "QodeAssist.useFilePathInContext"; +const char SYSTEM_PROMPT[] = "QodeAssist.systemPrompt"; +const char USE_PROJECT_CHANGES_CACHE[] = "QodeAssist.useProjectChangesCache"; +const char MAX_CHANGES_CACHE_SIZE[] = "QodeAssist.maxChangesCacheSize"; +const char USE_CHAT_SYSTEM_PROMPT[] = "QodeAssist.useChatSystemPrompt"; +const char CHAT_SYSTEM_PROMPT[] = "QodeAssist.chatSystemPrompt"; + +// preset prompt settings +const char FIM_TEMPERATURE[] = "QodeAssist.fimTemperature"; +const char FIM_MAX_TOKENS[] = "QodeAssist.fimMaxTokens"; +const char FIM_USE_TOP_P[] = "QodeAssist.fimUseTopP"; +const char FIM_TOP_P[] = "QodeAssist.fimTopP"; +const char FIM_USE_TOP_K[] = "QodeAssist.fimUseTopK"; +const char FIM_TOP_K[] = "QodeAssist.fimTopK"; +const char FIM_USE_PRESENCE_PENALTY[] = "QodeAssist.fimUsePresencePenalty"; +const char FIM_PRESENCE_PENALTY[] = "QodeAssist.fimPresencePenalty"; +const char FIM_USE_FREQUENCY_PENALTY[] = "QodeAssist.fimUseFrequencyPenalty"; +const char FIM_FREQUENCY_PENALTY[] = "QodeAssist.fimFrequencyPenalty"; +const char FIM_OLLAMA_LIVETIME[] = "QodeAssist.fimOllamaLivetime"; +const char FIM_API_KEY[] = "QodeAssist.apiKey"; +const char CHAT_TEMPERATURE[] = "QodeAssist.chatTemperature"; +const char CHAT_MAX_TOKENS[] = "QodeAssist.chatMaxTokens"; +const char CHAT_USE_TOP_P[] = "QodeAssist.chatUseTopP"; +const char CHAT_TOP_P[] = "QodeAssist.chatTopP"; +const char CHAT_USE_TOP_K[] = "QodeAssist.chatUseTopK"; +const char CHAT_TOP_K[] = "QodeAssist.chatTopK"; +const char CHAT_USE_PRESENCE_PENALTY[] = "QodeAssist.chatUsePresencePenalty"; +const char CHAT_PRESENCE_PENALTY[] = "QodeAssist.chatPresencePenalty"; +const char CHAT_USE_FREQUENCY_PENALTY[] = "QodeAssist.chatUseFrequencyPenalty"; +const char CHAT_FREQUENCY_PENALTY[] = "QodeAssist.chatFrequencyPenalty"; +const char CHAT_OLLAMA_LIVETIME[] = "QodeAssist.chatOllamaLivetime"; +const char CHAT_API_KEY[] = "QodeAssist.chatApiKey"; + +} // namespace QodeAssist::Constants diff --git a/settings/SettingsUtils.hpp b/settings/SettingsUtils.hpp index 0e56712..6ff07bf 100644 --- a/settings/SettingsUtils.hpp +++ b/settings/SettingsUtils.hpp @@ -19,12 +19,53 @@ #pragma once +#include +#include +#include #include +#include #include #include namespace QodeAssist::Settings { +struct Tr +{ + Q_DECLARE_TR_FUNCTIONS(QtC::QodeAssist) +}; + +inline bool pingUrl(const QUrl &url, int timeout = 5000) +{ + if (!url.isValid()) { + return false; + } + + QNetworkAccessManager manager; + QNetworkRequest request(url); + request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, true); + + QScopedPointer reply(manager.get(request)); + + QTimer timer; + timer.setSingleShot(true); + + QEventLoop loop; + QObject::connect(reply.data(), &QNetworkReply::finished, &loop, &QEventLoop::quit); + QObject::connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit); + + timer.start(timeout); + loop.exec(); + + if (timer.isActive()) { + timer.stop(); + return (reply->error() == QNetworkReply::NoError); + } else { + QObject::disconnect(reply.data(), &QNetworkReply::finished, &loop, &QEventLoop::quit); + reply->abort(); + return false; + } +} + template void resetAspect(AspectType &aspect) { diff --git a/templates/CodeLlamaInstruct.hpp b/templates/CodeLlamaChat.hpp similarity index 80% rename from templates/CodeLlamaInstruct.hpp rename to templates/CodeLlamaChat.hpp index 4c29f72..2342e3c 100644 --- a/templates/CodeLlamaInstruct.hpp +++ b/templates/CodeLlamaChat.hpp @@ -20,19 +20,19 @@ #pragma once #include -#include "PromptTemplate.hpp" +#include "llmcore/PromptTemplate.hpp" namespace QodeAssist::Templates { -class CodeLlamaInstructTemplate : public PromptTemplate +class CodeLlamaChat : public LLMCore::PromptTemplate { public: - TemplateType type() const override { return TemplateType::Chat; } - QString name() const override { return "CodeLLama Chat"; } + LLMCore::TemplateType type() const override { return LLMCore::TemplateType::Chat; } + QString name() const override { return "CodeLlama Chat"; } QString promptTemplate() const override { return "[INST] %1 [/INST]"; } QStringList stopWords() const override { return QStringList() << "[INST]" << "[/INST]"; } - void prepareRequest(QJsonObject &request, const ContextData &context) const override + void prepareRequest(QJsonObject &request, const LLMCore::ContextData &context) const override { QString formattedPrompt = promptTemplate().arg(context.prefix); QJsonArray messages = request["messages"].toArray(); diff --git a/templates/CodeLlamaFimTemplate.hpp b/templates/CodeLlamaFim.hpp similarity index 74% rename from templates/CodeLlamaFimTemplate.hpp rename to templates/CodeLlamaFim.hpp index 729f79e..58ed26d 100644 --- a/templates/CodeLlamaFimTemplate.hpp +++ b/templates/CodeLlamaFim.hpp @@ -19,24 +19,24 @@ #pragma once -#include "PromptTemplate.hpp" +#include "llmcore/PromptTemplate.hpp" namespace QodeAssist::Templates { -class CodeLlamaFimTemplate : public PromptTemplate +class CodeLlamaFim : public LLMCore::PromptTemplate { public: - TemplateType type() const override { return TemplateType::Fim; } - QString name() const override { return "CodeLLama FIM"; } + LLMCore::TemplateType type() const override { return LLMCore::TemplateType::Fim; } + QString name() const override { return "CodeLlama FIM"; } QString promptTemplate() const override { return "%1
 %2 %3 "; }
     QStringList stopWords() const override
     {
         return QStringList() << "" << "
" << "";
     }
 
-    void prepareRequest(QJsonObject &request, const ContextData &context) const override
+    void prepareRequest(QJsonObject &request, const LLMCore::ContextData &context) const override
     {
-        QString formattedPrompt = promptTemplate().arg(context.instriuctions,
+        QString formattedPrompt = promptTemplate().arg(context.systemPrompt,
                                                        context.prefix,
                                                        context.suffix);
         request["prompt"] = formattedPrompt;
diff --git a/templates/CustomTemplate.hpp b/templates/CustomFimTemplate.hpp
similarity index 80%
rename from templates/CustomTemplate.hpp
rename to templates/CustomFimTemplate.hpp
index 5e1b913..9fe64da 100644
--- a/templates/CustomTemplate.hpp
+++ b/templates/CustomFimTemplate.hpp
@@ -19,20 +19,20 @@
 
 #pragma once
 
-#include "PromptTemplate.hpp"
+#include "llmcore/PromptTemplate.hpp"
 
 #include 
 #include 
 
-#include "QodeAssistUtils.hpp"
+#include "logger/Logger.hpp"
 #include "settings/CustomPromptSettings.hpp"
 
 namespace QodeAssist::Templates {
 
-class CustomTemplate : public PromptTemplate
+class CustomTemplate : public LLMCore::PromptTemplate
 {
 public:
-    TemplateType type() const override { return TemplateType::Fim; }
+    LLMCore::TemplateType type() const override { return LLMCore::TemplateType::Fim; }
     QString name() const override { return "Custom FIM Template"; }
     QString promptTemplate() const override
     {
@@ -40,11 +40,11 @@ public:
     }
     QStringList stopWords() const override { return QStringList(); }
 
-    void prepareRequest(QJsonObject &request, const ContextData &context) const override
+    void prepareRequest(QJsonObject &request, const LLMCore::ContextData &context) const override
     {
         QJsonDocument doc = QJsonDocument::fromJson(promptTemplate().toUtf8());
         if (doc.isNull() || !doc.isObject()) {
-            logMessage(QString("Invalid JSON template in settings"));
+            LOG_MESSAGE(QString("Invalid JSON template in settings"));
 
             return;
         }
@@ -58,11 +58,11 @@ public:
     }
 
 private:
-    QJsonValue processJsonValue(const QJsonValue &value, const ContextData &context) const
+    QJsonValue processJsonValue(const QJsonValue &value, const LLMCore::ContextData &context) const
     {
         if (value.isString()) {
             QString str = value.toString();
-            str.replace("{{QODE_INSTRUCTIONS}}", context.instriuctions);
+            str.replace("{{QODE_INSTRUCTIONS}}", context.systemPrompt);
             str.replace("{{QODE_PREFIX}}", context.prefix);
             str.replace("{{QODE_SUFFIX}}", context.suffix);
             return str;
@@ -78,7 +78,8 @@ private:
         return value;
     }
 
-    QJsonObject processJsonTemplate(const QJsonObject &templateObj, const ContextData &context) const
+    QJsonObject processJsonTemplate(const QJsonObject &templateObj,
+                                    const LLMCore::ContextData &context) const
     {
         QJsonObject result;
         for (auto it = templateObj.begin(); it != templateObj.end(); ++it) {
diff --git a/templates/DeepSeekCoderChat.hpp b/templates/DeepSeekCoderChat.hpp
new file mode 100644
index 0000000..44e8922
--- /dev/null
+++ b/templates/DeepSeekCoderChat.hpp
@@ -0,0 +1,54 @@
+/* 
+ * 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 .
+ */
+
+#pragma once
+
+#include 
+#include "llmcore/PromptTemplate.hpp"
+
+namespace QodeAssist::Templates {
+
+class DeepSeekCoderChat : public LLMCore::PromptTemplate
+{
+public:
+    QString name() const override { return "DeepSeekCoder Chat"; }
+    LLMCore::TemplateType type() const override { return LLMCore::TemplateType::Chat; }
+
+    QString promptTemplate() const override { return "### Instruction:\n%1\n### Response:\n"; }
+
+    QStringList stopWords() const override
+    {
+        return QStringList() << "### Instruction:" << "### Response:" << "\n\n### " << "<|EOT|>";
+    }
+
+    void prepareRequest(QJsonObject &request, const LLMCore::ContextData &context) const override
+    {
+        QString formattedPrompt = promptTemplate().arg(context.prefix);
+        QJsonArray messages = request["messages"].toArray();
+
+        QJsonObject newMessage;
+        newMessage["role"] = "user";
+        newMessage["content"] = formattedPrompt;
+        messages.append(newMessage);
+
+        request["messages"] = messages;
+    }
+};
+
+} // namespace QodeAssist::Templates
diff --git a/templates/DeepSeekCoderV2.hpp b/templates/DeepSeekCoderFim.hpp
similarity index 78%
rename from templates/DeepSeekCoderV2.hpp
rename to templates/DeepSeekCoderFim.hpp
index 4273a2e..10f269d 100644
--- a/templates/DeepSeekCoderV2.hpp
+++ b/templates/DeepSeekCoderFim.hpp
@@ -19,23 +19,23 @@
 
 #pragma once
 
-#include "PromptTemplate.hpp"
+#include "llmcore/PromptTemplate.hpp"
 
 namespace QodeAssist::Templates {
 
-class DeepSeekCoderV2Template : public PromptTemplate
+class DeepSeekCoderFim : public LLMCore::PromptTemplate
 {
 public:
-    TemplateType type() const override { return TemplateType::Fim; }
+    LLMCore::TemplateType type() const override { return LLMCore::TemplateType::Fim; }
     QString name() const override { return "DeepSeekCoder FIM"; }
     QString promptTemplate() const override
     {
         return "%1<|fim▁begin|>%2<|fim▁hole|>%3<|fim▁end|>";
     }
     QStringList stopWords() const override { return QStringList(); }
-    void prepareRequest(QJsonObject &request, const ContextData &context) const override
+    void prepareRequest(QJsonObject &request, const LLMCore::ContextData &context) const override
     {
-        QString formattedPrompt = promptTemplate().arg(context.instriuctions,
+        QString formattedPrompt = promptTemplate().arg(context.systemPrompt,
                                                        context.prefix,
                                                        context.suffix);
         request["prompt"] = formattedPrompt;
diff --git a/templates/DeepSeekCoderChatTemplate.hpp b/templates/QwenChat.hpp
similarity index 81%
rename from templates/DeepSeekCoderChatTemplate.hpp
rename to templates/QwenChat.hpp
index 775e614..f9dbb36 100644
--- a/templates/DeepSeekCoderChatTemplate.hpp
+++ b/templates/QwenChat.hpp
@@ -20,15 +20,15 @@
 #pragma once
 
 #include 
-#include "PromptTemplate.hpp"
+#include "llmcore/PromptTemplate.hpp"
 
 namespace QodeAssist::Templates {
 
-class DeepSeekCoderChatTemplate : public PromptTemplate
+class QwenChat : public LLMCore::PromptTemplate
 {
 public:
-    QString name() const override { return "DeepSeek Coder Chat"; }
-    TemplateType type() const override { return TemplateType::Chat; }
+    QString name() const override { return "Qwen Chat"; }
+    LLMCore::TemplateType type() const override { return LLMCore::TemplateType::Chat; }
 
     QString promptTemplate() const override { return "### Instruction:\n%1\n### Response:\n"; }
 
@@ -37,7 +37,7 @@ public:
         return QStringList() << "### Instruction:" << "### Response:" << "\n\n### " << "<|EOT|>";
     }
 
-    void prepareRequest(QJsonObject &request, const ContextData &context) const override
+    void prepareRequest(QJsonObject &request, const LLMCore::ContextData &context) const override
     {
         QString formattedPrompt = promptTemplate().arg(context.prefix);
         QJsonArray messages = request["messages"].toArray();
diff --git a/templates/StarCoder2Template.hpp b/templates/StarCoder2Fim.hpp
similarity index 79%
rename from templates/StarCoder2Template.hpp
rename to templates/StarCoder2Fim.hpp
index 4d61c88..cd18a6b 100644
--- a/templates/StarCoder2Template.hpp
+++ b/templates/StarCoder2Fim.hpp
@@ -19,14 +19,14 @@
 
 #pragma once
 
-#include "PromptTemplate.hpp"
+#include "llmcore/PromptTemplate.hpp"
 
 namespace QodeAssist::Templates {
 
-class StarCoder2Template : public PromptTemplate
+class StarCoder2Fim : public LLMCore::PromptTemplate
 {
 public:
-    TemplateType type() const override { return TemplateType::Fim; }
+    LLMCore::TemplateType type() const override { return LLMCore::TemplateType::Fim; }
     QString name() const override { return "StarCoder2 FIM"; }
     QString promptTemplate() const override { return "%1%2%3"; }
     QStringList stopWords() const override
@@ -34,9 +34,9 @@ public:
         return QStringList() << "<|endoftext|>" << "" << "" << ""
                              << "";
     }
-    void prepareRequest(QJsonObject &request, const ContextData &context) const override
+    void prepareRequest(QJsonObject &request, const LLMCore::ContextData &context) const override
     {
-        QString formattedPrompt = promptTemplate().arg(context.instriuctions,
+        QString formattedPrompt = promptTemplate().arg(context.systemPrompt,
                                                        context.prefix,
                                                        context.suffix);
         request["prompt"] = formattedPrompt;