diff --git a/CMakeLists.txt b/CMakeLists.txt index 0b5d121..9e98f4f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,14 +34,13 @@ add_qtc_plugin(QodeAssist templates/PromptTemplate.hpp templates/CodeLLamaTemplate.hpp templates/StarCoder2Template.hpp - templates/CodeQwenChat.hpp templates/DeepSeekCoderV2.hpp + templates/CustomTemplate.hpp providers/LLMProvider.hpp providers/OllamaProvider.hpp providers/OllamaProvider.cpp providers/LMStudioProvider.hpp providers/LMStudioProvider.cpp providers/OpenAICompatProvider.hpp providers/OpenAICompatProvider.cpp LLMProvidersManager.hpp LLMProvidersManager.cpp - QodeAssistSettings.hpp QodeAssistSettings.cpp QodeAssist.qrc LSPCompletion.hpp LLMSuggestion.hpp LLMSuggestion.cpp @@ -50,4 +49,9 @@ add_qtc_plugin(QodeAssist 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 ) diff --git a/DocumentContextReader.cpp b/DocumentContextReader.cpp index 0aa42b5..063162b 100644 --- a/DocumentContextReader.cpp +++ b/DocumentContextReader.cpp @@ -23,7 +23,7 @@ #include #include -#include "QodeAssistSettings.hpp" +#include "settings/ContextSettings.hpp" const QRegularExpression &getYearRegex() { @@ -136,7 +136,7 @@ QString DocumentContextReader::getLanguageAndFileInfo() const QString DocumentContextReader::getSpecificInstructions() const { - QString specificInstruction = settings().specificInstractions().arg( + QString specificInstruction = Settings::contextSettings().specificInstractions().arg( LanguageServerProtocol::TextDocumentItem::mimeTypeToLanguageId(m_textDocument->mimeType())); return QString("%1").arg(specificInstruction); } diff --git a/LLMClientInterface.cpp b/LLMClientInterface.cpp index 9bfbc62..a473ac1 100644 --- a/LLMClientInterface.cpp +++ b/LLMClientInterface.cpp @@ -28,8 +28,9 @@ #include "DocumentContextReader.hpp" #include "LLMProvidersManager.hpp" #include "PromptTemplateManager.hpp" -#include "QodeAssistSettings.hpp" #include "QodeAssistUtils.hpp" +#include "settings/ContextSettings.hpp" +#include "settings/GeneralSettings.hpp" namespace QodeAssist { @@ -130,12 +131,13 @@ QString LLMClientInterface::сontextBefore(TextEditor::TextEditorWidget *widget, return QString(); QString contextBefore; - if (settings().readFullFile()) { + if (Settings::contextSettings().readFullFile()) { contextBefore = reader.readWholeFileBefore(lineNumber, cursorPosition); } else { - contextBefore = reader.getContextBefore(lineNumber, - cursorPosition, - settings().readStringsBeforeCursor()); + contextBefore + = reader.getContextBefore(lineNumber, + cursorPosition, + Settings::contextSettings().readStringsBeforeCursor()); } return contextBefore; @@ -153,12 +155,12 @@ QString LLMClientInterface::сontextAfter(TextEditor::TextEditorWidget *widget, return QString(); QString contextAfter; - if (settings().readFullFile()) { + if (Settings::contextSettings().readFullFile()) { contextAfter = reader.readWholeFileAfter(lineNumber, cursorPosition); } else { contextAfter = reader.getContextAfter(lineNumber, cursorPosition, - settings().readStringsAfterCursor()); + Settings::contextSettings().readStringsAfterCursor()); } return contextAfter; @@ -227,7 +229,7 @@ void LLMClientInterface::handleLLMResponse(QNetworkReply *reply, const QJsonObje QJsonObject position = request["params"].toObject()["doc"].toObject()["position"].toObject(); - if (!settings().multiLineCompletion() + if (!Settings::generalSettings().multiLineCompletion() && processSingleLineCompletion(reply, request, accumulatedResponse)) { return; } @@ -244,14 +246,14 @@ void LLMClientInterface::handleLLMResponse(QNetworkReply *reply, const QJsonObje } void LLMClientInterface::handleCompletion(const QJsonObject &request, - const QString &accumulatedCompletion) + const QStringView &accumulatedCompletion) { auto updatedContext = prepareContext(request, accumulatedCompletion); sendLLMRequest(request, updatedContext); } ContextData LLMClientInterface::prepareContext(const QJsonObject &request, - const QString &accumulatedCompletion) + const QStringView &accumulatedCompletion) { QJsonObject params = request["params"].toObject(); QJsonObject doc = params["doc"].toObject(); @@ -264,7 +266,7 @@ ContextData LLMClientInterface::prepareContext(const QJsonObject &request, if (!textDocument) { logMessage("Error: Document is not available for" + filePath.toString()); - return {"", ""}; + return ContextData{}; } int cursorPosition = position["character"].toInt(); @@ -277,21 +279,20 @@ ContextData LLMClientInterface::prepareContext(const QJsonObject &request, QString contextBefore = сontextBefore(widget, lineNumber, cursorPosition); QString contextAfter = сontextAfter(widget, lineNumber, cursorPosition); - QString instructions = QString("%1%2").arg(settings().useSpecificInstructions() + QString instructions = QString("%1%2").arg(Settings::contextSettings().useSpecificInstructions() ? reader.getSpecificInstructions() : QString(), - settings().useFilePathInContext() + Settings::contextSettings().useFilePathInContext() ? reader.getLanguageAndFileInfo() : QString()); - QString updatedContextBefore = contextBefore + accumulatedCompletion; - - return {updatedContextBefore, contextAfter, instructions}; + return {QString("%1%2").arg(contextBefore, accumulatedCompletion), contextAfter, instructions}; } void LLMClientInterface::updateProvider() { - m_serverUrl = QUrl(QString("%1%2").arg(settings().url(), settings().endPoint())); + m_serverUrl = QUrl(QString("%1%2").arg(Settings::generalSettings().url(), + Settings::generalSettings().endPoint())); } void LLMClientInterface::sendCompletionToClient(const QString &completion, @@ -332,7 +333,8 @@ void LLMClientInterface::sendCompletionToClient(const QString &completion, void LLMClientInterface::sendLLMRequest(const QJsonObject &request, const ContextData &prompt) { - QJsonObject providerRequest = {{"model", settings().modelName.value()}, {"stream", true}}; + QJsonObject providerRequest = {{"model", Settings::generalSettings().modelName.value()}, + {"stream", true}}; auto currentTemplate = PromptTemplateManager::instance().getCurrentTemplate(); currentTemplate->prepareRequest(providerRequest, prompt); @@ -378,9 +380,9 @@ void LLMClientInterface::sendLLMRequest(const QJsonObject &request, const Contex }); } -QString LLMClientInterface::removeStopWords(const QString &completion) +QString LLMClientInterface::removeStopWords(const QStringView &completion) { - QString filteredCompletion = completion; + QString filteredCompletion = completion.toString(); auto currentTemplate = PromptTemplateManager::instance().getCurrentTemplate(); QStringList stopWords = currentTemplate->stopWords(); diff --git a/LLMClientInterface.hpp b/LLMClientInterface.hpp index 04ce29a..3de65d5 100644 --- a/LLMClientInterface.hpp +++ b/LLMClientInterface.hpp @@ -45,12 +45,12 @@ public: bool isComplete); void handleCompletion(const QJsonObject &request, - const QString &accumulatedCompletion = QString()); + const QStringView &accumulatedCompletion = QString()); void sendLLMRequest(const QJsonObject &request, const ContextData &prompt); void handleLLMResponse(QNetworkReply *reply, const QJsonObject &request); ContextData prepareContext(const QJsonObject &request, - const QString &accumulatedCompletion = QString{}); + const QStringView &accumulatedCompletion = QString{}); void updateProvider(); protected: @@ -71,7 +71,7 @@ private: QString сontextBefore(TextEditor::TextEditorWidget *widget, int lineNumber, int cursorPosition); QString сontextAfter(TextEditor::TextEditorWidget *widget, int lineNumber, int cursorPosition); - QString removeStopWords(const QString &completion); + QString removeStopWords(const QStringView &completion); QUrl m_serverUrl; QNetworkAccessManager *m_manager; diff --git a/QodeAssist.json.in b/QodeAssist.json.in index 9b060ef..d8b1f23 100644 --- a/QodeAssist.json.in +++ b/QodeAssist.json.in @@ -1,6 +1,6 @@ { "Name" : "QodeAssist", - "Version" : "0.0.8", + "Version" : "0.1.0", "CompatVersion" : "${IDE_VERSION_COMPAT}", "Vendor" : "Petr Mironychev", "Copyright" : "(C) ${IDE_COPYRIGHT_YEAR} Petr Mironychev, (C) ${IDE_COPYRIGHT_YEAR} The Qt Company Ltd", diff --git a/QodeAssistClient.cpp b/QodeAssistClient.cpp index 296c519..d8593ec 100644 --- a/QodeAssistClient.cpp +++ b/QodeAssistClient.cpp @@ -31,7 +31,7 @@ #include "LLMClientInterface.hpp" #include "LLMSuggestion.hpp" -#include "QodeAssistSettings.hpp" +#include "settings/GeneralSettings.hpp" using namespace LanguageServerProtocol; using namespace TextEditor; @@ -43,6 +43,7 @@ namespace QodeAssist { QodeAssistClient::QodeAssistClient() : LanguageClient::Client(new LLMClientInterface()) + , m_recentCharCount(0) { setName("Qode Assist"); LanguageClient::LanguageFilter filter; @@ -51,6 +52,8 @@ QodeAssistClient::QodeAssistClient() start(); setupConnections(); + + m_typingTimer.start(); } QodeAssistClient::~QodeAssistClient() @@ -70,7 +73,7 @@ void QodeAssistClient::openDocument(TextEditor::TextDocument *document) this, [this, document](int position, int charsRemoved, int charsAdded) { Q_UNUSED(charsRemoved) - if (!settings().enableAutoComplete()) + if (!Settings::generalSettings().enableAutoComplete()) return; auto project = ProjectManager::projectForFile(document->filePath()); @@ -86,7 +89,18 @@ void QodeAssistClient::openDocument(TextEditor::TextDocument *document) const int cursorPosition = widget->textCursor().position(); if (cursorPosition < position || cursorPosition > position + charsAdded) return; - scheduleRequest(widget); + + m_recentCharCount += charsAdded; + + if (m_typingTimer.elapsed() + > Settings::generalSettings().autoCompletionTypingInterval()) { + m_recentCharCount = charsAdded; + m_typingTimer.restart(); + } + + if (m_recentCharCount > Settings::generalSettings().autoCompletionCharThreshold()) { + scheduleRequest(widget); + } }); } @@ -130,7 +144,8 @@ void QodeAssistClient::scheduleRequest(TextEditor::TextEditorWidget *editor) connect(timer, &QTimer::timeout, this, [this, editor]() { if (editor && editor->textCursor().position() - == m_scheduledRequests[editor]->property("cursorPosition").toInt()) + == m_scheduledRequests[editor]->property("cursorPosition").toInt() + && m_recentCharCount > Settings::generalSettings().autoCompletionCharThreshold()) requestCompletions(editor); }); connect(editor, &TextEditorWidget::destroyed, this, [this, editor]() { @@ -144,9 +159,8 @@ void QodeAssistClient::scheduleRequest(TextEditor::TextEditorWidget *editor) } it.value()->setProperty("cursorPosition", editor->textCursor().position()); - it.value()->start(settings().startSuggestionTimer()); + it.value()->start(Settings::generalSettings().startSuggestionTimer()); } - void QodeAssistClient::handleCompletions(const GetCompletionRequest::Response &response, TextEditor::TextEditorWidget *editor) { @@ -204,7 +218,7 @@ void QodeAssistClient::cancelRunningRequest(TextEditor::TextEditorWidget *editor bool QodeAssistClient::isEnabled(ProjectExplorer::Project *project) const { - return settings().enableQodeAssist(); + return Settings::generalSettings().enableQodeAssist(); } void QodeAssistClient::setupConnections() diff --git a/QodeAssistClient.hpp b/QodeAssistClient.hpp index 0271839..2605b26 100644 --- a/QodeAssistClient.hpp +++ b/QodeAssistClient.hpp @@ -55,6 +55,9 @@ private: QHash m_scheduledRequests; QMetaObject::Connection m_documentOpenedConnection; QMetaObject::Connection m_documentClosedConnection; + + QElapsedTimer m_typingTimer; + int m_recentCharCount; }; } // namespace QodeAssist diff --git a/QodeAssistConstants.hpp b/QodeAssistConstants.hpp index 4cb9971..774bdca 100644 --- a/QodeAssistConstants.hpp +++ b/QodeAssistConstants.hpp @@ -49,6 +49,8 @@ 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 SPECIFIC_INSTRUCTIONS[] = "QodeAssist.specificInstractions"; @@ -56,8 +58,15 @@ const char MULTILINE_COMPLETION[] = "QodeAssist.multilineCompletion"; const char API_KEY[] = "QodeAssist.apiKey"; const char USE_SPECIFIC_INSTRUCTIONS[] = "QodeAssist.useSpecificInstructions"; const char USE_FILE_PATH_IN_CONTEXT[] = "QodeAssist.useFilePathInContext"; +const char CUSTOM_JSON_TEMPLATE[] = "QodeAssist.customJsonTemplate"; 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"; diff --git a/QodeAssistSettings.cpp b/QodeAssistSettings.cpp deleted file mode 100644 index 188441e..0000000 --- a/QodeAssistSettings.cpp +++ /dev/null @@ -1,398 +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 "QodeAssistSettings.hpp" - -#include -#include -#include -#include - -#include "QodeAssistConstants.hpp" -#include "QodeAssisttr.h" - -#include "LLMProvidersManager.hpp" -#include "PromptTemplateManager.hpp" -#include "QodeAssistUtils.hpp" - -namespace QodeAssist { - -QodeAssistSettings &settings() -{ - static QodeAssistSettings settings; - return settings; -} - -QodeAssistSettings::QodeAssistSettings() -{ - setAutoApply(false); - - enableQodeAssist.setSettingsKey(Constants::ENABLE_QODE_ASSIST); - enableQodeAssist.setLabelText(Tr::tr("Enable Qode Assist")); - enableQodeAssist.setDefaultValue(true); - - enableAutoComplete.setSettingsKey(Constants::ENABLE_AUTO_COMPLETE); - enableAutoComplete.setLabelText(Tr::tr("Enable Auto Complete")); - enableAutoComplete.setDefaultValue(true); - - enableLogging.setSettingsKey(Constants::ENABLE_LOGGING); - enableLogging.setLabelText(Tr::tr("Enable Logging")); - enableLogging.setDefaultValue(false); - - llmProviders.setSettingsKey(Constants::LLM_PROVIDERS); - llmProviders.setDisplayName(Tr::tr("LLM Providers:")); - llmProviders.setDisplayStyle(Utils::SelectionAspect::DisplayStyle::ComboBox); - llmProviders.setDefaultValue(0); - - url.setSettingsKey(Constants::URL); - url.setLabelText(Tr::tr("URL:")); - url.setDisplayStyle(Utils::StringAspect::LineEditDisplay); - - endPoint.setSettingsKey(Constants::END_POINT); - endPoint.setLabelText(Tr::tr("Endpoint:")); - endPoint.setDisplayStyle(Utils::StringAspect::LineEditDisplay); - - modelName.setSettingsKey(Constants::MODEL_NAME); - modelName.setLabelText(Tr::tr("LLM Name:")); - modelName.setDisplayStyle(Utils::StringAspect::LineEditDisplay); - - temperature.setSettingsKey(Constants::TEMPERATURE); - temperature.setLabelText(Tr::tr("Temperature:")); - temperature.setDefaultValue(0.2); - temperature.setRange(0.0, 10.0); - - selectModels.m_buttonText = Tr::tr("Select Model"); - - ollamaLivetime.setSettingsKey(Constants::OLLAMA_LIVETIME); - ollamaLivetime.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); - - fimPrompts.setDisplayName(Tr::tr("Fill-In-Middle Prompt")); - fimPrompts.setSettingsKey(Constants::FIM_PROMPTS); - fimPrompts.setDefaultValue(0); - fimPrompts.setDisplayStyle(Utils::SelectionAspect::DisplayStyle::ComboBox); - - readFullFile.setSettingsKey(Constants::READ_FULL_FILE); - readFullFile.setLabelText(Tr::tr("Read Full File")); - readFullFile.setDefaultValue(false); - - maxFileThreshold.setSettingsKey(Constants::MAX_FILE_THRESHOLD); - maxFileThreshold.setLabelText(Tr::tr("Max File Threshold:")); - maxFileThreshold.setRange(10, 100000); - maxFileThreshold.setDefaultValue(600); - - readStringsBeforeCursor.setSettingsKey(Constants::READ_STRINGS_BEFORE_CURSOR); - readStringsBeforeCursor.setLabelText(Tr::tr("Read Strings Before Cursor")); - readStringsBeforeCursor.setDefaultValue(50); - - readStringsAfterCursor.setSettingsKey(Constants::READ_STRINGS_AFTER_CURSOR); - readStringsAfterCursor.setLabelText(Tr::tr("Read Strings After Cursor")); - readStringsAfterCursor.setDefaultValue(30); - - maxTokens.setSettingsKey(Constants::MAX_TOKENS); - maxTokens.setLabelText(Tr::tr("Max Tokens")); - maxTokens.setRange(-1, 10000); - maxTokens.setDefaultValue(150); - - useTopP.setSettingsKey(Constants::USE_TOP_P); - useTopP.setDefaultValue(false); - - topP.setSettingsKey(Constants::TOP_P); - topP.setLabelText(Tr::tr("top_p")); - topP.setDefaultValue(0.9); - topP.setRange(0.0, 1.0); - - useTopK.setSettingsKey(Constants::USE_TOP_K); - useTopK.setDefaultValue(false); - - topK.setSettingsKey(Constants::TOP_K); - topK.setLabelText(Tr::tr("top_k")); - topK.setDefaultValue(50); - topK.setRange(1, 1000); - - usePresencePenalty.setSettingsKey(Constants::USE_PRESENCE_PENALTY); - usePresencePenalty.setDefaultValue(false); - - presencePenalty.setSettingsKey(Constants::PRESENCE_PENALTY); - presencePenalty.setLabelText(Tr::tr("presence_penalty")); - presencePenalty.setDefaultValue(0.0); - presencePenalty.setRange(-2.0, 2.0); - - useFrequencyPenalty.setSettingsKey(Constants::USE_FREQUENCY_PENALTY); - useFrequencyPenalty.setDefaultValue(false); - - frequencyPenalty.setSettingsKey(Constants::FREQUENCY_PENALTY); - frequencyPenalty.setLabelText(Tr::tr("frequency_penalty")); - frequencyPenalty.setDefaultValue(0.0); - frequencyPenalty.setRange(-2.0, 2.0); - - startSuggestionTimer.setSettingsKey(Constants::START_SUGGESTION_TIMER); - startSuggestionTimer.setLabelText(Tr::tr("Start Suggestion Timer:")); - startSuggestionTimer.setRange(10, 10000); - startSuggestionTimer.setDefaultValue(500); - - useFilePathInContext.setSettingsKey(Constants::USE_FILE_PATH_IN_CONTEXT); - useFilePathInContext.setDefaultValue(false); - useFilePathInContext.setLabelText(Tr::tr("Use File Path in Context")); - - useSpecificInstructions.setSettingsKey(Constants::USE_SPECIFIC_INSTRUCTIONS); - useSpecificInstructions.setDefaultValue(false); - useSpecificInstructions.setLabelText(Tr::tr("Use Specific Instructions")); - - specificInstractions.setSettingsKey(Constants::SPECIFIC_INSTRUCTIONS); - 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"); - - resetToDefaults.m_buttonText = Tr::tr("Reset to Defaults"); - multiLineCompletion.setSettingsKey(Constants::MULTILINE_COMPLETION); - multiLineCompletion.setDefaultValue(true); - multiLineCompletion.setLabelText(Tr::tr("Enable Multiline Completion")); - - 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")); - - const auto &manager = LLMProvidersManager::instance(); - if (!manager.getProviderNames().isEmpty()) { - const auto providerNames = manager.getProviderNames(); - for (const QString &name : providerNames) { - llmProviders.addOption(name); - } - } - - const auto &promptManager = PromptTemplateManager::instance(); - if (!promptManager.getTemplateNames().isEmpty()) { - const auto promptNames = promptManager.getTemplateNames(); - for (const QString &name : promptNames) { - fimPrompts.addOption(name); - } - } - - readSettings(); - - topK.setEnabled(useTopK()); - topP.setEnabled(useTopP()); - presencePenalty.setEnabled(usePresencePenalty()); - frequencyPenalty.setEnabled(useFrequencyPenalty()); - readStringsAfterCursor.setEnabled(!readFullFile()); - readStringsBeforeCursor.setEnabled(!readFullFile()); - specificInstractions.setEnabled(useSpecificInstructions()); - PromptTemplateManager::instance().setCurrentTemplate(fimPrompts.stringValue()); - LLMProvidersManager::instance().setCurrentProvider(llmProviders.stringValue()); - - setLoggingEnabled(enableLogging()); - - setLayouter([this]() { - using namespace Layouting; - - return Column{Group{title(Tr::tr("General Settings")), - Form{Column{enableQodeAssist, - enableAutoComplete, - multiLineCompletion, - enableLogging, - Row{Stretch{1}, resetToDefaults}}}}, - Group{title(Tr::tr("LLM Providers")), - Form{Column{llmProviders, Row{url, endPoint}}}}, - Group{title(Tr::tr("LLM Model Settings")), - Form{Column{Row{selectModels, modelName}}}}, - Group{title(Tr::tr("FIM Prompt Settings")), - Form{Column{fimPrompts, - readFullFile, - maxFileThreshold, - readStringsBeforeCursor, - readStringsAfterCursor, - ollamaLivetime, - apiKey, - useFilePathInContext, - useSpecificInstructions, - specificInstractions, - temperature, - maxTokens, - startSuggestionTimer, - Row{useTopP, topP, Stretch{1}}, - Row{useTopK, topK, Stretch{1}}, - Row{usePresencePenalty, presencePenalty, Stretch{1}}, - Row{useFrequencyPenalty, frequencyPenalty, Stretch{1}}}}}, - st}; - }); - - setupConnections(); -} - -void QodeAssistSettings::setupConnections() -{ - connect(&llmProviders, &Utils::SelectionAspect::volatileValueChanged, this, [this]() { - int index = llmProviders.volatileValue(); - logMessage(QString("currentProvider %1").arg(llmProviders.displayForIndex(index))); - LLMProvidersManager::instance().setCurrentProvider(llmProviders.displayForIndex(index)); - updateProviderSettings(); - }); - - connect(&fimPrompts, &Utils::SelectionAspect::volatileValueChanged, this, [this]() { - int index = fimPrompts.volatileValue(); - logMessage(QString("currentPrompt %1").arg(fimPrompts.displayForIndex(index))); - PromptTemplateManager::instance().setCurrentTemplate(fimPrompts.displayForIndex(index)); - }); - - connect(&selectModels, &ButtonAspect::clicked, this, [this]() { showModelSelectionDialog(); }); - connect(&useTopP, &Utils::BoolAspect::volatileValueChanged, this, [this]() { - topP.setEnabled(useTopP.volatileValue()); - }); - connect(&useTopK, &Utils::BoolAspect::volatileValueChanged, this, [this]() { - topK.setEnabled(useTopK.volatileValue()); - }); - connect(&usePresencePenalty, &Utils::BoolAspect::volatileValueChanged, this, [this]() { - presencePenalty.setEnabled(usePresencePenalty.volatileValue()); - }); - connect(&useFrequencyPenalty, &Utils::BoolAspect::volatileValueChanged, this, [this]() { - frequencyPenalty.setEnabled(useFrequencyPenalty.volatileValue()); - }); - connect(&readFullFile, &Utils::BoolAspect::volatileValueChanged, this, [this]() { - readStringsAfterCursor.setEnabled(!readFullFile.volatileValue()); - readStringsBeforeCursor.setEnabled(!readFullFile.volatileValue()); - }); - connect(&resetToDefaults, - &ButtonAspect::clicked, - this, - &QodeAssistSettings::resetSettingsToDefaults); - connect(&enableLogging, &Utils::BoolAspect::volatileValueChanged, this, [this]() { - setLoggingEnabled(enableLogging.volatileValue()); - }); - connect(&useSpecificInstructions, &Utils::BoolAspect::volatileValueChanged, this, [this]() { - specificInstractions.setEnabled(useSpecificInstructions.volatileValue()); - }); -} - -void QodeAssistSettings::updateProviderSettings() -{ - auto *provider = LLMProvidersManager::instance().getCurrentProvider(); - - if (provider) { - logMessage(QString("currentProvider %1").arg(provider->name())); - url.setValue(provider->url()); - endPoint.setValue(provider->completionEndpoint()); - ollamaLivetime.setEnabled(provider->name() == "Ollama"); - } -} - -QStringList QodeAssistSettings::getInstalledModels() -{ - auto *provider = LLMProvidersManager::instance().getCurrentProvider(); - if (provider) { - Utils::Environment env = Utils::Environment::systemEnvironment(); - return provider->getInstalledModels(env); - } - return {}; -} - -void QodeAssistSettings::showModelSelectionDialog() -{ - QStringList models = getInstalledModels(); - - bool ok; - QString selectedModel = QInputDialog::getItem(Core::ICore::dialogParent(), - Tr::tr("Select LLM Model"), - Tr::tr("Choose a model:"), - models, - 0, - false, - &ok); - - if (ok && !selectedModel.isEmpty()) { - modelName.setValue(selectedModel); - writeSettings(); - } -} - -void QodeAssistSettings::resetSettingsToDefaults() -{ - QMessageBox::StandardButton reply; - reply = QMessageBox::question( - Core::ICore::dialogParent(), - Tr::tr("Reset Settings"), - Tr::tr("Are you sure you want to reset all settings to default values?"), - QMessageBox::Yes | QMessageBox::No); - - if (reply == QMessageBox::Yes) { - resetAspect(enableQodeAssist); - resetAspect(enableAutoComplete); - resetAspect(llmProviders); - resetAspect(url); - resetAspect(endPoint); - resetAspect(modelName); - resetAspect(fimPrompts); - resetAspect(temperature); - resetAspect(maxTokens); - resetAspect(readFullFile); - resetAspect(maxFileThreshold); - resetAspect(readStringsBeforeCursor); - resetAspect(readStringsAfterCursor); - resetAspect(useTopP); - resetAspect(topP); - resetAspect(useTopK); - resetAspect(topK); - resetAspect(usePresencePenalty); - resetAspect(presencePenalty); - resetAspect(useFrequencyPenalty); - resetAspect(frequencyPenalty); - resetAspect(startSuggestionTimer); - resetAspect(enableLogging); - resetAspect(ollamaLivetime); - resetAspect(specificInstractions); - resetAspect(multiLineCompletion); - resetAspect(useFilePathInContext); - resetAspect(useSpecificInstructions); - - fimPrompts.setStringValue("StarCoder2"); - llmProviders.setStringValue("Ollama"); - - updateProviderSettings(); - apply(); - - QMessageBox::information(Core::ICore::dialogParent(), - Tr::tr("Settings Reset"), - Tr::tr("All settings have been reset to their default values.")); - } -} - -class QodeAssistSettingsPage : public Core::IOptionsPage -{ -public: - QodeAssistSettingsPage() - { - setId(Constants::QODE_ASSIST_GENERAL_OPTIONS_ID); - setDisplayName("Qode Assist"); - setCategory(Constants::QODE_ASSIST_GENERAL_OPTIONS_CATEGORY); - setDisplayCategory(Constants::QODE_ASSIST_GENERAL_OPTIONS_DISPLAY_CATEGORY); - setCategoryIconPath(":/resources/images/qoderassist-icon.png"); - setSettingsProvider([] { return &settings(); }); - } -}; - -const QodeAssistSettingsPage settingsPage; - -} // namespace QodeAssist diff --git a/QodeAssistSettings.hpp b/QodeAssistSettings.hpp deleted file mode 100644 index 641f9d2..0000000 --- a/QodeAssistSettings.hpp +++ /dev/null @@ -1,115 +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 - -namespace QodeAssist { - -template -void resetAspect(AspectType &aspect) -{ - aspect.setValue(aspect.defaultValue()); -} - -class ButtonAspect : public Utils::BaseAspect -{ - Q_OBJECT - -public: - ButtonAspect(Utils::AspectContainer *container = nullptr) - : Utils::BaseAspect(container) - {} - - void addToLayout(Layouting::Layout &parent) override - { - auto button = new QPushButton(m_buttonText); - connect(button, &QPushButton::clicked, this, &ButtonAspect::clicked); - parent.addItem(button); - } - - QString m_buttonText; -signals: - void clicked(); -}; - -class QodeAssistSettings : public Utils::AspectContainer -{ -public: - QodeAssistSettings(); - - Utils::BoolAspect enableQodeAssist{this}; - Utils::BoolAspect enableAutoComplete{this}; - Utils::BoolAspect enableLogging{this}; - - Utils::SelectionAspect llmProviders{this}; - Utils::StringAspect url{this}; - Utils::StringAspect endPoint{this}; - - Utils::StringAspect modelName{this}; - ButtonAspect selectModels{this}; - - Utils::SelectionAspect fimPrompts{this}; - Utils::DoubleAspect temperature{this}; - Utils::IntegerAspect maxTokens{this}; - - Utils::BoolAspect readFullFile{this}; - Utils::IntegerAspect readStringsBeforeCursor{this}; - Utils::IntegerAspect readStringsAfterCursor{this}; - - Utils::BoolAspect useTopP{this}; - Utils::DoubleAspect topP{this}; - - Utils::BoolAspect useTopK{this}; - Utils::IntegerAspect topK{this}; - - Utils::BoolAspect usePresencePenalty{this}; - Utils::DoubleAspect presencePenalty{this}; - - Utils::BoolAspect useFrequencyPenalty{this}; - Utils::DoubleAspect frequencyPenalty{this}; - - Utils::IntegerAspect startSuggestionTimer{this}; - Utils::IntegerAspect maxFileThreshold{this}; - - Utils::StringAspect ollamaLivetime{this}; - Utils::StringAspect specificInstractions{this}; - Utils::BoolAspect useSpecificInstructions{this}; - Utils::BoolAspect useFilePathInContext{this}; - Utils::BoolAspect multiLineCompletion{this}; - - Utils::StringAspect apiKey{this}; - - ButtonAspect resetToDefaults{this}; - -private: - void setupConnections(); - void updateProviderSettings(); - QStringList getInstalledModels(); - void showModelSelectionDialog(); - Utils::Environment getEnvironmentWithProviderPaths() const; - void resetSettingsToDefaults(); -}; - -QodeAssistSettings &settings(); - -} // namespace QodeAssist diff --git a/providers/LMStudioProvider.cpp b/providers/LMStudioProvider.cpp index 4aa5df3..dba91bc 100644 --- a/providers/LMStudioProvider.cpp +++ b/providers/LMStudioProvider.cpp @@ -26,8 +26,8 @@ #include #include "PromptTemplateManager.hpp" -#include "QodeAssistSettings.hpp" #include "QodeAssistUtils.hpp" +#include "settings/PresetPromptsSettings.hpp" namespace QodeAssist::Providers { @@ -50,25 +50,27 @@ QString LMStudioProvider::completionEndpoint() const void LMStudioProvider::prepareRequest(QJsonObject &request) { + auto &settings = Settings::presetPromptsSettings(); const auto ¤tTemplate = PromptTemplateManager::instance().getCurrentTemplate(); - + if (currentTemplate->name() == "Custom Template") + return; if (request.contains("prompt")) { QJsonArray messages{ {QJsonObject{{"role", "user"}, {"content", request.take("prompt").toString()}}}}; request["messages"] = std::move(messages); } - request["max_tokens"] = settings().maxTokens(); - request["temperature"] = settings().temperature(); + request["max_tokens"] = settings.maxTokens(); + request["temperature"] = settings.temperature(); request["stop"] = QJsonArray::fromStringList(currentTemplate->stopWords()); - 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(); + 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) diff --git a/providers/OllamaProvider.cpp b/providers/OllamaProvider.cpp index 41aaaa7..ab9a1ce 100644 --- a/providers/OllamaProvider.cpp +++ b/providers/OllamaProvider.cpp @@ -26,8 +26,8 @@ #include #include "PromptTemplateManager.hpp" -#include "QodeAssistSettings.hpp" #include "QodeAssistUtils.hpp" +#include "settings/PresetPromptsSettings.hpp" namespace QodeAssist::Providers { @@ -50,21 +50,24 @@ QString OllamaProvider::completionEndpoint() const void OllamaProvider::prepareRequest(QJsonObject &request) { + auto &settings = Settings::presetPromptsSettings(); auto currentTemplate = PromptTemplateManager::instance().getCurrentTemplate(); + if (currentTemplate->name() == "Custom Template") + return; QJsonObject options; - options["num_predict"] = settings().maxTokens(); - options["keep_alive"] = settings().ollamaLivetime(); - options["temperature"] = settings().temperature(); + options["num_predict"] = settings.maxTokens(); + options["keep_alive"] = settings.ollamaLivetime(); + options["temperature"] = settings.temperature(); options["stop"] = QJsonArray::fromStringList(currentTemplate->stopWords()); - 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(); + 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; } diff --git a/providers/OpenAICompatProvider.cpp b/providers/OpenAICompatProvider.cpp index 64d0b3a..6214678 100644 --- a/providers/OpenAICompatProvider.cpp +++ b/providers/OpenAICompatProvider.cpp @@ -25,7 +25,7 @@ #include #include "PromptTemplateManager.hpp" -#include "QodeAssistSettings.hpp" +#include "settings/PresetPromptsSettings.hpp" namespace QodeAssist::Providers { @@ -48,7 +48,10 @@ QString OpenAICompatProvider::completionEndpoint() const void OpenAICompatProvider::prepareRequest(QJsonObject &request) { + auto &settings = Settings::presetPromptsSettings(); const auto ¤tTemplate = PromptTemplateManager::instance().getCurrentTemplate(); + if (currentTemplate->name() == "Custom Template") + return; if (request.contains("prompt")) { QJsonArray messages{ @@ -56,19 +59,19 @@ void OpenAICompatProvider::prepareRequest(QJsonObject &request) request["messages"] = std::move(messages); } - request["max_tokens"] = settings().maxTokens(); - request["temperature"] = settings().temperature(); + request["max_tokens"] = settings.maxTokens(); + request["temperature"] = settings.temperature(); request["stop"] = QJsonArray::fromStringList(currentTemplate->stopWords()); - 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(); + 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.value(); if (!apiKey.isEmpty()) { request["api_key"] = apiKey; } diff --git a/qodeassist.cpp b/qodeassist.cpp index ffcd0f6..a679547 100644 --- a/qodeassist.cpp +++ b/qodeassist.cpp @@ -45,7 +45,7 @@ #include "providers/OllamaProvider.hpp" #include "providers/OpenAICompatProvider.hpp" #include "templates/CodeLLamaTemplate.hpp" -#include "templates/CodeQwenChat.hpp" +#include "templates/CustomTemplate.hpp" #include "templates/DeepSeekCoderV2.hpp" #include "templates/StarCoder2Template.hpp" @@ -80,8 +80,8 @@ public: auto &templateManager = PromptTemplateManager::instance(); templateManager.registerTemplate(); templateManager.registerTemplate(); - templateManager.registerTemplate(); templateManager.registerTemplate(); + templateManager.registerTemplate(); Utils::Icon QCODEASSIST_ICON( {{":/resources/images/qoderassist-icon.png", Utils::Theme::IconsBaseColor}}); diff --git a/rawPromptExamples/OllamaStarCoder2FIM.json b/rawPromptExamples/OllamaStarCoder2FIM.json new file mode 100644 index 0000000..7ce65cc --- /dev/null +++ b/rawPromptExamples/OllamaStarCoder2FIM.json @@ -0,0 +1,19 @@ +{ + "prompt": "{{QODE_INSTRUCTIONS}}{{QODE_PREFIX}}{{QODE_SUFFIX}}", + "options": { + "temperature": 0.7, + "top_p": 0.95, + "top_k": 40, + "num_predict": 175, + "stop": [ + "<|endoftext|>", + "", + "", + "", + "" + ], + "frequency_penalty": 0, + "presence_penalty": 0 + }, + "stream": true +} \ No newline at end of file diff --git a/rawPromptExamples/OpenAICodeqwenChat.json b/rawPromptExamples/OpenAICodeqwenChat.json new file mode 100644 index 0000000..6ff0546 --- /dev/null +++ b/rawPromptExamples/OpenAICodeqwenChat.json @@ -0,0 +1,16 @@ +{ + "max_tokens": 150, + "messages": [ + { + "content": "{{QODE_INSTRUCTIONS}}\n### Instruction:{{QODE_PREFIX}}{{QODE_SUFFIX}} ### Response:\n", + "role": "user" + } + ], + "stop": [ + "### Instruction:", + "### Response:", + "\n\n### " + ], + "stream": true, + "temperature": 0.2 +} \ No newline at end of file diff --git a/settings/ContextSettings.cpp b/settings/ContextSettings.cpp new file mode 100644 index 0000000..f9af061 --- /dev/null +++ b/settings/ContextSettings.cpp @@ -0,0 +1,148 @@ +/* + * 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 "ContextSettings.hpp" + +#include +#include +#include +#include + +#include "QodeAssistConstants.hpp" +#include "QodeAssisttr.h" + +namespace QodeAssist::Settings { +ContextSettings &contextSettings() +{ + static ContextSettings settings; + return settings; +} + +ContextSettings::ContextSettings() +{ + setAutoApply(false); + + setDisplayName(Tr::tr("Context")); + + readFullFile.setSettingsKey(Constants::READ_FULL_FILE); + readFullFile.setLabelText(Tr::tr("Read Full File")); + readFullFile.setDefaultValue(false); + + readStringsBeforeCursor.setSettingsKey(Constants::READ_STRINGS_BEFORE_CURSOR); + readStringsBeforeCursor.setLabelText(Tr::tr("Read Strings Before Cursor")); + readStringsBeforeCursor.setRange(0, 10000); + readStringsBeforeCursor.setDefaultValue(50); + + readStringsAfterCursor.setSettingsKey(Constants::READ_STRINGS_AFTER_CURSOR); + readStringsAfterCursor.setLabelText(Tr::tr("Read Strings After Cursor")); + readStringsAfterCursor.setRange(0, 10000); + readStringsAfterCursor.setDefaultValue(30); + + useFilePathInContext.setSettingsKey(Constants::USE_FILE_PATH_IN_CONTEXT); + useFilePathInContext.setDefaultValue(false); + useFilePathInContext.setLabelText(Tr::tr("Use File Path in Context")); + + useSpecificInstructions.setSettingsKey(Constants::USE_SPECIFIC_INSTRUCTIONS); + useSpecificInstructions.setDefaultValue(false); + useSpecificInstructions.setLabelText(Tr::tr("Use Specific Instructions")); + + specificInstractions.setSettingsKey(Constants::SPECIFIC_INSTRUCTIONS); + 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"); + + resetToDefaults.m_buttonText = Tr::tr("Reset Page to Defaults"); + + readSettings(); + + readStringsAfterCursor.setEnabled(!readFullFile()); + readStringsBeforeCursor.setEnabled(!readFullFile()); + specificInstractions.setEnabled(useSpecificInstructions()); + + setupConnection(); + + setLayouter([this]() { + using namespace Layouting; + return Column{Row{readFullFile, Stretch{1}, resetToDefaults}, + readStringsBeforeCursor, + readStringsAfterCursor, + useFilePathInContext, + useSpecificInstructions, + specificInstractions, + Stretch{1}}; + }); +} + +void ContextSettings::setupConnection() +{ + connect(&readFullFile, &Utils::BoolAspect::volatileValueChanged, this, [this]() { + readStringsAfterCursor.setEnabled(!readFullFile.volatileValue()); + readStringsBeforeCursor.setEnabled(!readFullFile.volatileValue()); + }); + connect(&useSpecificInstructions, &Utils::BoolAspect::volatileValueChanged, this, [this]() { + specificInstractions.setEnabled(useSpecificInstructions.volatileValue()); + }); + connect(&resetToDefaults, &ButtonAspect::clicked, this, &ContextSettings::resetPageToDefaults); +} + +void ContextSettings::resetPageToDefaults() +{ + QMessageBox::StandardButton reply; + reply = QMessageBox::question( + Core::ICore::dialogParent(), + Tr::tr("Reset Settings"), + Tr::tr("Are you sure you want to reset all settings to default values?"), + QMessageBox::Yes | QMessageBox::No); + + if (reply == QMessageBox::Yes) { + resetAspect(readFullFile); + resetAspect(readStringsBeforeCursor); + resetAspect(readStringsAfterCursor); + resetAspect(useFilePathInContext); + resetAspect(useSpecificInstructions); + resetAspect(specificInstractions); + } + + QMessageBox::information(Core::ICore::dialogParent(), + Tr::tr("Settings Reset"), + Tr::tr("All settings have been reset to their default values.")); +} + +class ContextSettingsPage : public Core::IOptionsPage +{ +public: + ContextSettingsPage() + { + setId(Constants::QODE_ASSIST_CONTEXT_SETTINGS_PAGE_ID); + setDisplayName(Tr::tr("Context")); + setCategory(Constants::QODE_ASSIST_GENERAL_OPTIONS_CATEGORY); + setDisplayCategory(Constants::QODE_ASSIST_GENERAL_OPTIONS_DISPLAY_CATEGORY); + setCategoryIconPath(":/resources/images/qoderassist-icon.png"); + setSettingsProvider([] { return &contextSettings(); }); + } +}; + +const ContextSettingsPage contextSettingsPage; + +} // namespace QodeAssist::Settings diff --git a/settings/ContextSettings.hpp b/settings/ContextSettings.hpp new file mode 100644 index 0000000..4a6f539 --- /dev/null +++ b/settings/ContextSettings.hpp @@ -0,0 +1,50 @@ +/* + * 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 "SettingsUtils.hpp" + +namespace QodeAssist::Settings { + +class ContextSettings : public Utils::AspectContainer +{ +public: + ContextSettings(); + + Utils::BoolAspect readFullFile{this}; + Utils::IntegerAspect readStringsBeforeCursor{this}; + Utils::IntegerAspect readStringsAfterCursor{this}; + + Utils::StringAspect specificInstractions{this}; + Utils::BoolAspect useSpecificInstructions{this}; + Utils::BoolAspect useFilePathInContext{this}; + + ButtonAspect resetToDefaults{this}; + +private: + void setupConnection(); + void resetPageToDefaults(); +}; + +ContextSettings &contextSettings(); + +} // namespace QodeAssist::Settings diff --git a/settings/CustomPromptSettings.cpp b/settings/CustomPromptSettings.cpp new file mode 100644 index 0000000..ee5ad78 --- /dev/null +++ b/settings/CustomPromptSettings.cpp @@ -0,0 +1,203 @@ +/* + * 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 "CustomPromptSettings.hpp" + +#include +#include +#include +#include +#include +#include + +#include "QodeAssistConstants.hpp" +#include "QodeAssisttr.h" + +namespace QodeAssist::Settings { + +CustomPromptSettings &customPromptSettings() +{ + static CustomPromptSettings settings; + return settings; +} + +CustomPromptSettings::CustomPromptSettings() +{ + setAutoApply(false); + + setDisplayName(Tr::tr("Custom Prompt")); + + customJsonLabel.setLabelText("Custom JSON Template:"); + customJsonLabel.setDisplayStyle(Utils::StringAspect::LabelDisplay); + + customJsonLegend.setLabelText(Tr::tr(R"(Prompt components: +- model is set on General Page +- {{QODE_INSTRUCTIONS}}: Placeholder for specific instructions or context. +- {{QODE_PREFIX}}: Will be replaced with the actual code before the cursor. +- {{QODE_SUFFIX}}: Will be replaced with the actual code after the cursor. +)")); + + customJsonTemplate.setSettingsKey(Constants::CUSTOM_JSON_TEMPLATE); + customJsonTemplate.setDisplayStyle(Utils::StringAspect::TextEditDisplay); + + customJsonTemplate.setDefaultValue(R"({ + "prompt": "{{QODE_INSTRUCTIONS}}{{QODE_PREFIX}}{{QODE_SUFFIX}}", + "options": { + "temperature": 0.7, + "top_p": 0.95, + "top_k": 40, + "num_predict": 100, + "stop": [ + "<|endoftext|>", + "", + "", + "", + "" + ], + "frequency_penalty": 0, + "presence_penalty": 0 + }, + "stream": true +})"); + saveCustomTemplateButton.m_buttonText = (Tr::tr("Save Custom Template to JSON")); + loadCustomTemplateButton.m_buttonText = (Tr::tr("Load Custom Template from JSON")); + resetToDefaults.m_buttonText = Tr::tr("Reset Page to Defaults"); + + readSettings(); + + setupConnection(); + + setLayouter([this]() { + using namespace Layouting; + return Column{Row{customJsonLabel, Stretch{1}, resetToDefaults}, + Row{customJsonTemplate, + Column{saveCustomTemplateButton, + loadCustomTemplateButton, + customJsonLegend, + Stretch{1}}}}; + }); +} + +void CustomPromptSettings::setupConnection() +{ + connect(&resetToDefaults, + &ButtonAspect::clicked, + this, + &CustomPromptSettings::resetSettingsToDefaults); + connect(&saveCustomTemplateButton, + &ButtonAspect::clicked, + this, + &CustomPromptSettings::saveCustomTemplate); + connect(&loadCustomTemplateButton, + &ButtonAspect::clicked, + this, + &CustomPromptSettings::loadCustomTemplate); +} + +void CustomPromptSettings::resetSettingsToDefaults() +{ + QMessageBox::StandardButton reply; + reply = QMessageBox::question( + Core::ICore::dialogParent(), + Tr::tr("Reset Settings"), + Tr::tr("Are you sure you want to reset all settings to default values?"), + QMessageBox::Yes | QMessageBox::No); + + if (reply == QMessageBox::Yes) { + resetAspect(customJsonTemplate); + } + + QMessageBox::information(Core::ICore::dialogParent(), + Tr::tr("Settings Reset"), + Tr::tr("All settings have been reset to their default values.")); +} + +void CustomPromptSettings::saveCustomTemplate() +{ + QString fileName = QFileDialog::getSaveFileName(nullptr, + Tr::tr("Save JSON Template"), + QString(), + Tr::tr("JSON Files (*.json)")); + if (fileName.isEmpty()) + return; + + QFile file(fileName); + if (file.open(QIODevice::WriteOnly | QIODevice::Text)) { + QTextStream out(&file); + out << customJsonTemplate.value(); + file.close(); + QMessageBox::information(nullptr, + Tr::tr("Save Successful"), + Tr::tr("JSON template has been saved successfully.")); + } else { + QMessageBox::critical(nullptr, + Tr::tr("Save Failed"), + Tr::tr("Failed to save JSON template.")); + } +} + +void CustomPromptSettings::loadCustomTemplate() +{ + QString fileName = QFileDialog::getOpenFileName(nullptr, + Tr::tr("Load JSON Template"), + QString(), + Tr::tr("JSON Files (*.json)")); + if (fileName.isEmpty()) + return; + + QFile file(fileName); + if (file.open(QIODevice::ReadOnly | QIODevice::Text)) { + QTextStream in(&file); + QString jsonContent = in.readAll(); + file.close(); + + QJsonParseError parseError; + QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonContent.toUtf8(), &parseError); + if (parseError.error == QJsonParseError::NoError) { + customJsonTemplate.setVolatileValue(jsonContent); + QMessageBox::information(nullptr, + Tr::tr("Load Successful"), + Tr::tr("JSON template has been loaded successfully.")); + } else { + QMessageBox::critical(nullptr, + Tr::tr("Invalid JSON"), + Tr::tr("The selected file contains invalid JSON.")); + } + } else { + QMessageBox::critical(nullptr, + Tr::tr("Load Failed"), + Tr::tr("Failed to load JSON template.")); + } +} + +class CustomPromptSettingsPage : public Core::IOptionsPage +{ +public: + CustomPromptSettingsPage() + { + setId(Constants::QODE_ASSIST_CUSTOM_PROMPT_SETTINGS_PAGE_ID); + setDisplayName(Tr::tr("Custom Prompt")); + setCategory(Constants::QODE_ASSIST_GENERAL_OPTIONS_CATEGORY); + setSettingsProvider([] { return &customPromptSettings(); }); + } +}; + +const CustomPromptSettingsPage customPromptSettingsPage; + +} // namespace QodeAssist::Settings diff --git a/settings/CustomPromptSettings.hpp b/settings/CustomPromptSettings.hpp new file mode 100644 index 0000000..7ff0515 --- /dev/null +++ b/settings/CustomPromptSettings.hpp @@ -0,0 +1,48 @@ +/* + * 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 "settings/SettingsUtils.hpp" +#include + +namespace QodeAssist::Settings { + +class CustomPromptSettings : public Utils::AspectContainer +{ +public: + CustomPromptSettings(); + + Utils::StringAspect customJsonLabel{this}; + Utils::StringAspect customJsonTemplate{this}; + Utils::StringAspect customJsonLegend{this}; + ButtonAspect saveCustomTemplateButton{this}; + ButtonAspect loadCustomTemplateButton{this}; + ButtonAspect resetToDefaults{this}; + +private: + void setupConnection(); + void resetSettingsToDefaults(); + void saveCustomTemplate(); + void loadCustomTemplate(); +}; + +CustomPromptSettings &customPromptSettings(); + +} // namespace QodeAssist::Settings diff --git a/settings/GeneralSettings.cpp b/settings/GeneralSettings.cpp new file mode 100644 index 0000000..0edb0b0 --- /dev/null +++ b/settings/GeneralSettings.cpp @@ -0,0 +1,257 @@ +/* + * 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 "GeneralSettings.hpp" + +#include +#include +#include +#include +#include + +#include "LLMProvidersManager.hpp" +#include "PromptTemplateManager.hpp" +#include "QodeAssistConstants.hpp" +#include "QodeAssistUtils.hpp" +#include "QodeAssisttr.h" + +namespace QodeAssist::Settings { + +GeneralSettings &generalSettings() +{ + static GeneralSettings settings; + return settings; +} + +GeneralSettings::GeneralSettings() +{ + setAutoApply(false); + + setDisplayName(Tr::tr("General")); + + enableQodeAssist.setSettingsKey(Constants::ENABLE_QODE_ASSIST); + enableQodeAssist.setLabelText(Tr::tr("Enable Qode Assist")); + enableQodeAssist.setDefaultValue(true); + + enableAutoComplete.setSettingsKey(Constants::ENABLE_AUTO_COMPLETE); + enableAutoComplete.setLabelText(Tr::tr("Enable Auto Complete")); + enableAutoComplete.setDefaultValue(true); + + enableLogging.setSettingsKey(Constants::ENABLE_LOGGING); + enableLogging.setLabelText(Tr::tr("Enable Logging")); + enableLogging.setDefaultValue(false); + + multiLineCompletion.setSettingsKey(Constants::MULTILINE_COMPLETION); + multiLineCompletion.setDefaultValue(true); + multiLineCompletion.setLabelText(Tr::tr("Enable Multiline Completion")); + + startSuggestionTimer.setSettingsKey(Constants::START_SUGGESTION_TIMER); + startSuggestionTimer.setLabelText(Tr::tr("Start Suggestion Timer:")); + startSuggestionTimer.setRange(10, 10000); + startSuggestionTimer.setDefaultValue(500); + + autoCompletionCharThreshold.setSettingsKey(Constants::AUTO_COMPLETION_CHAR_THRESHOLD); + autoCompletionCharThreshold.setLabelText( + Tr::tr("Character threshold for AI suggestion start:")); + autoCompletionCharThreshold.setToolTip( + Tr::tr("The number of characters that need to be typed within the typing interval " + "before an AI suggestion request is sent.")); + autoCompletionCharThreshold.setRange(1, 10); + autoCompletionCharThreshold.setDefaultValue(2); + + autoCompletionTypingInterval.setSettingsKey(Constants::AUTO_COMPLETION_TYPING_INTERVAL); + autoCompletionTypingInterval.setLabelText( + Tr::tr("Typing interval for AI suggestion start(ms):")); + autoCompletionTypingInterval.setToolTip( + Tr::tr("The time window (in milliseconds) during which the character threshold " + "must be met to trigger an AI suggestion request.")); + autoCompletionTypingInterval.setRange(500, 5000); + autoCompletionTypingInterval.setDefaultValue(2000); + + llmProviders.setSettingsKey(Constants::LLM_PROVIDERS); + llmProviders.setDisplayName(Tr::tr("FIM Provider:")); + llmProviders.setDisplayStyle(Utils::SelectionAspect::DisplayStyle::ComboBox); + llmProviders.setDefaultValue(0); + + url.setSettingsKey(Constants::URL); + url.setLabelText(Tr::tr("URL:")); + url.setDisplayStyle(Utils::StringAspect::LineEditDisplay); + + endPoint.setSettingsKey(Constants::END_POINT); + endPoint.setLabelText(Tr::tr("FIM Endpoint:")); + endPoint.setDisplayStyle(Utils::StringAspect::LineEditDisplay); + + modelName.setSettingsKey(Constants::MODEL_NAME); + modelName.setLabelText(Tr::tr("LLM Name:")); + modelName.setDisplayStyle(Utils::StringAspect::LineEditDisplay); + + selectModels.m_buttonText = Tr::tr("Select Fill-In-the-Middle Model"); + + fimPrompts.setDisplayName(Tr::tr("Fill-In-the-Middle Prompt")); + fimPrompts.setSettingsKey(Constants::FIM_PROMPTS); + fimPrompts.setDefaultValue(0); + fimPrompts.setDisplayStyle(Utils::SelectionAspect::DisplayStyle::ComboBox); + resetToDefaults.m_buttonText = Tr::tr("Reset Page to Defaults"); + + const auto &manager = LLMProvidersManager::instance(); + if (!manager.getProviderNames().isEmpty()) { + const auto providerNames = manager.getProviderNames(); + for (const QString &name : providerNames) { + llmProviders.addOption(name); + } + } + + const auto &promptManager = PromptTemplateManager::instance(); + if (!promptManager.getTemplateNames().isEmpty()) { + const auto promptNames = promptManager.getTemplateNames(); + for (const QString &name : promptNames) { + fimPrompts.addOption(name); + } + } + + readSettings(); + + LLMProvidersManager::instance().setCurrentProvider(llmProviders.stringValue()); + PromptTemplateManager::instance().setCurrentTemplate(fimPrompts.stringValue()); + setLoggingEnabled(enableLogging()); + + setupConnections(); + + setLayouter([this]() { + using namespace Layouting; + + auto rootLayout = Column{Row{enableQodeAssist, Stretch{1}, resetToDefaults}, + enableAutoComplete, + startSuggestionTimer, + autoCompletionCharThreshold, + autoCompletionTypingInterval, + multiLineCompletion, + Space{8}, + enableLogging, + Space{8}, + llmProviders, + Row{url, endPoint}, + Space{8}, + Row{selectModels, modelName}, + Space{8}, + fimPrompts, + Stretch{1}}; + return rootLayout; + }); +} + +void GeneralSettings::setupConnections() +{ + connect(&llmProviders, &Utils::SelectionAspect::volatileValueChanged, this, [this]() { + int index = llmProviders.volatileValue(); + logMessage(QString("currentProvider %1").arg(llmProviders.displayForIndex(index))); + LLMProvidersManager::instance().setCurrentProvider(llmProviders.displayForIndex(index)); + updateProviderSettings(); + }); + connect(&fimPrompts, &Utils::SelectionAspect::volatileValueChanged, this, [this]() { + int index = fimPrompts.volatileValue(); + logMessage(QString("currentPrompt %1").arg(fimPrompts.displayForIndex(index))); + PromptTemplateManager::instance().setCurrentTemplate(fimPrompts.displayForIndex(index)); + }); + connect(&selectModels, &ButtonAspect::clicked, this, [this]() { showModelSelectionDialog(); }); + connect(&enableLogging, &Utils::BoolAspect::volatileValueChanged, this, [this]() { + setLoggingEnabled(enableLogging.volatileValue()); + }); + connect(&resetToDefaults, &ButtonAspect::clicked, this, &GeneralSettings::resetPageToDefaults); +} + +void GeneralSettings::updateProviderSettings() +{ + const auto provider = LLMProvidersManager::instance().getCurrentProvider(); + + if (provider) { + url.setVolatileValue(provider->url()); + endPoint.setVolatileValue(provider->completionEndpoint()); + } +} + +void GeneralSettings::showModelSelectionDialog() +{ + auto *provider = LLMProvidersManager::instance().getCurrentProvider(); + Utils::Environment env = Utils::Environment::systemEnvironment(); + + if (provider) { + QStringList models = provider->getInstalledModels(env); + bool ok; + QString selectedModel = QInputDialog::getItem(Core::ICore::dialogParent(), + Tr::tr("Select LLM Model"), + Tr::tr("Choose a model:"), + models, + 0, + false, + &ok); + + if (ok && !selectedModel.isEmpty()) { + modelName.setVolatileValue(selectedModel); + writeSettings(); + } + } +} + +void GeneralSettings::resetPageToDefaults() +{ + QMessageBox::StandardButton reply; + reply = QMessageBox::question( + Core::ICore::dialogParent(), + Tr::tr("Reset Settings"), + Tr::tr("Are you sure you want to reset all settings to default values?"), + QMessageBox::Yes | QMessageBox::No); + + if (reply == QMessageBox::Yes) { + resetAspect(enableQodeAssist); + resetAspect(enableAutoComplete); + resetAspect(llmProviders); + resetAspect(url); + resetAspect(endPoint); + resetAspect(modelName); + resetAspect(fimPrompts); + resetAspect(enableLogging); + resetAspect(startSuggestionTimer); + resetAspect(autoCompletionTypingInterval); + resetAspect(autoCompletionCharThreshold); + } + + fimPrompts.setStringValue("StarCoder2"); + llmProviders.setStringValue("Ollama"); + + QMessageBox::information(Core::ICore::dialogParent(), + Tr::tr("Settings Reset"), + Tr::tr("All settings have been reset to their default values.")); +} + +class GeneralSettingsPage : public Core::IOptionsPage +{ +public: + GeneralSettingsPage() + { + setId(Constants::QODE_ASSIST_GENERAL_SETTINGS_PAGE_ID); + setDisplayName(Tr::tr("General")); + setCategory(Constants::QODE_ASSIST_GENERAL_OPTIONS_CATEGORY); + setSettingsProvider([] { return &generalSettings(); }); + } +}; + +const GeneralSettingsPage generalSettingsPage; + +} // namespace QodeAssist::Settings diff --git a/settings/GeneralSettings.hpp b/settings/GeneralSettings.hpp new file mode 100644 index 0000000..d82da9c --- /dev/null +++ b/settings/GeneralSettings.hpp @@ -0,0 +1,59 @@ +/* + * 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 "settings/SettingsUtils.hpp" + +namespace QodeAssist::Settings { + +class GeneralSettings : public Utils::AspectContainer +{ +public: + GeneralSettings(); + + Utils::BoolAspect enableQodeAssist{this}; + Utils::BoolAspect enableAutoComplete{this}; + Utils::BoolAspect multiLineCompletion{this}; + Utils::BoolAspect enableLogging{this}; + Utils::IntegerAspect startSuggestionTimer{this}; + Utils::IntegerAspect autoCompletionCharThreshold{this}; + Utils::IntegerAspect autoCompletionTypingInterval{this}; + + Utils::SelectionAspect llmProviders{this}; + Utils::StringAspect url{this}; + Utils::StringAspect endPoint{this}; + + Utils::StringAspect modelName{this}; + ButtonAspect selectModels{this}; + Utils::SelectionAspect fimPrompts{this}; + ButtonAspect resetToDefaults{this}; + +private: + void setupConnections(); + void updateProviderSettings(); + void showModelSelectionDialog(); + void resetPageToDefaults(); +}; + +GeneralSettings &generalSettings(); + +} // namespace QodeAssist::Settings diff --git a/settings/PresetPromptsSettings.cpp b/settings/PresetPromptsSettings.cpp new file mode 100644 index 0000000..89c8de5 --- /dev/null +++ b/settings/PresetPromptsSettings.cpp @@ -0,0 +1,185 @@ +/* + * 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 "PresetPromptsSettings.hpp" + +#include +#include +#include +#include + +#include "QodeAssistConstants.hpp" +#include "QodeAssisttr.h" + +namespace QodeAssist::Settings { + +PresetPromptsSettings &presetPromptsSettings() +{ + static PresetPromptsSettings settings; + return settings; +} + +PresetPromptsSettings::PresetPromptsSettings() +{ + setAutoApply(false); + + 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); + + ollamaLivetime.setSettingsKey(Constants::OLLAMA_LIVETIME); + ollamaLivetime.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); + + maxTokens.setSettingsKey(Constants::MAX_TOKENS); + maxTokens.setLabelText(Tr::tr("Max Tokens")); + maxTokens.setRange(-1, 10000); + maxTokens.setDefaultValue(150); + + useTopP.setSettingsKey(Constants::USE_TOP_P); + useTopP.setDefaultValue(false); + + topP.setSettingsKey(Constants::TOP_P); + topP.setLabelText(Tr::tr("top_p")); + topP.setDefaultValue(0.9); + topP.setRange(0.0, 1.0); + + useTopK.setSettingsKey(Constants::USE_TOP_K); + useTopK.setDefaultValue(false); + + topK.setSettingsKey(Constants::TOP_K); + topK.setLabelText(Tr::tr("top_k")); + topK.setDefaultValue(50); + topK.setRange(1, 1000); + + usePresencePenalty.setSettingsKey(Constants::USE_PRESENCE_PENALTY); + usePresencePenalty.setDefaultValue(false); + + presencePenalty.setSettingsKey(Constants::PRESENCE_PENALTY); + presencePenalty.setLabelText(Tr::tr("presence_penalty")); + presencePenalty.setDefaultValue(0.0); + presencePenalty.setRange(-2.0, 2.0); + + useFrequencyPenalty.setSettingsKey(Constants::USE_FREQUENCY_PENALTY); + useFrequencyPenalty.setDefaultValue(false); + + frequencyPenalty.setSettingsKey(Constants::FREQUENCY_PENALTY); + frequencyPenalty.setLabelText(Tr::tr("frequency_penalty")); + frequencyPenalty.setDefaultValue(0.0); + frequencyPenalty.setRange(-2.0, 2.0); + + 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")); + + resetToDefaults.m_buttonText = Tr::tr("Reset Page to Defaults"); + + readSettings(); + + topK.setEnabled(useTopK()); + topP.setEnabled(useTopP()); + presencePenalty.setEnabled(usePresencePenalty()); + frequencyPenalty.setEnabled(useFrequencyPenalty()); + + setupConnections(); + + setLayouter([this]() { + using namespace Layouting; + return Column{Row{temperature, Stretch{1}, resetToDefaults}, + maxTokens, + Row{useTopP, topP, Stretch{1}}, + Row{useTopK, topK, Stretch{1}}, + Row{usePresencePenalty, presencePenalty, Stretch{1}}, + Row{useFrequencyPenalty, frequencyPenalty, Stretch{1}}, + apiKey, + Stretch{1}}; + }); +} + +void PresetPromptsSettings::setupConnections() +{ + connect(&useTopP, &Utils::BoolAspect::volatileValueChanged, this, [this]() { + topP.setEnabled(useTopP.volatileValue()); + }); + connect(&useTopK, &Utils::BoolAspect::volatileValueChanged, this, [this]() { + topK.setEnabled(useTopK.volatileValue()); + }); + connect(&usePresencePenalty, &Utils::BoolAspect::volatileValueChanged, this, [this]() { + presencePenalty.setEnabled(usePresencePenalty.volatileValue()); + }); + connect(&useFrequencyPenalty, &Utils::BoolAspect::volatileValueChanged, this, [this]() { + frequencyPenalty.setEnabled(useFrequencyPenalty.volatileValue()); + }); + + connect(&resetToDefaults, + &ButtonAspect::clicked, + this, + &PresetPromptsSettings::resetSettingsToDefaults); +} + +void PresetPromptsSettings::resetSettingsToDefaults() +{ + QMessageBox::StandardButton reply; + reply = QMessageBox::question( + Core::ICore::dialogParent(), + Tr::tr("Reset Settings"), + Tr::tr("Are you sure you want to reset all settings to default values?"), + 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); + } + + QMessageBox::information(Core::ICore::dialogParent(), + Tr::tr("Settings Reset"), + Tr::tr("All settings have been reset to their default values.")); +} + +class PresetPromptsSettingsPage : public Core::IOptionsPage +{ +public: + PresetPromptsSettingsPage() + { + setId(Constants::QODE_ASSIST_PRESET_PROMPTS_SETTINGS_PAGE_ID); + setDisplayName(Tr::tr("Preset Prompts Params")); + setCategory(Constants::QODE_ASSIST_GENERAL_OPTIONS_CATEGORY); + setSettingsProvider([] { return &presetPromptsSettings(); }); + } +}; + +const PresetPromptsSettingsPage presetPromptsSettingsPage; + +} // namespace QodeAssist::Settings diff --git a/settings/PresetPromptsSettings.hpp b/settings/PresetPromptsSettings.hpp new file mode 100644 index 0000000..b953c4b --- /dev/null +++ b/settings/PresetPromptsSettings.hpp @@ -0,0 +1,59 @@ +/* + * 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 "settings/SettingsUtils.hpp" +#include + +namespace QodeAssist::Settings { + +class PresetPromptsSettings : public Utils::AspectContainer +{ +public: + PresetPromptsSettings(); + + Utils::DoubleAspect temperature{this}; + Utils::IntegerAspect maxTokens{this}; + + Utils::BoolAspect useTopP{this}; + Utils::DoubleAspect topP{this}; + + Utils::BoolAspect useTopK{this}; + Utils::IntegerAspect topK{this}; + + Utils::BoolAspect usePresencePenalty{this}; + Utils::DoubleAspect presencePenalty{this}; + + Utils::BoolAspect useFrequencyPenalty{this}; + Utils::DoubleAspect frequencyPenalty{this}; + + Utils::StringAspect ollamaLivetime{this}; + Utils::StringAspect apiKey{this}; + + ButtonAspect resetToDefaults{this}; + +private: + void setupConnections(); + void resetSettingsToDefaults(); +}; + +PresetPromptsSettings &presetPromptsSettings(); + +} // namespace QodeAssist::Settings diff --git a/settings/SettingsUtils.hpp b/settings/SettingsUtils.hpp new file mode 100644 index 0000000..0e56712 --- /dev/null +++ b/settings/SettingsUtils.hpp @@ -0,0 +1,55 @@ +/* + * 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 + +namespace QodeAssist::Settings { + +template +void resetAspect(AspectType &aspect) +{ + aspect.setVolatileValue(aspect.defaultValue()); +} + +class ButtonAspect : public Utils::BaseAspect +{ + Q_OBJECT + +public: + ButtonAspect(Utils::AspectContainer *container = nullptr) + : Utils::BaseAspect(container) + {} + + void addToLayout(Layouting::Layout &parent) override + { + auto button = new QPushButton(m_buttonText); + connect(button, &QPushButton::clicked, this, &ButtonAspect::clicked); + parent.addItem(button); + } + + QString m_buttonText; +signals: + void clicked(); +}; + +} // namespace QodeAssist::Settings diff --git a/templates/CodeQwenChat.hpp b/templates/CodeQwenChat.hpp deleted file mode 100644 index dfae27e..0000000 --- a/templates/CodeQwenChat.hpp +++ /dev/null @@ -1,44 +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 "PromptTemplate.hpp" - -namespace QodeAssist::Templates { - -class CodeQwenChatTemplate : public PromptTemplate -{ -public: - QString name() const override { return "CodeQwenChat (experimental)"; } - QString promptTemplate() const override { return "%1\n### Instruction:%2%3 ### Response:\n"; } - QStringList stopWords() const override - { - return QStringList() << "### Instruction:" << "### Response:" << "\n\n### "; - } - void prepareRequest(QJsonObject &request, const ContextData &context) const override - { - QString formattedPrompt = promptTemplate().arg(context.instriuctions, - context.prefix, - context.suffix); - request["prompt"] = formattedPrompt; - } -}; - -} // namespace QodeAssist::Templates diff --git a/templates/CustomTemplate.hpp b/templates/CustomTemplate.hpp new file mode 100644 index 0000000..5ae2e24 --- /dev/null +++ b/templates/CustomTemplate.hpp @@ -0,0 +1,89 @@ +/* + * 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 "PromptTemplate.hpp" + +#include +#include + +#include "QodeAssistUtils.hpp" +#include "settings/CustomPromptSettings.hpp" + +namespace QodeAssist::Templates { + +class CustomTemplate : public PromptTemplate +{ +public: + QString name() const override { return "Custom Template"; } + QString promptTemplate() const override + { + return Settings::customPromptSettings().customJsonTemplate(); + } + QStringList stopWords() const override { return QStringList(); } + + void prepareRequest(QJsonObject &request, const ContextData &context) const override + { + QJsonDocument doc = QJsonDocument::fromJson(promptTemplate().toUtf8()); + if (doc.isNull() || !doc.isObject()) { + logMessage(QString("Invalid JSON template in settings")); + + return; + } + + QJsonObject templateObj = doc.object(); + QJsonObject processedObj = processJsonTemplate(templateObj, context); + + for (auto it = processedObj.begin(); it != processedObj.end(); ++it) { + request[it.key()] = it.value(); + } + } + +private: + QJsonValue processJsonValue(const QJsonValue &value, const ContextData &context) const + { + if (value.isString()) { + QString str = value.toString(); + str.replace("{{QODE_INSTRUCTIONS}}", context.instriuctions); + str.replace("{{QODE_PREFIX}}", context.prefix); + str.replace("{{QODE_SUFFIX}}", context.suffix); + return str; + } else if (value.isObject()) { + return processJsonTemplate(value.toObject(), context); + } else if (value.isArray()) { + QJsonArray newArray; + for (const QJsonValue &arrayValue : value.toArray()) { + newArray.append(processJsonValue(arrayValue, context)); + } + return newArray; + } + return value; + } + + QJsonObject processJsonTemplate(const QJsonObject &templateObj, const ContextData &context) const + { + QJsonObject result; + for (auto it = templateObj.begin(); it != templateObj.end(); ++it) { + result[it.key()] = processJsonValue(it.value(), context); + } + return result; + } +}; +} // namespace QodeAssist::Templates