diff --git a/ConfigurationManager.cpp b/ConfigurationManager.cpp index 77a8225..603e20b 100644 --- a/ConfigurationManager.cpp +++ b/ConfigurationManager.cpp @@ -57,6 +57,13 @@ void ConfigurationManager::setupConnections() connect(&m_generalSettings.caSelectTemplate, &Button::clicked, this, &Config::selectTemplate); connect(&m_generalSettings.ccSetUrl, &Button::clicked, this, &Config::selectUrl); connect(&m_generalSettings.caSetUrl, &Button::clicked, this, &Config::selectUrl); + + connect( + &m_generalSettings.ccPreset1SelectProvider, &Button::clicked, this, &Config::selectProvider); + connect(&m_generalSettings.ccPreset1SetUrl, &Button::clicked, this, &Config::selectUrl); + connect(&m_generalSettings.ccPreset1SelectModel, &Button::clicked, this, &Config::selectModel); + connect( + &m_generalSettings.ccPreset1SelectTemplate, &Button::clicked, this, &Config::selectTemplate); } void ConfigurationManager::selectProvider() @@ -69,6 +76,8 @@ void ConfigurationManager::selectProvider() auto &targetSettings = (settingsButton == &m_generalSettings.ccSelectProvider) ? m_generalSettings.ccProvider + : settingsButton == &m_generalSettings.ccPreset1SelectProvider + ? m_generalSettings.ccPreset1Provider : m_generalSettings.caProvider; QTimer::singleShot(0, this, [this, providersList, &targetSettings] { @@ -86,14 +95,19 @@ void ConfigurationManager::selectModel() return; const bool isCodeCompletion = (settingsButton == &m_generalSettings.ccSelectModel); + const bool isPreset1 = (settingsButton == &m_generalSettings.ccPreset1SelectModel); const QString providerName = isCodeCompletion ? m_generalSettings.ccProvider.volatileValue() - : m_generalSettings.caProvider.volatileValue(); + : isPreset1 ? m_generalSettings.ccPreset1Provider.volatileValue() + : m_generalSettings.caProvider.volatileValue(); const auto providerUrl = isCodeCompletion ? m_generalSettings.ccUrl.volatileValue() + : isPreset1 ? m_generalSettings.ccPreset1Url.volatileValue() : m_generalSettings.caUrl.volatileValue(); - auto &targetSettings = isCodeCompletion ? m_generalSettings.ccModel : m_generalSettings.caModel; + auto &targetSettings = isCodeCompletion ? m_generalSettings.ccModel + : isPreset1 ? m_generalSettings.ccPreset1Model + : m_generalSettings.caModel; if (auto provider = m_providersManager.getProviderByName(providerName)) { if (!provider->supportsModelListing()) { @@ -122,11 +136,13 @@ void ConfigurationManager::selectTemplate() return; const bool isCodeCompletion = (settingsButton == &m_generalSettings.ccSelectTemplate); + const bool isPreset1 = (settingsButton == &m_generalSettings.ccPreset1SelectTemplate); - const auto templateList = isCodeCompletion ? m_templateManger.fimTemplatesNames() - : m_templateManger.chatTemplatesNames(); + const auto templateList = isCodeCompletion || isPreset1 ? m_templateManger.fimTemplatesNames() + : m_templateManger.chatTemplatesNames(); auto &targetSettings = isCodeCompletion ? m_generalSettings.ccTemplate + : isPreset1 ? m_generalSettings.ccPreset1Template : m_generalSettings.caTemplate; QTimer::singleShot(0, &m_generalSettings, [this, templateList, &targetSettings]() { @@ -150,8 +166,9 @@ void ConfigurationManager::selectUrl() urls.append(url); } - auto &targetSettings = (settingsButton == &m_generalSettings.ccSetUrl) - ? m_generalSettings.ccUrl + auto &targetSettings = (settingsButton == &m_generalSettings.ccSetUrl) ? m_generalSettings.ccUrl + : settingsButton == &m_generalSettings.ccPreset1SetUrl + ? m_generalSettings.ccPreset1Url : m_generalSettings.caUrl; QTimer::singleShot(0, &m_generalSettings, [this, urls, &targetSettings]() { diff --git a/LLMClientInterface.cpp b/LLMClientInterface.cpp index 1c547e7..8e974b7 100644 --- a/LLMClientInterface.cpp +++ b/LLMClientInterface.cpp @@ -146,20 +146,41 @@ void LLMClientInterface::handleExit(const QJsonObject &request) emit finished(); } +bool QodeAssist::LLMClientInterface::isSpecifyCompletion(const QJsonObject &request) +{ + auto &generalSettings = Settings::generalSettings(); + + Context::ProgrammingLanguage documentLanguage = getDocumentLanguage(request); + Context::ProgrammingLanguage preset1Language = Context::ProgrammingLanguageUtils::fromString( + generalSettings.preset1Language.displayForIndex(generalSettings.preset1Language())); + + return generalSettings.specifyPreset1() && documentLanguage == preset1Language; +} + void LLMClientInterface::handleCompletion(const QJsonObject &request) { - auto updatedContext = prepareContext(request); + const auto updatedContext = prepareContext(request); auto &completeSettings = Settings::codeCompletionSettings(); + auto &generalSettings = Settings::generalSettings(); + + bool isPreset1Active = isSpecifyCompletion(request); + + const auto providerName = !isPreset1Active ? generalSettings.ccProvider() + : generalSettings.ccPreset1Provider(); + const auto modelName = !isPreset1Active ? generalSettings.ccModel() + : generalSettings.ccPreset1Model(); + const auto url = !isPreset1Active ? generalSettings.ccUrl() : generalSettings.ccPreset1Url(); - auto providerName = Settings::generalSettings().ccProvider(); - auto provider = LLMCore::ProvidersManager::instance().getProviderByName(providerName); + const auto provider = LLMCore::ProvidersManager::instance().getProviderByName(providerName); if (!provider) { LOG_MESSAGE(QString("No provider found with name: %1").arg(providerName)); return; } - auto templateName = Settings::generalSettings().ccTemplate(); + auto templateName = !isPreset1Active ? generalSettings.ccTemplate() + : generalSettings.ccPreset1Template(); + auto promptTemplate = LLMCore::PromptTemplateManager::instance().getFimTemplateByName( templateName); @@ -168,19 +189,18 @@ void LLMClientInterface::handleCompletion(const QJsonObject &request) return; } + // TODO refactor to dynamic presets system LLMCore::LLMConfig config; config.requestType = LLMCore::RequestType::CodeCompletion; config.provider = provider; config.promptTemplate = promptTemplate; config.url = QUrl(QString("%1%2").arg( - Settings::generalSettings().ccUrl(), + url, promptTemplate->type() == LLMCore::TemplateType::Fim ? provider->completionEndpoint() : provider->chatEndpoint())); config.apiKey = provider->apiKey(); - config.providerRequest - = {{"model", Settings::generalSettings().ccModel()}, - {"stream", Settings::codeCompletionSettings().stream()}}; + config.providerRequest = {{"model", modelName}, {"stream", completeSettings.stream()}}; config.multiLineCompletion = completeSettings.multiLineCompletion(); @@ -246,11 +266,33 @@ LLMCore::ContextData LLMClientInterface::prepareContext(const QJsonObject &reque return reader.prepareContext(lineNumber, cursorPosition); } +Context::ProgrammingLanguage LLMClientInterface::getDocumentLanguage(const QJsonObject &request) const +{ + QJsonObject params = request["params"].toObject(); + QJsonObject doc = params["doc"].toObject(); + QString uri = doc["uri"].toString(); + + Utils::FilePath filePath = Utils::FilePath::fromString(QUrl(uri).toLocalFile()); + TextEditor::TextDocument *textDocument = TextEditor::TextDocument::textDocumentForFilePath( + filePath); + + if (!textDocument) { + LOG_MESSAGE("Error: Document is not available for" + filePath.toString()); + return Context::ProgrammingLanguage::Unknown; + } + + return Context::ProgrammingLanguageUtils::fromMimeType(textDocument->mimeType()); +} + void LLMClientInterface::sendCompletionToClient(const QString &completion, const QJsonObject &request, bool isComplete) { - auto templateName = Settings::generalSettings().ccTemplate(); + bool isPreset1Active = isSpecifyCompletion(request); + + auto templateName = !isPreset1Active ? Settings::generalSettings().ccTemplate() + : Settings::generalSettings().ccPreset1Template(); + auto promptTemplate = LLMCore::PromptTemplateManager::instance().getFimTemplateByName( templateName); diff --git a/LLMClientInterface.hpp b/LLMClientInterface.hpp index f3e69e7..624b60e 100644 --- a/LLMClientInterface.hpp +++ b/LLMClientInterface.hpp @@ -22,6 +22,7 @@ #include #include +#include #include #include @@ -58,8 +59,10 @@ class LLMClientInterface : public LanguageClient::BaseClientInterface void handleExit(const QJsonObject &request); void handleCancelRequest(const QJsonObject &request); - LLMCore::ContextData prepareContext(const QJsonObject &request, - const QStringView &accumulatedCompletion = QString{}); + LLMCore::ContextData prepareContext( + const QJsonObject &request, const QStringView &accumulatedCompletion = QString{}); + Context::ProgrammingLanguage getDocumentLanguage(const QJsonObject &request) const; + bool isSpecifyCompletion(const QJsonObject &request); LLMCore::RequestHandler m_requestHandler; QElapsedTimer m_completionTimer; diff --git a/context/CMakeLists.txt b/context/CMakeLists.txt index 577dd0f..0032f98 100644 --- a/context/CMakeLists.txt +++ b/context/CMakeLists.txt @@ -4,6 +4,7 @@ add_library(Context STATIC ContextManager.hpp ContextManager.cpp ContentFile.hpp TokenUtils.hpp TokenUtils.cpp + ProgrammingLanguage.hpp ProgrammingLanguage.cpp ) target_link_libraries(Context diff --git a/context/ProgrammingLanguage.cpp b/context/ProgrammingLanguage.cpp new file mode 100644 index 0000000..dbc4335 --- /dev/null +++ b/context/ProgrammingLanguage.cpp @@ -0,0 +1,70 @@ +/* + * 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 "ProgrammingLanguage.hpp" + +namespace QodeAssist::Context { + +ProgrammingLanguage ProgrammingLanguageUtils::fromMimeType(const QString &mimeType) +{ + if (mimeType == "text/x-qml" || mimeType == "application/javascript" + || mimeType == "text/javascript" || mimeType == "text/x-javascript") { + return ProgrammingLanguage::QML; + } + if (mimeType == "text/x-c++src" || mimeType == "text/x-c++hdr" || mimeType == "text/x-csrc" + || mimeType == "text/x-chdr") { + return ProgrammingLanguage::Cpp; + } + if (mimeType == "text/x-python") { + return ProgrammingLanguage::Python; + } + return ProgrammingLanguage::Unknown; +} + +QString ProgrammingLanguageUtils::toString(ProgrammingLanguage language) +{ + switch (language) { + case ProgrammingLanguage::Cpp: + return "c/c++"; + case ProgrammingLanguage::QML: + return "qml"; + case ProgrammingLanguage::Python: + return "python"; + case ProgrammingLanguage::Unknown: + default: + return QString(); + } +} + +ProgrammingLanguage ProgrammingLanguageUtils::fromString(const QString &str) +{ + QString lower = str.toLower(); + if (lower == "c/c++") { + return ProgrammingLanguage::Cpp; + } + if (lower == "qml") { + return ProgrammingLanguage::QML; + } + if (lower == "python") { + return ProgrammingLanguage::Python; + } + return ProgrammingLanguage::Unknown; +} + +} // namespace QodeAssist::Context diff --git a/context/ProgrammingLanguage.hpp b/context/ProgrammingLanguage.hpp new file mode 100644 index 0000000..49bc14b --- /dev/null +++ b/context/ProgrammingLanguage.hpp @@ -0,0 +1,43 @@ +/* + * 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::Context { + +enum class ProgrammingLanguage { + QML, // QML/JavaScript + Cpp, // C/C++ + Python, + Unknown, +}; + +namespace ProgrammingLanguageUtils { + +ProgrammingLanguage fromMimeType(const QString &mimeType); + +QString toString(ProgrammingLanguage language); + +ProgrammingLanguage fromString(const QString &str); + +} // namespace ProgrammingLanguageUtils + +} // namespace QodeAssist::Context diff --git a/settings/ButtonAspect.hpp b/settings/ButtonAspect.hpp index 95de288..ef833db 100644 --- a/settings/ButtonAspect.hpp +++ b/settings/ButtonAspect.hpp @@ -35,11 +35,26 @@ class ButtonAspect : public Utils::BaseAspect void addToLayoutImpl(Layouting::Layout &parent) override { auto button = new QPushButton(m_buttonText); + button->setVisible(m_visible); connect(button, &QPushButton::clicked, this, &ButtonAspect::clicked); + connect(this, &ButtonAspect::visibleChanged, button, &QPushButton::setVisible); parent.addItem(button); } + void updateVisibility(bool visible) + { + if (m_visible == visible) + return; + m_visible = visible; + emit visibleChanged(visible); + } + QString m_buttonText; + signals: void clicked(); + void visibleChanged(bool state); + +private: + bool m_visible = true; }; diff --git a/settings/GeneralSettings.cpp b/settings/GeneralSettings.cpp index bd03a71..2661ae0 100644 --- a/settings/GeneralSettings.cpp +++ b/settings/GeneralSettings.cpp @@ -89,6 +89,39 @@ GeneralSettings::GeneralSettings() ccStatus.setDefaultValue(""); ccTest.m_buttonText = TrConstants::TEST; + // preset1 + specifyPreset1.setSettingsKey(Constants::CC_SPECIFY_PRESET1); + specifyPreset1.setLabelText(TrConstants::ADD_NEW_PRESET); + specifyPreset1.setDefaultValue(false); + + preset1Language.setSettingsKey(Constants::CC_PRESET1_LANGUAGE); + preset1Language.setDisplayStyle(Utils::SelectionAspect::DisplayStyle::ComboBox); + // see ProgrammingLanguageUtils + preset1Language.addOption("qml"); + preset1Language.addOption("c/c++"); + preset1Language.addOption("python"); + + initStringAspect( + ccPreset1Provider, Constants::CC_PRESET1_PROVIDER, TrConstants::PROVIDER, "Ollama"); + ccPreset1Provider.setReadOnly(true); + ccPreset1SelectProvider.m_buttonText = TrConstants::SELECT; + + initStringAspect( + ccPreset1Url, Constants::CC_PRESET1_URL, TrConstants::URL, "http://localhost:11434"); + ccPreset1Url.setHistoryCompleter(Constants::CC_PRESET1_URL_HISTORY); + ccPreset1SetUrl.m_buttonText = TrConstants::SELECT; + + initStringAspect( + ccPreset1Model, Constants::CC_PRESET1_MODEL, TrConstants::MODEL, "qwen2.5-coder:7b"); + ccPreset1Model.setHistoryCompleter(Constants::CC_PRESET1_MODEL_HISTORY); + ccPreset1SelectModel.m_buttonText = TrConstants::SELECT; + + initStringAspect( + ccPreset1Template, Constants::CC_PRESET1_TEMPLATE, TrConstants::TEMPLATE, "Ollama Auto FIM"); + ccPreset1Template.setReadOnly(true); + ccPreset1SelectTemplate.m_buttonText = TrConstants::SELECT; + + // chat assistance initStringAspect(caProvider, Constants::CA_PROVIDER, TrConstants::PROVIDER, "Ollama"); caProvider.setReadOnly(true); caSelectProvider.m_buttonText = TrConstants::SELECT; @@ -117,6 +150,8 @@ GeneralSettings::GeneralSettings() setupConnections(); + updatePreset1Visiblity(specifyPreset1.value()); + setLayouter([this]() { using namespace Layouting; @@ -126,13 +161,21 @@ GeneralSettings::GeneralSettings() ccGrid.addRow({ccModel, ccSelectModel}); ccGrid.addRow({ccTemplate, ccSelectTemplate}); + auto ccPreset1Grid = Grid{}; + ccPreset1Grid.addRow({ccPreset1Provider, ccPreset1SelectProvider}); + ccPreset1Grid.addRow({ccPreset1Url, ccPreset1SetUrl}); + ccPreset1Grid.addRow({ccPreset1Model, ccPreset1SelectModel}); + ccPreset1Grid.addRow({ccPreset1Template, ccPreset1SelectTemplate}); + auto caGrid = Grid{}; caGrid.addRow({caProvider, caSelectProvider}); caGrid.addRow({caUrl, caSetUrl}); caGrid.addRow({caModel, caSelectModel}); caGrid.addRow({caTemplate, caSelectTemplate}); - auto ccGroup = Group{title(TrConstants::CODE_COMPLETION), ccGrid}; + auto ccGroup = Group{ + title(TrConstants::CODE_COMPLETION), + Column{ccGrid, Row{specifyPreset1, preset1Language, Stretch{1}}, ccPreset1Grid}}; auto caGroup = Group{title(TrConstants::CHAT_ASSISTANT), caGrid}; auto rootLayout = Column{ @@ -267,9 +310,11 @@ void GeneralSettings::showUrlSelectionDialog( dialog.addSpacing(); QStringList allUrls = predefinedUrls; - QString key - = QString("CompleterHistory/") - .append((&aspect == &ccUrl) ? Constants::CC_URL_HISTORY : Constants::CA_URL_HISTORY); + QString key = QString("CompleterHistory/") + .append( + (&aspect == &ccUrl) ? Constants::CC_URL_HISTORY + : (&aspect == &ccPreset1Url) ? Constants::CC_PRESET1_URL_HISTORY + : Constants::CA_URL_HISTORY); QStringList historyList = qtcSettings()->value(Utils::Key(key.toLocal8Bit())).toStringList(); allUrls.append(historyList); allUrls.removeDuplicates(); @@ -297,6 +342,18 @@ void GeneralSettings::showUrlSelectionDialog( dialog.exec(); } +void GeneralSettings::updatePreset1Visiblity(bool state) +{ + ccPreset1Provider.setVisible(specifyPreset1.volatileValue()); + ccPreset1SelectProvider.updateVisibility(specifyPreset1.volatileValue()); + ccPreset1Url.setVisible(specifyPreset1.volatileValue()); + ccPreset1SetUrl.updateVisibility(specifyPreset1.volatileValue()); + ccPreset1Model.setVisible(specifyPreset1.volatileValue()); + ccPreset1SelectModel.updateVisibility(specifyPreset1.volatileValue()); + ccPreset1Template.setVisible(specifyPreset1.volatileValue()); + ccPreset1SelectTemplate.updateVisibility(specifyPreset1.volatileValue()); +} + void GeneralSettings::setupConnections() { connect(&enableLogging, &Utils::BoolAspect::volatileValueChanged, this, [this]() { @@ -306,6 +363,10 @@ void GeneralSettings::setupConnections() connect(&checkUpdate, &ButtonAspect::clicked, this, [this]() { QodeAssist::UpdateDialog::checkForUpdatesAndShow(Core::ICore::dialogParent()); }); + + connect(&specifyPreset1, &Utils::BoolAspect::volatileValueChanged, this, [this]() { + updatePreset1Visiblity(specifyPreset1.volatileValue()); + }); } void GeneralSettings::resetPageToDefaults() @@ -328,6 +389,12 @@ void GeneralSettings::resetPageToDefaults() resetAspect(caTemplate); resetAspect(caUrl); resetAspect(enableCheckUpdate); + resetAspect(specifyPreset1); + resetAspect(preset1Language); + resetAspect(ccPreset1Provider); + resetAspect(ccPreset1Model); + resetAspect(ccPreset1Template); + resetAspect(ccPreset1Url); writeSettings(); } } diff --git a/settings/GeneralSettings.hpp b/settings/GeneralSettings.hpp index 7e44278..9a1a902 100644 --- a/settings/GeneralSettings.hpp +++ b/settings/GeneralSettings.hpp @@ -55,6 +55,23 @@ class GeneralSettings : public Utils::AspectContainer Utils::StringAspect ccStatus{this}; ButtonAspect ccTest{this}; + // TODO create dynamic presets system + // preset1 for code completion settings + Utils::BoolAspect specifyPreset1{this}; + Utils::SelectionAspect preset1Language{this}; + + Utils::StringAspect ccPreset1Provider{this}; + ButtonAspect ccPreset1SelectProvider{this}; + + Utils::StringAspect ccPreset1Url{this}; + ButtonAspect ccPreset1SetUrl{this}; + + Utils::StringAspect ccPreset1Model{this}; + ButtonAspect ccPreset1SelectModel{this}; + + Utils::StringAspect ccPreset1Template{this}; + ButtonAspect ccPreset1SelectTemplate{this}; + // chat assistant settings Utils::StringAspect caProvider{this}; ButtonAspect caSelectProvider{this}; @@ -82,6 +99,8 @@ class GeneralSettings : public Utils::AspectContainer void showUrlSelectionDialog(Utils::StringAspect &aspect, const QStringList &predefinedUrls); + void updatePreset1Visiblity(bool state); + private: void setupConnections(); void resetPageToDefaults(); diff --git a/settings/SettingsConstants.hpp b/settings/SettingsConstants.hpp index 018519b..4df7c20 100644 --- a/settings/SettingsConstants.hpp +++ b/settings/SettingsConstants.hpp @@ -46,6 +46,15 @@ const char CA_TEMPLATE[] = "QodeAssist.caTemplate"; const char CA_URL[] = "QodeAssist.caUrl"; const char CA_URL_HISTORY[] = "QodeAssist.caUrlHistory"; +const char CC_SPECIFY_PRESET1[] = "QodeAssist.ccSpecifyPreset1"; +const char CC_PRESET1_LANGUAGE[] = "QodeAssist.ccPreset1Language"; +const char CC_PRESET1_PROVIDER[] = "QodeAssist.ccPreset1Provider"; +const char CC_PRESET1_MODEL[] = "QodeAssist.ccPreset1Model"; +const char CC_PRESET1_MODEL_HISTORY[] = "QodeAssist.ccPreset1ModelHistory"; +const char CC_PRESET1_TEMPLATE[] = "QodeAssist.ccPreset1Template"; +const char CC_PRESET1_URL[] = "QodeAssist.ccPreset1Url"; +const char CC_PRESET1_URL_HISTORY[] = "QodeAssist.ccPreset1UrlHistory"; + // settings const char ENABLE_QODE_ASSIST[] = "QodeAssist.enableQodeAssist"; const char CC_AUTO_COMPLETION[] = "QodeAssist.ccAutoCompletion"; diff --git a/settings/SettingsTr.hpp b/settings/SettingsTr.hpp index ca8aac3..50a4c2c 100644 --- a/settings/SettingsTr.hpp +++ b/settings/SettingsTr.hpp @@ -86,6 +86,9 @@ inline const char ENTER_MODEL_MANUALLY_BUTTON[] inline const char AUTO_COMPLETION_SETTINGS[] = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Auto Completion Settings"); +inline const char ADD_NEW_PRESET[] + = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Add new preset for language"); + } // namespace TrConstants struct Tr