Compare commits

...

17 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
ec26a31ec5 Merge pull request #4 from Palm1r/version-0.0.6
Version 0.0.6
- Fix authorization to API
- Fix reset to provider default settings after reboot qtc
2024-08-30 08:48:09 +02:00
55d359e44e Fix reset to provider default settings on restart qtc 2024-08-30 08:28:27 +02:00
46258a11f6 Fix authorization to the API. 2024-08-30 07:47:33 +02:00
4bccd8db91 Add new provider to README.md 2024-08-29 23:35:34 +02:00
31 changed files with 418 additions and 332 deletions

View File

@ -35,17 +35,19 @@ add_qtc_plugin(QodeAssist
templates/CodeLLamaTemplate.hpp templates/CodeLLamaTemplate.hpp
templates/StarCoder2Template.hpp templates/StarCoder2Template.hpp
templates/CodeQwenChat.hpp templates/CodeQwenChat.hpp
templates/DeepSeekCoderV2.hpp
providers/LLMProvider.hpp providers/LLMProvider.hpp
providers/OllamaProvider.hpp providers/OllamaProvider.cpp providers/OllamaProvider.hpp providers/OllamaProvider.cpp
providers/LMStudioProvider.hpp providers/LMStudioProvider.cpp providers/LMStudioProvider.hpp providers/LMStudioProvider.cpp
providers/OpenAICompatProvider.h providers/OpenAICompatProvider.cpp providers/OpenAICompatProvider.hpp providers/OpenAICompatProvider.cpp
LLMProvidersManager.hpp LLMProvidersManager.cpp LLMProvidersManager.hpp LLMProvidersManager.cpp
QodeAssistSettings.hpp QodeAssistSettings.cpp QodeAssistSettings.hpp QodeAssistSettings.cpp
QodeAssist.qrc QodeAssist.qrc
LSPCompletion.hpp LSPCompletion.hpp
LLMSuggestion.hpp LLMSuggestion.cpp LLMSuggestion.hpp LLMSuggestion.cpp
QodeAssistHoverHandler.hpp QodeAssistHoverHandler.cpp
QodeAssistClient.hpp QodeAssistClient.cpp QodeAssistClient.hpp QodeAssistClient.cpp
QodeAssistUtils.hpp QodeAssistUtils.hpp
DocumentContextReader.hpp DocumentContextReader.cpp 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 filePath = m_textDocument->filePath().toString();
QString fileExtension = QFileInfo(filePath).suffix(); 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); .arg(language, mimeType, filePath, fileExtension);
} }
@ -138,7 +138,7 @@ QString DocumentContextReader::getSpecificInstructions() const
{ {
QString specificInstruction = settings().specificInstractions().arg( QString specificInstruction = settings().specificInstractions().arg(
LanguageServerProtocol::TextDocumentItem::mimeTypeToLanguageId(m_textDocument->mimeType())); LanguageServerProtocol::TextDocumentItem::mimeTypeToLanguageId(m_textDocument->mimeType()));
return QString("//Instructions: %1").arg(specificInstruction); return QString("%1").arg(specificInstruction);
} }
CopyrightInfo DocumentContextReader::findCopyright() CopyrightInfo DocumentContextReader::findCopyright()

View File

@ -69,6 +69,8 @@ void LLMClientInterface::sendData(const QByteArray &data)
} else if (method == "textDocument/didOpen") { } else if (method == "textDocument/didOpen") {
handleTextDocumentDidOpen(request); handleTextDocumentDidOpen(request);
} else if (method == "getCompletionsCycling") { } else if (method == "getCompletionsCycling") {
QString requestId = request["id"].toString();
startTimeMeasurement(requestId);
handleCompletion(request); handleCompletion(request);
} else if (method == "$/cancelRequest") { } else if (method == "$/cancelRequest") {
handleCancelRequest(request); handleCancelRequest(request);
@ -136,8 +138,7 @@ QString LLMClientInterface::сontextBefore(TextEditor::TextEditorWidget *widget,
settings().readStringsBeforeCursor()); settings().readStringsBeforeCursor());
} }
return QString("%1\n%2\n%3") return contextBefore;
.arg(reader.getSpecificInstructions(), reader.getLanguageAndFileInfo(), contextBefore);
} }
QString LLMClientInterface::сontextAfter(TextEditor::TextEditorWidget *widget, QString LLMClientInterface::сontextAfter(TextEditor::TextEditorWidget *widget,
@ -249,8 +250,8 @@ void LLMClientInterface::handleCompletion(const QJsonObject &request,
sendLLMRequest(request, updatedContext); sendLLMRequest(request, updatedContext);
} }
LLMClientInterface::ContextPair LLMClientInterface::prepareContext( ContextData LLMClientInterface::prepareContext(const QJsonObject &request,
const QJsonObject &request, const QString &accumulatedCompletion) const QString &accumulatedCompletion)
{ {
QJsonObject params = request["params"].toObject(); QJsonObject params = request["params"].toObject();
QJsonObject doc = params["doc"].toObject(); QJsonObject doc = params["doc"].toObject();
@ -272,20 +273,25 @@ LLMClientInterface::ContextPair LLMClientInterface::prepareContext(
auto textEditor = TextEditor::BaseTextEditor::currentTextEditor(); auto textEditor = TextEditor::BaseTextEditor::currentTextEditor();
TextEditor::TextEditorWidget *widget = textEditor->editorWidget(); TextEditor::TextEditorWidget *widget = textEditor->editorWidget();
DocumentContextReader reader(widget->textDocument());
QString contextBefore = сontextBefore(widget, lineNumber, cursorPosition); QString contextBefore = сontextBefore(widget, lineNumber, cursorPosition);
QString contextAfter = сontextAfter(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; QString updatedContextBefore = contextBefore + accumulatedCompletion;
return {updatedContextBefore, contextAfter}; return {updatedContextBefore, contextAfter, instructions};
} }
void LLMClientInterface::updateProvider() void LLMClientInterface::updateProvider()
{ {
m_serverUrl = QUrl(QString("%1:%2%3") m_serverUrl = QUrl(QString("%1%2").arg(settings().url(), settings().endPoint()));
.arg(settings().url.value())
.arg(settings().port.value())
.arg(settings().endPoint.value()));
} }
void LLMClientInterface::sendCompletionToClient(const QString &completion, void LLMClientInterface::sendCompletionToClient(const QString &completion,
@ -319,27 +325,36 @@ void LLMClientInterface::sendCompletionToClient(const QString &completion,
logMessage(QString("Full response: \n%1") logMessage(QString("Full response: \n%1")
.arg(QString::fromUtf8(QJsonDocument(response).toJson(QJsonDocument::Indented)))); .arg(QString::fromUtf8(QJsonDocument(response).toJson(QJsonDocument::Indented))));
QString requestId = request["id"].toString();
endTimeMeasurement(requestId);
emit messageReceived(LanguageServerProtocol::JsonRpcMessage(response)); emit messageReceived(LanguageServerProtocol::JsonRpcMessage(response));
} }
void LLMClientInterface::sendLLMRequest(const QJsonObject &request, const ContextPair &prompt) void LLMClientInterface::sendLLMRequest(const QJsonObject &request, const ContextData &prompt)
{ {
QJsonObject qodeRequest = {{"model", settings().modelName.value()}, {"stream", true}}; QJsonObject providerRequest = {{"model", settings().modelName.value()}, {"stream", true}};
auto currentTemplate = PromptTemplateManager::instance().getCurrentTemplate(); auto currentTemplate = PromptTemplateManager::instance().getCurrentTemplate();
currentTemplate->prepareRequest(qodeRequest, prompt.prefix, prompt.suffix); currentTemplate->prepareRequest(providerRequest, prompt);
auto &providerManager = LLMProvidersManager::instance(); auto &providerManager = LLMProvidersManager::instance();
providerManager.getCurrentProvider()->prepareRequest(qodeRequest); providerManager.getCurrentProvider()->prepareRequest(providerRequest);
logMessage( logMessage(QString("Sending request to llm: \nurl: %1\nRequest body:\n%2")
QString("Sending request to llm: \nurl: %1\nRequest body:\n%2") .arg(m_serverUrl.toString(),
.arg(m_serverUrl.toString()) QString::fromUtf8(
.arg(QString::fromUtf8(QJsonDocument(qodeRequest).toJson(QJsonDocument::Indented)))); QJsonDocument(providerRequest).toJson(QJsonDocument::Indented))));
QNetworkRequest networkRequest(m_serverUrl); QNetworkRequest networkRequest(m_serverUrl);
networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
QNetworkReply *reply = m_manager->post(networkRequest, QJsonDocument(qodeRequest).toJson());
if (providerRequest.contains("api_key")) {
QString apiKey = providerRequest["api_key"].toString();
networkRequest.setRawHeader("Authorization", QString("Bearer %1").arg(apiKey).toUtf8());
providerRequest.remove("api_key");
}
QNetworkReply *reply = m_manager->post(networkRequest, QJsonDocument(providerRequest).toJson());
if (!reply) { if (!reply) {
logMessage("Error: Failed to create network reply"); logMessage("Error: Failed to create network reply");
return; return;
@ -377,6 +392,29 @@ QString LLMClientInterface::removeStopWords(const QString &completion)
return filteredCompletion; 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() {} void LLMClientInterface::parseCurrentMessage() {}
} // namespace QodeAssist } // namespace QodeAssist

View File

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

View File

@ -19,10 +19,17 @@
#include "LLMSuggestion.hpp" #include "LLMSuggestion.hpp"
#include <QTextCursor>
#include <QtWidgets/qtoolbar.h>
#include <texteditor/texteditor.h>
#include <utils/stringutils.h>
#include <utils/tooltip/tooltip.h>
namespace QodeAssist { namespace QodeAssist {
LLMSuggestion::LLMSuggestion(const Completion &completion, QTextDocument *origin) LLMSuggestion::LLMSuggestion(const Completion &completion, QTextDocument *origin)
: m_completion(completion) : m_completion(completion)
, m_linesCount(0)
{ {
int startPos = completion.range().start().toPositionInDocument(origin); int startPos = completion.range().start().toPositionInDocument(origin);
int endPos = completion.range().end().toPositionInDocument(origin); int endPos = completion.range().end().toPositionInDocument(origin);
@ -63,8 +70,35 @@ bool LLMSuggestion::apply()
bool LLMSuggestion::applyWord(TextEditor::TextEditorWidget *widget) bool LLMSuggestion::applyWord(TextEditor::TextEditorWidget *widget)
{ {
Q_UNUSED(widget) return applyNextLine(widget);
return apply(); }
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() void LLMSuggestion::reset()
@ -77,4 +111,14 @@ int LLMSuggestion::position()
return m_start.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 } // namespace QodeAssist

View File

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

View File

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

View File

@ -1,6 +1,6 @@
{ {
"Name" : "QodeAssist", "Name" : "QodeAssist",
"Version" : "0.0.5", "Version" : "0.0.8",
"CompatVersion" : "${IDE_VERSION_COMPAT}", "CompatVersion" : "${IDE_VERSION_COMPAT}",
"Vendor" : "Petr Mironychev", "Vendor" : "Petr Mironychev",
"Copyright" : "(C) ${IDE_COPYRIGHT_YEAR} Petr Mironychev, (C) ${IDE_COPYRIGHT_YEAR} The Qt Company Ltd", "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; return;
editor->insertSuggestion( editor->insertSuggestion(
std::make_unique<LLMSuggestion>(completions.first(), editor->document())); std::make_unique<LLMSuggestion>(completions.first(), editor->document()));
editor->addHoverHandler(&m_hoverHandler);
} }
} }
@ -237,11 +236,6 @@ void QodeAssistClient::cleanupConnections()
disconnect(m_documentOpenedConnection); disconnect(m_documentOpenedConnection);
disconnect(m_documentClosedConnection); disconnect(m_documentClosedConnection);
for (IEditor *editor : DocumentModel::editorsForOpenedDocuments()) {
if (auto textEditor = qobject_cast<BaseTextEditor *>(editor))
textEditor->editorWidget()->removeHoverHandler(&m_hoverHandler);
}
qDeleteAll(m_scheduledRequests); qDeleteAll(m_scheduledRequests);
m_scheduledRequests.clear(); m_scheduledRequests.clear();
} }

View File

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

View File

@ -30,7 +30,6 @@ const char ENABLE_AUTO_COMPLETE[] = "QodeAssist.enableAutoComplete";
const char ENABLE_LOGGING[] = "QodeAssist.enableLogging"; const char ENABLE_LOGGING[] = "QodeAssist.enableLogging";
const char LLM_PROVIDERS[] = "QodeAssist.llmProviders"; const char LLM_PROVIDERS[] = "QodeAssist.llmProviders";
const char URL[] = "QodeAssist.url"; const char URL[] = "QodeAssist.url";
const char PORT[] = "QodeAssist.port";
const char END_POINT[] = "QodeAssist.endPoint"; const char END_POINT[] = "QodeAssist.endPoint";
const char MODEL_NAME[] = "QodeAssist.modelName"; const char MODEL_NAME[] = "QodeAssist.modelName";
const char SELECT_MODELS[] = "QodeAssist.selectModels"; const char SELECT_MODELS[] = "QodeAssist.selectModels";
@ -55,6 +54,8 @@ const char OLLAMA_LIVETIME[] = "QodeAssist.ollamaLivetime";
const char SPECIFIC_INSTRUCTIONS[] = "QodeAssist.specificInstractions"; const char SPECIFIC_INSTRUCTIONS[] = "QodeAssist.specificInstractions";
const char MULTILINE_COMPLETION[] = "QodeAssist.multilineCompletion"; const char MULTILINE_COMPLETION[] = "QodeAssist.multilineCompletion";
const char API_KEY[] = "QodeAssist.apiKey"; 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_ID[] = "QodeAssist.GeneralOptions";
const char QODE_ASSIST_GENERAL_OPTIONS_CATEGORY[] = "QodeAssist.Category"; 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.setSettingsKey(Constants::LLM_PROVIDERS);
llmProviders.setDisplayName(Tr::tr("LLM Providers:")); llmProviders.setDisplayName(Tr::tr("LLM Providers:"));
llmProviders.setDisplayStyle(Utils::SelectionAspect::DisplayStyle::ComboBox); llmProviders.setDisplayStyle(Utils::SelectionAspect::DisplayStyle::ComboBox);
llmProviders.setDefaultValue(1); llmProviders.setDefaultValue(0);
url.setSettingsKey(Constants::URL); url.setSettingsKey(Constants::URL);
url.setLabelText(Tr::tr("URL:")); url.setLabelText(Tr::tr("URL:"));
@ -68,10 +68,6 @@ QodeAssistSettings::QodeAssistSettings()
endPoint.setLabelText(Tr::tr("Endpoint:")); endPoint.setLabelText(Tr::tr("Endpoint:"));
endPoint.setDisplayStyle(Utils::StringAspect::LineEditDisplay); endPoint.setDisplayStyle(Utils::StringAspect::LineEditDisplay);
port.setSettingsKey(Constants::PORT);
port.setLabelText(Tr::tr("Port:"));
port.setRange(1, 65535);
modelName.setSettingsKey(Constants::MODEL_NAME); modelName.setSettingsKey(Constants::MODEL_NAME);
modelName.setLabelText(Tr::tr("LLM Name:")); modelName.setLabelText(Tr::tr("LLM Name:"));
modelName.setDisplayStyle(Utils::StringAspect::LineEditDisplay); modelName.setDisplayStyle(Utils::StringAspect::LineEditDisplay);
@ -81,7 +77,7 @@ QodeAssistSettings::QodeAssistSettings()
temperature.setDefaultValue(0.2); temperature.setDefaultValue(0.2);
temperature.setRange(0.0, 10.0); 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.setSettingsKey(Constants::OLLAMA_LIVETIME);
ollamaLivetime.setLabelText( ollamaLivetime.setLabelText(
@ -92,7 +88,7 @@ QodeAssistSettings::QodeAssistSettings()
fimPrompts.setDisplayName(Tr::tr("Fill-In-Middle Prompt")); fimPrompts.setDisplayName(Tr::tr("Fill-In-Middle Prompt"));
fimPrompts.setSettingsKey(Constants::FIM_PROMPTS); fimPrompts.setSettingsKey(Constants::FIM_PROMPTS);
fimPrompts.setDefaultValue(1); fimPrompts.setDefaultValue(0);
fimPrompts.setDisplayStyle(Utils::SelectionAspect::DisplayStyle::ComboBox); fimPrompts.setDisplayStyle(Utils::SelectionAspect::DisplayStyle::ComboBox);
readFullFile.setSettingsKey(Constants::READ_FULL_FILE); readFullFile.setSettingsKey(Constants::READ_FULL_FILE);
@ -115,7 +111,7 @@ QodeAssistSettings::QodeAssistSettings()
maxTokens.setSettingsKey(Constants::MAX_TOKENS); maxTokens.setSettingsKey(Constants::MAX_TOKENS);
maxTokens.setLabelText(Tr::tr("Max Tokens")); maxTokens.setLabelText(Tr::tr("Max Tokens"));
maxTokens.setRange(-1, 10000); maxTokens.setRange(-1, 10000);
maxTokens.setDefaultValue(250); maxTokens.setDefaultValue(150);
useTopP.setSettingsKey(Constants::USE_TOP_P); useTopP.setSettingsKey(Constants::USE_TOP_P);
useTopP.setDefaultValue(false); useTopP.setDefaultValue(false);
@ -149,14 +145,19 @@ QodeAssistSettings::QodeAssistSettings()
frequencyPenalty.setDefaultValue(0.0); frequencyPenalty.setDefaultValue(0.0);
frequencyPenalty.setRange(-2.0, 2.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.setSettingsKey(Constants::START_SUGGESTION_TIMER);
startSuggestionTimer.setLabelText(Tr::tr("Start Suggestion Timer:")); startSuggestionTimer.setLabelText(Tr::tr("Start Suggestion Timer:"));
startSuggestionTimer.setRange(10, 10000); startSuggestionTimer.setRange(10, 10000);
startSuggestionTimer.setDefaultValue(500); 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.setSettingsKey(Constants::SPECIFIC_INSTRUCTIONS);
specificInstractions.setDisplayStyle(Utils::StringAspect::TextEditDisplay); specificInstractions.setDisplayStyle(Utils::StringAspect::TextEditDisplay);
specificInstractions.setLabelText( specificInstractions.setLabelText(
@ -199,9 +200,9 @@ QodeAssistSettings::QodeAssistSettings()
frequencyPenalty.setEnabled(useFrequencyPenalty()); frequencyPenalty.setEnabled(useFrequencyPenalty());
readStringsAfterCursor.setEnabled(!readFullFile()); readStringsAfterCursor.setEnabled(!readFullFile());
readStringsBeforeCursor.setEnabled(!readFullFile()); readStringsBeforeCursor.setEnabled(!readFullFile());
specificInstractions.setEnabled(useSpecificInstructions());
PromptTemplateManager::instance().setCurrentTemplate(fimPrompts.stringValue()); PromptTemplateManager::instance().setCurrentTemplate(fimPrompts.stringValue());
LLMProvidersManager::instance().setCurrentProvider(llmProviders.stringValue()); LLMProvidersManager::instance().setCurrentProvider(llmProviders.stringValue());
updateProviderSettings();
setLoggingEnabled(enableLogging()); setLoggingEnabled(enableLogging());
@ -215,7 +216,7 @@ QodeAssistSettings::QodeAssistSettings()
enableLogging, enableLogging,
Row{Stretch{1}, resetToDefaults}}}}, Row{Stretch{1}, resetToDefaults}}}},
Group{title(Tr::tr("LLM Providers")), Group{title(Tr::tr("LLM Providers")),
Form{Column{llmProviders, Row{url, port, endPoint}, providerPaths}}}, Form{Column{llmProviders, Row{url, endPoint}}}},
Group{title(Tr::tr("LLM Model Settings")), Group{title(Tr::tr("LLM Model Settings")),
Form{Column{Row{selectModels, modelName}}}}, Form{Column{Row{selectModels, modelName}}}},
Group{title(Tr::tr("FIM Prompt Settings")), Group{title(Tr::tr("FIM Prompt Settings")),
@ -226,6 +227,8 @@ QodeAssistSettings::QodeAssistSettings()
readStringsAfterCursor, readStringsAfterCursor,
ollamaLivetime, ollamaLivetime,
apiKey, apiKey,
useFilePathInContext,
useSpecificInstructions,
specificInstractions, specificInstractions,
temperature, temperature,
maxTokens, maxTokens,
@ -279,6 +282,9 @@ void QodeAssistSettings::setupConnections()
connect(&enableLogging, &Utils::BoolAspect::volatileValueChanged, this, [this]() { connect(&enableLogging, &Utils::BoolAspect::volatileValueChanged, this, [this]() {
setLoggingEnabled(enableLogging.volatileValue()); setLoggingEnabled(enableLogging.volatileValue());
}); });
connect(&useSpecificInstructions, &Utils::BoolAspect::volatileValueChanged, this, [this]() {
specificInstractions.setEnabled(useSpecificInstructions.volatileValue());
});
} }
void QodeAssistSettings::updateProviderSettings() void QodeAssistSettings::updateProviderSettings()
@ -288,7 +294,6 @@ void QodeAssistSettings::updateProviderSettings()
if (provider) { if (provider) {
logMessage(QString("currentProvider %1").arg(provider->name())); logMessage(QString("currentProvider %1").arg(provider->name()));
url.setValue(provider->url()); url.setValue(provider->url());
port.setValue(provider->defaultPort());
endPoint.setValue(provider->completionEndpoint()); endPoint.setValue(provider->completionEndpoint());
ollamaLivetime.setEnabled(provider->name() == "Ollama"); ollamaLivetime.setEnabled(provider->name() == "Ollama");
} }
@ -298,7 +303,7 @@ QStringList QodeAssistSettings::getInstalledModels()
{ {
auto *provider = LLMProvidersManager::instance().getCurrentProvider(); auto *provider = LLMProvidersManager::instance().getCurrentProvider();
if (provider) { if (provider) {
auto env = getEnvironmentWithProviderPaths(); Utils::Environment env = Utils::Environment::systemEnvironment();
return provider->getInstalledModels(env); return provider->getInstalledModels(env);
} }
return {}; return {};
@ -323,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() void QodeAssistSettings::resetSettingsToDefaults()
{ {
QMessageBox::StandardButton reply; QMessageBox::StandardButton reply;
@ -347,7 +342,6 @@ void QodeAssistSettings::resetSettingsToDefaults()
resetAspect(enableAutoComplete); resetAspect(enableAutoComplete);
resetAspect(llmProviders); resetAspect(llmProviders);
resetAspect(url); resetAspect(url);
resetAspect(port);
resetAspect(endPoint); resetAspect(endPoint);
resetAspect(modelName); resetAspect(modelName);
resetAspect(fimPrompts); resetAspect(fimPrompts);
@ -369,6 +363,12 @@ void QodeAssistSettings::resetSettingsToDefaults()
resetAspect(enableLogging); resetAspect(enableLogging);
resetAspect(ollamaLivetime); resetAspect(ollamaLivetime);
resetAspect(specificInstractions); resetAspect(specificInstractions);
resetAspect(multiLineCompletion);
resetAspect(useFilePathInContext);
resetAspect(useSpecificInstructions);
fimPrompts.setStringValue("StarCoder2");
llmProviders.setStringValue("Ollama");
updateProviderSettings(); updateProviderSettings();
apply(); apply();

View File

@ -63,7 +63,6 @@ public:
Utils::SelectionAspect llmProviders{this}; Utils::SelectionAspect llmProviders{this};
Utils::StringAspect url{this}; Utils::StringAspect url{this};
Utils::IntegerAspect port{this};
Utils::StringAspect endPoint{this}; Utils::StringAspect endPoint{this};
Utils::StringAspect modelName{this}; Utils::StringAspect modelName{this};
@ -89,13 +88,13 @@ public:
Utils::BoolAspect useFrequencyPenalty{this}; Utils::BoolAspect useFrequencyPenalty{this};
Utils::DoubleAspect frequencyPenalty{this}; Utils::DoubleAspect frequencyPenalty{this};
Utils::StringListAspect providerPaths{this};
Utils::IntegerAspect startSuggestionTimer{this}; Utils::IntegerAspect startSuggestionTimer{this};
Utils::IntegerAspect maxFileThreshold{this}; Utils::IntegerAspect maxFileThreshold{this};
Utils::StringAspect ollamaLivetime{this}; Utils::StringAspect ollamaLivetime{this};
Utils::StringAspect specificInstractions{this}; Utils::StringAspect specificInstractions{this};
Utils::BoolAspect useSpecificInstructions{this};
Utils::BoolAspect useFilePathInContext{this};
Utils::BoolAspect multiLineCompletion{this}; Utils::BoolAspect multiLineCompletion{this};
Utils::StringAspect apiKey{this}; Utils::StringAspect apiKey{this};

View File

@ -7,13 +7,15 @@ QodeAssist is an AI-powered coding assistant plugin for Qt Creator. It provides
QodeAssist currently supports the following LLM (Large Language Model) providers: QodeAssist currently supports the following LLM (Large Language Model) providers:
- [Ollama](https://ollama.com) - [Ollama](https://ollama.com)
- [LM Studio](https://lmstudio.ai) - [LM Studio](https://lmstudio.ai)
- OpenAI compatible providers
## Supported Models ## Supported Models
QodeAssist has been tested with the following language models: QodeAssist has been tested with the following language models, all trained for Fill-in-theMiddle:
Ollama: Ollama:
- [starcoder2](https://ollama.com/library/starcoder2) - [starcoder2](https://ollama.com/library/starcoder2)
- [codellama](https://ollama.com/library/codellama) - [codellama](https://ollama.com/library/codellama)
- DeepSeek-Coder-V2-Lite-Base
LM studio: LM studio:
- [second-state/StarCoder2-7B-GGUF](https://huggingface.co/second-state/StarCoder2-7B-GGUF) - [second-state/StarCoder2-7B-GGUF](https://huggingface.co/second-state/StarCoder2-7B-GGUF)
@ -29,7 +31,7 @@ If you've successfully used a model that's not listed here, please let us know b
- [ ] Add chat functionality - [ ] Add chat functionality
- [ ] Support for more providers and models - [ ] Support for more providers and models
## Installation Plugin ## Plugin installation using Ollama as an example
1. Install QtCreator 14.0 1. Install QtCreator 14.0
2. Install [Ollama](https://ollama.com). Make sure to review the system requirements before installation. 2. Install [Ollama](https://ollama.com). Make sure to review the system requirements before installation.
@ -49,14 +51,50 @@ ollama run starcoder2:7b
1. Open Qt Creator settings 1. Open Qt Creator settings
2. Navigate to the "Qode Assist" tab 2. Navigate to the "Qode Assist" tab
3. Choose your LLM provider (e.g., Ollama) 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 by the "Select Model" button
4. Select the installed model - For LM Studio you will see current load 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
5. Choose the prompt template that corresponds to your model 5. Choose the prompt template that corresponds to your model
6. Apply the settings 6. Apply the settings
You're all set! QodeAssist is now ready to use in Qt Creator. 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 ## Support the development of QodeAssist
If you find QodeAssist helpful, there are several ways you can support the project: If you find QodeAssist helpful, there are several ways you can support the project:

View File

@ -34,7 +34,6 @@ public:
virtual QString name() const = 0; virtual QString name() const = 0;
virtual QString url() const = 0; virtual QString url() const = 0;
virtual int defaultPort() const = 0;
virtual QString completionEndpoint() const = 0; virtual QString completionEndpoint() const = 0;
virtual void prepareRequest(QJsonObject &request) = 0; virtual void prepareRequest(QJsonObject &request) = 0;

View File

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

View File

@ -30,7 +30,6 @@ public:
QString name() const override; QString name() const override;
QString url() const override; QString url() const override;
int defaultPort() const override;
QString completionEndpoint() const override; QString completionEndpoint() const override;
void prepareRequest(QJsonObject &request) override; void prepareRequest(QJsonObject &request) override;
bool handleResponse(QNetworkReply *reply, QString &accumulatedResponse) override; bool handleResponse(QNetworkReply *reply, QString &accumulatedResponse) override;

View File

@ -23,10 +23,11 @@
#include <QJsonDocument> #include <QJsonDocument>
#include <QJsonObject> #include <QJsonObject>
#include <QNetworkReply> #include <QNetworkReply>
#include <QProcess> #include <QtCore/qeventloop.h>
#include "PromptTemplateManager.hpp" #include "PromptTemplateManager.hpp"
#include "QodeAssistSettings.hpp" #include "QodeAssistSettings.hpp"
#include "QodeAssistUtils.hpp"
namespace QodeAssist::Providers { namespace QodeAssist::Providers {
@ -39,12 +40,7 @@ QString OllamaProvider::name() const
QString OllamaProvider::url() const QString OllamaProvider::url() const
{ {
return "http://localhost"; return "http://localhost:11434";
}
int OllamaProvider::defaultPort() const
{
return 11434;
} }
QString OllamaProvider::completionEndpoint() const QString OllamaProvider::completionEndpoint() const
@ -101,50 +97,31 @@ bool OllamaProvider::handleResponse(QNetworkReply *reply, QString &accumulatedRe
QList<QString> OllamaProvider::getInstalledModels(const Utils::Environment &env) QList<QString> OllamaProvider::getInstalledModels(const Utils::Environment &env)
{ {
QProcess process; QList<QString> models;
process.setEnvironment(env.toStringList()); QNetworkAccessManager manager;
QString ollamaConsoleName; QNetworkRequest request(QUrl(url() + "/api/tags"));
#ifdef Q_OS_WIN QNetworkReply *reply = manager.get(request);
ollamaConsoleName = "ollama.exe";
#else
ollamaConsoleName = "ollama";
#endif
auto ollamaPath = env.searchInPath(ollamaConsoleName).toString(); QEventLoop loop;
QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
loop.exec();
if (!QFileInfo::exists(ollamaPath)) { if (reply->error() == QNetworkReply::NoError) {
qWarning() << "Ollama executable not found at" << ollamaPath; QByteArray responseData = reply->readAll();
return {}; QJsonDocument jsonResponse = QJsonDocument::fromJson(responseData);
} QJsonObject jsonObject = jsonResponse.object();
QJsonArray modelArray = jsonObject["models"].toArray();
process.start(ollamaPath, QStringList() << "list"); for (const QJsonValue &value : modelArray) {
if (!process.waitForStarted()) { QJsonObject modelObject = value.toObject();
qWarning() << "Failed to start Ollama process:" << process.errorString(); QString modelName = modelObject["name"].toString();
return {}; models.append(modelName);
}
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);
}
} }
} else { } else {
qWarning() << "Error running 'ollama list':" << process.errorString(); logMessage(QString("Error fetching models: %1").arg(reply->errorString()));
} }
reply->deleteLater();
return models; return models;
} }

View File

@ -30,7 +30,6 @@ public:
QString name() const override; QString name() const override;
QString url() const override; QString url() const override;
int defaultPort() const override;
QString completionEndpoint() const override; QString completionEndpoint() const override;
void prepareRequest(QJsonObject &request) override; void prepareRequest(QJsonObject &request) override;
bool handleResponse(QNetworkReply *reply, QString &accumulatedResponse) override; bool handleResponse(QNetworkReply *reply, QString &accumulatedResponse) override;

View File

@ -17,13 +17,12 @@
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>. * along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
*/ */
#include "OpenAICompatProvider.h" #include "OpenAICompatProvider.hpp"
#include <QJsonArray> #include <QJsonArray>
#include <QJsonDocument> #include <QJsonDocument>
#include <QJsonObject> #include <QJsonObject>
#include <QNetworkReply> #include <QNetworkReply>
#include <QProcess>
#include "PromptTemplateManager.hpp" #include "PromptTemplateManager.hpp"
#include "QodeAssistSettings.hpp" #include "QodeAssistSettings.hpp"
@ -39,12 +38,7 @@ QString OpenAICompatProvider::name() const
QString OpenAICompatProvider::url() const QString OpenAICompatProvider::url() const
{ {
return "http://localhost"; return "http://localhost:1234";
}
int OpenAICompatProvider::defaultPort() const
{
return 1234;
} }
QString OpenAICompatProvider::completionEndpoint() const QString OpenAICompatProvider::completionEndpoint() const

View File

@ -30,7 +30,6 @@ public:
QString name() const override; QString name() const override;
QString url() const override; QString url() const override;
int defaultPort() const override;
QString completionEndpoint() const override; QString completionEndpoint() const override;
void prepareRequest(QJsonObject &request) override; void prepareRequest(QJsonObject &request) override;
bool handleResponse(QNetworkReply *reply, QString &accumulatedResponse) override; bool handleResponse(QNetworkReply *reply, QString &accumulatedResponse) override;

View File

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

View File

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

View File

@ -27,16 +27,16 @@ class CodeQwenChatTemplate : public PromptTemplate
{ {
public: public:
QString name() const override { return "CodeQwenChat (experimental)"; } 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 QStringList stopWords() const override
{ {
return QStringList() << "### Instruction:" << "### Response:" << "\n\n### "; return QStringList() << "### Instruction:" << "### Response:" << "\n\n### ";
} }
void prepareRequest(QJsonObject &request, void prepareRequest(QJsonObject &request, const ContextData &context) const override
const QString &prefix,
const QString &suffix) const override
{ {
QString formattedPrompt = promptTemplate().arg(prefix, suffix); QString formattedPrompt = promptTemplate().arg(context.instriuctions,
context.prefix,
context.suffix);
request["prompt"] = formattedPrompt; 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 <QList>
#include <QString> #include <QString>
#include "QodeAssistData.hpp"
namespace QodeAssist::Templates { namespace QodeAssist::Templates {
class PromptTemplate class PromptTemplate
@ -32,9 +34,6 @@ public:
virtual QString name() const = 0; virtual QString name() const = 0;
virtual QString promptTemplate() const = 0; virtual QString promptTemplate() const = 0;
virtual QStringList stopWords() const = 0; virtual QStringList stopWords() const = 0;
virtual void prepareRequest(QJsonObject &request, virtual void prepareRequest(QJsonObject &request, const ContextData &context) const = 0;
const QString &prefix,
const QString &suffix) const
= 0;
}; };
} // namespace QodeAssist::Templates } // namespace QodeAssist::Templates

View File

@ -27,17 +27,17 @@ class StarCoder2Template : public PromptTemplate
{ {
public: public:
QString name() const override { return "StarCoder2"; } 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 QStringList stopWords() const override
{ {
return QStringList() << "<|endoftext|>" << "<file_sep>" << "<fim_prefix>" << "<fim_suffix>" return QStringList() << "<|endoftext|>" << "<file_sep>" << "<fim_prefix>" << "<fim_suffix>"
<< "<fim_middle>"; << "<fim_middle>";
} }
void prepareRequest(QJsonObject &request, void prepareRequest(QJsonObject &request, const ContextData &context) const override
const QString &prefix,
const QString &suffix) const override
{ {
QString formattedPrompt = promptTemplate().arg(prefix, suffix); QString formattedPrompt = promptTemplate().arg(context.instriuctions,
context.prefix,
context.suffix);
request["prompt"] = formattedPrompt; 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 * 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 * QodeAssist is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
@ -24,24 +19,30 @@
#pragma once #pragma once
#include <QTextBlock> #include <QLabel>
#include <texteditor/basehoverhandler.h> #include <QTimer>
#include <QToolBar>
#include <QWidget>
namespace QodeAssist { namespace QodeAssist {
class QodeAssistHoverHandler : public TextEditor::BaseHoverHandler class CounterTooltip : public QToolBar
{ {
public: Q_OBJECT
QodeAssistHoverHandler() = default;
protected: public:
void identifyMatch(TextEditor::TextEditorWidget *editorWidget, CounterTooltip(int count);
int pos, ~CounterTooltip();
ReportPriority report) final;
void operateTooltip(TextEditor::TextEditorWidget *editorWidget, const QPoint &point) final; signals:
void finished(int count);
private: private:
QTextBlock m_block; void updateLabel();
QLabel *m_label;
QTimer *m_timer;
int m_count;
}; };
} // namespace QodeAssist } // namespace QodeAssist