Compare commits

...

31 Commits

Author SHA1 Message Date
6e56646b4c Version 0.1.3
Remove unnecessary log
Improve settings
2024-09-15 01:33:48 +02:00
c89fe1451b Improve settings accessibility 2024-09-15 01:20:26 +02:00
938636ab48 Remove unnecessary log 2024-09-15 01:15:53 +02:00
e3a2b5a64c Version 0.1.2
- Added sending cache of latest changes to model
- Changed default value of suggestion trigger
2024-09-11 02:16:29 +02:00
0ae12e0fc6 Update README 2024-09-11 02:01:00 +02:00
793b855819 Change default value for start ai suggestion 2024-09-11 01:59:45 +02:00
8e052ff45c Add settings for cache of changes 2024-09-11 01:59:25 +02:00
2fb876ff00 Exclude current file cache from request 2024-09-11 01:37:57 +02:00
cd1a9e16e0 Add basic cache manager 2024-09-11 01:25:00 +02:00
f07610df5c Upgrade version to 0.1.2 2024-09-11 01:24:22 +02:00
7cd35082b2 Add using custom template to README.md 2024-09-10 17:41:24 +02:00
c1dd59e65c Add gifs to README.md 2024-09-08 15:34:20 +02:00
faaf59f163 Version 0.1.1
Fixed settings layouts
2024-09-08 14:39:08 +02:00
539a220771 Fix settings layouts 2024-09-08 14:31:52 +02:00
397dd33a96 Version 0.1.0
## What's New
- Reworked plugin settings to multipage settings
- Added creating, saving and loading custom templates in json format
- Added smart trigger for call suggestion after input by default 2 characters
2024-09-08 03:03:07 +02:00
9361c27d6e Add smart trigger for call suggestion 2024-09-08 02:51:56 +02:00
15af137728 Add examples of customs request 2024-09-08 02:25:21 +02:00
24ad5fd015 Fix saving template after closing qtc 2024-09-08 01:47:17 +02:00
216c28aa5e Fix names, icon and tips 2024-09-08 01:42:23 +02:00
f64ea42071 Remove origin qodesettings 2024-09-08 01:07:39 +02:00
314ba06db1 Move custom promt to settings page 2024-09-08 00:59:44 +02:00
384e07ba62 Move suggestionTimer to general settings 2024-09-08 00:18:23 +02:00
4d06541a36 Move preset params to separate settings page 2024-09-08 00:13:48 +02:00
356f28a97b Move context settings to page 2024-09-07 23:51:23 +02:00
b5ca11ed38 Move settings to General Page 2024-09-07 23:10:11 +02:00
d49cd07dd0 Add empty pages for future settings 2024-09-07 20:11:54 +02:00
8e61651bae Add button for save and load Custom Template 2024-09-06 09:27:10 +02:00
1e0063a7bb Add custom template prompt 2024-09-06 01:07:28 +02:00
44add994b9 Optimize string 2024-09-05 22:18:57 +02:00
99136d7b76 Add supports way to README.md 2024-09-04 13:38:37 +02:00
da7abbd9be Create FUNDING.yml
Add ko-fi button
2024-09-04 13:29:18 +02:00
32 changed files with 1599 additions and 647 deletions

15
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1,15 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: petrmdev
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
polar: # Replace with a single Polar username
buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
thanks_dev: # Replace with a single thanks.dev username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

View File

@ -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,10 @@ 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
core/ChangesManager.h core/ChangesManager.cpp
)

View File

@ -23,7 +23,7 @@
#include <QTextBlock>
#include <languageserverprotocol/lsptypes.h>
#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);
}

View File

@ -28,8 +28,10 @@
#include "DocumentContextReader.hpp"
#include "LLMProvidersManager.hpp"
#include "PromptTemplateManager.hpp"
#include "QodeAssistSettings.hpp"
#include "QodeAssistUtils.hpp"
#include "core/ChangesManager.h"
#include "settings/ContextSettings.hpp"
#include "settings/GeneralSettings.hpp"
namespace QodeAssist {
@ -130,12 +132,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 +156,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 +230,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 +247,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 +267,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();
@ -275,23 +278,27 @@ ContextData LLMClientInterface::prepareContext(const QJsonObject &request,
DocumentContextReader reader(widget->textDocument());
QString recentChanges = ChangesManager::instance().getRecentChangesContext(textDocument);
QString contextBefore = сontextBefore(widget, lineNumber, cursorPosition);
QString contextAfter = сontextAfter(widget, lineNumber, cursorPosition);
QString instructions = QString("%1%2").arg(settings().useSpecificInstructions()
? reader.getSpecificInstructions()
: QString(),
settings().useFilePathInContext()
? reader.getLanguageAndFileInfo()
: QString());
QString instructions
= QString("%1%2%3").arg(Settings::contextSettings().useSpecificInstructions()
? reader.getSpecificInstructions()
: QString(),
Settings::contextSettings().useFilePathInContext()
? reader.getLanguageAndFileInfo()
: QString(),
Settings::contextSettings().useProjectChangesCache() ? recentChanges
: 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 +339,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 +386,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();

View File

@ -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;

View File

@ -1,6 +1,6 @@
{
"Name" : "QodeAssist",
"Version" : "0.0.8",
"Version" : "0.1.3",
"CompatVersion" : "${IDE_VERSION_COMPAT}",
"Vendor" : "Petr Mironychev",
"Copyright" : "(C) ${IDE_COPYRIGHT_YEAR} Petr Mironychev, (C) ${IDE_COPYRIGHT_YEAR} The Qt Company Ltd",

View File

@ -31,7 +31,9 @@
#include "LLMClientInterface.hpp"
#include "LLMSuggestion.hpp"
#include "QodeAssistSettings.hpp"
#include "core/ChangesManager.h"
#include "settings/ContextSettings.hpp"
#include "settings/GeneralSettings.hpp"
using namespace LanguageServerProtocol;
using namespace TextEditor;
@ -43,6 +45,7 @@ namespace QodeAssist {
QodeAssistClient::QodeAssistClient()
: LanguageClient::Client(new LLMClientInterface())
, m_recentCharCount(0)
{
setName("Qode Assist");
LanguageClient::LanguageFilter filter;
@ -51,6 +54,8 @@ QodeAssistClient::QodeAssistClient()
start();
setupConnections();
m_typingTimer.start();
}
QodeAssistClient::~QodeAssistClient()
@ -70,7 +75,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());
@ -80,13 +85,31 @@ void QodeAssistClient::openDocument(TextEditor::TextDocument *document)
auto textEditor = BaseTextEditor::currentTextEditor();
if (!textEditor || textEditor->document() != document)
return;
if (Settings::contextSettings().useProjectChangesCache())
ChangesManager::instance().addChange(document,
position,
charsRemoved,
charsAdded);
TextEditorWidget *widget = textEditor->editorWidget();
if (widget->isReadOnly() || widget->multiTextCursor().hasMultipleCursors())
return;
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 +153,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 +168,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 +227,7 @@ void QodeAssistClient::cancelRunningRequest(TextEditor::TextEditorWidget *editor
bool QodeAssistClient::isEnabled(ProjectExplorer::Project *project) const
{
return settings().enableQodeAssist();
return Settings::generalSettings().enableQodeAssist();
}
void QodeAssistClient::setupConnections()

View File

@ -55,6 +55,9 @@ private:
QHash<TextEditor::TextEditorWidget *, QTimer *> m_scheduledRequests;
QMetaObject::Connection m_documentOpenedConnection;
QMetaObject::Connection m_documentClosedConnection;
QElapsedTimer m_typingTimer;
int m_recentCharCount;
};
} // namespace QodeAssist

View File

@ -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,17 @@ 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 USE_PROJECT_CHANGES_CACHE[] = "QodeAssist.useProjectChangesCache";
const char MAX_CHANGES_CACHE_SIZE[] = "QodeAssist.maxChangesCacheSize";
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";

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
#include "QodeAssistSettings.hpp"
#include <QInputDialog>
#include <QtWidgets/qmessagebox.h>
#include <coreplugin/dialogs/ioptionspage.h>
#include <coreplugin/icore.h>
#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

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <QPushButton>
#include <utils/aspects.h>
#include <utils/layoutbuilder.h>
namespace QodeAssist {
template<typename AspectType>
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

View File

@ -19,7 +19,12 @@
#pragma once
#include <QEventLoop>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QString>
#include <QTimer>
#include <QUrl>
#include <coreplugin/messagemanager.h>
#include <utils/qtcassert.h>
@ -65,4 +70,36 @@ inline void logMessages(const QStringList &messages, bool silent = true)
}
}
inline bool pingUrl(const QUrl &url, int timeout = 5000)
{
if (!url.isValid()) {
return false;
}
QNetworkAccessManager manager;
QNetworkRequest request(url);
request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, true);
QScopedPointer<QNetworkReply> reply(manager.get(request));
QTimer timer;
timer.setSingleShot(true);
QEventLoop loop;
QObject::connect(reply.data(), &QNetworkReply::finished, &loop, &QEventLoop::quit);
QObject::connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit);
timer.start(timeout);
loop.exec();
if (timer.isActive()) {
timer.stop();
return (reply->error() == QNetworkReply::NoError);
} else {
QObject::disconnect(reply.data(), &QNetworkReply::finished, &loop, &QEventLoop::quit);
reply->abort();
return false;
}
}
} // namespace QodeAssist

View File

@ -3,6 +3,8 @@
QodeAssist is an AI-powered coding assistant plugin for Qt Creator. It provides intelligent code completion and suggestions for C++ and QML, leveraging large language models through local providers like Ollama. Enhance your coding productivity with context-aware AI assistance directly in your Qt development environment.
<img src="https://github.com/user-attachments/assets/255a52f1-5cc0-4ca3-b05c-c4cf9cdbe25a" width="600" alt="QodeAssistPreview">
## Supported LLM Providers
QodeAssist currently supports the following LLM (Large Language Model) providers:
- [Ollama](https://ollama.com)
@ -10,25 +12,53 @@ QodeAssist currently supports the following LLM (Large Language Model) providers
- OpenAI compatible providers
## Supported Models
QodeAssist has been tested with the following language models, all trained for Fill-in-theMiddle:
Ollama:
- [starcoder2](https://ollama.com/library/starcoder2)
- [codellama](https://ollama.com/library/codellama)
QodeAssist has been thoroughly tested and optimized for use with the following language models, all of which are specifically trained for Fill-in-the-Middle (FIM) tasks:
- CodeLlama
- StarCoder2
- DeepSeek-Coder-V2-Lite-Base
LM studio:
These models have demonstrated excellent performance in code completion and assistance tasks within the QodeAssist environment.
### Custom Prompts
For advanced users or those with specific requirements, QodeAssist offers the flexibility to create, save, and load custom prompts using JSON templates. This feature allows you to tailor the AI's behavior to your exact needs.
To get started with custom prompts:
1. Navigate to the "Custom Template" option in the FIM Prompt Settings.
2. Create your custom JSON prompt template.
3. Use the "Save Custom Template to JSON" button to store your template for future use.
4. To use a previously saved template, click "Load Custom Template from JSON".
5. Make sure to select "Custom Template" from the dropdown menu in the FIM Prompt Settings on the General page to activate your custom template.
For inspiration and examples of effective custom prompts, please refer to the `rawPromptExamples` folder in our repository.
<img width="600" alt="Custom template" src="https://github.com/user-attachments/assets/4a14c552-baba-4531-ab4f-cb1f9ac6620b">
<img width="600" alt="Select custom template" src="https://github.com/user-attachments/assets/3651dafd-83f9-4df9-943f-69c28cd3d8a3">
### Tested Models
#### Ollama:
- [starcoder2](https://ollama.com/library/starcoder2)
- [codellama](https://ollama.com/library/codellama)
#### LM Studio:
- [second-state/StarCoder2-7B-GGUF](https://huggingface.co/second-state/StarCoder2-7B-GGUF)
- [TheBloke/CodeLlama-7B-GGUF](https://huggingface.co/TheBloke/CodeLlama-7B-GGUF)
Please note that while these models have been specifically tested and confirmed to work well with QodeAssist, other models compatible with the supported providers may also work. We encourage users to experiment with different models and report their experiences.
If you've successfully used a model that's not listed here, please let us know by opening an issue or submitting a pull request to update this list.
## Development Progress
- [x] Basic plugin with code autocomplete functionality
- [ ] Improve and automate settings
- [x] Improve and automate settings
- [ ] Add chat functionality
- [x] Sharing diff with model
- [ ] Sharing project source with model
- [ ] Support for more providers and models
## Plugin installation using Ollama as an example
@ -48,13 +78,16 @@ ollama run starcoder2:7b
## Configure Plugin
<img src="https://github.com/user-attachments/assets/0743d09e-1f02-44ed-9a1a-85e2a0a0c01a" width="800" alt="QodeAssist в действии">
1. Open Qt Creator settings
2. Navigate to the "Qode Assist" tab
3. Choose your LLM provider (e.g., Ollama)
4. Select the installed model by the "Select Model" button
3. Select "General" page
4. Choose your LLM provider (e.g., Ollama)
5. Select the installed model by the "Select Model" button
- For LM Studio you will see current load model
5. Choose the prompt template that corresponds to your model
6. Apply the settings
6. Choose the prompt template that corresponds to your model
7. Apply the settings
You're all set! QodeAssist is now ready to use in Qt Creator.
@ -91,7 +124,8 @@ Verify that the selected prompt template matches the model you're using
If you're still experiencing issues with QodeAssist, you can try resetting the settings to their default values:
1. Open Qt Creator settings
2. Navigate to the "Qode Assist" tab
3. Click on the "Reset to Defaults" button
3. Pick settings page for reset
4. Click on the "Reset Page to Defaults" button
- The API key will not reset
- Select model after reset
@ -104,8 +138,8 @@ If you find QodeAssist helpful, there are several ways you can support the proje
3. **Spread the Word**: Star our GitHub repository and share QodeAssist with your fellow developers.
4. **Financial Support**: If you'd like to support the development financially, you can make a donation using one of the following cryptocurrency addresses:
4. **Financial Support**: If you'd like to support the development financially, you can make a donation using one of the following:
- [![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/P5P412V96G)
- Bitcoin (BTC): `bc1qndq7f0mpnlya48vk7kugvyqj5w89xrg4wzg68t`
- Ethereum (ETH): `0xA5e8c37c94b24e25F9f1f292a01AF55F03099D8D`
- Litecoin (LTC): `ltc1qlrxnk30s2pcjchzx4qrxvdjt5gzuervy5mv0vy`

83
core/ChangesManager.cpp Normal file
View File

@ -0,0 +1,83 @@
/*
* Copyright (C) 2024 Petr Mironychev
*
* This file is part of QodeAssist.
*
* QodeAssist is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* QodeAssist is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
*/
#include "ChangesManager.h"
#include "QodeAssistUtils.hpp"
#include "settings/ContextSettings.hpp"
namespace QodeAssist {
ChangesManager &ChangesManager::instance()
{
static ChangesManager instance;
return instance;
}
ChangesManager::ChangesManager()
: QObject(nullptr)
{
}
ChangesManager::~ChangesManager()
{
}
void ChangesManager::addChange(TextEditor::TextDocument *document,
int position,
int charsRemoved,
int charsAdded)
{
auto &documentQueue = m_documentChanges[document];
QTextBlock block = document->document()->findBlock(position);
int lineNumber = block.blockNumber();
QString lineContent = block.text();
QString fileName = document->filePath().fileName();
ChangeInfo change{fileName, lineNumber, lineContent};
auto it = std::find_if(documentQueue.begin(),
documentQueue.end(),
[lineNumber](const ChangeInfo &c) { return c.lineNumber == lineNumber; });
if (it != documentQueue.end()) {
it->lineContent = lineContent;
} else {
documentQueue.enqueue(change);
if (documentQueue.size() > Settings::contextSettings().maxChangesCacheSize()) {
documentQueue.dequeue();
}
}
}
QString ChangesManager::getRecentChangesContext(const TextEditor::TextDocument *currentDocument) const
{
QString context;
for (auto it = m_documentChanges.constBegin(); it != m_documentChanges.constEnd(); ++it) {
if (it.key() != currentDocument) {
for (const auto &change : it.value()) {
context += change.lineContent + "\n";
}
}
}
return context;
}
} // namespace QodeAssist

61
core/ChangesManager.h Normal file
View File

@ -0,0 +1,61 @@
/*
* Copyright (C) 2024 Petr Mironychev
*
* This file is part of QodeAssist.
*
* QodeAssist is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* QodeAssist is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <QDateTime>
#include <QHash>
#include <QQueue>
#include <QTimer>
#include <texteditor/textdocument.h>
namespace QodeAssist {
class ChangesManager : public QObject
{
Q_OBJECT
public:
struct ChangeInfo
{
QString fileName;
int lineNumber;
QString lineContent;
};
static ChangesManager &instance();
void addChange(TextEditor::TextDocument *document,
int position,
int charsRemoved,
int charsAdded);
QString getRecentChangesContext(const TextEditor::TextDocument *currentDocument) const;
private:
ChangesManager();
~ChangesManager();
ChangesManager(const ChangesManager &) = delete;
ChangesManager &operator=(const ChangesManager &) = delete;
void cleanupOldChanges();
QHash<TextEditor::TextDocument *, QQueue<ChangeInfo>> m_documentChanges;
};
} // namespace QodeAssist

View File

@ -26,8 +26,8 @@
#include <QNetworkReply>
#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 &currentTemplate = 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)

View File

@ -26,8 +26,8 @@
#include <QtCore/qeventloop.h>
#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;
}

View File

@ -25,7 +25,7 @@
#include <QNetworkReply>
#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 &currentTemplate = 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;
}

View File

@ -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<Templates::CodeLLamaTemplate>();
templateManager.registerTemplate<Templates::StarCoder2Template>();
templateManager.registerTemplate<Templates::CodeQwenChatTemplate>();
templateManager.registerTemplate<Templates::DeepSeekCoderV2Template>();
templateManager.registerTemplate<Templates::CustomTemplate>();
Utils::Icon QCODEASSIST_ICON(
{{":/resources/images/qoderassist-icon.png", Utils::Theme::IconsBaseColor}});

View File

@ -0,0 +1,19 @@
{
"prompt": "{{QODE_INSTRUCTIONS}}<fim_prefix>{{QODE_PREFIX}}<fim_suffix>{{QODE_SUFFIX}}<fim_middle>",
"options": {
"temperature": 0.7,
"top_p": 0.95,
"top_k": 40,
"num_predict": 175,
"stop": [
"<|endoftext|>",
"<file_sep>",
"<fim_prefix>",
"<fim_suffix>",
"<fim_middle>"
],
"frequency_penalty": 0,
"presence_penalty": 0
},
"stream": true
}

View File

@ -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
}

View File

@ -0,0 +1,155 @@
/*
* Copyright (C) 2024 Petr Mironychev
*
* This file is part of QodeAssist.
*
* QodeAssist is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* QodeAssist is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include "ContextSettings.hpp"
#include <QMessageBox>
#include <coreplugin/dialogs/ioptionspage.h>
#include <coreplugin/icore.h>
#include <utils/layoutbuilder.h>
#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(true);
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");
useProjectChangesCache.setSettingsKey(Constants::USE_PROJECT_CHANGES_CACHE);
useProjectChangesCache.setDefaultValue(true);
useProjectChangesCache.setLabelText(Tr::tr("Use Project Changes Cache"));
maxChangesCacheSize.setSettingsKey(Constants::MAX_CHANGES_CACHE_SIZE);
maxChangesCacheSize.setLabelText(Tr::tr("Max Changes Cache Size"));
maxChangesCacheSize.setRange(2, 1000);
maxChangesCacheSize.setDefaultValue(20);
readSettings();
readStringsAfterCursor.setEnabled(!readFullFile());
readStringsBeforeCursor.setEnabled(!readFullFile());
specificInstractions.setEnabled(useSpecificInstructions());
setupConnection();
setLayouter([this]() {
using namespace Layouting;
return Column{Row{readFullFile, Stretch{1}, resetToDefaults},
Row{readStringsBeforeCursor, Stretch{1}},
Row{readStringsAfterCursor, Stretch{1}},
useFilePathInContext,
useSpecificInstructions,
specificInstractions,
useProjectChangesCache,
Row{maxChangesCacheSize, Stretch{1}},
Stretch{1}};
});
}
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);
}
}
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

View File

@ -0,0 +1,52 @@
/*
* Copyright (C) 2024 Petr Mironychev
*
* This file is part of QodeAssist.
*
* QodeAssist is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* QodeAssist is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <utils/aspects.h>
#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};
Utils::BoolAspect useProjectChangesCache{this};
Utils::IntegerAspect maxChangesCacheSize{this};
ButtonAspect resetToDefaults{this};
private:
void setupConnection();
void resetPageToDefaults();
};
ContextSettings &contextSettings();
} // namespace QodeAssist::Settings

View File

@ -0,0 +1,199 @@
/*
* Copyright (C) 2024 Petr Mironychev
*
* This file is part of QodeAssist.
*
* QodeAssist is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* QodeAssist is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
*/
#include "CustomPromptSettings.hpp"
#include <QFileDialog>
#include <QJsonParseError>
#include <QMessageBox>
#include <coreplugin/dialogs/ioptionspage.h>
#include <coreplugin/icore.h>
#include <utils/layoutbuilder.h>
#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}}<fim_prefix>{{QODE_PREFIX}}<fim_suffix>{{QODE_SUFFIX}}<fim_middle>",
"options": {
"temperature": 0.7,
"top_p": 0.95,
"top_k": 40,
"num_predict": 100,
"stop": [
"<|endoftext|>",
"<file_sep>",
"<fim_prefix>",
"<fim_suffix>",
"<fim_middle>"
],
"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);
}
}
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

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
#pragma once
#include "settings/SettingsUtils.hpp"
#include <utils/aspects.h>
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

View File

@ -0,0 +1,297 @@
/*
* Copyright (C) 2024 Petr Mironychev
*
* This file is part of QodeAssist.
*
* QodeAssist is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* QodeAssist is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
*/
#include "GeneralSettings.hpp"
#include <QInputDialog>
#include <QMessageBox>
#include <coreplugin/dialogs/ioptionspage.h>
#include <coreplugin/icore.h>
#include <utils/layoutbuilder.h>
#include <utils/utilsicons.h>
#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("with delay(ms)"));
startSuggestionTimer.setRange(10, 10000);
startSuggestionTimer.setDefaultValue(500);
autoCompletionCharThreshold.setSettingsKey(Constants::AUTO_COMPLETION_CHAR_THRESHOLD);
autoCompletionCharThreshold.setLabelText(Tr::tr("AI suggestion triggers after typing"));
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(0, 10);
autoCompletionCharThreshold.setDefaultValue(0);
autoCompletionTypingInterval.setSettingsKey(Constants::AUTO_COMPLETION_TYPING_INTERVAL);
autoCompletionTypingInterval.setLabelText(Tr::tr("character(s) within(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,
multiLineCompletion,
Row{autoCompletionCharThreshold,
autoCompletionTypingInterval,
startSuggestionTimer,
Stretch{1}},
Space{8},
enableLogging,
Space{8},
Row{llmProviders, Stretch{1}},
Row{url, endPoint, urlIndicator},
Space{8},
Row{selectModels, modelName, modelIndicator},
Space{8},
fimPrompts,
Stretch{1}};
return rootLayout;
});
updateStatusIndicators();
}
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);
connect(&url,
&Utils::StringAspect::volatileValueChanged,
this,
&GeneralSettings::updateStatusIndicators);
connect(&modelName,
&Utils::StringAspect::volatileValueChanged,
this,
&GeneralSettings::updateStatusIndicators);
}
void GeneralSettings::updateProviderSettings()
{
const auto provider = LLMProvidersManager::instance().getCurrentProvider();
if (provider) {
url.setVolatileValue(provider->url());
endPoint.setVolatileValue(provider->completionEndpoint());
modelName.setVolatileValue("");
}
}
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");
updateStatusIndicators();
}
void GeneralSettings::updateStatusIndicators()
{
bool urlValid = !url.volatileValue().isEmpty() && !endPoint.volatileValue().isEmpty();
bool modelValid = !modelName.volatileValue().isEmpty();
bool pingSuccessful = false;
if (urlValid) {
QUrl pingUrl(url.volatileValue());
pingSuccessful = QodeAssist::pingUrl(pingUrl);
}
setIndicatorStatus(modelIndicator,
modelValid ? tr("Model is properly configured")
: tr("No model selected or model name is invalid"),
modelValid);
setIndicatorStatus(urlIndicator,
pingSuccessful ? tr("Server is reachable")
: tr("Server is not reachable or URL is invalid"),
pingSuccessful);
}
void GeneralSettings::setIndicatorStatus(Utils::StringAspect &indicator,
const QString &tooltip,
bool isValid)
{
const Utils::Icon &icon = isValid ? Utils::Icons::OK : Utils::Icons::WARNING;
indicator.setLabelPixmap(icon.pixmap());
indicator.setToolTip(tooltip);
}
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

View File

@ -0,0 +1,65 @@
/*
* Copyright (C) 2024 Petr Mironychev
*
* This file is part of QodeAssist.
*
* QodeAssist is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* QodeAssist is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <utils/aspects.h>
#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};
Utils::StringAspect modelIndicator{this};
Utils::StringAspect urlIndicator{this};
private:
void setupConnections();
void updateProviderSettings();
void showModelSelectionDialog();
void resetPageToDefaults();
void updateStatusIndicators();
void setIndicatorStatus(Utils::StringAspect &indicator, const QString &tooltip, bool isValid);
};
GeneralSettings &generalSettings();
} // namespace QodeAssist::Settings

View File

@ -0,0 +1,167 @@
/*
* Copyright (C) 2024 Petr Mironychev
*
* This file is part of QodeAssist.
*
* QodeAssist is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* QodeAssist is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
*/
#include "PresetPromptsSettings.hpp"
#include <QMessageBox>
#include <coreplugin/dialogs/ioptionspage.h>
#include <coreplugin/icore.h>
#include <utils/layoutbuilder.h>
#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);
temperature.setSingleStep(0.1);
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("use top_p"));
topP.setDefaultValue(0.9);
topP.setRange(0.0, 1.0);
topP.setSingleStep(0.1);
useTopK.setSettingsKey(Constants::USE_TOP_K);
useTopK.setDefaultValue(false);
topK.setSettingsKey(Constants::TOP_K);
topK.setLabelText(Tr::tr("use 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("use presence_penalty"));
presencePenalty.setDefaultValue(0.0);
presencePenalty.setRange(-2.0, 2.0);
presencePenalty.setSingleStep(0.1);
useFrequencyPenalty.setSettingsKey(Constants::USE_FREQUENCY_PENALTY);
useFrequencyPenalty.setDefaultValue(false);
frequencyPenalty.setSettingsKey(Constants::FREQUENCY_PENALTY);
frequencyPenalty.setLabelText(Tr::tr("use frequency_penalty"));
frequencyPenalty.setDefaultValue(0.0);
frequencyPenalty.setRange(-2.0, 2.0);
frequencyPenalty.setSingleStep(0.1);
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();
setupConnections();
setLayouter([this]() {
using namespace Layouting;
return Column{Row{temperature, Stretch{1}, resetToDefaults},
Row{maxTokens, Stretch{1}},
Row{useTopP, topP, Stretch{1}},
Row{useTopK, topK, Stretch{1}},
Row{usePresencePenalty, presencePenalty, Stretch{1}},
Row{useFrequencyPenalty, frequencyPenalty, Stretch{1}},
apiKey,
Stretch{1}};
});
}
void PresetPromptsSettings::setupConnections()
{
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);
}
}
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

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
#pragma once
#include "settings/SettingsUtils.hpp"
#include <utils/aspects.h>
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

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <QPushButton>
#include <utils/aspects.h>
#include <utils/layoutbuilder.h>
namespace QodeAssist::Settings {
template<typename AspectType>
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

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
#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

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
#pragma once
#include "PromptTemplate.hpp"
#include <QJsonArray>
#include <QJsonDocument>
#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