Compare commits

...

13 Commits

Author SHA1 Message Date
16035d6de6 Merge pull request #7 from Palm1r/version-0.0.9
Version 0.0.9
- Fix default settings
2024-09-03 10:41:56 +02:00
4891cc4b1e Additional info for troubleshooting 2024-09-03 10:37:34 +02:00
7370258485 Fix default settings 2024-09-03 10:30:03 +02:00
a1df602182 Add troubleshooing chapter 2024-09-03 10:29:27 +02:00
a1b813206a Merge pull request #6 from Palm1r/version-0.0.8
Version 0.0.8
- Implement model list retrieval using GET requests
- Introduce line-by-line insertion for multiline suggestions
2024-09-02 11:06:29 +02:00
ada65e9ef7 Add hotkeys tip 2024-09-02 11:02:23 +02:00
753365ea52 Add multiline insert support 2024-09-02 10:54:14 +02:00
4d9adf75ff Add line inserting 2024-09-01 21:41:46 +02:00
a974b0aa82 Move to get models by request to provider 2024-09-01 18:19:37 +02:00
6703a7026d Merge pull request #5 from Palm1r/version-0.0.7
Version 0.0.7
- Add DeepSeekV2 template
- Add performance benchmark
- Move instructions and file path from FIM
2024-09-01 12:09:15 +02:00
d7fc62f94b Move instractoins out from FIM request 2024-09-01 12:06:55 +02:00
dec8967df2 Add performance benchmark 2024-09-01 00:06:11 +02:00
4f0f9338dc Add DeepSeekCoderV2 template 2024-08-31 23:20:23 +02:00
28 changed files with 398 additions and 289 deletions

View File

@ -35,17 +35,19 @@ add_qtc_plugin(QodeAssist
templates/CodeLLamaTemplate.hpp
templates/StarCoder2Template.hpp
templates/CodeQwenChat.hpp
templates/DeepSeekCoderV2.hpp
providers/LLMProvider.hpp
providers/OllamaProvider.hpp providers/OllamaProvider.cpp
providers/LMStudioProvider.hpp providers/LMStudioProvider.cpp
providers/OpenAICompatProvider.h providers/OpenAICompatProvider.cpp
providers/OpenAICompatProvider.hpp providers/OpenAICompatProvider.cpp
LLMProvidersManager.hpp LLMProvidersManager.cpp
QodeAssistSettings.hpp QodeAssistSettings.cpp
QodeAssist.qrc
LSPCompletion.hpp
LLMSuggestion.hpp LLMSuggestion.cpp
QodeAssistHoverHandler.hpp QodeAssistHoverHandler.cpp
QodeAssistClient.hpp QodeAssistClient.cpp
QodeAssistUtils.hpp
DocumentContextReader.hpp DocumentContextReader.cpp
QodeAssistData.hpp
utils/CounterTooltip.hpp utils/CounterTooltip.cpp
)

View File

@ -130,7 +130,7 @@ QString DocumentContextReader::getLanguageAndFileInfo() const
QString filePath = m_textDocument->filePath().toString();
QString fileExtension = QFileInfo(filePath).suffix();
return QString("//Language: %1 (MIME: %2) filepath: %3(%4)\n\n")
return QString("Language: %1 (MIME: %2) filepath: %3(%4)\n\n")
.arg(language, mimeType, filePath, fileExtension);
}
@ -138,7 +138,7 @@ QString DocumentContextReader::getSpecificInstructions() const
{
QString specificInstruction = settings().specificInstractions().arg(
LanguageServerProtocol::TextDocumentItem::mimeTypeToLanguageId(m_textDocument->mimeType()));
return QString("//Instructions: %1").arg(specificInstruction);
return QString("%1").arg(specificInstruction);
}
CopyrightInfo DocumentContextReader::findCopyright()

View File

@ -69,6 +69,8 @@ void LLMClientInterface::sendData(const QByteArray &data)
} else if (method == "textDocument/didOpen") {
handleTextDocumentDidOpen(request);
} else if (method == "getCompletionsCycling") {
QString requestId = request["id"].toString();
startTimeMeasurement(requestId);
handleCompletion(request);
} else if (method == "$/cancelRequest") {
handleCancelRequest(request);
@ -136,8 +138,7 @@ QString LLMClientInterface::сontextBefore(TextEditor::TextEditorWidget *widget,
settings().readStringsBeforeCursor());
}
return QString("%1\n%2\n%3")
.arg(reader.getSpecificInstructions(), reader.getLanguageAndFileInfo(), contextBefore);
return contextBefore;
}
QString LLMClientInterface::сontextAfter(TextEditor::TextEditorWidget *widget,
@ -249,8 +250,8 @@ void LLMClientInterface::handleCompletion(const QJsonObject &request,
sendLLMRequest(request, updatedContext);
}
LLMClientInterface::ContextPair LLMClientInterface::prepareContext(
const QJsonObject &request, const QString &accumulatedCompletion)
ContextData LLMClientInterface::prepareContext(const QJsonObject &request,
const QString &accumulatedCompletion)
{
QJsonObject params = request["params"].toObject();
QJsonObject doc = params["doc"].toObject();
@ -272,12 +273,20 @@ LLMClientInterface::ContextPair LLMClientInterface::prepareContext(
auto textEditor = TextEditor::BaseTextEditor::currentTextEditor();
TextEditor::TextEditorWidget *widget = textEditor->editorWidget();
DocumentContextReader reader(widget->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 updatedContextBefore = contextBefore + accumulatedCompletion;
return {updatedContextBefore, contextAfter};
return {updatedContextBefore, contextAfter, instructions};
}
void LLMClientInterface::updateProvider()
@ -316,15 +325,17 @@ void LLMClientInterface::sendCompletionToClient(const QString &completion,
logMessage(QString("Full response: \n%1")
.arg(QString::fromUtf8(QJsonDocument(response).toJson(QJsonDocument::Indented))));
QString requestId = request["id"].toString();
endTimeMeasurement(requestId);
emit messageReceived(LanguageServerProtocol::JsonRpcMessage(response));
}
void LLMClientInterface::sendLLMRequest(const QJsonObject &request, const ContextPair &prompt)
void LLMClientInterface::sendLLMRequest(const QJsonObject &request, const ContextData &prompt)
{
QJsonObject providerRequest = {{"model", settings().modelName.value()}, {"stream", true}};
auto currentTemplate = PromptTemplateManager::instance().getCurrentTemplate();
currentTemplate->prepareRequest(providerRequest, prompt.prefix, prompt.suffix);
currentTemplate->prepareRequest(providerRequest, prompt);
auto &providerManager = LLMProvidersManager::instance();
providerManager.getCurrentProvider()->prepareRequest(providerRequest);
@ -381,6 +392,29 @@ QString LLMClientInterface::removeStopWords(const QString &completion)
return filteredCompletion;
}
void LLMClientInterface::startTimeMeasurement(const QString &requestId)
{
m_requestStartTimes[requestId] = QDateTime::currentMSecsSinceEpoch();
}
void LLMClientInterface::endTimeMeasurement(const QString &requestId)
{
if (m_requestStartTimes.contains(requestId)) {
qint64 startTime = m_requestStartTimes[requestId];
qint64 endTime = QDateTime::currentMSecsSinceEpoch();
qint64 totalTime = endTime - startTime;
logPerformance(requestId, "TotalCompletionTime", totalTime);
m_requestStartTimes.remove(requestId);
}
}
void LLMClientInterface::logPerformance(const QString &requestId,
const QString &operation,
qint64 elapsedMs)
{
logMessage(QString("Performance: %1 %2 took %3 ms").arg(requestId, operation).arg(elapsedMs));
}
void LLMClientInterface::parseCurrentMessage() {}
} // namespace QodeAssist

View File

@ -22,6 +22,8 @@
#include <languageclient/languageclientinterface.h>
#include <texteditor/texteditor.h>
#include "QodeAssistData.hpp"
class QNetworkReply;
class QNetworkAccessManager;
@ -35,12 +37,6 @@ public:
LLMClientInterface();
public:
struct ContextPair
{
QString prefix;
QString suffix;
};
Utils::FilePath serverDeviceTemplate() const override;
void sendCompletionToClient(const QString &completion,
@ -50,10 +46,10 @@ public:
void handleCompletion(const QJsonObject &request,
const QString &accumulatedCompletion = QString());
void sendLLMRequest(const QJsonObject &request, const ContextPair &prompt);
void sendLLMRequest(const QJsonObject &request, const ContextData &prompt);
void handleLLMResponse(QNetworkReply *reply, const QJsonObject &request);
ContextPair prepareContext(const QJsonObject &request,
ContextData prepareContext(const QJsonObject &request,
const QString &accumulatedCompletion = QString{});
void updateProvider();
@ -81,6 +77,13 @@ private:
QNetworkAccessManager *m_manager;
QMap<QString, QNetworkReply *> m_activeRequests;
QMap<QNetworkReply *, QString> m_accumulatedResponses;
QElapsedTimer m_completionTimer;
QMap<QString, qint64> m_requestStartTimes;
void startTimeMeasurement(const QString &requestId);
void endTimeMeasurement(const QString &requestId);
void logPerformance(const QString &requestId, const QString &operation, qint64 elapsedMs);
};
} // namespace QodeAssist

View File

@ -19,10 +19,17 @@
#include "LLMSuggestion.hpp"
#include <QTextCursor>
#include <QtWidgets/qtoolbar.h>
#include <texteditor/texteditor.h>
#include <utils/stringutils.h>
#include <utils/tooltip/tooltip.h>
namespace QodeAssist {
LLMSuggestion::LLMSuggestion(const Completion &completion, QTextDocument *origin)
: m_completion(completion)
, m_linesCount(0)
{
int startPos = completion.range().start().toPositionInDocument(origin);
int endPos = completion.range().end().toPositionInDocument(origin);
@ -63,8 +70,35 @@ bool LLMSuggestion::apply()
bool LLMSuggestion::applyWord(TextEditor::TextEditorWidget *widget)
{
Q_UNUSED(widget)
return apply();
return applyNextLine(widget);
}
bool LLMSuggestion::applyNextLine(TextEditor::TextEditorWidget *widget)
{
const QString text = m_completion.text();
QStringList lines = text.split('\n');
if (m_linesCount < lines.size())
m_linesCount++;
showTooltip(widget, m_linesCount);
return m_linesCount == lines.size() && !Utils::ToolTip::isVisible();
}
void LLMSuggestion::onCounterFinished(int count)
{
Utils::ToolTip::hide();
m_linesCount = 0;
QTextCursor cursor = m_completion.range().toSelection(m_start.document());
cursor.beginEditBlock();
cursor.removeSelectedText();
QStringList lines = m_completion.text().split('\n');
QString textToInsert = lines.mid(0, count).join('\n');
cursor.insertText(textToInsert);
cursor.endEditBlock();
}
void LLMSuggestion::reset()
@ -77,4 +111,14 @@ int LLMSuggestion::position()
return m_start.position();
}
void LLMSuggestion::showTooltip(TextEditor::TextEditorWidget *widget, int count)
{
Utils::ToolTip::hide();
QPoint pos = widget->mapToGlobal(widget->cursorRect().topRight());
pos += QPoint(-10, -50);
m_counterTooltip = new CounterTooltip(count);
Utils::ToolTip::show(pos, m_counterTooltip, widget);
connect(m_counterTooltip, &CounterTooltip::finished, this, &LLMSuggestion::onCounterFinished);
}
} // namespace QodeAssist

View File

@ -19,27 +19,37 @@
#pragma once
#include <QObject>
#include "LSPCompletion.hpp"
#include <texteditor/textdocumentlayout.h>
#include "LSPCompletion.hpp"
#include "utils/CounterTooltip.hpp"
namespace QodeAssist {
class LLMSuggestion final : public TextEditor::TextSuggestion
class LLMSuggestion final : public QObject, public TextEditor::TextSuggestion
{
Q_OBJECT
public:
LLMSuggestion(const Completion &completion, QTextDocument *origin);
bool apply() final;
bool applyWord(TextEditor::TextEditorWidget *widget) final;
bool applyNextLine(TextEditor::TextEditorWidget *widget);
void reset() final;
int position() final;
const Completion &completion() const { return m_completion; }
void showTooltip(TextEditor::TextEditorWidget *widget, int count);
void onCounterFinished(int count);
private:
Completion m_completion;
QTextCursor m_start;
int m_linesCount;
CounterTooltip *m_counterTooltip = nullptr;
};
} // namespace QodeAssist

View File

@ -43,6 +43,10 @@ public:
{
return typedValue<LanguageServerProtocol::Position>(LanguageServerProtocol::positionKey);
}
void setRange(const LanguageServerProtocol::Range &range)
{
insert(LanguageServerProtocol::rangeKey, range);
}
LanguageServerProtocol::Range range() const
{
return typedValue<LanguageServerProtocol::Range>(LanguageServerProtocol::rangeKey);

View File

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

View File

@ -190,7 +190,6 @@ void QodeAssistClient::handleCompletions(const GetCompletionRequest::Response &r
return;
editor->insertSuggestion(
std::make_unique<LLMSuggestion>(completions.first(), editor->document()));
editor->addHoverHandler(&m_hoverHandler);
}
}
@ -237,11 +236,6 @@ void QodeAssistClient::cleanupConnections()
disconnect(m_documentOpenedConnection);
disconnect(m_documentClosedConnection);
for (IEditor *editor : DocumentModel::editorsForOpenedDocuments()) {
if (auto textEditor = qobject_cast<BaseTextEditor *>(editor))
textEditor->editorWidget()->removeHoverHandler(&m_hoverHandler);
}
qDeleteAll(m_scheduledRequests);
m_scheduledRequests.clear();
}

View File

@ -27,7 +27,6 @@
#include <languageclient/client.h>
#include "LSPCompletion.hpp"
#include "QodeAssistHoverHandler.hpp"
namespace QodeAssist {
@ -54,7 +53,6 @@ private:
QHash<TextEditor::TextEditorWidget *, GetCompletionRequest> m_runningRequests;
QHash<TextEditor::TextEditorWidget *, QTimer *> m_scheduledRequests;
QodeAssistHoverHandler m_hoverHandler;
QMetaObject::Connection m_documentOpenedConnection;
QMetaObject::Connection m_documentClosedConnection;
};

View File

@ -54,6 +54,8 @@ const char OLLAMA_LIVETIME[] = "QodeAssist.ollamaLivetime";
const char SPECIFIC_INSTRUCTIONS[] = "QodeAssist.specificInstractions";
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 QODE_ASSIST_GENERAL_OPTIONS_ID[] = "QodeAssist.GeneralOptions";
const char QODE_ASSIST_GENERAL_OPTIONS_CATEGORY[] = "QodeAssist.Category";

33
QodeAssistData.hpp Normal file
View File

@ -0,0 +1,33 @@
/*
* 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 <QString>
namespace QodeAssist {
struct ContextData
{
QString prefix;
QString suffix;
QString instriuctions;
};
} // namespace QodeAssist

View File

@ -1,114 +0,0 @@
/*
* Copyright (C) 2023 The Qt Company Ltd.
* Copyright (C) 2024 Petr Mironychev
*
* This file is part of Qode Assist.
*
* The Qt Company portions:
* SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
*
* Petr Mironychev portions:
* 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 "QodeAssistHoverHandler.hpp"
#include <QPushButton>
#include <QScopeGuard>
#include <QToolBar>
#include <QToolButton>
#include <texteditor/textdocument.h>
#include <texteditor/textdocumentlayout.h>
#include <texteditor/texteditor.h>
#include <utils/tooltip/tooltip.h>
#include <utils/utilsicons.h>
#include "LLMSuggestion.hpp"
#include "LSPCompletion.hpp"
#include "QodeAssisttr.h"
using namespace TextEditor;
using namespace LanguageServerProtocol;
using namespace Utils;
namespace QodeAssist {
class QodeAssistCompletionToolTip : public QToolBar
{
public:
QodeAssistCompletionToolTip(TextEditorWidget *editor)
: m_editor(editor)
{
auto apply = addAction(Tr::tr("Apply (%1)").arg(QKeySequence(Qt::Key_Tab).toString()));
connect(apply, &QAction::triggered, this, &QodeAssistCompletionToolTip::apply);
}
private:
void apply()
{
if (TextSuggestion *suggestion = m_editor->currentSuggestion()) {
if (!suggestion->apply())
return;
}
ToolTip::hide();
}
TextEditorWidget *m_editor;
};
void QodeAssistHoverHandler::identifyMatch(TextEditor::TextEditorWidget *editorWidget,
int pos,
ReportPriority report)
{
QScopeGuard cleanup([&] { report(Priority_None); });
if (!editorWidget->suggestionVisible())
return;
QTextCursor cursor(editorWidget->document());
cursor.setPosition(pos);
m_block = cursor.block();
auto *suggestion = dynamic_cast<LLMSuggestion *>(TextDocumentLayout::suggestion(m_block));
if (!suggestion)
return;
const Completion completion = suggestion->completion();
if (completion.text().isEmpty())
return;
cleanup.dismiss();
report(Priority_Suggestion);
}
void QodeAssistHoverHandler::operateTooltip(TextEditor::TextEditorWidget *editorWidget,
const QPoint &point)
{
Q_UNUSED(point)
auto *suggestion = dynamic_cast<LLMSuggestion *>(TextDocumentLayout::suggestion(m_block));
if (!suggestion)
return;
auto tooltipWidget = new QodeAssistCompletionToolTip(editorWidget);
const QRect cursorRect = editorWidget->cursorRect(editorWidget->textCursor());
QPoint pos = editorWidget->viewport()->mapToGlobal(cursorRect.topLeft())
- Utils::ToolTip::offsetFromPosition();
pos.ry() -= tooltipWidget->sizeHint().height();
ToolTip::show(pos, tooltipWidget, editorWidget);
}
} // namespace QodeAssist

View File

@ -58,7 +58,7 @@ QodeAssistSettings::QodeAssistSettings()
llmProviders.setSettingsKey(Constants::LLM_PROVIDERS);
llmProviders.setDisplayName(Tr::tr("LLM Providers:"));
llmProviders.setDisplayStyle(Utils::SelectionAspect::DisplayStyle::ComboBox);
llmProviders.setDefaultValue(1);
llmProviders.setDefaultValue(0);
url.setSettingsKey(Constants::URL);
url.setLabelText(Tr::tr("URL:"));
@ -77,7 +77,7 @@ QodeAssistSettings::QodeAssistSettings()
temperature.setDefaultValue(0.2);
temperature.setRange(0.0, 10.0);
selectModels.m_buttonText = Tr::tr("Select Models");
selectModels.m_buttonText = Tr::tr("Select Model");
ollamaLivetime.setSettingsKey(Constants::OLLAMA_LIVETIME);
ollamaLivetime.setLabelText(
@ -88,7 +88,7 @@ QodeAssistSettings::QodeAssistSettings()
fimPrompts.setDisplayName(Tr::tr("Fill-In-Middle Prompt"));
fimPrompts.setSettingsKey(Constants::FIM_PROMPTS);
fimPrompts.setDefaultValue(1);
fimPrompts.setDefaultValue(0);
fimPrompts.setDisplayStyle(Utils::SelectionAspect::DisplayStyle::ComboBox);
readFullFile.setSettingsKey(Constants::READ_FULL_FILE);
@ -111,7 +111,7 @@ QodeAssistSettings::QodeAssistSettings()
maxTokens.setSettingsKey(Constants::MAX_TOKENS);
maxTokens.setLabelText(Tr::tr("Max Tokens"));
maxTokens.setRange(-1, 10000);
maxTokens.setDefaultValue(250);
maxTokens.setDefaultValue(150);
useTopP.setSettingsKey(Constants::USE_TOP_P);
useTopP.setDefaultValue(false);
@ -145,14 +145,19 @@ QodeAssistSettings::QodeAssistSettings()
frequencyPenalty.setDefaultValue(0.0);
frequencyPenalty.setRange(-2.0, 2.0);
providerPaths.setSettingsKey(Constants::PROVIDER_PATHS);
providerPaths.setLabelText(Tr::tr("Provider Paths:"));
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(
@ -195,6 +200,7 @@ QodeAssistSettings::QodeAssistSettings()
frequencyPenalty.setEnabled(useFrequencyPenalty());
readStringsAfterCursor.setEnabled(!readFullFile());
readStringsBeforeCursor.setEnabled(!readFullFile());
specificInstractions.setEnabled(useSpecificInstructions());
PromptTemplateManager::instance().setCurrentTemplate(fimPrompts.stringValue());
LLMProvidersManager::instance().setCurrentProvider(llmProviders.stringValue());
@ -210,7 +216,7 @@ QodeAssistSettings::QodeAssistSettings()
enableLogging,
Row{Stretch{1}, resetToDefaults}}}},
Group{title(Tr::tr("LLM Providers")),
Form{Column{llmProviders, Row{url, endPoint}, providerPaths}}},
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")),
@ -221,6 +227,8 @@ QodeAssistSettings::QodeAssistSettings()
readStringsAfterCursor,
ollamaLivetime,
apiKey,
useFilePathInContext,
useSpecificInstructions,
specificInstractions,
temperature,
maxTokens,
@ -274,6 +282,9 @@ void QodeAssistSettings::setupConnections()
connect(&enableLogging, &Utils::BoolAspect::volatileValueChanged, this, [this]() {
setLoggingEnabled(enableLogging.volatileValue());
});
connect(&useSpecificInstructions, &Utils::BoolAspect::volatileValueChanged, this, [this]() {
specificInstractions.setEnabled(useSpecificInstructions.volatileValue());
});
}
void QodeAssistSettings::updateProviderSettings()
@ -292,7 +303,7 @@ QStringList QodeAssistSettings::getInstalledModels()
{
auto *provider = LLMProvidersManager::instance().getCurrentProvider();
if (provider) {
auto env = getEnvironmentWithProviderPaths();
Utils::Environment env = Utils::Environment::systemEnvironment();
return provider->getInstalledModels(env);
}
return {};
@ -317,16 +328,6 @@ void QodeAssistSettings::showModelSelectionDialog()
}
}
Utils::Environment QodeAssistSettings::getEnvironmentWithProviderPaths() const
{
Utils::Environment env = Utils::Environment::systemEnvironment();
const QStringList additionalPaths = providerPaths.volatileValue();
for (const QString &path : additionalPaths) {
env.prependOrSetPath(path);
}
return env;
}
void QodeAssistSettings::resetSettingsToDefaults()
{
QMessageBox::StandardButton reply;
@ -362,6 +363,12 @@ void QodeAssistSettings::resetSettingsToDefaults()
resetAspect(enableLogging);
resetAspect(ollamaLivetime);
resetAspect(specificInstractions);
resetAspect(multiLineCompletion);
resetAspect(useFilePathInContext);
resetAspect(useSpecificInstructions);
fimPrompts.setStringValue("StarCoder2");
llmProviders.setStringValue("Ollama");
updateProviderSettings();
apply();

View File

@ -88,13 +88,13 @@ public:
Utils::BoolAspect useFrequencyPenalty{this};
Utils::DoubleAspect frequencyPenalty{this};
Utils::StringListAspect providerPaths{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};

View File

@ -15,6 +15,7 @@ QodeAssist has been tested with the following language models, all trained for F
Ollama:
- [starcoder2](https://ollama.com/library/starcoder2)
- [codellama](https://ollama.com/library/codellama)
- DeepSeek-Coder-V2-Lite-Base
LM studio:
- [second-state/StarCoder2-7B-GGUF](https://huggingface.co/second-state/StarCoder2-7B-GGUF)
@ -30,7 +31,7 @@ If you've successfully used a model that's not listed here, please let us know b
- [ ] Add chat functionality
- [ ] Support for more providers and models
## Installation Plugin
## Plugin installation using Ollama as an example
1. Install QtCreator 14.0
2. Install [Ollama](https://ollama.com). Make sure to review the system requirements before installation.
@ -50,14 +51,50 @@ ollama run starcoder2:7b
1. Open Qt Creator settings
2. Navigate to the "Qode Assist" tab
3. Choose your LLM provider (e.g., Ollama)
- If you haven't added the provider to your system PATH, specify the path to the provider executable in the "Provider Paths" field
4. Select the installed model
- If you need to enter the model name manually, it indicates that the plugin cannot locate the provider's executable file. However, this doesn't affect the plugin's functionality it will still work correctly. This autoselector input option is provided for your convenience, allowing you to easily select and use different models
4. 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
You're all set! QodeAssist is now ready to use in Qt Creator.
## Hotkeys
- To call manual request to suggestion, you can use or change it in settings
- on Mac: Option + Command + Q
- on Windows: Ctrl + Alt + Q
- To insert the full suggestion, you can use the TAB key
- To insert line by line, you can use the "Move cursor word right" shortcut:
- On Mac: Option + Right Arrow
- On Windows: Alt + Right Arrow
## Troubleshooting
If QodeAssist is having problems connecting to the LLM provider, please check the following:
1. Verify the IP address and port:
- For Ollama, the default is usually http://localhost:11434
- For LM Studio, the default is usually http://localhost:1234
2. Check the endpoint:
Make sure the endpoint in the settings matches the one required by your provider
- For Ollama, it should be /api/generate
- For LM Studio and OpenAI compatible providers, it's usually /v1/chat/completions
3. Confirm that the selected model and template are compatible:
Ensure you've chosen the correct model in the "Select Models" option
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
- The API key will not reset
- Select model after reset
## Support the development of QodeAssist
If you find QodeAssist helpful, there are several ways you can support the project:

View File

@ -19,14 +19,15 @@
#include "LMStudioProvider.hpp"
#include <QEventLoop>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QNetworkReply>
#include <QProcess>
#include "PromptTemplateManager.hpp"
#include "QodeAssistSettings.hpp"
#include "QodeAssistUtils.hpp"
namespace QodeAssist::Providers {
@ -113,53 +114,32 @@ bool LMStudioProvider::handleResponse(QNetworkReply *reply, QString &accumulated
QList<QString> LMStudioProvider::getInstalledModels(const Utils::Environment &env)
{
QProcess process;
process.setEnvironment(env.toStringList());
QString lmsConsoleName;
#ifdef Q_OS_WIN
lmsConsoleName = "lms.exe";
#else
lmsConsoleName = "lms";
#endif
auto lmsPath = env.searchInPath(lmsConsoleName).toString();
QList<QString> models;
QNetworkAccessManager manager;
QNetworkRequest request(QUrl(url() + "/v1/models"));
if (!QFileInfo::exists(lmsPath)) {
qWarning() << "LMS executable not found at" << lmsPath;
return {};
}
QNetworkReply *reply = manager.get(request);
process.start(lmsPath, QStringList() << "ls");
if (!process.waitForStarted()) {
qWarning() << "Failed to start LMS process:" << process.errorString();
return {};
}
QEventLoop loop;
QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
loop.exec();
if (!process.waitForFinished()) {
qWarning() << "LMS process did not finish:" << process.errorString();
return {};
}
if (reply->error() == QNetworkReply::NoError) {
QByteArray responseData = reply->readAll();
QJsonDocument jsonResponse = QJsonDocument::fromJson(responseData);
QJsonObject jsonObject = jsonResponse.object();
QJsonArray modelArray = jsonObject["data"].toArray();
QStringList models;
if (process.exitCode() == 0) {
QString output = QString::fromUtf8(process.readAllStandardOutput());
QStringList lines = output.split('\n', Qt::SkipEmptyParts);
// Skip the header lines
for (int i = 2; i < lines.size(); ++i) {
QString line = lines[i].trimmed();
if (!line.isEmpty()) {
// The model name is the first column
QString modelName = line.split(QRegularExpression("\\s+"), Qt::SkipEmptyParts)
.first();
models.append(modelName);
}
for (const QJsonValue &value : modelArray) {
QJsonObject modelObject = value.toObject();
QString modelId = modelObject["id"].toString();
models.append(modelId);
}
qDebug() << "Models:" << models;
} else {
// Handle error
qWarning() << "Error running 'lms list':" << process.errorString();
logMessage(QString("Error fetching models: %1").arg(reply->errorString()));
}
reply->deleteLater();
return models;
}

View File

@ -23,10 +23,11 @@
#include <QJsonDocument>
#include <QJsonObject>
#include <QNetworkReply>
#include <QProcess>
#include <QtCore/qeventloop.h>
#include "PromptTemplateManager.hpp"
#include "QodeAssistSettings.hpp"
#include "QodeAssistUtils.hpp"
namespace QodeAssist::Providers {
@ -96,50 +97,31 @@ bool OllamaProvider::handleResponse(QNetworkReply *reply, QString &accumulatedRe
QList<QString> OllamaProvider::getInstalledModels(const Utils::Environment &env)
{
QProcess process;
process.setEnvironment(env.toStringList());
QString ollamaConsoleName;
#ifdef Q_OS_WIN
ollamaConsoleName = "ollama.exe";
#else
ollamaConsoleName = "ollama";
#endif
QList<QString> models;
QNetworkAccessManager manager;
QNetworkRequest request(QUrl(url() + "/api/tags"));
QNetworkReply *reply = manager.get(request);
auto ollamaPath = env.searchInPath(ollamaConsoleName).toString();
QEventLoop loop;
QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
loop.exec();
if (!QFileInfo::exists(ollamaPath)) {
qWarning() << "Ollama executable not found at" << ollamaPath;
return {};
}
if (reply->error() == QNetworkReply::NoError) {
QByteArray responseData = reply->readAll();
QJsonDocument jsonResponse = QJsonDocument::fromJson(responseData);
QJsonObject jsonObject = jsonResponse.object();
QJsonArray modelArray = jsonObject["models"].toArray();
process.start(ollamaPath, QStringList() << "list");
if (!process.waitForStarted()) {
qWarning() << "Failed to start Ollama process:" << process.errorString();
return {};
}
if (!process.waitForFinished()) {
qWarning() << "Ollama process did not finish:" << process.errorString();
return {};
}
QStringList models;
if (process.exitCode() == 0) {
QString output = QString::fromUtf8(process.readAllStandardOutput());
QStringList lines = output.split('\n', Qt::SkipEmptyParts);
for (int i = 1; i < lines.size(); ++i) {
QString line = lines[i].trimmed();
if (!line.isEmpty()) {
QString modelName = line.split(QRegularExpression("\\s+"), Qt::SkipEmptyParts)
.first();
models.append(modelName);
}
for (const QJsonValue &value : modelArray) {
QJsonObject modelObject = value.toObject();
QString modelName = modelObject["name"].toString();
models.append(modelName);
}
} else {
qWarning() << "Error running 'ollama list':" << process.errorString();
logMessage(QString("Error fetching models: %1").arg(reply->errorString()));
}
reply->deleteLater();
return models;
}

View File

@ -17,13 +17,12 @@
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
*/
#include "OpenAICompatProvider.h"
#include "OpenAICompatProvider.hpp"
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QNetworkReply>
#include <QProcess>
#include "PromptTemplateManager.hpp"
#include "QodeAssistSettings.hpp"

View File

@ -43,9 +43,10 @@
#include "QodeAssistClient.hpp"
#include "providers/LMStudioProvider.hpp"
#include "providers/OllamaProvider.hpp"
#include "providers/OpenAICompatProvider.h"
#include "providers/OpenAICompatProvider.hpp"
#include "templates/CodeLLamaTemplate.hpp"
#include "templates/CodeQwenChat.hpp"
#include "templates/DeepSeekCoderV2.hpp"
#include "templates/StarCoder2Template.hpp"
using namespace Utils;
@ -80,6 +81,7 @@ public:
templateManager.registerTemplate<Templates::CodeLLamaTemplate>();
templateManager.registerTemplate<Templates::StarCoder2Template>();
templateManager.registerTemplate<Templates::CodeQwenChatTemplate>();
templateManager.registerTemplate<Templates::DeepSeekCoderV2Template>();
Utils::Icon QCODEASSIST_ICON(
{{":/resources/images/qoderassist-icon.png", Utils::Theme::IconsBaseColor}});

View File

@ -27,17 +27,17 @@ class CodeLLamaTemplate : public PromptTemplate
{
public:
QString name() const override { return "CodeLlama"; }
QString promptTemplate() const override { return "<PRE> %1 <SUF>%2 <MID>"; }
QString promptTemplate() const override { return "%1<PRE> %2 <SUF>%3 <MID>"; }
QStringList stopWords() const override
{
return QStringList() << "<EOT>" << "<PRE>" << "<SUF" << "<MID>";
}
void prepareRequest(QJsonObject &request,
const QString &prefix,
const QString &suffix) const override
void prepareRequest(QJsonObject &request, const ContextData &context) const override
{
QString formattedPrompt = promptTemplate().arg(prefix, suffix);
QString formattedPrompt = promptTemplate().arg(context.instriuctions,
context.prefix,
context.suffix);
request["prompt"] = formattedPrompt;
}
};

View File

@ -27,16 +27,16 @@ class CodeQwenChatTemplate : public PromptTemplate
{
public:
QString name() const override { return "CodeQwenChat (experimental)"; }
QString promptTemplate() const override { return "\n### Instruction:%1%2 ### Response:\n"; }
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 QString &prefix,
const QString &suffix) const override
void prepareRequest(QJsonObject &request, const ContextData &context) const override
{
QString formattedPrompt = promptTemplate().arg(prefix, suffix);
QString formattedPrompt = promptTemplate().arg(context.instriuctions,
context.prefix,
context.suffix);
request["prompt"] = formattedPrompt;
}
};

View File

@ -0,0 +1,44 @@
/*
* 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 DeepSeekCoderV2Template : public PromptTemplate
{
public:
QString name() const override { return "DeepSeekCoderV2"; }
QString promptTemplate() const override
{
return "%1<fim▁begin>%2<fim▁hole>%3<fim▁end>";
}
QStringList stopWords() const override { return QStringList(); }
void prepareRequest(QJsonObject &request, const ContextData &context) const override
{
QString formattedPrompt = promptTemplate().arg(context.instriuctions,
context.prefix,
context.suffix);
request["prompt"] = formattedPrompt;
}
};
} // namespace QodeAssist::Templates

View File

@ -23,6 +23,8 @@
#include <QList>
#include <QString>
#include "QodeAssistData.hpp"
namespace QodeAssist::Templates {
class PromptTemplate
@ -32,9 +34,6 @@ public:
virtual QString name() const = 0;
virtual QString promptTemplate() const = 0;
virtual QStringList stopWords() const = 0;
virtual void prepareRequest(QJsonObject &request,
const QString &prefix,
const QString &suffix) const
= 0;
virtual void prepareRequest(QJsonObject &request, const ContextData &context) const = 0;
};
} // namespace QodeAssist::Templates

View File

@ -27,17 +27,17 @@ class StarCoder2Template : public PromptTemplate
{
public:
QString name() const override { return "StarCoder2"; }
QString promptTemplate() const override { return "<fim_prefix>%1<fim_suffix>%2<fim_middle>"; }
QString promptTemplate() const override { return "%1<fim_prefix>%2<fim_suffix>%3<fim_middle>"; }
QStringList stopWords() const override
{
return QStringList() << "<|endoftext|>" << "<file_sep>" << "<fim_prefix>" << "<fim_suffix>"
<< "<fim_middle>";
}
void prepareRequest(QJsonObject &request,
const QString &prefix,
const QString &suffix) const override
void prepareRequest(QJsonObject &request, const ContextData &context) const override
{
QString formattedPrompt = promptTemplate().arg(prefix, suffix);
QString formattedPrompt = promptTemplate().arg(context.instriuctions,
context.prefix,
context.suffix);
request["prompt"] = formattedPrompt;
}
};

48
utils/CounterTooltip.cpp Normal file
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/>.
*/
#include "CounterTooltip.hpp"
namespace QodeAssist {
CounterTooltip::CounterTooltip(int count)
: m_count(count)
{
m_label = new QLabel(this);
addWidget(m_label);
updateLabel();
m_timer = new QTimer(this);
m_timer->setSingleShot(true);
m_timer->setInterval(2000);
connect(m_timer, &QTimer::timeout, this, [this] { emit finished(m_count); });
m_timer->start();
}
CounterTooltip::~CounterTooltip() {}
void CounterTooltip::updateLabel()
{
const auto hotkey = QKeySequence(QKeySequence::MoveToNextWord).toString();
m_label->setText(QString("Insert Next %1 line(s) (%2)").arg(m_count).arg(hotkey));
}
} // namespace QodeAssist

View File

@ -1,13 +1,8 @@
/*
* Copyright (C) 2023 The Qt Company Ltd.
/*
* Copyright (C) 2024 Petr Mironychev
*
* This file is part of Qode Assist.
* This file is part of QodeAssist.
*
* The Qt Company portions:
* SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
*
* Petr Mironychev portions:
* 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
@ -24,24 +19,30 @@
#pragma once
#include <QTextBlock>
#include <texteditor/basehoverhandler.h>
#include <QLabel>
#include <QTimer>
#include <QToolBar>
#include <QWidget>
namespace QodeAssist {
class QodeAssistHoverHandler : public TextEditor::BaseHoverHandler
class CounterTooltip : public QToolBar
{
public:
QodeAssistHoverHandler() = default;
Q_OBJECT
protected:
void identifyMatch(TextEditor::TextEditorWidget *editorWidget,
int pos,
ReportPriority report) final;
void operateTooltip(TextEditor::TextEditorWidget *editorWidget, const QPoint &point) final;
public:
CounterTooltip(int count);
~CounterTooltip();
signals:
void finished(int count);
private:
QTextBlock m_block;
void updateLabel();
QLabel *m_label;
QTimer *m_timer;
int m_count;
};
} // namespace QodeAssist