Compare commits

...

77 Commits

Author SHA1 Message Date
40a568ebd9 Version 0.3.3
- Add streaming response to chat
- Add stopping chat request and button
2024-10-17 00:10:47 +02:00
5b43eb4fd2 Increase default max tokens for chat before deleting first messages 2024-10-17 00:05:31 +02:00
9c2516cd4c Add stopping chat requests and button 2024-10-17 00:03:12 +02:00
2257e6e45f Fix unbehavior settings of provider and template 2024-10-17 00:02:14 +02:00
80eda8c167 Add stream text to chat 2024-10-16 22:51:34 +02:00
3db2691114 Upgrade version 2024-10-16 22:44:51 +02:00
bf518b4a01 Version 0.3.2
Add StarCoder2 instruct support
2024-10-16 10:50:19 +02:00
46829720d8 Add StarCoder2 instruct support 2024-10-16 10:45:48 +02:00
9158a3ac0d Add Llama support to README.md 2024-10-14 21:52:36 +02:00
d6e02d9d2a Version 0.3.1
Improve chat text input
Add Llama chat support
Fix monospace font
2024-10-14 21:41:04 +02:00
9c8cac4e3a Upgrade to version 0.3.1 2024-10-14 21:35:49 +02:00
965af4a945 Add Llama chat support 2024-10-14 21:35:17 +02:00
95f29fefc7 Fix monospace font 2024-10-14 21:25:18 +02:00
1dd50b6c83 Replace textinput to textfield 2024-10-14 21:18:48 +02:00
146e772514 Update README.md 2024-10-14 01:38:50 +02:00
4b851f1662 Update README.md for 0.3.0 2024-10-14 01:36:48 +02:00
6fea300825 Version 0.3.0
- new QML chat in outpane and in side panels
- qwen chat model template
- long long list of fixes
2024-10-14 00:27:43 +02:00
14bf0e6c94 Upgrade plugin version to 0.3.0 2024-10-14 00:22:24 +02:00
0c045e65df Upgrade to version 0.3.0
new QML Chat
Qwen chat model
2024-10-14 00:10:24 +02:00
15138b4644 Update FUNDING.yml 2024-10-13 14:00:18 +02:00
5c98de7440 Add components for chat 2024-10-07 00:53:35 +02:00
b808d0ec10 Remove temporary code 2024-10-07 00:49:49 +02:00
30fcd7e019 Add ChatRootView QML item 2024-10-06 18:47:10 +02:00
7442256bab Add ChatModel 2024-10-06 18:19:12 +02:00
8be279a5fd Add base qml chat view to navigation panel 2024-10-06 17:54:00 +02:00
d77e13cddb Change to scope pointer 2024-10-06 16:03:50 +02:00
162c068431 Upgrade version to 0.2.4 2024-10-05 20:50:15 +02:00
4e8ff55355 Add QtCreator Version Compatibility to README 2024-10-03 17:36:45 +02:00
8df21e96bd Add QtCreator Version Compatibility to README 2024-10-03 17:28:58 +02:00
57bec94ee4 Version 0.2.3, Update to QtCreator 14.0.2 2024-10-03 16:44:22 +02:00
1760a2d5ff Upgrade QtCreator to 14.0.2
Add no print support

Test disabling qt6 print component

Clean unnecessary code

Add library
2024-10-03 15:30:16 +02:00
1649a246e1 Version 0.2.2
- Improve chat history
- Fix system prompt
- Fix ollama model livetime
- Fix providers and prompt default value
2024-10-02 22:32:59 +02:00
0ab4b51520 Upgrade plugin version 2024-10-02 22:28:52 +02:00
d235d0fcdf Fix providers and prompt default value 2024-10-02 22:25:53 +02:00
1cbde3d55b Fix ollama model livetime 2024-10-02 21:44:32 +02:00
9903ac8f7b Fix system prompt for FIM 2024-10-02 21:44:15 +02:00
fbe363689f Change message history 2024-10-01 00:26:28 +02:00
d7f0cc92e6 Version 0.2.1
- Fix getting models list from providers
- Fix clearing message history in chat
- Remove unnecessary logs
2024-09-24 09:31:40 +02:00
8311db5b08 Removed unessary logs 2024-09-24 09:28:53 +02:00
cd6dd94cd2 Upgrade version to 0.2.1 2024-09-24 09:20:26 +02:00
b559a17407 Fix getting models list 2024-09-24 08:59:26 +02:00
9745997952 Fix clear message history 2024-09-23 23:54:36 +02:00
aac58d6933 Version 0.2.0
- Separate handling messages from LSP and LLM providers
- Chat basic components and settings
- Templates for chat
2024-09-23 01:56:27 +02:00
93c69df1b9 Fix typo 2024-09-23 00:19:38 +02:00
4b0b589d0e Upgrade version 2024-09-23 00:12:25 +02:00
04c44f5916 Add basic chat widgets and functionality 2024-09-23 00:10:23 +02:00
6e56646b4c Version 0.1.3
Remove unnecessary log
Improve settings
2024-09-15 01:33:48 +02:00
c89fe1451b Improve settings accessibility 2024-09-15 01:20:26 +02:00
938636ab48 Remove unnecessary log 2024-09-15 01:15:53 +02:00
e3a2b5a64c Version 0.1.2
- Added sending cache of latest changes to model
- Changed default value of suggestion trigger
2024-09-11 02:16:29 +02:00
0ae12e0fc6 Update README 2024-09-11 02:01:00 +02:00
793b855819 Change default value for start ai suggestion 2024-09-11 01:59:45 +02:00
8e052ff45c Add settings for cache of changes 2024-09-11 01:59:25 +02:00
2fb876ff00 Exclude current file cache from request 2024-09-11 01:37:57 +02:00
cd1a9e16e0 Add basic cache manager 2024-09-11 01:25:00 +02:00
f07610df5c Upgrade version to 0.1.2 2024-09-11 01:24:22 +02:00
7cd35082b2 Add using custom template to README.md 2024-09-10 17:41:24 +02:00
c1dd59e65c Add gifs to README.md 2024-09-08 15:34:20 +02:00
faaf59f163 Version 0.1.1
Fixed settings layouts
2024-09-08 14:39:08 +02:00
539a220771 Fix settings layouts 2024-09-08 14:31:52 +02:00
397dd33a96 Version 0.1.0
## What's New
- Reworked plugin settings to multipage settings
- Added creating, saving and loading custom templates in json format
- Added smart trigger for call suggestion after input by default 2 characters
2024-09-08 03:03:07 +02:00
9361c27d6e Add smart trigger for call suggestion 2024-09-08 02:51:56 +02:00
15af137728 Add examples of customs request 2024-09-08 02:25:21 +02:00
24ad5fd015 Fix saving template after closing qtc 2024-09-08 01:47:17 +02:00
216c28aa5e Fix names, icon and tips 2024-09-08 01:42:23 +02:00
f64ea42071 Remove origin qodesettings 2024-09-08 01:07:39 +02:00
314ba06db1 Move custom promt to settings page 2024-09-08 00:59:44 +02:00
384e07ba62 Move suggestionTimer to general settings 2024-09-08 00:18:23 +02:00
4d06541a36 Move preset params to separate settings page 2024-09-08 00:13:48 +02:00
356f28a97b Move context settings to page 2024-09-07 23:51:23 +02:00
b5ca11ed38 Move settings to General Page 2024-09-07 23:10:11 +02:00
d49cd07dd0 Add empty pages for future settings 2024-09-07 20:11:54 +02:00
8e61651bae Add button for save and load Custom Template 2024-09-06 09:27:10 +02:00
1e0063a7bb Add custom template prompt 2024-09-06 01:07:28 +02:00
44add994b9 Optimize string 2024-09-05 22:18:57 +02:00
99136d7b76 Add supports way to README.md 2024-09-04 13:38:37 +02:00
da7abbd9be Create FUNDING.yml
Add ko-fi button
2024-09-04 13:29:18 +02:00
82 changed files with 4615 additions and 1124 deletions

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

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

View File

@ -4,8 +4,8 @@ on: [push]
env:
PLUGIN_NAME: QodeAssist
QT_VERSION: 6.7.2
QT_CREATOR_VERSION: 14.0.0
QT_VERSION: 6.7.3
QT_CREATOR_VERSION: 14.0.2
QT_CREATOR_SNAPSHOT: NO
MACOS_DEPLOYMENT_TARGET: "11.0"
CMAKE_VERSION: "3.29.6"
@ -100,7 +100,7 @@ jobs:
COMMAND sudo apt update
)
execute_process(
COMMAND sudo apt install libgl1-mesa-dev
COMMAND sudo apt install libgl1-mesa-dev libcups2-dev
RESULT_VARIABLE result
)
if (NOT result EQUAL 0)

View File

@ -10,7 +10,12 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
find_package(QtCreator REQUIRED COMPONENTS Core)
find_package(Qt6 COMPONENTS Widgets REQUIRED)
find_package(Qt6 COMPONENTS Core Gui Widgets Network REQUIRED)
add_subdirectory(llmcore)
add_subdirectory(settings)
add_subdirectory(logger)
add_subdirectory(chatview)
add_qtc_plugin(QodeAssist
PLUGIN_DEPENDS
@ -30,24 +35,26 @@ add_qtc_plugin(QodeAssist
QodeAssistConstants.hpp
QodeAssisttr.h
LLMClientInterface.hpp LLMClientInterface.cpp
PromptTemplateManager.hpp PromptTemplateManager.cpp
templates/PromptTemplate.hpp
templates/CodeLLamaTemplate.hpp
templates/StarCoder2Template.hpp
templates/CodeQwenChat.hpp
templates/DeepSeekCoderV2.hpp
providers/LLMProvider.hpp
templates/CodeLlamaFim.hpp
templates/StarCoder2Fim.hpp
templates/DeepSeekCoderFim.hpp
templates/CustomFimTemplate.hpp
templates/DeepSeekCoderChat.hpp
templates/CodeLlamaChat.hpp
templates/QwenChat.hpp
templates/StarCoderChat.hpp
providers/OllamaProvider.hpp providers/OllamaProvider.cpp
providers/LMStudioProvider.hpp providers/LMStudioProvider.cpp
providers/OpenAICompatProvider.hpp providers/OpenAICompatProvider.cpp
LLMProvidersManager.hpp LLMProvidersManager.cpp
QodeAssistSettings.hpp QodeAssistSettings.cpp
QodeAssist.qrc
LSPCompletion.hpp
LLMSuggestion.hpp LLMSuggestion.cpp
QodeAssistClient.hpp QodeAssistClient.cpp
QodeAssistUtils.hpp
DocumentContextReader.hpp DocumentContextReader.cpp
QodeAssistData.hpp
utils/CounterTooltip.hpp utils/CounterTooltip.cpp
core/ChangesManager.h core/ChangesManager.cpp
chat/ChatOutputPane.h chat/ChatOutputPane.cpp
chat/NavigationPanel.hpp chat/NavigationPanel.cpp
)
target_link_libraries(QodeAssist PRIVATE QodeAssistChatViewplugin)

View File

@ -23,7 +23,8 @@
#include <QTextBlock>
#include <languageserverprotocol/lsptypes.h>
#include "QodeAssistSettings.hpp"
#include "core/ChangesManager.h"
#include "settings/ContextSettings.hpp"
const QRegularExpression &getYearRegex()
{
@ -134,13 +135,6 @@ QString DocumentContextReader::getLanguageAndFileInfo() const
.arg(language, mimeType, filePath, fileExtension);
}
QString DocumentContextReader::getSpecificInstructions() const
{
QString specificInstruction = settings().specificInstractions().arg(
LanguageServerProtocol::TextDocumentItem::mimeTypeToLanguageId(m_textDocument->mimeType()));
return QString("%1").arg(specificInstruction);
}
CopyrightInfo DocumentContextReader::findCopyright()
{
CopyrightInfo result = {-1, -1, false};
@ -209,4 +203,53 @@ CopyrightInfo DocumentContextReader::copyrightInfo() const
return m_copyrightInfo;
}
LLMCore::ContextData DocumentContextReader::prepareContext(int lineNumber, int cursorPosition) const
{
QString contextBefore = getContextBefore(lineNumber, cursorPosition);
QString contextAfter = getContextAfter(lineNumber, cursorPosition);
QString instructions = getInstructions();
return {contextBefore, contextAfter, instructions};
}
QString DocumentContextReader::getContextBefore(int lineNumber, int cursorPosition) const
{
if (Settings::contextSettings().readFullFile()) {
return readWholeFileBefore(lineNumber, cursorPosition);
} else {
int effectiveStartLine;
int beforeCursor = Settings::contextSettings().readStringsBeforeCursor();
if (m_copyrightInfo.found) {
effectiveStartLine = qMax(m_copyrightInfo.endLine + 1, lineNumber - beforeCursor);
} else {
effectiveStartLine = qMax(0, lineNumber - beforeCursor);
}
return getContextBetween(effectiveStartLine, lineNumber, cursorPosition);
}
}
QString DocumentContextReader::getContextAfter(int lineNumber, int cursorPosition) const
{
if (Settings::contextSettings().readFullFile()) {
return readWholeFileAfter(lineNumber, cursorPosition);
} else {
int endLine = qMin(m_document->blockCount() - 1,
lineNumber + Settings::contextSettings().readStringsAfterCursor());
return getContextBetween(lineNumber + 1, endLine, -1);
}
}
QString DocumentContextReader::getInstructions() const
{
QString instructions;
if (Settings::contextSettings().useFilePathInContext())
instructions += getLanguageAndFileInfo();
if (Settings::contextSettings().useProjectChangesCache())
instructions += ChangesManager::instance().getRecentChangesContext(m_textDocument);
return instructions;
}
} // namespace QodeAssist

View File

@ -22,6 +22,8 @@
#include <QTextDocument>
#include <texteditor/textdocument.h>
#include <llmcore/ContextData.hpp>
namespace QodeAssist {
struct CopyrightInfo
@ -42,12 +44,18 @@ public:
QString readWholeFileBefore(int lineNumber, int cursorPosition) const;
QString readWholeFileAfter(int lineNumber, int cursorPosition) const;
QString getLanguageAndFileInfo() const;
QString getSpecificInstructions() const;
CopyrightInfo findCopyright();
QString getContextBetween(int startLine, int endLine, int cursorPosition) const;
CopyrightInfo copyrightInfo() const;
LLMCore::ContextData prepareContext(int lineNumber, int cursorPosition) const;
private:
QString getContextBefore(int lineNumber, int cursorPosition) const;
QString getContextAfter(int lineNumber, int cursorPosition) const;
QString getInstructions() const;
private:
TextEditor::TextDocument *m_textDocument;
QTextDocument *m_document;

View File

@ -23,20 +23,25 @@
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <llmcore/RequestConfig.hpp>
#include <texteditor/textdocument.h>
#include "DocumentContextReader.hpp"
#include "LLMProvidersManager.hpp"
#include "PromptTemplateManager.hpp"
#include "QodeAssistSettings.hpp"
#include "QodeAssistUtils.hpp"
#include "logger/Logger.hpp"
#include "llmcore/PromptTemplateManager.hpp"
#include "llmcore/ProvidersManager.hpp"
#include "settings/ContextSettings.hpp"
#include "settings/GeneralSettings.hpp"
namespace QodeAssist {
LLMClientInterface::LLMClientInterface()
: m_manager(new QNetworkAccessManager(this))
: m_requestHandler(this)
{
updateProvider();
connect(&m_requestHandler,
&LLMCore::RequestHandler::completionReceived,
this,
&LLMClientInterface::sendCompletionToClient);
}
Utils::FilePath LLMClientInterface::serverDeviceTemplate() const
@ -51,8 +56,6 @@ void LLMClientInterface::startImpl()
void LLMClientInterface::sendData(const QByteArray &data)
{
updateProvider();
QJsonDocument doc = QJsonDocument::fromJson(data);
if (!doc.isObject())
return;
@ -77,93 +80,20 @@ void LLMClientInterface::sendData(const QByteArray &data)
} else if (method == "exit") {
// TODO make exit handler
} else {
logMessage(QString("Unknown method: %1").arg(method));
LOG_MESSAGE(QString("Unknown method: %1").arg(method));
}
}
void LLMClientInterface::handleCancelRequest(const QJsonObject &request)
{
QString id = request["params"].toObject()["id"].toString();
if (m_activeRequests.contains(id)) {
m_activeRequests[id]->abort();
m_activeRequests.remove(id);
logMessage(QString("Request %1 cancelled successfully").arg(id));
if (m_requestHandler.cancelRequest(id)) {
LOG_MESSAGE(QString("Request %1 cancelled successfully").arg(id));
} else {
logMessage(QString("Request %1 not found").arg(id));
LOG_MESSAGE(QString("Request %1 not found").arg(id));
}
}
bool LLMClientInterface::processSingleLineCompletion(QNetworkReply *reply,
const QJsonObject &request,
const QString &accumulatedCompletion)
{
int newlinePos = accumulatedCompletion.indexOf('\n');
if (newlinePos != -1) {
QString singleLineCompletion = accumulatedCompletion.left(newlinePos).trimmed();
singleLineCompletion = removeStopWords(singleLineCompletion);
QJsonObject position = request["params"].toObject()["doc"].toObject()["position"].toObject();
sendCompletionToClient(singleLineCompletion, request, position, true);
m_accumulatedResponses.remove(reply);
reply->abort();
return true;
}
return false;
}
QString LLMClientInterface::сontextBefore(TextEditor::TextEditorWidget *widget,
int lineNumber,
int cursorPosition)
{
if (!widget)
return QString();
DocumentContextReader reader(widget->textDocument());
const auto &copyright = reader.copyrightInfo();
logMessage(QString{"Line Number: %1"}.arg(lineNumber));
logMessage(QString("Copyright found %1 %2").arg(copyright.found).arg(copyright.endLine));
if (lineNumber < reader.findCopyright().endLine)
return QString();
QString contextBefore;
if (settings().readFullFile()) {
contextBefore = reader.readWholeFileBefore(lineNumber, cursorPosition);
} else {
contextBefore = reader.getContextBefore(lineNumber,
cursorPosition,
settings().readStringsBeforeCursor());
}
return contextBefore;
}
QString LLMClientInterface::сontextAfter(TextEditor::TextEditorWidget *widget,
int lineNumber,
int cursorPosition)
{
if (!widget)
return QString();
DocumentContextReader reader(widget->textDocument());
if (lineNumber < reader.findCopyright().endLine)
return QString();
QString contextAfter;
if (settings().readFullFile()) {
contextAfter = reader.readWholeFileAfter(lineNumber, cursorPosition);
} else {
contextAfter = reader.getContextAfter(lineNumber,
cursorPosition,
settings().readStringsAfterCursor());
}
return contextAfter;
}
void LLMClientInterface::handleInitialize(const QJsonObject &request)
{
QJsonObject response;
@ -214,44 +144,34 @@ void LLMClientInterface::handleExit(const QJsonObject &request)
emit finished();
}
void LLMClientInterface::handleLLMResponse(QNetworkReply *reply, const QJsonObject &request)
void LLMClientInterface::handleCompletion(const QJsonObject &request)
{
QString &accumulatedResponse = m_accumulatedResponses[reply];
auto updatedContext = prepareContext(request);
auto &templateManager = PromptTemplateManager::instance();
const Templates::PromptTemplate *currentTemplate = templateManager.getCurrentTemplate();
LLMCore::LLMConfig config;
config.requestType = LLMCore::RequestType::Fim;
config.provider = LLMCore::ProvidersManager::instance().getCurrentFimProvider();
config.promptTemplate = LLMCore::PromptTemplateManager::instance().getCurrentFimTemplate();
config.url = QUrl(QString("%1%2").arg(Settings::generalSettings().url(),
Settings::generalSettings().endPoint()));
auto &providerManager = LLMProvidersManager::instance();
bool isComplete = providerManager.getCurrentProvider()->handleResponse(reply,
accumulatedResponse);
config.providerRequest = {{"model", Settings::generalSettings().modelName.value()},
{"stream", true},
{"stop",
QJsonArray::fromStringList(config.promptTemplate->stopWords())}};
config.multiLineCompletion = Settings::generalSettings().multiLineCompletion();
QJsonObject position = request["params"].toObject()["doc"].toObject()["position"].toObject();
if (Settings::contextSettings().useSystemPrompt())
config.providerRequest["system"] = Settings::contextSettings().systemPrompt();
if (!settings().multiLineCompletion()
&& processSingleLineCompletion(reply, request, accumulatedResponse)) {
return;
}
config.promptTemplate->prepareRequest(config.providerRequest, updatedContext);
config.provider->prepareRequest(config.providerRequest, LLMCore::RequestType::Fim);
if (isComplete || reply->isFinished()) {
if (isComplete) {
auto cleanedCompletion = removeStopWords(accumulatedResponse);
sendCompletionToClient(cleanedCompletion, request, position, true);
} else {
handleCompletion(request, accumulatedResponse);
}
m_accumulatedResponses.remove(reply);
}
m_requestHandler.sendLLMRequest(config, request);
}
void LLMClientInterface::handleCompletion(const QJsonObject &request,
const QString &accumulatedCompletion)
{
auto updatedContext = prepareContext(request, accumulatedCompletion);
sendLLMRequest(request, updatedContext);
}
ContextData LLMClientInterface::prepareContext(const QJsonObject &request,
const QString &accumulatedCompletion)
LLMCore::ContextData LLMClientInterface::prepareContext(const QJsonObject &request,
const QStringView &accumulatedCompletion)
{
QJsonObject params = request["params"].toObject();
QJsonObject doc = params["doc"].toObject();
@ -263,42 +183,23 @@ ContextData LLMClientInterface::prepareContext(const QJsonObject &request,
filePath);
if (!textDocument) {
logMessage("Error: Document is not available for" + filePath.toString());
return {"", ""};
LOG_MESSAGE("Error: Document is not available for" + filePath.toString());
return LLMCore::ContextData{};
}
int cursorPosition = position["character"].toInt();
int lineNumber = position["line"].toInt();
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, instructions};
}
void LLMClientInterface::updateProvider()
{
m_serverUrl = QUrl(QString("%1%2").arg(settings().url(), settings().endPoint()));
DocumentContextReader reader(textDocument);
return reader.prepareContext(lineNumber, cursorPosition);
}
void LLMClientInterface::sendCompletionToClient(const QString &completion,
const QJsonObject &request,
const QJsonObject &position,
bool isComplete)
{
QJsonObject position = request["params"].toObject()["doc"].toObject()["position"].toObject();
QJsonObject response;
response["jsonrpc"] = "2.0";
response[LanguageServerProtocol::idKey] = request["id"];
@ -318,11 +219,11 @@ void LLMClientInterface::sendCompletionToClient(const QString &completion,
result[LanguageServerProtocol::isIncompleteKey] = !isComplete;
response[LanguageServerProtocol::resultKey] = result;
logMessage(
LOG_MESSAGE(
QString("Completions: \n%1")
.arg(QString::fromUtf8(QJsonDocument(completions).toJson(QJsonDocument::Indented))));
logMessage(QString("Full response: \n%1")
LOG_MESSAGE(QString("Full response: \n%1")
.arg(QString::fromUtf8(QJsonDocument(response).toJson(QJsonDocument::Indented))));
QString requestId = request["id"].toString();
@ -330,68 +231,6 @@ void LLMClientInterface::sendCompletionToClient(const QString &completion,
emit messageReceived(LanguageServerProtocol::JsonRpcMessage(response));
}
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);
auto &providerManager = LLMProvidersManager::instance();
providerManager.getCurrentProvider()->prepareRequest(providerRequest);
logMessage(QString("Sending request to llm: \nurl: %1\nRequest body:\n%2")
.arg(m_serverUrl.toString(),
QString::fromUtf8(
QJsonDocument(providerRequest).toJson(QJsonDocument::Indented))));
QNetworkRequest networkRequest(m_serverUrl);
networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
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) {
logMessage("Error: Failed to create network reply");
return;
}
QString requestId = request["id"].toString();
m_activeRequests[requestId] = reply;
connect(reply, &QNetworkReply::readyRead, this, [this, reply, request]() {
handleLLMResponse(reply, request);
});
connect(reply, &QNetworkReply::finished, this, [this, reply, requestId]() {
reply->deleteLater();
m_activeRequests.remove(requestId);
if (reply->error() != QNetworkReply::NoError) {
logMessage(QString("Error in QodeAssist request: %1").arg(reply->errorString()));
} else {
logMessage("Request finished successfully");
}
});
}
QString LLMClientInterface::removeStopWords(const QString &completion)
{
QString filteredCompletion = completion;
auto currentTemplate = PromptTemplateManager::instance().getCurrentTemplate();
QStringList stopWords = currentTemplate->stopWords();
for (const QString &stopWord : stopWords) {
filteredCompletion = filteredCompletion.replace(stopWord, "");
}
return filteredCompletion;
}
void LLMClientInterface::startTimeMeasurement(const QString &requestId)
{
m_requestStartTimes[requestId] = QDateTime::currentMSecsSinceEpoch();
@ -412,7 +251,7 @@ void LLMClientInterface::logPerformance(const QString &requestId,
const QString &operation,
qint64 elapsedMs)
{
logMessage(QString("Performance: %1 %2 took %3 ms").arg(requestId, operation).arg(elapsedMs));
LOG_MESSAGE(QString("Performance: %1 %2 took %3 ms").arg(requestId, operation).arg(elapsedMs));
}
void LLMClientInterface::parseCurrentMessage() {}

View File

@ -22,7 +22,8 @@
#include <languageclient/languageclientinterface.h>
#include <texteditor/texteditor.h>
#include "QodeAssistData.hpp"
#include <llmcore/ContextData.hpp>
#include <llmcore/RequestHandler.hpp>
class QNetworkReply;
class QNetworkAccessManager;
@ -36,22 +37,13 @@ class LLMClientInterface : public LanguageClient::BaseClientInterface
public:
LLMClientInterface();
public:
Utils::FilePath serverDeviceTemplate() const override;
void sendCompletionToClient(const QString &completion,
const QJsonObject &request,
const QJsonObject &position,
bool isComplete);
void handleCompletion(const QJsonObject &request,
const QString &accumulatedCompletion = QString());
void sendLLMRequest(const QJsonObject &request, const ContextData &prompt);
void handleLLMResponse(QNetworkReply *reply, const QJsonObject &request);
ContextData prepareContext(const QJsonObject &request,
const QString &accumulatedCompletion = QString{});
void updateProvider();
void handleCompletion(const QJsonObject &request);
protected:
void startImpl() override;
@ -65,19 +57,11 @@ private:
void handleInitialized(const QJsonObject &request);
void handleExit(const QJsonObject &request);
void handleCancelRequest(const QJsonObject &request);
bool processSingleLineCompletion(QNetworkReply *reply,
const QJsonObject &request,
const QString &accumulatedCompletion);
QString сontextBefore(TextEditor::TextEditorWidget *widget, int lineNumber, int cursorPosition);
QString сontextAfter(TextEditor::TextEditorWidget *widget, int lineNumber, int cursorPosition);
QString removeStopWords(const QString &completion);
QUrl m_serverUrl;
QNetworkAccessManager *m_manager;
QMap<QString, QNetworkReply *> m_activeRequests;
QMap<QNetworkReply *, QString> m_accumulatedResponses;
LLMCore::ContextData prepareContext(const QJsonObject &request,
const QStringView &accumulatedCompletion = QString{});
LLMCore::RequestHandler m_requestHandler;
QElapsedTimer m_completionTimer;
QMap<QString, qint64> m_requestStartTimes;

View File

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

View File

@ -31,7 +31,9 @@
#include "LLMClientInterface.hpp"
#include "LLMSuggestion.hpp"
#include "QodeAssistSettings.hpp"
#include "core/ChangesManager.h"
#include "settings/ContextSettings.hpp"
#include "settings/GeneralSettings.hpp"
using namespace LanguageServerProtocol;
using namespace TextEditor;
@ -43,6 +45,7 @@ namespace QodeAssist {
QodeAssistClient::QodeAssistClient()
: LanguageClient::Client(new LLMClientInterface())
, m_recentCharCount(0)
{
setName("Qode Assist");
LanguageClient::LanguageFilter filter;
@ -51,6 +54,8 @@ QodeAssistClient::QodeAssistClient()
start();
setupConnections();
m_typingTimer.start();
}
QodeAssistClient::~QodeAssistClient()
@ -70,7 +75,7 @@ void QodeAssistClient::openDocument(TextEditor::TextDocument *document)
this,
[this, document](int position, int charsRemoved, int charsAdded) {
Q_UNUSED(charsRemoved)
if (!settings().enableAutoComplete())
if (!Settings::generalSettings().enableAutoComplete())
return;
auto project = ProjectManager::projectForFile(document->filePath());
@ -80,13 +85,31 @@ void QodeAssistClient::openDocument(TextEditor::TextDocument *document)
auto textEditor = BaseTextEditor::currentTextEditor();
if (!textEditor || textEditor->document() != document)
return;
if (Settings::contextSettings().useProjectChangesCache())
ChangesManager::instance().addChange(document,
position,
charsRemoved,
charsAdded);
TextEditorWidget *widget = textEditor->editorWidget();
if (widget->isReadOnly() || widget->multiTextCursor().hasMultipleCursors())
return;
const int cursorPosition = widget->textCursor().position();
if (cursorPosition < position || cursorPosition > position + charsAdded)
return;
scheduleRequest(widget);
m_recentCharCount += charsAdded;
if (m_typingTimer.elapsed()
> Settings::generalSettings().autoCompletionTypingInterval()) {
m_recentCharCount = charsAdded;
m_typingTimer.restart();
}
if (m_recentCharCount > Settings::generalSettings().autoCompletionCharThreshold()) {
scheduleRequest(widget);
}
});
}
@ -130,7 +153,8 @@ void QodeAssistClient::scheduleRequest(TextEditor::TextEditorWidget *editor)
connect(timer, &QTimer::timeout, this, [this, editor]() {
if (editor
&& editor->textCursor().position()
== m_scheduledRequests[editor]->property("cursorPosition").toInt())
== m_scheduledRequests[editor]->property("cursorPosition").toInt()
&& m_recentCharCount > Settings::generalSettings().autoCompletionCharThreshold())
requestCompletions(editor);
});
connect(editor, &TextEditorWidget::destroyed, this, [this, editor]() {
@ -144,9 +168,8 @@ void QodeAssistClient::scheduleRequest(TextEditor::TextEditorWidget *editor)
}
it.value()->setProperty("cursorPosition", editor->textCursor().position());
it.value()->start(settings().startSuggestionTimer());
it.value()->start(Settings::generalSettings().startSuggestionTimer());
}
void QodeAssistClient::handleCompletions(const GetCompletionRequest::Response &response,
TextEditor::TextEditorWidget *editor)
{
@ -204,7 +227,7 @@ void QodeAssistClient::cancelRunningRequest(TextEditor::TextEditorWidget *editor
bool QodeAssistClient::isEnabled(ProjectExplorer::Project *project) const
{
return settings().enableQodeAssist();
return Settings::generalSettings().enableQodeAssist();
}
void QodeAssistClient::setupConnections()

View File

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

View File

@ -24,43 +24,6 @@ namespace QodeAssist::Constants {
const char ACTION_ID[] = "QodeAssist.Action";
const char MENU_ID[] = "QodeAssist.Menu";
// settings
const char ENABLE_QODE_ASSIST[] = "QodeAssist.enableQodeAssist";
const char ENABLE_AUTO_COMPLETE[] = "QodeAssist.enableAutoComplete";
const char ENABLE_LOGGING[] = "QodeAssist.enableLogging";
const char LLM_PROVIDERS[] = "QodeAssist.llmProviders";
const char URL[] = "QodeAssist.url";
const char END_POINT[] = "QodeAssist.endPoint";
const char MODEL_NAME[] = "QodeAssist.modelName";
const char SELECT_MODELS[] = "QodeAssist.selectModels";
const char FIM_PROMPTS[] = "QodeAssist.fimPrompts";
const char TEMPERATURE[] = "QodeAssist.temperature";
const char MAX_TOKENS[] = "QodeAssist.maxTokens";
const char READ_FULL_FILE[] = "QodeAssist.readFullFile";
const char READ_STRINGS_BEFORE_CURSOR[] = "QodeAssist.readStringsBeforeCursor";
const char READ_STRINGS_AFTER_CURSOR[] = "QodeAssist.readStringsAfterCursor";
const char USE_TOP_P[] = "QodeAssist.useTopP";
const char TOP_P[] = "QodeAssist.topP";
const char USE_TOP_K[] = "QodeAssist.useTopK";
const char TOP_K[] = "QodeAssist.topK";
const char USE_PRESENCE_PENALTY[] = "QodeAssist.usePresencePenalty";
const char PRESENCE_PENALTY[] = "QodeAssist.presencePenalty";
const char USE_FREQUENCY_PENALTY[] = "QodeAssist.useFrequencyPenalty";
const char FREQUENCY_PENALTY[] = "QodeAssist.frequencyPenalty";
const char PROVIDER_PATHS[] = "QodeAssist.providerPaths";
const char START_SUGGESTION_TIMER[] = "QodeAssist.startSuggestionTimer";
const char MAX_FILE_THRESHOLD[] = "QodeAssist.maxFileThreshold";
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";
const char QODE_ASSIST_GENERAL_OPTIONS_DISPLAY_CATEGORY[] = "Qode Assist";
const char QODE_ASSIST_REQUEST_SUGGESTION[] = "QodeAssist.RequestSuggestion";
} // namespace QodeAssist::Constants

View File

@ -1,398 +0,0 @@
/*
* Copyright (C) 2024 Petr Mironychev
*
* This file is part of QodeAssist.
*
* QodeAssist is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* QodeAssist is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
*/
#include "QodeAssistSettings.hpp"
#include <QInputDialog>
#include <QtWidgets/qmessagebox.h>
#include <coreplugin/dialogs/ioptionspage.h>
#include <coreplugin/icore.h>
#include "QodeAssistConstants.hpp"
#include "QodeAssisttr.h"
#include "LLMProvidersManager.hpp"
#include "PromptTemplateManager.hpp"
#include "QodeAssistUtils.hpp"
namespace QodeAssist {
QodeAssistSettings &settings()
{
static QodeAssistSettings settings;
return settings;
}
QodeAssistSettings::QodeAssistSettings()
{
setAutoApply(false);
enableQodeAssist.setSettingsKey(Constants::ENABLE_QODE_ASSIST);
enableQodeAssist.setLabelText(Tr::tr("Enable Qode Assist"));
enableQodeAssist.setDefaultValue(true);
enableAutoComplete.setSettingsKey(Constants::ENABLE_AUTO_COMPLETE);
enableAutoComplete.setLabelText(Tr::tr("Enable Auto Complete"));
enableAutoComplete.setDefaultValue(true);
enableLogging.setSettingsKey(Constants::ENABLE_LOGGING);
enableLogging.setLabelText(Tr::tr("Enable Logging"));
enableLogging.setDefaultValue(false);
llmProviders.setSettingsKey(Constants::LLM_PROVIDERS);
llmProviders.setDisplayName(Tr::tr("LLM Providers:"));
llmProviders.setDisplayStyle(Utils::SelectionAspect::DisplayStyle::ComboBox);
llmProviders.setDefaultValue(0);
url.setSettingsKey(Constants::URL);
url.setLabelText(Tr::tr("URL:"));
url.setDisplayStyle(Utils::StringAspect::LineEditDisplay);
endPoint.setSettingsKey(Constants::END_POINT);
endPoint.setLabelText(Tr::tr("Endpoint:"));
endPoint.setDisplayStyle(Utils::StringAspect::LineEditDisplay);
modelName.setSettingsKey(Constants::MODEL_NAME);
modelName.setLabelText(Tr::tr("LLM Name:"));
modelName.setDisplayStyle(Utils::StringAspect::LineEditDisplay);
temperature.setSettingsKey(Constants::TEMPERATURE);
temperature.setLabelText(Tr::tr("Temperature:"));
temperature.setDefaultValue(0.2);
temperature.setRange(0.0, 10.0);
selectModels.m_buttonText = Tr::tr("Select Model");
ollamaLivetime.setSettingsKey(Constants::OLLAMA_LIVETIME);
ollamaLivetime.setLabelText(
Tr::tr("Time to suspend Ollama after completion request (in minutes), "
"Only Ollama, -1 to disable"));
ollamaLivetime.setDefaultValue("5m");
ollamaLivetime.setDisplayStyle(Utils::StringAspect::LineEditDisplay);
fimPrompts.setDisplayName(Tr::tr("Fill-In-Middle Prompt"));
fimPrompts.setSettingsKey(Constants::FIM_PROMPTS);
fimPrompts.setDefaultValue(0);
fimPrompts.setDisplayStyle(Utils::SelectionAspect::DisplayStyle::ComboBox);
readFullFile.setSettingsKey(Constants::READ_FULL_FILE);
readFullFile.setLabelText(Tr::tr("Read Full File"));
readFullFile.setDefaultValue(false);
maxFileThreshold.setSettingsKey(Constants::MAX_FILE_THRESHOLD);
maxFileThreshold.setLabelText(Tr::tr("Max File Threshold:"));
maxFileThreshold.setRange(10, 100000);
maxFileThreshold.setDefaultValue(600);
readStringsBeforeCursor.setSettingsKey(Constants::READ_STRINGS_BEFORE_CURSOR);
readStringsBeforeCursor.setLabelText(Tr::tr("Read Strings Before Cursor"));
readStringsBeforeCursor.setDefaultValue(50);
readStringsAfterCursor.setSettingsKey(Constants::READ_STRINGS_AFTER_CURSOR);
readStringsAfterCursor.setLabelText(Tr::tr("Read Strings After Cursor"));
readStringsAfterCursor.setDefaultValue(30);
maxTokens.setSettingsKey(Constants::MAX_TOKENS);
maxTokens.setLabelText(Tr::tr("Max Tokens"));
maxTokens.setRange(-1, 10000);
maxTokens.setDefaultValue(150);
useTopP.setSettingsKey(Constants::USE_TOP_P);
useTopP.setDefaultValue(false);
topP.setSettingsKey(Constants::TOP_P);
topP.setLabelText(Tr::tr("top_p"));
topP.setDefaultValue(0.9);
topP.setRange(0.0, 1.0);
useTopK.setSettingsKey(Constants::USE_TOP_K);
useTopK.setDefaultValue(false);
topK.setSettingsKey(Constants::TOP_K);
topK.setLabelText(Tr::tr("top_k"));
topK.setDefaultValue(50);
topK.setRange(1, 1000);
usePresencePenalty.setSettingsKey(Constants::USE_PRESENCE_PENALTY);
usePresencePenalty.setDefaultValue(false);
presencePenalty.setSettingsKey(Constants::PRESENCE_PENALTY);
presencePenalty.setLabelText(Tr::tr("presence_penalty"));
presencePenalty.setDefaultValue(0.0);
presencePenalty.setRange(-2.0, 2.0);
useFrequencyPenalty.setSettingsKey(Constants::USE_FREQUENCY_PENALTY);
useFrequencyPenalty.setDefaultValue(false);
frequencyPenalty.setSettingsKey(Constants::FREQUENCY_PENALTY);
frequencyPenalty.setLabelText(Tr::tr("frequency_penalty"));
frequencyPenalty.setDefaultValue(0.0);
frequencyPenalty.setRange(-2.0, 2.0);
startSuggestionTimer.setSettingsKey(Constants::START_SUGGESTION_TIMER);
startSuggestionTimer.setLabelText(Tr::tr("Start Suggestion Timer:"));
startSuggestionTimer.setRange(10, 10000);
startSuggestionTimer.setDefaultValue(500);
useFilePathInContext.setSettingsKey(Constants::USE_FILE_PATH_IN_CONTEXT);
useFilePathInContext.setDefaultValue(false);
useFilePathInContext.setLabelText(Tr::tr("Use File Path in Context"));
useSpecificInstructions.setSettingsKey(Constants::USE_SPECIFIC_INSTRUCTIONS);
useSpecificInstructions.setDefaultValue(false);
useSpecificInstructions.setLabelText(Tr::tr("Use Specific Instructions"));
specificInstractions.setSettingsKey(Constants::SPECIFIC_INSTRUCTIONS);
specificInstractions.setDisplayStyle(Utils::StringAspect::TextEditDisplay);
specificInstractions.setLabelText(
Tr::tr("Instructions: Please keep %1 for languge name, warning, it shouldn't too big"));
specificInstractions.setDefaultValue(
"You are an expert %1 code completion AI."
"CRITICAL: Please provide minimal the best possible code completion suggestions.\n");
resetToDefaults.m_buttonText = Tr::tr("Reset to Defaults");
multiLineCompletion.setSettingsKey(Constants::MULTILINE_COMPLETION);
multiLineCompletion.setDefaultValue(true);
multiLineCompletion.setLabelText(Tr::tr("Enable Multiline Completion"));
apiKey.setSettingsKey(Constants::API_KEY);
apiKey.setLabelText(Tr::tr("API Key:"));
apiKey.setDisplayStyle(Utils::StringAspect::LineEditDisplay);
apiKey.setPlaceHolderText(Tr::tr("Enter your API key here"));
const auto &manager = LLMProvidersManager::instance();
if (!manager.getProviderNames().isEmpty()) {
const auto providerNames = manager.getProviderNames();
for (const QString &name : providerNames) {
llmProviders.addOption(name);
}
}
const auto &promptManager = PromptTemplateManager::instance();
if (!promptManager.getTemplateNames().isEmpty()) {
const auto promptNames = promptManager.getTemplateNames();
for (const QString &name : promptNames) {
fimPrompts.addOption(name);
}
}
readSettings();
topK.setEnabled(useTopK());
topP.setEnabled(useTopP());
presencePenalty.setEnabled(usePresencePenalty());
frequencyPenalty.setEnabled(useFrequencyPenalty());
readStringsAfterCursor.setEnabled(!readFullFile());
readStringsBeforeCursor.setEnabled(!readFullFile());
specificInstractions.setEnabled(useSpecificInstructions());
PromptTemplateManager::instance().setCurrentTemplate(fimPrompts.stringValue());
LLMProvidersManager::instance().setCurrentProvider(llmProviders.stringValue());
setLoggingEnabled(enableLogging());
setLayouter([this]() {
using namespace Layouting;
return Column{Group{title(Tr::tr("General Settings")),
Form{Column{enableQodeAssist,
enableAutoComplete,
multiLineCompletion,
enableLogging,
Row{Stretch{1}, resetToDefaults}}}},
Group{title(Tr::tr("LLM Providers")),
Form{Column{llmProviders, Row{url, endPoint}}}},
Group{title(Tr::tr("LLM Model Settings")),
Form{Column{Row{selectModels, modelName}}}},
Group{title(Tr::tr("FIM Prompt Settings")),
Form{Column{fimPrompts,
readFullFile,
maxFileThreshold,
readStringsBeforeCursor,
readStringsAfterCursor,
ollamaLivetime,
apiKey,
useFilePathInContext,
useSpecificInstructions,
specificInstractions,
temperature,
maxTokens,
startSuggestionTimer,
Row{useTopP, topP, Stretch{1}},
Row{useTopK, topK, Stretch{1}},
Row{usePresencePenalty, presencePenalty, Stretch{1}},
Row{useFrequencyPenalty, frequencyPenalty, Stretch{1}}}}},
st};
});
setupConnections();
}
void QodeAssistSettings::setupConnections()
{
connect(&llmProviders, &Utils::SelectionAspect::volatileValueChanged, this, [this]() {
int index = llmProviders.volatileValue();
logMessage(QString("currentProvider %1").arg(llmProviders.displayForIndex(index)));
LLMProvidersManager::instance().setCurrentProvider(llmProviders.displayForIndex(index));
updateProviderSettings();
});
connect(&fimPrompts, &Utils::SelectionAspect::volatileValueChanged, this, [this]() {
int index = fimPrompts.volatileValue();
logMessage(QString("currentPrompt %1").arg(fimPrompts.displayForIndex(index)));
PromptTemplateManager::instance().setCurrentTemplate(fimPrompts.displayForIndex(index));
});
connect(&selectModels, &ButtonAspect::clicked, this, [this]() { showModelSelectionDialog(); });
connect(&useTopP, &Utils::BoolAspect::volatileValueChanged, this, [this]() {
topP.setEnabled(useTopP.volatileValue());
});
connect(&useTopK, &Utils::BoolAspect::volatileValueChanged, this, [this]() {
topK.setEnabled(useTopK.volatileValue());
});
connect(&usePresencePenalty, &Utils::BoolAspect::volatileValueChanged, this, [this]() {
presencePenalty.setEnabled(usePresencePenalty.volatileValue());
});
connect(&useFrequencyPenalty, &Utils::BoolAspect::volatileValueChanged, this, [this]() {
frequencyPenalty.setEnabled(useFrequencyPenalty.volatileValue());
});
connect(&readFullFile, &Utils::BoolAspect::volatileValueChanged, this, [this]() {
readStringsAfterCursor.setEnabled(!readFullFile.volatileValue());
readStringsBeforeCursor.setEnabled(!readFullFile.volatileValue());
});
connect(&resetToDefaults,
&ButtonAspect::clicked,
this,
&QodeAssistSettings::resetSettingsToDefaults);
connect(&enableLogging, &Utils::BoolAspect::volatileValueChanged, this, [this]() {
setLoggingEnabled(enableLogging.volatileValue());
});
connect(&useSpecificInstructions, &Utils::BoolAspect::volatileValueChanged, this, [this]() {
specificInstractions.setEnabled(useSpecificInstructions.volatileValue());
});
}
void QodeAssistSettings::updateProviderSettings()
{
auto *provider = LLMProvidersManager::instance().getCurrentProvider();
if (provider) {
logMessage(QString("currentProvider %1").arg(provider->name()));
url.setValue(provider->url());
endPoint.setValue(provider->completionEndpoint());
ollamaLivetime.setEnabled(provider->name() == "Ollama");
}
}
QStringList QodeAssistSettings::getInstalledModels()
{
auto *provider = LLMProvidersManager::instance().getCurrentProvider();
if (provider) {
Utils::Environment env = Utils::Environment::systemEnvironment();
return provider->getInstalledModels(env);
}
return {};
}
void QodeAssistSettings::showModelSelectionDialog()
{
QStringList models = getInstalledModels();
bool ok;
QString selectedModel = QInputDialog::getItem(Core::ICore::dialogParent(),
Tr::tr("Select LLM Model"),
Tr::tr("Choose a model:"),
models,
0,
false,
&ok);
if (ok && !selectedModel.isEmpty()) {
modelName.setValue(selectedModel);
writeSettings();
}
}
void QodeAssistSettings::resetSettingsToDefaults()
{
QMessageBox::StandardButton reply;
reply = QMessageBox::question(
Core::ICore::dialogParent(),
Tr::tr("Reset Settings"),
Tr::tr("Are you sure you want to reset all settings to default values?"),
QMessageBox::Yes | QMessageBox::No);
if (reply == QMessageBox::Yes) {
resetAspect(enableQodeAssist);
resetAspect(enableAutoComplete);
resetAspect(llmProviders);
resetAspect(url);
resetAspect(endPoint);
resetAspect(modelName);
resetAspect(fimPrompts);
resetAspect(temperature);
resetAspect(maxTokens);
resetAspect(readFullFile);
resetAspect(maxFileThreshold);
resetAspect(readStringsBeforeCursor);
resetAspect(readStringsAfterCursor);
resetAspect(useTopP);
resetAspect(topP);
resetAspect(useTopK);
resetAspect(topK);
resetAspect(usePresencePenalty);
resetAspect(presencePenalty);
resetAspect(useFrequencyPenalty);
resetAspect(frequencyPenalty);
resetAspect(startSuggestionTimer);
resetAspect(enableLogging);
resetAspect(ollamaLivetime);
resetAspect(specificInstractions);
resetAspect(multiLineCompletion);
resetAspect(useFilePathInContext);
resetAspect(useSpecificInstructions);
fimPrompts.setStringValue("StarCoder2");
llmProviders.setStringValue("Ollama");
updateProviderSettings();
apply();
QMessageBox::information(Core::ICore::dialogParent(),
Tr::tr("Settings Reset"),
Tr::tr("All settings have been reset to their default values."));
}
}
class QodeAssistSettingsPage : public Core::IOptionsPage
{
public:
QodeAssistSettingsPage()
{
setId(Constants::QODE_ASSIST_GENERAL_OPTIONS_ID);
setDisplayName("Qode Assist");
setCategory(Constants::QODE_ASSIST_GENERAL_OPTIONS_CATEGORY);
setDisplayCategory(Constants::QODE_ASSIST_GENERAL_OPTIONS_DISPLAY_CATEGORY);
setCategoryIconPath(":/resources/images/qoderassist-icon.png");
setSettingsProvider([] { return &settings(); });
}
};
const QodeAssistSettingsPage settingsPage;
} // namespace QodeAssist

View File

@ -1,115 +0,0 @@
/*
* Copyright (C) 2024 Petr Mironychev
*
* This file is part of QodeAssist.
*
* QodeAssist is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* QodeAssist is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <QPushButton>
#include <utils/aspects.h>
#include <utils/layoutbuilder.h>
namespace QodeAssist {
template<typename AspectType>
void resetAspect(AspectType &aspect)
{
aspect.setValue(aspect.defaultValue());
}
class ButtonAspect : public Utils::BaseAspect
{
Q_OBJECT
public:
ButtonAspect(Utils::AspectContainer *container = nullptr)
: Utils::BaseAspect(container)
{}
void addToLayout(Layouting::Layout &parent) override
{
auto button = new QPushButton(m_buttonText);
connect(button, &QPushButton::clicked, this, &ButtonAspect::clicked);
parent.addItem(button);
}
QString m_buttonText;
signals:
void clicked();
};
class QodeAssistSettings : public Utils::AspectContainer
{
public:
QodeAssistSettings();
Utils::BoolAspect enableQodeAssist{this};
Utils::BoolAspect enableAutoComplete{this};
Utils::BoolAspect enableLogging{this};
Utils::SelectionAspect llmProviders{this};
Utils::StringAspect url{this};
Utils::StringAspect endPoint{this};
Utils::StringAspect modelName{this};
ButtonAspect selectModels{this};
Utils::SelectionAspect fimPrompts{this};
Utils::DoubleAspect temperature{this};
Utils::IntegerAspect maxTokens{this};
Utils::BoolAspect readFullFile{this};
Utils::IntegerAspect readStringsBeforeCursor{this};
Utils::IntegerAspect readStringsAfterCursor{this};
Utils::BoolAspect useTopP{this};
Utils::DoubleAspect topP{this};
Utils::BoolAspect useTopK{this};
Utils::IntegerAspect topK{this};
Utils::BoolAspect usePresencePenalty{this};
Utils::DoubleAspect presencePenalty{this};
Utils::BoolAspect useFrequencyPenalty{this};
Utils::DoubleAspect frequencyPenalty{this};
Utils::IntegerAspect startSuggestionTimer{this};
Utils::IntegerAspect maxFileThreshold{this};
Utils::StringAspect ollamaLivetime{this};
Utils::StringAspect specificInstractions{this};
Utils::BoolAspect useSpecificInstructions{this};
Utils::BoolAspect useFilePathInContext{this};
Utils::BoolAspect multiLineCompletion{this};
Utils::StringAspect apiKey{this};
ButtonAspect resetToDefaults{this};
private:
void setupConnections();
void updateProviderSettings();
QStringList getInstalledModels();
void showModelSelectionDialog();
Utils::Environment getEnvironmentWithProviderPaths() const;
void resetSettingsToDefaults();
};
QodeAssistSettings &settings();
} // namespace QodeAssist

View File

@ -1,68 +0,0 @@
/*
* Copyright (C) 2024 Petr Mironychev
*
* This file is part of QodeAssist.
*
* QodeAssist is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* QodeAssist is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <QString>
#include <coreplugin/messagemanager.h>
#include <utils/qtcassert.h>
namespace QodeAssist {
inline bool &loggingEnabled()
{
static bool enabled = false;
return enabled;
}
inline void setLoggingEnabled(bool enable)
{
loggingEnabled() = enable;
}
inline void logMessage(const QString &message, bool silent = true)
{
if (!loggingEnabled())
return;
const QString prefixedMessage = QLatin1String("[Qode Assist] ") + message;
if (silent) {
Core::MessageManager::writeSilently(prefixedMessage);
} else {
Core::MessageManager::writeFlashing(prefixedMessage);
}
}
inline void logMessages(const QStringList &messages, bool silent = true)
{
if (!loggingEnabled())
return;
QStringList prefixedMessages;
for (const QString &message : messages) {
prefixedMessages << (QLatin1String("[Qode Assist] ") + message);
}
if (silent) {
Core::MessageManager::writeSilently(prefixedMessages);
} else {
Core::MessageManager::writeFlashing(prefixedMessages);
}
}
} // namespace QodeAssist

128
README.md
View File

@ -1,45 +1,36 @@
# QodeAssist
# QodeAssist - AI-powered coding assistant plugin for Qt Creator
[![Build plugin](https://github.com/Palm1r/QodeAssist/actions/workflows/build_cmake.yml/badge.svg?branch=main)](https://github.com/Palm1r/QodeAssist/actions/workflows/build_cmake.yml)
QodeAssist is an AI-powered coding assistant plugin for Qt Creator. It provides intelligent code completion and suggestions for C++ and QML, leveraging large language models through local providers like Ollama. Enhance your coding productivity with context-aware AI assistance directly in your Qt development environment.
## Supported LLM Providers
QodeAssist currently supports the following LLM (Large Language Model) providers:
- [Ollama](https://ollama.com)
- [LM Studio](https://lmstudio.ai)
- OpenAI compatible providers
Code completion:
## Supported Models
QodeAssist has been tested with the following language models, all trained for Fill-in-theMiddle:
<img src="https://github.com/user-attachments/assets/255a52f1-5cc0-4ca3-b05c-c4cf9cdbe25a" width="600" alt="QodeAssistPreview">
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)
- [TheBloke/CodeLlama-7B-GGUF](https://huggingface.co/TheBloke/CodeLlama-7B-GGUF)
Chat with LLM models in side panels:
Please note that while these models have been specifically tested and confirmed to work well with QodeAssist, other models compatible with the supported providers may also work. We encourage users to experiment with different models and report their experiences.
If you've successfully used a model that's not listed here, please let us know by opening an issue or submitting a pull request to update this list.
<img src="https://github.com/user-attachments/assets/ead5a5d9-b40a-4f17-af05-77fa2bcb3a61" width="600" alt="QodeAssistChat">
## Development Progress
Chat with LLM models in bottom panel:
- [x] Basic plugin with code autocomplete functionality
- [ ] Improve and automate settings
- [ ] Add chat functionality
- [ ] Support for more providers and models
<img width="326" alt="QodeAssistBottomPanel" src="https://github.com/user-attachments/assets/4cc64c23-a294-4df8-9153-39ad6fdab34b">
## Plugin installation using Ollama as an example
1. Install QtCreator 14.0
1. Install Latest QtCreator
2. Install [Ollama](https://ollama.com). Make sure to review the system requirements before installation.
3. Install a language model in Ollama. For example, you can run:
3. Install a language models in Ollama via terminal. For example, you can run:
For suggestions:
```
ollama run starcoder2:7b
ollama run codellama:7b-code
```
4. Download the QodeAssist plugin.
For chat:
```
ollama run codellama:7b-instruct
```
4. Download the QodeAssist plugin for your QtCreator.
5. Launch Qt Creator and install the plugin:
- Go to MacOS: Qt Creator -> About Plugins...
Windows\Linux: Help -> About Plugins...
@ -48,16 +39,84 @@ ollama run starcoder2:7b
## Configure Plugin
<img src="https://github.com/user-attachments/assets/00ad980f-b470-48eb-9aaa-077783d38798" width="600" alt="Configuere QodeAssist">
1. Open Qt Creator settings
2. Navigate to the "Qode Assist" tab
3. Choose your LLM provider (e.g., Ollama)
4. Select the installed model by the "Select Model" button
- For LM Studio you will see current load model
5. Choose the prompt template that corresponds to your model
6. Apply the settings
3. Select "General" page
4. Choose your LLM provider (e.g., Ollama)
5. Select the installed model by the "Select Model" button
- For LM Studio you will see current loaded model
6. Choose the prompt template that corresponds to your model
7. Apply the settings
You're all set! QodeAssist is now ready to use in Qt Creator.
[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/P5P412V96G)
## Supported LLM Providers
QodeAssist currently supports the following LLM (Large Language Model) providers:
- [Ollama](https://ollama.com)
- [LM Studio](https://lmstudio.ai)
- OpenAI compatible providers
## QtCreator Version Compatibility
- QtCreator 14.0.2 - 0.2.3 - 0.3.x
- QtCreator 14.0.1 - 0.2.2 plugin version and below
## Supported Models
QodeAssist has been thoroughly tested and optimized for use with the following language models:
- Llama
- CodeLlama
- StarCoder2
- DeepSeek-Coder-V2
- Qwen-2.5
These models have demonstrated excellent performance in code completion and assistance tasks within the QodeAssist environment.
### Tested Models
#### Ollama:
- [starcoder2](https://ollama.com/library/starcoder2)
- [codellama](https://ollama.com/library/codellama)
#### LM Studio:
- [second-state/StarCoder2-7B-GGUF](https://huggingface.co/second-state/StarCoder2-7B-GGUF)
- [TheBloke/CodeLlama-7B-GGUF](https://huggingface.co/TheBloke/CodeLlama-7B-GGUF)
Please note that while these models have been specifically tested and confirmed to work well with QodeAssist, other models compatible with the supported providers may also work. We encourage users to experiment with different models and report their experiences.
If you've successfully used a model that's not listed here, please let us know by opening an issue or submitting a pull request to update this list.
### Custom Prompts
For advanced users or those with specific requirements, QodeAssist offers the flexibility to create, save, and load custom prompts using JSON templates. This feature allows you to tailor the AI's behavior to your exact needs.
To get started with custom prompts:
1. Navigate to the "Custom Template" option in the FIM Prompt Settings.
2. Create your custom JSON prompt template.
3. Use the "Save Custom Template to JSON" button to store your template for future use.
4. To use a previously saved template, click "Load Custom Template from JSON".
5. Make sure to select "Custom Template" from the dropdown menu in the FIM Prompt Settings on the General page to activate your custom template.
For inspiration and examples of effective custom prompts, please refer to the `rawPromptExamples` folder in our repository.
<img width="600" alt="Custom template" src="https://github.com/user-attachments/assets/4a14c552-baba-4531-ab4f-cb1f9ac6620b">
<img width="600" alt="Select custom template" src="https://github.com/user-attachments/assets/3651dafd-83f9-4df9-943f-69c28cd3d8a3">
## Development Progress
- [x] Basic plugin with code autocomplete functionality
- [x] Improve and automate settings
- [x] Add chat functionality
- [x] Sharing diff with model
- [ ] Sharing project source with model
- [ ] Support for more providers and models
## Hotkeys
- To call manual request to suggestion, you can use or change it in settings
@ -91,7 +150,8 @@ Verify that the selected prompt template matches the model you're using
If you're still experiencing issues with QodeAssist, you can try resetting the settings to their default values:
1. Open Qt Creator settings
2. Navigate to the "Qode Assist" tab
3. Click on the "Reset to Defaults" button
3. Pick settings page for reset
4. Click on the "Reset Page to Defaults" button
- The API key will not reset
- Select model after reset
@ -104,8 +164,8 @@ If you find QodeAssist helpful, there are several ways you can support the proje
3. **Spread the Word**: Star our GitHub repository and share QodeAssist with your fellow developers.
4. **Financial Support**: If you'd like to support the development financially, you can make a donation using one of the following cryptocurrency addresses:
4. **Financial Support**: If you'd like to support the development financially, you can make a donation using one of the following:
- [![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/P5P412V96G)
- Bitcoin (BTC): `bc1qndq7f0mpnlya48vk7kugvyqj5w89xrg4wzg68t`
- Ethereum (ETH): `0xA5e8c37c94b24e25F9f1f292a01AF55F03099D8D`
- Litecoin (LTC): `ltc1qlrxnk30s2pcjchzx4qrxvdjt5gzuervy5mv0vy`

95
chat/ChatOutputPane.cpp Normal file
View File

@ -0,0 +1,95 @@
/*
* 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 "ChatOutputPane.h"
#include "QodeAssisttr.h"
namespace QodeAssist::Chat {
ChatOutputPane::ChatOutputPane(QObject *parent)
: Core::IOutputPane(parent)
, m_chatWidget(new ChatWidget)
{
setId("QodeAssistChat");
setDisplayName(Tr::tr("QodeAssist Chat"));
setPriorityInStatusBar(-40);
}
ChatOutputPane::~ChatOutputPane()
{
delete m_chatWidget;
}
QWidget *ChatOutputPane::outputWidget(QWidget *)
{
return m_chatWidget;
}
QList<QWidget *> ChatOutputPane::toolBarWidgets() const
{
return {};
}
void ChatOutputPane::clearContents()
{
m_chatWidget->clear();
}
void ChatOutputPane::visibilityChanged(bool visible)
{
if (visible)
m_chatWidget->scrollToBottom();
}
void ChatOutputPane::setFocus()
{
m_chatWidget->setFocus();
}
bool ChatOutputPane::hasFocus() const
{
return m_chatWidget->hasFocus();
}
bool ChatOutputPane::canFocus() const
{
return true;
}
bool ChatOutputPane::canNavigate() const
{
return false;
}
bool ChatOutputPane::canNext() const
{
return false;
}
bool ChatOutputPane::canPrevious() const
{
return false;
}
void ChatOutputPane::goToNext() {}
void ChatOutputPane::goToPrev() {}
} // namespace QodeAssist::Chat

52
chat/ChatOutputPane.h Normal file
View File

@ -0,0 +1,52 @@
/*
* Copyright (C) 2024 Petr Mironychev
*
* This file is part of QodeAssist.
*
* QodeAssist is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* QodeAssist is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include "chatview/ChatWidget.hpp"
#include <coreplugin/ioutputpane.h>
namespace QodeAssist::Chat {
class ChatOutputPane : public Core::IOutputPane
{
Q_OBJECT
public:
explicit ChatOutputPane(QObject *parent = nullptr);
~ChatOutputPane() override;
QWidget *outputWidget(QWidget *parent) override;
QList<QWidget *> toolBarWidgets() const override;
void clearContents() override;
void visibilityChanged(bool visible) override;
void setFocus() override;
bool hasFocus() const override;
bool canFocus() const override;
bool canNavigate() const override;
bool canNext() const override;
bool canPrevious() const override;
void goToNext() override;
void goToPrev() override;
private:
ChatWidget *m_chatWidget;
};
} // namespace QodeAssist::Chat

View File

@ -1,4 +1,4 @@
/*
/*
* Copyright (C) 2024 Petr Mironychev
*
* This file is part of QodeAssist.
@ -17,37 +17,29 @@
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
*/
#include "PromptTemplateManager.hpp"
#include "NavigationPanel.hpp"
namespace QodeAssist {
#include "chatview/ChatWidget.hpp"
PromptTemplateManager &PromptTemplateManager::instance()
{
static PromptTemplateManager instance;
return instance;
namespace QodeAssist::Chat {
NavigationPanel::NavigationPanel() {
setDisplayName(tr("QodeAssist Chat"));
setPriority(500);
setId("QodeAssistChat");
setActivationSequence(QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key_C));
}
void PromptTemplateManager::setCurrentTemplate(const QString &name)
NavigationPanel::~NavigationPanel()
{
if (m_templates.contains(name)) {
m_currentTemplateName = name;
}
}
const Templates::PromptTemplate *PromptTemplateManager::getCurrentTemplate() const
Core::NavigationView NavigationPanel::createWidget()
{
auto it = m_templates.find(m_currentTemplateName);
return it != m_templates.end() ? it.value() : nullptr;
Core::NavigationView view;
view.widget = new ChatWidget;
return view;
}
QStringList PromptTemplateManager::getTemplateNames() const
{
return m_templates.keys();
}
PromptTemplateManager::~PromptTemplateManager()
{
qDeleteAll(m_templates);
}
} // namespace QodeAssist

37
chat/NavigationPanel.hpp Normal file
View File

@ -0,0 +1,37 @@
/*
* 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 <QObject>
#include <coreplugin/inavigationwidgetfactory.h>
namespace QodeAssist::Chat {
class NavigationPanel : public Core::INavigationWidgetFactory
{
Q_OBJECT
public:
explicit NavigationPanel();
~NavigationPanel();
Core::NavigationView createWidget() override;
};
}

35
chatview/CMakeLists.txt Normal file
View File

@ -0,0 +1,35 @@
qt_add_library(QodeAssistChatView STATIC)
qt_add_qml_module(QodeAssistChatView
URI ChatView
VERSION 1.0
QML_FILES
qml/RootItem.qml
qml/ChatItem.qml
qml/Badge.qml
qml/dialog/CodeBlock.qml
qml/dialog/TextBlock.qml
SOURCES
ChatWidget.hpp ChatWidget.cpp
ChatModel.hpp ChatModel.cpp
ChatRootView.hpp ChatRootView.cpp
ClientInterface.hpp ClientInterface.cpp
MessagePart.hpp
ChatUtils.h ChatUtils.cpp
)
target_link_libraries(QodeAssistChatView
PUBLIC
Qt::Widgets
Qt::Quick
Qt::QuickWidgets
Qt::Network
QtCreator::Core
QtCreator::Utils
LLMCore
QodeAssistSettings
)
target_include_directories(QodeAssistChatView
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}
)

195
chatview/ChatModel.cpp Normal file
View File

@ -0,0 +1,195 @@
/*
* 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 "ChatModel.hpp"
#include <QtCore/qjsonobject.h>
#include <QtQml>
#include <utils/aspects.h>
#include "GeneralSettings.hpp"
namespace QodeAssist::Chat {
ChatModel::ChatModel(QObject *parent)
: QAbstractListModel(parent)
, m_totalTokens(0)
{
auto &settings = Settings::generalSettings();
connect(&settings.chatTokensThreshold,
&Utils::BaseAspect::changed,
this,
&ChatModel::tokensThresholdChanged);
}
int ChatModel::rowCount(const QModelIndex &parent) const
{
return m_messages.size();
}
QVariant ChatModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid() || index.row() >= m_messages.size())
return QVariant();
const Message &message = m_messages[index.row()];
switch (static_cast<Roles>(role)) {
case Roles::RoleType:
return QVariant::fromValue(message.role);
case Roles::Content: {
return message.content;
}
default:
return QVariant();
}
}
QHash<int, QByteArray> ChatModel::roleNames() const
{
QHash<int, QByteArray> roles;
roles[Roles::RoleType] = "roleType";
roles[Roles::Content] = "content";
return roles;
}
void ChatModel::addMessage(const QString &content, ChatRole role, const QString &id)
{
int tokenCount = estimateTokenCount(content);
if (!m_messages.isEmpty() && !id.isEmpty() && m_messages.last().id == id) {
Message &lastMessage = m_messages.last();
int oldTokenCount = lastMessage.tokenCount;
lastMessage.content = content;
lastMessage.tokenCount = tokenCount;
m_totalTokens += (tokenCount - oldTokenCount);
emit dataChanged(index(m_messages.size() - 1), index(m_messages.size() - 1));
} else {
beginInsertRows(QModelIndex(), m_messages.size(), m_messages.size());
m_messages.append({role, content, tokenCount, id});
m_totalTokens += tokenCount;
endInsertRows();
}
trim();
emit totalTokensChanged();
}
QVector<ChatModel::Message> ChatModel::getChatHistory() const
{
return m_messages;
}
void ChatModel::trim()
{
while (m_totalTokens > tokensThreshold()) {
if (!m_messages.isEmpty()) {
m_totalTokens -= m_messages.first().tokenCount;
beginRemoveRows(QModelIndex(), 0, 0);
m_messages.removeFirst();
endRemoveRows();
} else {
break;
}
}
}
int ChatModel::estimateTokenCount(const QString &text) const
{
return text.length() / 4;
}
void ChatModel::clear()
{
beginResetModel();
m_messages.clear();
m_totalTokens = 0;
endResetModel();
emit totalTokensChanged();
}
QList<MessagePart> ChatModel::processMessageContent(const QString &content) const
{
QList<MessagePart> parts;
QRegularExpression codeBlockRegex("```(\\w*)\\n?([\\s\\S]*?)```");
int lastIndex = 0;
auto blockMatches = codeBlockRegex.globalMatch(content);
while (blockMatches.hasNext()) {
auto match = blockMatches.next();
if (match.capturedStart() > lastIndex) {
QString textBetween = content.mid(lastIndex, match.capturedStart() - lastIndex).trimmed();
if (!textBetween.isEmpty()) {
parts.append({MessagePart::Text, textBetween, ""});
}
}
parts.append({MessagePart::Code, match.captured(2).trimmed(), match.captured(1)});
lastIndex = match.capturedEnd();
}
if (lastIndex < content.length()) {
QString remainingText = content.mid(lastIndex).trimmed();
if (!remainingText.isEmpty()) {
parts.append({MessagePart::Text, remainingText, ""});
}
}
return parts;
}
QJsonArray ChatModel::prepareMessagesForRequest(LLMCore::ContextData context) const
{
QJsonArray messages;
messages.append(QJsonObject{{"role", "system"}, {"content", context.systemPrompt}});
for (const auto &message : m_messages) {
QString role;
switch (message.role) {
case ChatRole::User:
role = "user";
break;
case ChatRole::Assistant:
role = "assistant";
break;
default:
continue;
}
messages.append(QJsonObject{{"role", role}, {"content", message.content}});
}
return messages;
}
int ChatModel::totalTokens() const
{
return m_totalTokens;
}
int ChatModel::tokensThreshold() const
{
auto &settings = Settings::generalSettings();
return settings.chatTokensThreshold();
}
QString ChatModel::lastMessageId() const
{
return !m_messages.isEmpty() ? m_messages.last().id : "";
}
} // namespace QodeAssist::Chat

85
chatview/ChatModel.hpp Normal file
View File

@ -0,0 +1,85 @@
/*
* 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 "ContextData.hpp"
#include "MessagePart.hpp"
#include <QAbstractListModel>
#include <QJsonArray>
#include <qqmlintegration.h>
namespace QodeAssist::Chat {
class ChatModel : public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(int totalTokens READ totalTokens NOTIFY totalTokensChanged FINAL)
Q_PROPERTY(int tokensThreshold READ tokensThreshold NOTIFY tokensThresholdChanged FINAL)
QML_ELEMENT
public:
enum Roles { RoleType = Qt::UserRole, Content };
enum ChatRole { System, User, Assistant };
Q_ENUM(ChatRole)
struct Message
{
ChatRole role;
QString content;
int tokenCount;
QString id;
};
explicit ChatModel(QObject *parent = nullptr);
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QHash<int, QByteArray> roleNames() const override;
Q_INVOKABLE void addMessage(const QString &content, ChatRole role, const QString &id);
Q_INVOKABLE void clear();
Q_INVOKABLE QList<MessagePart> processMessageContent(const QString &content) const;
QVector<Message> getChatHistory() const;
QJsonArray prepareMessagesForRequest(LLMCore::ContextData context) const;
int totalTokens() const;
int tokensThreshold() const;
QString currentModel() const;
QString lastMessageId() const;
signals:
void totalTokensChanged();
void tokensThresholdChanged();
private:
void trim();
int estimateTokenCount(const QString &text) const;
QVector<Message> m_messages;
int m_totalTokens = 0;
};
} // namespace QodeAssist::Chat
Q_DECLARE_METATYPE(QodeAssist::Chat::ChatModel::Message)
Q_DECLARE_METATYPE(QodeAssist::Chat::MessagePart)

132
chatview/ChatRootView.cpp Normal file
View File

@ -0,0 +1,132 @@
/*
* 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 "ChatRootView.hpp"
#include <QtGui/qclipboard.h>
#include <utils/theme/theme.h>
#include <utils/utilsicons.h>
#include "GeneralSettings.hpp"
namespace QodeAssist::Chat {
ChatRootView::ChatRootView(QQuickItem *parent)
: QQuickItem(parent)
, m_chatModel(new ChatModel(this))
, m_clientInterface(new ClientInterface(m_chatModel, this))
{
auto &settings = Settings::generalSettings();
connect(&settings.chatModelName,
&Utils::BaseAspect::changed,
this,
&ChatRootView::currentTemplateChanged);
generateColors();
}
ChatModel *ChatRootView::chatModel() const
{
return m_chatModel;
}
QColor ChatRootView::backgroundColor() const
{
return Utils::creatorColor(Utils::Theme::BackgroundColorNormal);
}
void ChatRootView::sendMessage(const QString &message) const
{
m_clientInterface->sendMessage(message);
}
void ChatRootView::copyToClipboard(const QString &text)
{
QGuiApplication::clipboard()->setText(text);
}
void ChatRootView::cancelRequest()
{
m_clientInterface->cancelRequest();
}
void ChatRootView::generateColors()
{
QColor baseColor = backgroundColor();
bool isDarkTheme = baseColor.lightness() < 128;
if (isDarkTheme) {
m_primaryColor = generateColor(baseColor, 0.1, 1.2, 1.4);
m_secondaryColor = generateColor(baseColor, -0.1, 1.1, 1.2);
m_codeColor = generateColor(baseColor, 0.05, 0.8, 1.1);
} else {
m_primaryColor = generateColor(baseColor, 0.05, 1.05, 1.1);
m_secondaryColor = generateColor(baseColor, -0.05, 1.1, 1.2);
m_codeColor = generateColor(baseColor, 0.02, 0.95, 1.05);
}
}
QColor ChatRootView::generateColor(const QColor &baseColor,
float hueShift,
float saturationMod,
float lightnessMod)
{
float h, s, l, a;
baseColor.getHslF(&h, &s, &l, &a);
bool isDarkTheme = l < 0.5;
h = fmod(h + hueShift + 1.0, 1.0);
s = qBound(0.0f, s * saturationMod, 1.0f);
if (isDarkTheme) {
l = qBound(0.0f, l * lightnessMod, 1.0f);
} else {
l = qBound(0.0f, l / lightnessMod, 1.0f);
}
h = qBound(0.0f, h, 1.0f);
s = qBound(0.0f, s, 1.0f);
l = qBound(0.0f, l, 1.0f);
a = qBound(0.0f, a, 1.0f);
return QColor::fromHslF(h, s, l, a);
}
QString ChatRootView::currentTemplate() const
{
auto &settings = Settings::generalSettings();
return settings.chatModelName();
}
QColor ChatRootView::primaryColor() const
{
return m_primaryColor;
}
QColor ChatRootView::secondaryColor() const
{
return m_secondaryColor;
}
QColor ChatRootView::codeColor() const
{
return m_codeColor;
}
} // namespace QodeAssist::Chat

76
chatview/ChatRootView.hpp Normal file
View File

@ -0,0 +1,76 @@
/*
* 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 <QQuickItem>
#include "ChatModel.hpp"
#include "ClientInterface.hpp"
namespace QodeAssist::Chat {
class ChatRootView : public QQuickItem
{
Q_OBJECT
Q_PROPERTY(ChatModel *chatModel READ chatModel NOTIFY chatModelChanged FINAL)
Q_PROPERTY(QString currentTemplate READ currentTemplate NOTIFY currentTemplateChanged FINAL)
Q_PROPERTY(QColor backgroundColor READ backgroundColor CONSTANT FINAL)
Q_PROPERTY(QColor primaryColor READ primaryColor CONSTANT FINAL)
Q_PROPERTY(QColor secondaryColor READ secondaryColor CONSTANT FINAL)
Q_PROPERTY(QColor codeColor READ codeColor CONSTANT FINAL)
QML_ELEMENT
public:
ChatRootView(QQuickItem *parent = nullptr);
ChatModel *chatModel() const;
QString currentTemplate() const;
QColor backgroundColor() const;
QColor primaryColor() const;
QColor secondaryColor() const;
QColor codeColor() const;
public slots:
void sendMessage(const QString &message) const;
void copyToClipboard(const QString &text);
void cancelRequest();
signals:
void chatModelChanged();
void currentTemplateChanged();
private:
void generateColors();
QColor generateColor(const QColor &baseColor,
float hueShift,
float saturationMod,
float lightnessMod);
ChatModel *m_chatModel;
ClientInterface *m_clientInterface;
QString m_currentTemplate;
QColor m_primaryColor;
QColor m_secondaryColor;
QColor m_codeColor;
};
} // namespace QodeAssist::Chat

13
chatview/ChatUtils.cpp Normal file
View File

@ -0,0 +1,13 @@
#include "ChatUtils.h"
#include <QClipboard>
#include <QGuiApplication>
namespace QodeAssist::Chat {
void ChatUtils::copyToClipboard(const QString &text)
{
QGuiApplication::clipboard()->setText(text);
}
} // namespace QodeAssist::Chat

21
chatview/ChatUtils.h Normal file
View File

@ -0,0 +1,21 @@
#pragma once
#include <qobject.h>
#include <qqmlintegration.h>
namespace QodeAssist::Chat {
// Q_NAMESPACE
class ChatUtils : public QObject
{
Q_OBJECT
QML_NAMED_ELEMENT(ChatUtils)
public:
explicit ChatUtils(QObject *parent = nullptr)
: QObject(parent) {};
Q_INVOKABLE void copyToClipboard(const QString &text);
};
} // namespace QodeAssist::Chat

43
chatview/ChatWidget.cpp Normal file
View File

@ -0,0 +1,43 @@
/*
* Copyright (C) 2024 Petr Mironychev
*
* This file is part of QodeAssist.
*
* QodeAssist is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* QodeAssist is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
*/
#include "ChatWidget.hpp"
#include <QQmlContext>
#include <QQmlEngine>
namespace QodeAssist::Chat {
ChatWidget::ChatWidget(QWidget *parent)
: QQuickWidget(parent)
{
setSource(QUrl("qrc:/ChatView/qml/RootItem.qml"));
setResizeMode(QQuickWidget::SizeRootObjectToView);
}
void ChatWidget::clear()
{
QMetaObject::invokeMethod(rootObject(), "clearChat");
}
void ChatWidget::scrollToBottom()
{
QMetaObject::invokeMethod(rootObject(), "scrollToBottom");
}
}

41
chatview/ChatWidget.hpp Normal file
View File

@ -0,0 +1,41 @@
/*
* 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 <QtQuickWidgets/QtQuickWidgets>
namespace QodeAssist::Chat {
class ChatWidget : public QQuickWidget
{
Q_OBJECT
public:
explicit ChatWidget(QWidget *parent = nullptr);
~ChatWidget() = default;
Q_INVOKABLE void clear();
Q_INVOKABLE void scrollToBottom();
signals:
void clearPressed();
};
}

View File

@ -0,0 +1,125 @@
/*
* 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 "ClientInterface.hpp"
#include "ContextSettings.hpp"
#include "GeneralSettings.hpp"
#include "Logger.hpp"
#include "PromptTemplateManager.hpp"
#include "ProvidersManager.hpp"
#include <QJsonArray>
#include <QJsonDocument>
#include <QUuid>
namespace QodeAssist::Chat {
ClientInterface::ClientInterface(ChatModel *chatModel, QObject *parent)
: QObject(parent)
, m_requestHandler(new LLMCore::RequestHandler(this))
, m_chatModel(chatModel)
{
connect(m_requestHandler,
&LLMCore::RequestHandler::completionReceived,
this,
[this](const QString &completion, const QJsonObject &request, bool isComplete) {
handleLLMResponse(completion, request, isComplete);
});
connect(m_requestHandler,
&LLMCore::RequestHandler::requestFinished,
this,
[this](const QString &, bool success, const QString &errorString) {
if (!success) {
emit errorOccurred(errorString);
}
});
}
ClientInterface::~ClientInterface() = default;
void ClientInterface::sendMessage(const QString &message)
{
cancelRequest();
LOG_MESSAGE("Sending message: " + message);
LOG_MESSAGE("chatProvider " + Settings::generalSettings().chatLlmProviders.stringValue());
LOG_MESSAGE("chatTemplate " + Settings::generalSettings().chatPrompts.stringValue());
auto chatTemplate = LLMCore::PromptTemplateManager::instance().getCurrentChatTemplate();
auto chatProvider = LLMCore::ProvidersManager::instance().getCurrentChatProvider();
LLMCore::ContextData context;
context.prefix = message;
context.suffix = "";
if (Settings::contextSettings().useChatSystemPrompt())
context.systemPrompt = Settings::contextSettings().chatSystemPrompt();
QJsonObject providerRequest;
providerRequest["model"] = Settings::generalSettings().chatModelName();
providerRequest["stream"] = true;
providerRequest["messages"] = m_chatModel->prepareMessagesForRequest(context);
if (!chatTemplate || !chatProvider) {
LOG_MESSAGE("Check settings, provider or template are not set");
}
chatTemplate->prepareRequest(providerRequest, context);
chatProvider->prepareRequest(providerRequest, LLMCore::RequestType::Chat);
LLMCore::LLMConfig config;
config.requestType = LLMCore::RequestType::Chat;
config.provider = chatProvider;
config.promptTemplate = chatTemplate;
config.url = QString("%1%2").arg(Settings::generalSettings().chatUrl(),
Settings::generalSettings().chatEndPoint());
config.providerRequest = providerRequest;
config.multiLineCompletion = Settings::generalSettings().multiLineCompletion();
QJsonObject request;
request["id"] = QUuid::createUuid().toString();
m_chatModel->addMessage(message, ChatModel::ChatRole::User, "");
m_requestHandler->sendLLMRequest(config, request);
}
void ClientInterface::clearMessages()
{
m_chatModel->clear();
LOG_MESSAGE("Chat history cleared");
}
void ClientInterface::cancelRequest()
{
auto id = m_chatModel->lastMessageId();
m_requestHandler->cancelRequest(id);
}
void ClientInterface::handleLLMResponse(const QString &response,
const QJsonObject &request,
bool isComplete)
{
QString messageId = request["id"].toString();
m_chatModel->addMessage(response.trimmed(), ChatModel::ChatRole::Assistant, messageId);
if (isComplete) {
LOG_MESSAGE("Message completed. Final response for message " + messageId + ": " + response);
}
}
} // namespace QodeAssist::Chat

View File

@ -0,0 +1,53 @@
/*
* 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 <QObject>
#include <QString>
#include <QVector>
#include "ChatModel.hpp"
#include "RequestHandler.hpp"
namespace QodeAssist::Chat {
class ClientInterface : public QObject
{
Q_OBJECT
public:
explicit ClientInterface(ChatModel *chatModel, QObject *parent = nullptr);
~ClientInterface();
void sendMessage(const QString &message);
void clearMessages();
void cancelRequest();
signals:
void errorOccurred(const QString &error);
private:
void handleLLMResponse(const QString &response, const QJsonObject &request, bool isComplete);
LLMCore::RequestHandler *m_requestHandler;
ChatModel *m_chatModel;
};
} // namespace QodeAssist::Chat

51
chatview/MessagePart.hpp Normal file
View File

@ -0,0 +1,51 @@
/*
* 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 <qobject.h>
#include <qqmlintegration.h>
namespace QodeAssist::Chat {
Q_NAMESPACE
class MessagePart
{
Q_GADGET
Q_PROPERTY(PartType type MEMBER type CONSTANT FINAL)
Q_PROPERTY(QString text MEMBER text CONSTANT FINAL)
Q_PROPERTY(QString language MEMBER language CONSTANT FINAL)
QML_VALUE_TYPE(messagePart)
public:
enum PartType { Code, Text };
Q_ENUM(PartType)
PartType type;
QString text;
QString language;
};
class MessagePartType : public MessagePart
{
Q_GADGET
};
QML_NAMED_ELEMENT(MessagePart)
QML_FOREIGN_NAMESPACE(QodeAssist::Chat::MessagePartType)
} // namespace QodeAssist::Chat

40
chatview/qml/Badge.qml Normal file
View File

@ -0,0 +1,40 @@
/*
* 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/>.
*/
import QtQuick
Rectangle {
id: root
property alias text: badgeText.text
property alias fontColor: badgeText.color
width: badgeText.implicitWidth + radius
height: badgeText.implicitHeight + 6
color: "lightgreen"
radius: height / 2
border.width: 1
border.color: "gray"
Text {
id: badgeText
anchors.centerIn: parent
}
}

91
chatview/qml/ChatItem.qml Normal file
View File

@ -0,0 +1,91 @@
/*
* 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/>.
*/
import QtQuick
import QtQuick.Controls
import ChatView
import "./dialog"
Rectangle {
id: root
property alias msgModel: msgCreator.model
property color fontColor
property color codeBgColor
property color selectionColor
height: msgColumn.height
radius: 8
Column {
id: msgColumn
anchors.verticalCenter: parent.verticalCenter
width: parent.width
spacing: 5
Repeater {
id: msgCreator
delegate: Loader {
property var itemData: modelData
width: parent.width
sourceComponent: {
switch(modelData.type) {
case MessagePart.Text: return textComponent;
case MessagePart.Code: return codeBlockComponent;
default: return textComponent;
}
}
}
}
}
Component {
id: textComponent
TextBlock {
height: implicitHeight + 10
verticalAlignment: Text.AlignVCenter
leftPadding: 10
text: itemData.text
color: fontColor
selectionColor: root.selectionColor
}
}
Component {
id: codeBlockComponent
CodeBlock {
anchors {
left: parent.left
leftMargin: 10
right: parent.right
rightMargin: 10
}
code: itemData.text
language: itemData.language
color: root.codeBgColor
selectionColor: root.selectionColor
}
}
}

176
chatview/qml/RootItem.qml Normal file
View File

@ -0,0 +1,176 @@
/*
* 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/>.
*/
import QtQuick
import QtQuick.Controls
import QtQuick.Controls.Basic as QQC
import QtQuick.Layouts
import ChatView
ChatRootView {
id: root
Rectangle {
id: bg
anchors.fill: parent
color: root.backgroundColor
}
ColumnLayout {
anchors {
fill: parent
}
spacing: 10
ListView {
id: chatListView
Layout.fillWidth: true
Layout.fillHeight: true
leftMargin: 5
model: root.chatModel
clip: true
spacing: 10
boundsBehavior: Flickable.StopAtBounds
cacheBuffer: 2000
delegate: ChatItem {
width: ListView.view.width - scroll.width
msgModel: root.chatModel.processMessageContent(model.content)
color: model.roleType === ChatModel.User ? root.primaryColor : root.secondaryColor
fontColor: root.primaryColor.hslLightness > 0.5 ? "black" : "white"
codeBgColor: root.codeColor
selectionColor: root.primaryColor.hslLightness > 0.5 ? Qt.darker(root.primaryColor, 1.5)
: Qt.lighter(root.primaryColor, 1.5)
}
header: Item {
width: ListView.view.width - scroll.width
height: 30
}
ScrollBar.vertical: ScrollBar {
id: scroll
}
onCountChanged: {
scrollToBottom()
}
onContentHeightChanged: {
if (atYEnd) {
scrollToBottom()
}
}
}
ScrollView {
id: view
Layout.fillWidth: true
Layout.minimumHeight: 30
Layout.maximumHeight: root.height / 2
QQC.TextArea {
id: messageInput
placeholderText: qsTr("Type your message here...")
placeholderTextColor: "#888"
color: root.primaryColor.hslLightness > 0.5 ? "black" : "white"
background: Rectangle {
radius: 2
color: root.primaryColor
border.color: root.primaryColor.hslLightness > 0.5 ? Qt.lighter(root.primaryColor, 1.5)
: Qt.darker(root.primaryColor, 1.5)
border.width: 1
}
Keys.onPressed: function(event) {
if ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && !(event.modifiers & Qt.ShiftModifier)) {
sendChatMessage()
event.accepted = true;
}
}
}
}
RowLayout {
Layout.fillWidth: true
spacing: 5
Button {
id: sendButton
Layout.alignment: Qt.AlignBottom
text: qsTr("Send")
onClicked: sendChatMessage()
}
Button {
id: stopButton
Layout.alignment: Qt.AlignBottom
text: qsTr("Stop")
onClicked: root.cancelRequest()
}
Button {
id: clearButton
Layout.alignment: Qt.AlignBottom
text: qsTr("Clear Chat")
onClicked: clearChat()
}
}
}
Row {
id: bar
layoutDirection: Qt.RightToLeft
anchors {
left: parent.left
leftMargin: 5
right: parent.right
rightMargin: scroll.width
}
spacing: 10
Badge {
text: "%1/%2".arg(root.chatModel.totalTokens).arg(root.chatModel.tokensThreshold)
color: root.codeColor
fontColor: root.primaryColor.hslLightness > 0.5 ? "black" : "white"
}
}
function clearChat() {
root.chatModel.clear()
}
function scrollToBottom() {
Qt.callLater(chatListView.positionViewAtEnd)
}
function sendChatMessage() {
root.sendMessage(messageInput.text);
messageInput.text = ""
scrollToBottom()
}
}

View File

@ -0,0 +1,100 @@
/*
* 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/>.
*/
import QtQuick
import QtQuick.Controls
import ChatView
Rectangle {
id: root
property string code: ""
property string language: ""
property color selectionColor
readonly property string monospaceFont: {
switch (Qt.platform.os) {
case "windows":
return "Consolas";
case "osx":
return "Menlo";
case "linux":
return "DejaVu Sans Mono";
default:
return "monospace";
}
}
border.color: root.color.hslLightness > 0.5 ? Qt.darker(root.color, 1.3)
: Qt.lighter(root.color, 1.3)
border.width: 2
radius: 4
implicitWidth: parent.width
implicitHeight: codeText.implicitHeight + 20
ChatUtils {
id: utils
}
TextEdit {
id: codeText
anchors.fill: parent
anchors.margins: 10
text: root.code
readOnly: true
selectByMouse: true
font.family: monospaceFont
font.pointSize: 12
color: parent.color.hslLightness > 0.5 ? "black" : "white"
wrapMode: Text.WordWrap
selectionColor: root.selectionColor
}
TextEdit {
anchors.top: parent.top
anchors.right: parent.right
anchors.margins: 5
readOnly: true
selectByMouse: true
text: root.language
color: root.color.hslLightness > 0.5 ? Qt.darker(root.color, 1.1)
: Qt.lighter(root.color, 1.1)
font.pointSize: 8
}
Button {
anchors.top: parent.top
anchors.right: parent.right
anchors.margins: 5
text: "Copy"
onClicked: {
utils.copyToClipboard(root.code)
text = qsTr("Copied")
copyTimer.start()
}
Timer {
id: copyTimer
interval: 2000
onTriggered: parent.text = qsTr("Copy")
}
}
}

View File

@ -0,0 +1,29 @@
/*
* 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/>.
*/
import QtQuick
TextEdit {
id: root
readOnly: true
selectByMouse: true
wrapMode: Text.WordWrap
textFormat: Text.StyledText
}

83
core/ChangesManager.cpp Normal file
View File

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

61
core/ChangesManager.h Normal file
View File

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

23
llmcore/CMakeLists.txt Normal file
View File

@ -0,0 +1,23 @@
add_library(LLMCore STATIC
RequestType.hpp
Provider.hpp
ProvidersManager.hpp ProvidersManager.cpp
ContextData.hpp
PromptTemplate.hpp
PromptTemplateManager.hpp PromptTemplateManager.cpp
RequestConfig.hpp
RequestHandler.hpp RequestHandler.cpp
)
target_link_libraries(LLMCore
PUBLIC
Qt::Core
Qt::Network
QtCreator::Core
QtCreator::Utils
QtCreator::ExtensionSystem
PRIVATE
QodeAssistLogger
)
target_include_directories(LLMCore PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})

View File

@ -21,13 +21,13 @@
#include <QString>
namespace QodeAssist {
namespace QodeAssist::LLMCore {
struct ContextData
{
QString prefix;
QString suffix;
QString instriuctions;
QString systemPrompt;
};
} // namespace QodeAssist
} // namespace QodeAssist::LLMCore

View File

@ -23,17 +23,20 @@
#include <QList>
#include <QString>
#include "QodeAssistData.hpp"
#include "ContextData.hpp"
namespace QodeAssist::Templates {
namespace QodeAssist::LLMCore {
enum class TemplateType { Chat, Fim };
class PromptTemplate
{
public:
virtual ~PromptTemplate() = default;
virtual TemplateType type() const = 0;
virtual QString name() const = 0;
virtual QString promptTemplate() const = 0;
virtual QStringList stopWords() const = 0;
virtual void prepareRequest(QJsonObject &request, const ContextData &context) const = 0;
};
} // namespace QodeAssist::Templates
} // namespace QodeAssist::LLMCore

View File

@ -0,0 +1,90 @@
/*
* 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 "PromptTemplateManager.hpp"
#include "Logger.hpp"
namespace QodeAssist::LLMCore {
PromptTemplateManager &PromptTemplateManager::instance()
{
static PromptTemplateManager instance;
return instance;
}
void PromptTemplateManager::setCurrentFimTemplate(const QString &name)
{
LOG_MESSAGE("Setting current FIM provider to: " + name);
if (!m_fimTemplates.contains(name) || m_fimTemplates[name] == nullptr) {
LOG_MESSAGE("Error to set current FIM template" + name);
return;
}
m_currentFimTemplate = m_fimTemplates[name];
}
PromptTemplate *PromptTemplateManager::getCurrentFimTemplate()
{
if (m_currentFimTemplate == nullptr) {
LOG_MESSAGE("Current fim provider is null, return first");
return m_fimTemplates.first();
}
return m_currentFimTemplate;
}
void PromptTemplateManager::setCurrentChatTemplate(const QString &name)
{
LOG_MESSAGE("Setting current chat provider to: " + name);
if (!m_chatTemplates.contains(name) || m_chatTemplates[name] == nullptr) {
LOG_MESSAGE("Error to set current chat template" + name);
return;
}
m_currentChatTemplate = m_chatTemplates[name];
}
PromptTemplate *PromptTemplateManager::getCurrentChatTemplate()
{
if (m_currentChatTemplate == nullptr) {
LOG_MESSAGE("Current chat provider is null, return first");
return m_chatTemplates.first();
}
return m_currentChatTemplate;
}
QStringList PromptTemplateManager::fimTemplatesNames() const
{
return m_fimTemplates.keys();
}
QStringList PromptTemplateManager::chatTemplatesNames() const
{
return m_chatTemplates.keys();
}
PromptTemplateManager::~PromptTemplateManager()
{
qDeleteAll(m_fimTemplates);
qDeleteAll(m_chatTemplates);
}
} // namespace QodeAssist::LLMCore

View File

@ -22,38 +22,48 @@
#include <QMap>
#include <QString>
#include "templates/PromptTemplate.hpp"
#include "PromptTemplate.hpp"
namespace QodeAssist {
namespace QodeAssist::LLMCore {
class PromptTemplateManager
{
public:
static PromptTemplateManager &instance();
~PromptTemplateManager();
template<typename T>
void registerTemplate()
{
static_assert(std::is_base_of<Templates::PromptTemplate, T>::value,
static_assert(std::is_base_of<PromptTemplate, T>::value,
"T must inherit from PromptTemplate");
T *template_ptr = new T();
QString name = template_ptr->name();
m_templates[name] = template_ptr;
if (template_ptr->type() == TemplateType::Fim) {
m_fimTemplates[name] = template_ptr;
} else if (template_ptr->type() == TemplateType::Chat) {
m_chatTemplates[name] = template_ptr;
}
}
void setCurrentTemplate(const QString &name);
const Templates::PromptTemplate *getCurrentTemplate() const;
QStringList getTemplateNames() const;
void setCurrentFimTemplate(const QString &name);
PromptTemplate *getCurrentFimTemplate();
~PromptTemplateManager();
void setCurrentChatTemplate(const QString &name);
PromptTemplate *getCurrentChatTemplate();
QStringList fimTemplatesNames() const;
QStringList chatTemplatesNames() const;
private:
PromptTemplateManager() = default;
PromptTemplateManager(const PromptTemplateManager &) = delete;
PromptTemplateManager &operator=(const PromptTemplateManager &) = delete;
QMap<QString, Templates::PromptTemplate *> m_templates;
QString m_currentTemplateName;
QMap<QString, PromptTemplate *> m_fimTemplates;
QMap<QString, PromptTemplate *> m_chatTemplates;
PromptTemplate *m_currentFimTemplate;
PromptTemplate *m_currentChatTemplate;
};
} // namespace QodeAssist
} // namespace QodeAssist::LLMCore

View File

@ -20,25 +20,27 @@
#pragma once
#include <QString>
#include "RequestType.hpp"
#include <utils/environment.h>
class QNetworkReply;
class QJsonObject;
namespace QodeAssist::Providers {
namespace QodeAssist::LLMCore {
class LLMProvider
class Provider
{
public:
virtual ~LLMProvider() = default;
virtual ~Provider() = default;
virtual QString name() const = 0;
virtual QString url() const = 0;
virtual QString completionEndpoint() const = 0;
virtual QString chatEndpoint() const = 0;
virtual void prepareRequest(QJsonObject &request) = 0;
virtual void prepareRequest(QJsonObject &request, RequestType type) = 0;
virtual bool handleResponse(QNetworkReply *reply, QString &accumulatedResponse) = 0;
virtual QList<QString> getInstalledModels(const Utils::Environment &env) = 0;
virtual QList<QString> getInstalledModels(const Utils::Environment &env, const QString &url) = 0;
};
} // namespace QodeAssist::Providers
} // namespace QodeAssist::LLMCore

View File

@ -0,0 +1,86 @@
/*
* 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 "ProvidersManager.hpp"
#include "Logger.hpp"
#include <coreplugin/messagemanager.h>
namespace QodeAssist::LLMCore {
ProvidersManager &ProvidersManager::instance()
{
static ProvidersManager instance;
return instance;
}
Provider *ProvidersManager::setCurrentFimProvider(const QString &name)
{
LOG_MESSAGE("Setting current FIM provider to: " + name);
if (!m_providers.contains(name)) {
LOG_MESSAGE("Can't find provider with name: " + name);
return nullptr;
}
m_currentFimProvider = m_providers[name];
return m_currentFimProvider;
}
Provider *ProvidersManager::setCurrentChatProvider(const QString &name)
{
LOG_MESSAGE("Setting current chat provider to: " + name);
if (!m_providers.contains(name)) {
LOG_MESSAGE("Can't find chat provider with name: " + name);
return nullptr;
}
m_currentChatProvider = m_providers[name];
return m_currentChatProvider;
}
Provider *ProvidersManager::getCurrentFimProvider()
{
if (m_currentFimProvider == nullptr) {
LOG_MESSAGE("Current fim provider is null, return first");
return m_providers.first();
}
return m_currentFimProvider;
}
Provider *ProvidersManager::getCurrentChatProvider()
{
if (m_currentChatProvider == nullptr) {
LOG_MESSAGE("Current chat provider is null, return first");
return m_providers.first();
}
return m_currentChatProvider;
}
QStringList ProvidersManager::providersNames() const
{
return m_providers.keys();
}
ProvidersManager::~ProvidersManager()
{
qDeleteAll(m_providers);
}
} // namespace QodeAssist::LLMCore

View File

@ -21,38 +21,41 @@
#include <QString>
#include "providers/LLMProvider.hpp"
#include "Provider.hpp"
namespace QodeAssist {
namespace QodeAssist::LLMCore {
class LLMProvidersManager
class ProvidersManager
{
public:
static LLMProvidersManager &instance();
static ProvidersManager &instance();
~ProvidersManager();
template<typename T>
void registerProvider()
{
static_assert(std::is_base_of<Providers::LLMProvider, T>::value,
"T must inherit from LLMProvider");
static_assert(std::is_base_of<Provider, T>::value, "T must inherit from Provider");
T *provider = new T();
QString name = provider->name();
m_providers[name] = provider;
}
QStringList getProviderNames() const;
void setCurrentProvider(const QString &name);
Providers::LLMProvider *getCurrentProvider();
Provider *setCurrentFimProvider(const QString &name);
Provider *setCurrentChatProvider(const QString &name);
~LLMProvidersManager();
Provider *getCurrentFimProvider();
Provider *getCurrentChatProvider();
QStringList providersNames() const;
private:
LLMProvidersManager() = default;
LLMProvidersManager(const LLMProvidersManager &) = delete;
LLMProvidersManager &operator=(const LLMProvidersManager &) = delete;
ProvidersManager() = default;
ProvidersManager(const ProvidersManager &) = delete;
ProvidersManager &operator=(const ProvidersManager &) = delete;
QMap<QString, Providers::LLMProvider *> m_providers;
QString m_currentProviderName;
QMap<QString, Provider *> m_providers;
Provider *m_currentFimProvider = nullptr;
Provider *m_currentChatProvider = nullptr;
};
} // namespace QodeAssist
} // namespace QodeAssist::LLMCore

40
llmcore/RequestConfig.hpp Normal file
View File

@ -0,0 +1,40 @@
/*
* 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 <QJsonObject>
#include <QUrl>
#include "PromptTemplate.hpp"
#include "Provider.hpp"
#include "RequestType.hpp"
namespace QodeAssist::LLMCore {
struct LLMConfig
{
QUrl url;
Provider *provider;
PromptTemplate *promptTemplate;
QJsonObject providerRequest;
RequestType requestType;
bool multiLineCompletion;
};
} // namespace QodeAssist::LLMCore

153
llmcore/RequestHandler.cpp Normal file
View File

@ -0,0 +1,153 @@
/*
* 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 "RequestHandler.hpp"
#include "Logger.hpp"
#include <QJsonDocument>
#include <QNetworkReply>
namespace QodeAssist::LLMCore {
RequestHandler::RequestHandler(QObject *parent)
: QObject(parent)
, m_manager(new QNetworkAccessManager(this))
{}
void RequestHandler::sendLLMRequest(const LLMConfig &config, const QJsonObject &request)
{
LOG_MESSAGE(QString("Sending request to llm: \nurl: %1\nRequest body:\n%2")
.arg(config.url.toString(),
QString::fromUtf8(
QJsonDocument(config.providerRequest).toJson(QJsonDocument::Indented))));
QNetworkRequest networkRequest(config.url);
prepareNetworkRequest(networkRequest, config.providerRequest);
QNetworkReply *reply = m_manager->post(networkRequest,
QJsonDocument(config.providerRequest).toJson());
if (!reply) {
LOG_MESSAGE("Error: Failed to create network reply");
return;
}
QString requestId = request["id"].toString();
m_activeRequests[requestId] = reply;
connect(reply, &QNetworkReply::readyRead, this, [this, reply, request, config]() {
handleLLMResponse(reply, request, config);
});
connect(reply, &QNetworkReply::finished, this, [this, reply, requestId]() {
reply->deleteLater();
m_activeRequests.remove(requestId);
if (reply->error() != QNetworkReply::NoError) {
LOG_MESSAGE(QString("Error in QodeAssist request: %1").arg(reply->errorString()));
emit requestFinished(requestId, false, reply->errorString());
} else {
LOG_MESSAGE("Request finished successfully");
emit requestFinished(requestId, true, QString());
}
});
}
void RequestHandler::handleLLMResponse(QNetworkReply *reply,
const QJsonObject &request,
const LLMConfig &config)
{
QString &accumulatedResponse = m_accumulatedResponses[reply];
bool isComplete = config.provider->handleResponse(reply, accumulatedResponse);
if (config.requestType == RequestType::Fim) {
if (!config.multiLineCompletion
&& processSingleLineCompletion(reply, request, accumulatedResponse, config)) {
return;
}
if (isComplete) {
auto cleanedCompletion = removeStopWords(accumulatedResponse,
config.promptTemplate->stopWords());
emit completionReceived(cleanedCompletion, request, true);
}
} else if (config.requestType == RequestType::Chat) {
emit completionReceived(accumulatedResponse, request, isComplete);
}
if (isComplete)
m_accumulatedResponses.remove(reply);
}
bool RequestHandler::cancelRequest(const QString &id)
{
if (m_activeRequests.contains(id)) {
QNetworkReply *reply = m_activeRequests[id];
reply->abort();
m_activeRequests.remove(id);
m_accumulatedResponses.remove(reply);
emit requestCancelled(id);
return true;
}
return false;
}
void RequestHandler::prepareNetworkRequest(QNetworkRequest &networkRequest,
const QJsonObject &providerRequest)
{
networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
if (providerRequest.contains("api_key")) {
QString apiKey = providerRequest["api_key"].toString();
networkRequest.setRawHeader("Authorization", QString("Bearer %1").arg(apiKey).toUtf8());
}
}
bool RequestHandler::processSingleLineCompletion(QNetworkReply *reply,
const QJsonObject &request,
const QString &accumulatedResponse,
const LLMConfig &config)
{
int newlinePos = accumulatedResponse.indexOf('\n');
if (newlinePos != -1) {
QString singleLineCompletion = accumulatedResponse.left(newlinePos).trimmed();
singleLineCompletion = removeStopWords(singleLineCompletion,
config.promptTemplate->stopWords());
emit completionReceived(singleLineCompletion, request, true);
m_accumulatedResponses.remove(reply);
reply->abort();
return true;
}
return false;
}
QString RequestHandler::removeStopWords(const QStringView &completion, const QStringList &stopWords)
{
QString filteredCompletion = completion.toString();
for (const QString &stopWord : stopWords) {
filteredCompletion = filteredCompletion.replace(stopWord, "");
}
return filteredCompletion;
}
} // namespace QodeAssist::LLMCore

View File

@ -0,0 +1,63 @@
/*
* 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 <QJsonObject>
#include <QNetworkAccessManager>
#include <QObject>
#include "RequestConfig.hpp"
class QNetworkReply;
namespace QodeAssist::LLMCore {
class RequestHandler : public QObject
{
Q_OBJECT
public:
explicit RequestHandler(QObject *parent = nullptr);
void sendLLMRequest(const LLMConfig &config, const QJsonObject &request);
void handleLLMResponse(QNetworkReply *reply,
const QJsonObject &request,
const LLMConfig &config);
bool cancelRequest(const QString &id);
signals:
void completionReceived(const QString &completion, const QJsonObject &request, bool isComplete);
void requestFinished(const QString &requestId, bool success, const QString &errorString);
void requestCancelled(const QString &id);
private:
QNetworkAccessManager *m_manager;
QMap<QString, QNetworkReply *> m_activeRequests;
QMap<QNetworkReply *, QString> m_accumulatedResponses;
void prepareNetworkRequest(QNetworkRequest &networkRequest, const QJsonObject &providerRequest);
bool processSingleLineCompletion(QNetworkReply *reply,
const QJsonObject &request,
const QString &accumulatedResponse,
const LLMConfig &config);
QString removeStopWords(const QStringView &completion, const QStringList &stopWords);
};
} // namespace QodeAssist::LLMCore

6
llmcore/RequestType.hpp Normal file
View File

@ -0,0 +1,6 @@
#pragma once
namespace QodeAssist::LLMCore {
enum RequestType { Fim, Chat };
}

14
logger/CMakeLists.txt Normal file
View File

@ -0,0 +1,14 @@
add_library(QodeAssistLogger STATIC
Logger.cpp
Logger.hpp
)
target_link_libraries(QodeAssistLogger
PUBLIC
Qt::Core
QtCreator::Core
)
target_include_directories(QodeAssistLogger
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}
)

55
logger/Logger.cpp Normal file
View File

@ -0,0 +1,55 @@
#include "Logger.hpp"
#include <coreplugin/messagemanager.h>
namespace QodeAssist {
Logger &Logger::instance()
{
static Logger instance;
return instance;
}
Logger::Logger()
: m_loggingEnabled(false)
{}
void Logger::setLoggingEnabled(bool enable)
{
m_loggingEnabled = enable;
}
bool Logger::isLoggingEnabled() const
{
return m_loggingEnabled;
}
void Logger::log(const QString &message, bool silent)
{
if (!m_loggingEnabled)
return;
const QString prefixedMessage = QLatin1String("[Qode Assist] ") + message;
if (silent) {
Core::MessageManager::writeSilently(prefixedMessage);
} else {
Core::MessageManager::writeFlashing(prefixedMessage);
}
}
void Logger::logMessages(const QStringList &messages, bool silent)
{
if (!m_loggingEnabled)
return;
QStringList prefixedMessages;
for (const QString &message : messages) {
prefixedMessages << (QLatin1String("[Qode Assist] ") + message);
}
if (silent) {
Core::MessageManager::writeSilently(prefixedMessages);
} else {
Core::MessageManager::writeFlashing(prefixedMessages);
}
}
} // namespace QodeAssist

33
logger/Logger.hpp Normal file
View File

@ -0,0 +1,33 @@
#pragma once
#include <QObject>
#include <QString>
namespace QodeAssist {
class Logger : public QObject
{
Q_OBJECT
public:
static Logger &instance();
void setLoggingEnabled(bool enable);
bool isLoggingEnabled() const;
void log(const QString &message, bool silent = true);
void logMessages(const QStringList &messages, bool silent = true);
private:
Logger();
~Logger() = default;
Logger(const Logger &) = delete;
Logger &operator=(const Logger &) = delete;
bool m_loggingEnabled;
};
#define LOG_MESSAGE(msg) QodeAssist::Logger::instance().log(msg)
#define LOG_MESSAGES(msgs) QodeAssist::Logger::instance().logMessages(msgs)
} // namespace QodeAssist

View File

@ -25,9 +25,8 @@
#include <QJsonObject>
#include <QNetworkReply>
#include "PromptTemplateManager.hpp"
#include "QodeAssistSettings.hpp"
#include "QodeAssistUtils.hpp"
#include "logger/Logger.hpp"
#include "settings/PresetPromptsSettings.hpp"
namespace QodeAssist::Providers {
@ -48,27 +47,43 @@ QString LMStudioProvider::completionEndpoint() const
return "/v1/chat/completions";
}
void LMStudioProvider::prepareRequest(QJsonObject &request)
QString LMStudioProvider::chatEndpoint() const
{
const auto &currentTemplate = PromptTemplateManager::instance().getCurrentTemplate();
return "/v1/chat/completions";
}
void LMStudioProvider::prepareRequest(QJsonObject &request, LLMCore::RequestType type)
{
auto &promptSettings = Settings::presetPromptsSettings();
auto settings = promptSettings.getSettings(type);
QJsonArray messages;
if (request.contains("system")) {
QJsonObject systemMessage{{"role", "system"},
{"content", request.take("system").toString()}};
messages.append(systemMessage);
}
if (request.contains("prompt")) {
QJsonArray messages{
{QJsonObject{{"role", "user"}, {"content", request.take("prompt").toString()}}}};
QJsonObject userMessage{{"role", "user"}, {"content", request.take("prompt").toString()}};
messages.append(userMessage);
}
if (!messages.isEmpty()) {
request["messages"] = std::move(messages);
}
request["max_tokens"] = settings().maxTokens();
request["temperature"] = settings().temperature();
request["stop"] = QJsonArray::fromStringList(currentTemplate->stopWords());
if (settings().useTopP())
request["top_p"] = settings().topP();
if (settings().useTopK())
request["top_k"] = settings().topK();
if (settings().useFrequencyPenalty())
request["frequency_penalty"] = settings().frequencyPenalty();
if (settings().usePresencePenalty())
request["presence_penalty"] = settings().presencePenalty();
request["max_tokens"] = settings.maxTokens;
request["temperature"] = settings.temperature;
if (settings.useTopP)
request["top_p"] = settings.topP;
if (settings.useTopK)
request["top_k"] = settings.topK;
if (settings.useFrequencyPenalty)
request["frequency_penalty"] = settings.frequencyPenalty;
if (settings.usePresencePenalty)
request["presence_penalty"] = settings.presencePenalty;
}
bool LMStudioProvider::handleResponse(QNetworkReply *reply, QString &accumulatedResponse)
@ -112,11 +127,12 @@ bool LMStudioProvider::handleResponse(QNetworkReply *reply, QString &accumulated
return isComplete;
}
QList<QString> LMStudioProvider::getInstalledModels(const Utils::Environment &env)
QList<QString> LMStudioProvider::getInstalledModels(const Utils::Environment &env,
const QString &url)
{
QList<QString> models;
QNetworkAccessManager manager;
QNetworkRequest request(QUrl(url() + "/v1/models"));
QNetworkRequest request(QString("%1%2").arg(url, "/v1/models"));
QNetworkReply *reply = manager.get(request);
@ -136,7 +152,7 @@ QList<QString> LMStudioProvider::getInstalledModels(const Utils::Environment &en
models.append(modelId);
}
} else {
logMessage(QString("Error fetching models: %1").arg(reply->errorString()));
LOG_MESSAGE(QString("Error fetching models: %1").arg(reply->errorString()));
}
reply->deleteLater();

View File

@ -19,11 +19,11 @@
#pragma once
#include "LLMProvider.hpp"
#include "llmcore/Provider.hpp"
namespace QodeAssist::Providers {
class LMStudioProvider : public LLMProvider
class LMStudioProvider : public LLMCore::Provider
{
public:
LMStudioProvider();
@ -31,9 +31,10 @@ public:
QString name() const override;
QString url() const override;
QString completionEndpoint() const override;
void prepareRequest(QJsonObject &request) override;
QString chatEndpoint() const override;
void prepareRequest(QJsonObject &request, LLMCore::RequestType type) override;
bool handleResponse(QNetworkReply *reply, QString &accumulatedResponse) override;
QList<QString> getInstalledModels(const Utils::Environment &env) override;
QList<QString> getInstalledModels(const Utils::Environment &env, const QString &url) override;
};
} // namespace QodeAssist::Providers

View File

@ -25,9 +25,9 @@
#include <QNetworkReply>
#include <QtCore/qeventloop.h>
#include "PromptTemplateManager.hpp"
#include "QodeAssistSettings.hpp"
#include "QodeAssistUtils.hpp"
#include "llmcore/PromptTemplateManager.hpp"
#include "logger/Logger.hpp"
#include "settings/PresetPromptsSettings.hpp"
namespace QodeAssist::Providers {
@ -48,58 +48,87 @@ QString OllamaProvider::completionEndpoint() const
return "/api/generate";
}
void OllamaProvider::prepareRequest(QJsonObject &request)
QString OllamaProvider::chatEndpoint() const
{
auto currentTemplate = PromptTemplateManager::instance().getCurrentTemplate();
return "/api/chat";
}
void OllamaProvider::prepareRequest(QJsonObject &request, LLMCore::RequestType type)
{
auto &promptSettings = Settings::presetPromptsSettings();
auto settings = promptSettings.getSettings(type);
QJsonObject options;
options["num_predict"] = settings().maxTokens();
options["keep_alive"] = settings().ollamaLivetime();
options["temperature"] = settings().temperature();
options["stop"] = QJsonArray::fromStringList(currentTemplate->stopWords());
if (settings().useTopP())
options["top_p"] = settings().topP();
if (settings().useTopK())
options["top_k"] = settings().topK();
if (settings().useFrequencyPenalty())
options["frequency_penalty"] = settings().frequencyPenalty();
if (settings().usePresencePenalty())
options["presence_penalty"] = settings().presencePenalty();
options["num_predict"] = settings.maxTokens;
options["temperature"] = settings.temperature;
if (settings.useTopP)
options["top_p"] = settings.topP;
if (settings.useTopK)
options["top_k"] = settings.topK;
if (settings.useFrequencyPenalty)
options["frequency_penalty"] = settings.frequencyPenalty;
if (settings.usePresencePenalty)
options["presence_penalty"] = settings.presencePenalty;
request["options"] = options;
request["keep_alive"] = settings.ollamaLivetime;
}
bool OllamaProvider::handleResponse(QNetworkReply *reply, QString &accumulatedResponse)
{
QString endpoint = reply->url().path();
bool isComplete = false;
while (reply->canReadLine()) {
QByteArray line = reply->readLine().trimmed();
if (line.isEmpty()) {
continue;
}
QJsonDocument jsonResponse = QJsonDocument::fromJson(line);
if (jsonResponse.isNull()) {
qWarning() << "Invalid JSON response from Ollama:" << line;
QJsonDocument doc = QJsonDocument::fromJson(line);
if (doc.isNull()) {
LOG_MESSAGE("Invalid JSON response from Ollama: " + QString::fromUtf8(line));
continue;
}
QJsonObject responseObj = jsonResponse.object();
if (responseObj.contains("response")) {
QString completion = responseObj["response"].toString();
accumulatedResponse += completion;
QJsonObject responseObj = doc.object();
if (responseObj.contains("error")) {
QString errorMessage = responseObj["error"].toString();
LOG_MESSAGE("Error in Ollama response: " + errorMessage);
return false;
}
if (responseObj["done"].toBool()) {
if (endpoint == completionEndpoint()) {
if (responseObj.contains("response")) {
QString completion = responseObj["response"].toString();
accumulatedResponse += completion;
}
} else if (endpoint == chatEndpoint()) {
if (responseObj.contains("message")) {
QJsonObject message = responseObj["message"].toObject();
if (message.contains("content")) {
QString content = message["content"].toString();
accumulatedResponse += content;
}
}
} else {
LOG_MESSAGE("Unknown endpoint: " + endpoint);
}
if (responseObj.contains("done") && responseObj["done"].toBool()) {
isComplete = true;
break;
}
}
return isComplete;
}
QList<QString> OllamaProvider::getInstalledModels(const Utils::Environment &env)
QList<QString> OllamaProvider::getInstalledModels(const Utils::Environment &env, const QString &url)
{
QList<QString> models;
QNetworkAccessManager manager;
QNetworkRequest request(QUrl(url() + "/api/tags"));
QNetworkRequest request(QString("%1%2").arg(url, "/api/tags"));
QNetworkReply *reply = manager.get(request);
QEventLoop loop;
@ -118,7 +147,7 @@ QList<QString> OllamaProvider::getInstalledModels(const Utils::Environment &env)
models.append(modelName);
}
} else {
logMessage(QString("Error fetching models: %1").arg(reply->errorString()));
LOG_MESSAGE(QString("Error fetching models: %1").arg(reply->errorString()));
}
reply->deleteLater();

View File

@ -19,11 +19,11 @@
#pragma once
#include "LLMProvider.hpp"
#include "llmcore/Provider.hpp"
namespace QodeAssist::Providers {
class OllamaProvider : public LLMProvider
class OllamaProvider : public LLMCore::Provider
{
public:
OllamaProvider();
@ -31,9 +31,10 @@ public:
QString name() const override;
QString url() const override;
QString completionEndpoint() const override;
void prepareRequest(QJsonObject &request) override;
QString chatEndpoint() const override;
void prepareRequest(QJsonObject &request, LLMCore::RequestType type) override;
bool handleResponse(QNetworkReply *reply, QString &accumulatedResponse) override;
QList<QString> getInstalledModels(const Utils::Environment &env) override;
QList<QString> getInstalledModels(const Utils::Environment &env, const QString &url) override;
};
} // namespace QodeAssist::Providers

View File

@ -24,8 +24,7 @@
#include <QJsonObject>
#include <QNetworkReply>
#include "PromptTemplateManager.hpp"
#include "QodeAssistSettings.hpp"
#include "settings/PresetPromptsSettings.hpp"
namespace QodeAssist::Providers {
@ -46,29 +45,44 @@ QString OpenAICompatProvider::completionEndpoint() const
return "/v1/chat/completions";
}
void OpenAICompatProvider::prepareRequest(QJsonObject &request)
QString OpenAICompatProvider::chatEndpoint() const
{
const auto &currentTemplate = PromptTemplateManager::instance().getCurrentTemplate();
return "/v1/chat/completions";
}
void OpenAICompatProvider::prepareRequest(QJsonObject &request, LLMCore::RequestType type)
{
auto &promptSettings = Settings::presetPromptsSettings();
auto settings = promptSettings.getSettings(type);
QJsonArray messages;
if (request.contains("system")) {
QJsonObject systemMessage{{"role", "system"},
{"content", request.take("system").toString()}};
messages.append(systemMessage);
}
if (request.contains("prompt")) {
QJsonArray messages{
{QJsonObject{{"role", "user"}, {"content", request.take("prompt").toString()}}}};
QJsonObject userMessage{{"role", "user"}, {"content", request.take("prompt").toString()}};
messages.append(userMessage);
}
if (!messages.isEmpty()) {
request["messages"] = std::move(messages);
}
request["max_tokens"] = settings().maxTokens();
request["temperature"] = settings().temperature();
request["stop"] = QJsonArray::fromStringList(currentTemplate->stopWords());
if (settings().useTopP())
request["top_p"] = settings().topP();
if (settings().useTopK())
request["top_k"] = settings().topK();
if (settings().useFrequencyPenalty())
request["frequency_penalty"] = settings().frequencyPenalty();
if (settings().usePresencePenalty())
request["presence_penalty"] = settings().presencePenalty();
request["max_tokens"] = settings.maxTokens;
request["temperature"] = settings.temperature;
if (settings.useTopP)
request["top_p"] = settings.topP;
if (settings.useTopK)
request["top_k"] = settings.topK;
if (settings.useFrequencyPenalty)
request["frequency_penalty"] = settings.frequencyPenalty;
if (settings.usePresencePenalty)
request["presence_penalty"] = settings.presencePenalty;
const QString &apiKey = settings().apiKey.value();
const QString &apiKey = settings.apiKey;
if (!apiKey.isEmpty()) {
request["api_key"] = apiKey;
}
@ -115,7 +129,8 @@ bool OpenAICompatProvider::handleResponse(QNetworkReply *reply, QString &accumul
return isComplete;
}
QList<QString> OpenAICompatProvider::getInstalledModels(const Utils::Environment &env)
QList<QString> OpenAICompatProvider::getInstalledModels(const Utils::Environment &env,
const QString &url)
{
return QStringList();
}

View File

@ -19,11 +19,11 @@
#pragma once
#include "LLMProvider.hpp"
#include "llmcore/Provider.hpp"
namespace QodeAssist::Providers {
class OpenAICompatProvider : public LLMProvider
class OpenAICompatProvider : public LLMCore::Provider
{
public:
OpenAICompatProvider();
@ -31,9 +31,10 @@ public:
QString name() const override;
QString url() const override;
QString completionEndpoint() const override;
void prepareRequest(QJsonObject &request) override;
QString chatEndpoint() const override;
void prepareRequest(QJsonObject &request, LLMCore::RequestType type) override;
bool handleResponse(QNetworkReply *reply, QString &accumulatedResponse) override;
QList<QString> getInstalledModels(const Utils::Environment &env) override;
QList<QString> getInstalledModels(const Utils::Environment &env, const QString &url) override;
};
} // namespace QodeAssist::Providers

View File

@ -26,8 +26,9 @@
#include <coreplugin/coreconstants.h>
#include <coreplugin/icontext.h>
#include <coreplugin/icore.h>
#include <coreplugin/messagemanager.h>
#include <coreplugin/modemanager.h>
#include <coreplugin/statusbarmanager.h>
#include <extensionsystem/iplugin.h>
#include <languageclient/languageclientmanager.h>
@ -38,16 +39,23 @@
#include <texteditor/texteditor.h>
#include <utils/icon.h>
#include "LLMProvidersManager.hpp"
#include "PromptTemplateManager.hpp"
#include "QodeAssistClient.hpp"
#include "chat/ChatOutputPane.h"
#include "chat/NavigationPanel.hpp"
#include "llmcore/PromptTemplateManager.hpp"
#include "llmcore/ProvidersManager.hpp"
#include "providers/LMStudioProvider.hpp"
#include "providers/OllamaProvider.hpp"
#include "providers/OpenAICompatProvider.hpp"
#include "templates/CodeLLamaTemplate.hpp"
#include "templates/CodeQwenChat.hpp"
#include "templates/DeepSeekCoderV2.hpp"
#include "templates/StarCoder2Template.hpp"
#include "templates/CodeLlamaChat.hpp"
#include "templates/CodeLlamaFim.hpp"
#include "templates/CustomFimTemplate.hpp"
#include "templates/DeepSeekCoderChat.hpp"
#include "templates/DeepSeekCoderFim.hpp"
#include "templates/QwenChat.hpp"
#include "templates/StarCoder2Fim.hpp"
#include "templates/StarCoderChat.hpp"
using namespace Utils;
using namespace Core;
@ -67,21 +75,28 @@ public:
~QodeAssistPlugin() final
{
delete m_qodeAssistClient;
delete m_chatOutputPane;
delete m_navigationPanel;
}
void initialize() final
{
auto &providerManager = LLMProvidersManager::instance();
auto &providerManager = LLMCore::ProvidersManager::instance();
providerManager.registerProvider<Providers::OllamaProvider>();
providerManager.registerProvider<Providers::LMStudioProvider>();
providerManager.registerProvider<Providers::OpenAICompatProvider>();
auto &templateManager = PromptTemplateManager::instance();
templateManager.registerTemplate<Templates::CodeLLamaTemplate>();
templateManager.registerTemplate<Templates::StarCoder2Template>();
templateManager.registerTemplate<Templates::CodeQwenChatTemplate>();
templateManager.registerTemplate<Templates::DeepSeekCoderV2Template>();
auto &templateManager = LLMCore::PromptTemplateManager::instance();
templateManager.registerTemplate<Templates::CodeLlamaFim>();
templateManager.registerTemplate<Templates::StarCoder2Fim>();
templateManager.registerTemplate<Templates::DeepSeekCoderFim>();
templateManager.registerTemplate<Templates::CustomTemplate>();
templateManager.registerTemplate<Templates::DeepSeekCoderChat>();
templateManager.registerTemplate<Templates::CodeLlamaChat>();
templateManager.registerTemplate<Templates::QwenChat>();
templateManager.registerTemplate<Templates::LlamaChat>();
templateManager.registerTemplate<Templates::StarCoderChat>();
Utils::Icon QCODEASSIST_ICON(
{{":/resources/images/qoderassist-icon.png", Utils::Theme::IconsBaseColor}});
@ -106,6 +121,9 @@ public:
auto toggleButton = new QToolButton;
toggleButton->setDefaultAction(requestAction.contextAction());
StatusBarManager::addStatusBarWidget(toggleButton, StatusBarManager::RightCorner);
m_chatOutputPane = new Chat::ChatOutputPane(this);
m_navigationPanel = new Chat::NavigationPanel();
}
void extensionsInitialized() final
@ -139,6 +157,8 @@ public:
private:
QPointer<QodeAssistClient> m_qodeAssistClient;
QPointer<Chat::ChatOutputPane> m_chatOutputPane;
QPointer<Chat::NavigationPanel> m_navigationPanel;
};
} // namespace QodeAssist::Internal

View File

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

View File

@ -0,0 +1,16 @@
{
"max_tokens": 150,
"messages": [
{
"content": "{{QODE_INSTRUCTIONS}}\n### Instruction:{{QODE_PREFIX}}{{QODE_SUFFIX}} ### Response:\n",
"role": "user"
}
],
"stop": [
"### Instruction:",
"### Response:",
"\n\n### "
],
"stream": true,
"temperature": 0.2
}

View File

@ -17,40 +17,15 @@
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
*/
#include "LLMProvidersManager.hpp"
#pragma once
#include <QCoreApplication>
namespace QodeAssist {
LLMProvidersManager &LLMProvidersManager::instance()
struct Tr
{
static LLMProvidersManager instance;
return instance;
}
QStringList LLMProvidersManager::getProviderNames() const
{
return m_providers.keys();
}
void LLMProvidersManager::setCurrentProvider(const QString &name)
{
if (m_providers.contains(name)) {
m_currentProviderName = name;
}
}
Providers::LLMProvider *LLMProvidersManager::getCurrentProvider()
{
if (m_currentProviderName.isEmpty()) {
return nullptr;
}
return m_providers[m_currentProviderName];
}
LLMProvidersManager::~LLMProvidersManager()
{
qDeleteAll(m_providers);
}
Q_DECLARE_TR_FUNCTIONS(QtC::QodeAssist)
};
} // namespace QodeAssist

20
settings/CMakeLists.txt Normal file
View File

@ -0,0 +1,20 @@
add_library(QodeAssistSettings STATIC
GeneralSettings.hpp GeneralSettings.cpp
ContextSettings.hpp ContextSettings.cpp
CustomPromptSettings.hpp CustomPromptSettings.cpp
PresetPromptsSettings.hpp PresetPromptsSettings.cpp
SettingsUtils.hpp
SettingsConstants.hpp
)
target_link_libraries(QodeAssistSettings
PUBLIC
Qt::Core
Qt::Network
QtCreator::Core
QtCreator::Utils
QodeAssistLogger
PRIVATE
LLMCore
)
target_include_directories(QodeAssistSettings PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})

View File

@ -0,0 +1,179 @@
/*
* 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 "ContextSettings.hpp"
#include <QMessageBox>
#include <coreplugin/dialogs/ioptionspage.h>
#include <coreplugin/icore.h>
#include <utils/layoutbuilder.h>
#include "SettingsConstants.hpp"
namespace QodeAssist::Settings {
ContextSettings &contextSettings()
{
static ContextSettings settings;
return settings;
}
ContextSettings::ContextSettings()
{
setAutoApply(false);
setDisplayName(Tr::tr("Context"));
readFullFile.setSettingsKey(Constants::READ_FULL_FILE);
readFullFile.setLabelText(Tr::tr("Read Full File"));
readFullFile.setDefaultValue(false);
readStringsBeforeCursor.setSettingsKey(Constants::READ_STRINGS_BEFORE_CURSOR);
readStringsBeforeCursor.setLabelText(Tr::tr("Read Strings Before Cursor"));
readStringsBeforeCursor.setRange(0, 10000);
readStringsBeforeCursor.setDefaultValue(50);
readStringsAfterCursor.setSettingsKey(Constants::READ_STRINGS_AFTER_CURSOR);
readStringsAfterCursor.setLabelText(Tr::tr("Read Strings After Cursor"));
readStringsAfterCursor.setRange(0, 10000);
readStringsAfterCursor.setDefaultValue(30);
useFilePathInContext.setSettingsKey(Constants::USE_FILE_PATH_IN_CONTEXT);
useFilePathInContext.setDefaultValue(false);
useFilePathInContext.setLabelText(Tr::tr("Use File Path in Context"));
useSystemPrompt.setSettingsKey(Constants::USE_SYSTEM_PROMPT);
useSystemPrompt.setDefaultValue(true);
useSystemPrompt.setLabelText(Tr::tr("Use System Prompt"));
systemPrompt.setSettingsKey(Constants::SYSTEM_PROMPT);
systemPrompt.setDisplayStyle(Utils::StringAspect::TextEditDisplay);
systemPrompt.setDefaultValue(
"You are an expert C++, Qt, and QML code completion AI. Your task is to provide accurate "
"and "
"contextually appropriate code suggestions. Focus on completing the code in a way that "
"follows best practices, is efficient, and matches the surrounding code style. Prioritize "
"Qt and QML-specific completions when appropriate. Avoid adding comments or explanations "
"in your completions.");
useChatSystemPrompt.setSettingsKey(Constants::USE_CHAT_SYSTEM_PROMPT);
useChatSystemPrompt.setDefaultValue(true);
useChatSystemPrompt.setLabelText(Tr::tr("Use System Prompt for chat"));
chatSystemPrompt.setSettingsKey(Constants::CHAT_SYSTEM_PROMPT);
chatSystemPrompt.setDisplayStyle(Utils::StringAspect::TextEditDisplay);
chatSystemPrompt.setDefaultValue(
"You are an advanced AI assistant specializing in C++, Qt, and QML development. Your role "
"is "
"to provide helpful, accurate, and detailed responses to questions about coding, "
"debugging, "
"and best practices in these technologies. Offer clear explanations, code examples when "
"appropriate, and guidance on Qt Creator usage. Always prioritize officially recommended "
"Qt "
"and C++ practices. If you're unsure about something, state it clearly and suggest where "
"the "
"user might find more information.");
resetToDefaults.m_buttonText = Tr::tr("Reset Page to Defaults");
useProjectChangesCache.setSettingsKey(Constants::USE_PROJECT_CHANGES_CACHE);
useProjectChangesCache.setDefaultValue(true);
useProjectChangesCache.setLabelText(Tr::tr("Use Project Changes Cache"));
maxChangesCacheSize.setSettingsKey(Constants::MAX_CHANGES_CACHE_SIZE);
maxChangesCacheSize.setLabelText(Tr::tr("Max Changes Cache Size"));
maxChangesCacheSize.setRange(2, 1000);
maxChangesCacheSize.setDefaultValue(20);
readSettings();
readStringsAfterCursor.setEnabled(!readFullFile());
readStringsBeforeCursor.setEnabled(!readFullFile());
systemPrompt.setEnabled(useSystemPrompt());
setupConnection();
setLayouter([this]() {
using namespace Layouting;
return Column{Row{Stretch{1}, resetToDefaults},
Group{title(Tr::tr("AI Suggestions Context")),
Column{Row{readFullFile, Stretch{1}},
Row{readStringsBeforeCursor, Stretch{1}},
Row{readStringsAfterCursor, Stretch{1}},
useFilePathInContext,
useSystemPrompt,
systemPrompt,
useProjectChangesCache,
Row{maxChangesCacheSize, Stretch{1}},
Stretch{1}}},
Space{16},
Group{title(Tr::tr("AI Chat Context")),
Column{useChatSystemPrompt, chatSystemPrompt}}};
});
}
void ContextSettings::setupConnection()
{
connect(&readFullFile, &Utils::BoolAspect::volatileValueChanged, this, [this]() {
readStringsAfterCursor.setEnabled(!readFullFile.volatileValue());
readStringsBeforeCursor.setEnabled(!readFullFile.volatileValue());
});
connect(&useSystemPrompt, &Utils::BoolAspect::volatileValueChanged, this, [this]() {
systemPrompt.setEnabled(useSystemPrompt.volatileValue());
});
connect(&resetToDefaults, &ButtonAspect::clicked, this, &ContextSettings::resetPageToDefaults);
}
void ContextSettings::resetPageToDefaults()
{
QMessageBox::StandardButton reply;
reply = QMessageBox::question(
Core::ICore::dialogParent(),
Tr::tr("Reset Settings"),
Tr::tr("Are you sure you want to reset all settings to default values?"),
QMessageBox::Yes | QMessageBox::No);
if (reply == QMessageBox::Yes) {
resetAspect(readFullFile);
resetAspect(readStringsBeforeCursor);
resetAspect(readStringsAfterCursor);
resetAspect(useFilePathInContext);
resetAspect(useSystemPrompt);
resetAspect(systemPrompt);
resetAspect(useChatSystemPrompt);
resetAspect(chatSystemPrompt);
}
}
class ContextSettingsPage : public Core::IOptionsPage
{
public:
ContextSettingsPage()
{
setId(Constants::QODE_ASSIST_CONTEXT_SETTINGS_PAGE_ID);
setDisplayName(Tr::tr("Context"));
setCategory(Constants::QODE_ASSIST_GENERAL_OPTIONS_CATEGORY);
setDisplayCategory(Constants::QODE_ASSIST_GENERAL_OPTIONS_DISPLAY_CATEGORY);
setCategoryIconPath(":/resources/images/qoderassist-icon.png");
setSettingsProvider([] { return &contextSettings(); });
}
};
const ContextSettingsPage contextSettingsPage;
} // namespace QodeAssist::Settings

View File

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

View File

@ -0,0 +1,199 @@
/*
* Copyright (C) 2024 Petr Mironychev
*
* This file is part of QodeAssist.
*
* QodeAssist is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* QodeAssist is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
*/
#include "CustomPromptSettings.hpp"
#include <QFileDialog>
#include <QJsonParseError>
#include <QMessageBox>
#include <coreplugin/dialogs/ioptionspage.h>
#include <coreplugin/icore.h>
#include <utils/layoutbuilder.h>
#include "SettingsConstants.hpp"
namespace QodeAssist::Settings {
CustomPromptSettings &customPromptSettings()
{
static CustomPromptSettings settings;
return settings;
}
CustomPromptSettings::CustomPromptSettings()
{
setAutoApply(false);
setDisplayName(Tr::tr("Custom Prompt"));
customJsonLabel.setLabelText("Custom JSON Template:");
customJsonLabel.setDisplayStyle(Utils::StringAspect::LabelDisplay);
customJsonLegend.setLabelText(Tr::tr(R"(Prompt components:
- model is set on General Page
- {{QODE_INSTRUCTIONS}}: Placeholder for specific instructions or context.
- {{QODE_PREFIX}}: Will be replaced with the actual code before the cursor.
- {{QODE_SUFFIX}}: Will be replaced with the actual code after the cursor.
)"));
customJsonTemplate.setSettingsKey(Constants::CUSTOM_JSON_TEMPLATE);
customJsonTemplate.setDisplayStyle(Utils::StringAspect::TextEditDisplay);
customJsonTemplate.setDefaultValue(R"({
"prompt": "{{QODE_INSTRUCTIONS}}<fim_prefix>{{QODE_PREFIX}}<fim_suffix>{{QODE_SUFFIX}}<fim_middle>",
"options": {
"temperature": 0.7,
"top_p": 0.95,
"top_k": 40,
"num_predict": 100,
"stop": [
"<|endoftext|>",
"<file_sep>",
"<fim_prefix>",
"<fim_suffix>",
"<fim_middle>"
],
"frequency_penalty": 0,
"presence_penalty": 0
},
"stream": true
})");
saveCustomTemplateButton.m_buttonText = (Tr::tr("Save Custom Template to JSON"));
loadCustomTemplateButton.m_buttonText = (Tr::tr("Load Custom Template from JSON"));
resetToDefaults.m_buttonText = Tr::tr("Reset Page to Defaults");
readSettings();
setupConnection();
setLayouter([this]() {
using namespace Layouting;
return Column{Group{title(Tr::tr("Custom prompt for FIM model")),
Column{Row{customJsonLabel, Stretch{1}, resetToDefaults},
Row{customJsonTemplate,
Column{saveCustomTemplateButton,
loadCustomTemplateButton,
customJsonLegend,
Stretch{1}}}}}};
});
}
void CustomPromptSettings::setupConnection()
{
connect(&resetToDefaults,
&ButtonAspect::clicked,
this,
&CustomPromptSettings::resetSettingsToDefaults);
connect(&saveCustomTemplateButton,
&ButtonAspect::clicked,
this,
&CustomPromptSettings::saveCustomTemplate);
connect(&loadCustomTemplateButton,
&ButtonAspect::clicked,
this,
&CustomPromptSettings::loadCustomTemplate);
}
void CustomPromptSettings::resetSettingsToDefaults()
{
QMessageBox::StandardButton reply;
reply = QMessageBox::question(
Core::ICore::dialogParent(),
Tr::tr("Reset Settings"),
Tr::tr("Are you sure you want to reset all settings to default values?"),
QMessageBox::Yes | QMessageBox::No);
if (reply == QMessageBox::Yes) {
resetAspect(customJsonTemplate);
}
}
void CustomPromptSettings::saveCustomTemplate()
{
QString fileName = QFileDialog::getSaveFileName(nullptr,
Tr::tr("Save JSON Template"),
QString(),
Tr::tr("JSON Files (*.json)"));
if (fileName.isEmpty())
return;
QFile file(fileName);
if (file.open(QIODevice::WriteOnly | QIODevice::Text)) {
QTextStream out(&file);
out << customJsonTemplate.value();
file.close();
QMessageBox::information(nullptr,
Tr::tr("Save Successful"),
Tr::tr("JSON template has been saved successfully."));
} else {
QMessageBox::critical(nullptr,
Tr::tr("Save Failed"),
Tr::tr("Failed to save JSON template."));
}
}
void CustomPromptSettings::loadCustomTemplate()
{
QString fileName = QFileDialog::getOpenFileName(nullptr,
Tr::tr("Load JSON Template"),
QString(),
Tr::tr("JSON Files (*.json)"));
if (fileName.isEmpty())
return;
QFile file(fileName);
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
QTextStream in(&file);
QString jsonContent = in.readAll();
file.close();
QJsonParseError parseError;
QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonContent.toUtf8(), &parseError);
if (parseError.error == QJsonParseError::NoError) {
customJsonTemplate.setVolatileValue(jsonContent);
QMessageBox::information(nullptr,
Tr::tr("Load Successful"),
Tr::tr("JSON template has been loaded successfully."));
} else {
QMessageBox::critical(nullptr,
Tr::tr("Invalid JSON"),
Tr::tr("The selected file contains invalid JSON."));
}
} else {
QMessageBox::critical(nullptr,
Tr::tr("Load Failed"),
Tr::tr("Failed to load JSON template."));
}
}
class CustomPromptSettingsPage : public Core::IOptionsPage
{
public:
CustomPromptSettingsPage()
{
setId(Constants::QODE_ASSIST_CUSTOM_PROMPT_SETTINGS_PAGE_ID);
setDisplayName(Tr::tr("Custom Prompt"));
setCategory(Constants::QODE_ASSIST_GENERAL_OPTIONS_CATEGORY);
setSettingsProvider([] { return &customPromptSettings(); });
}
};
const CustomPromptSettingsPage customPromptSettingsPage;
} // namespace QodeAssist::Settings

View File

@ -0,0 +1,48 @@
/*
* Copyright (C) 2024 Petr Mironychev
*
* This file is part of QodeAssist.
*
* QodeAssist is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* QodeAssist is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include "SettingsUtils.hpp"
#include <utils/aspects.h>
namespace QodeAssist::Settings {
class CustomPromptSettings : public Utils::AspectContainer
{
public:
CustomPromptSettings();
Utils::StringAspect customJsonLabel{this};
Utils::StringAspect customJsonTemplate{this};
Utils::StringAspect customJsonLegend{this};
ButtonAspect saveCustomTemplateButton{this};
ButtonAspect loadCustomTemplateButton{this};
ButtonAspect resetToDefaults{this};
private:
void setupConnection();
void resetSettingsToDefaults();
void saveCustomTemplate();
void loadCustomTemplate();
};
CustomPromptSettings &customPromptSettings();
} // namespace QodeAssist::Settings

View File

@ -0,0 +1,403 @@
/*
* Copyright (C) 2024 Petr Mironychev
*
* This file is part of QodeAssist.
*
* QodeAssist is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* QodeAssist is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
*/
#include "GeneralSettings.hpp"
#include <QInputDialog>
#include <QMessageBox>
#include <coreplugin/dialogs/ioptionspage.h>
#include <coreplugin/icore.h>
#include <utils/layoutbuilder.h>
#include <utils/utilsicons.h>
#include "Logger.hpp"
#include "PromptTemplateManager.hpp"
#include "Provider.hpp"
#include "ProvidersManager.hpp"
#include "SettingsConstants.hpp"
#include "SettingsUtils.hpp"
namespace QodeAssist::Settings {
GeneralSettings &generalSettings()
{
static GeneralSettings settings;
return settings;
}
GeneralSettings::GeneralSettings()
{
setAutoApply(false);
setDisplayName(Tr::tr("General"));
enableQodeAssist.setSettingsKey(Constants::ENABLE_QODE_ASSIST);
enableQodeAssist.setLabelText(Tr::tr("Enable Qode Assist"));
enableQodeAssist.setDefaultValue(true);
enableAutoComplete.setSettingsKey(Constants::ENABLE_AUTO_COMPLETE);
enableAutoComplete.setLabelText(Tr::tr("Enable Auto Complete"));
enableAutoComplete.setDefaultValue(true);
enableLogging.setSettingsKey(Constants::ENABLE_LOGGING);
enableLogging.setLabelText(Tr::tr("Enable Logging"));
enableLogging.setDefaultValue(false);
multiLineCompletion.setSettingsKey(Constants::MULTILINE_COMPLETION);
multiLineCompletion.setDefaultValue(false);
multiLineCompletion.setLabelText(Tr::tr("Enable Multiline Completion(experimental)"));
startSuggestionTimer.setSettingsKey(Constants::START_SUGGESTION_TIMER);
startSuggestionTimer.setLabelText(Tr::tr("with delay(ms)"));
startSuggestionTimer.setRange(10, 10000);
startSuggestionTimer.setDefaultValue(500);
autoCompletionCharThreshold.setSettingsKey(Constants::AUTO_COMPLETION_CHAR_THRESHOLD);
autoCompletionCharThreshold.setLabelText(Tr::tr("AI suggestion triggers after typing"));
autoCompletionCharThreshold.setToolTip(
Tr::tr("The number of characters that need to be typed within the typing interval "
"before an AI suggestion request is sent."));
autoCompletionCharThreshold.setRange(0, 10);
autoCompletionCharThreshold.setDefaultValue(0);
autoCompletionTypingInterval.setSettingsKey(Constants::AUTO_COMPLETION_TYPING_INTERVAL);
autoCompletionTypingInterval.setLabelText(Tr::tr("character(s) within(ms)"));
autoCompletionTypingInterval.setToolTip(
Tr::tr("The time window (in milliseconds) during which the character threshold "
"must be met to trigger an AI suggestion request."));
autoCompletionTypingInterval.setRange(500, 5000);
autoCompletionTypingInterval.setDefaultValue(2000);
llmProviders.setSettingsKey(Constants::LLM_PROVIDERS);
llmProviders.setDisplayName(Tr::tr("AI Suggest Provider:"));
llmProviders.setDisplayStyle(Utils::SelectionAspect::DisplayStyle::ComboBox);
url.setSettingsKey(Constants::URL);
url.setLabelText(Tr::tr("URL:"));
url.setDisplayStyle(Utils::StringAspect::LineEditDisplay);
endPoint.setSettingsKey(Constants::END_POINT);
endPoint.setLabelText(Tr::tr("FIM Endpoint:"));
endPoint.setDisplayStyle(Utils::StringAspect::LineEditDisplay);
modelName.setSettingsKey(Constants::MODEL_NAME);
modelName.setLabelText(Tr::tr("Model name:"));
modelName.setDisplayStyle(Utils::StringAspect::LineEditDisplay);
selectModels.m_buttonText = Tr::tr("Select Fill-In-the-Middle Model");
fimPrompts.setDisplayName(Tr::tr("Fill-In-the-Middle Prompt"));
fimPrompts.setSettingsKey(Constants::FIM_PROMPTS);
fimPrompts.setDisplayStyle(Utils::SelectionAspect::DisplayStyle::ComboBox);
resetToDefaults.m_buttonText = Tr::tr("Reset Page to Defaults");
chatLlmProviders.setSettingsKey(Constants::CHAT_LLM_PROVIDERS);
chatLlmProviders.setDisplayName(Tr::tr("AI Chat Provider:"));
chatLlmProviders.setDisplayStyle(Utils::SelectionAspect::DisplayStyle::ComboBox);
chatUrl.setSettingsKey(Constants::CHAT_URL);
chatUrl.setLabelText(Tr::tr("URL:"));
chatUrl.setDisplayStyle(Utils::StringAspect::LineEditDisplay);
chatEndPoint.setSettingsKey(Constants::CHAT_END_POINT);
chatEndPoint.setLabelText(Tr::tr("Chat Endpoint:"));
chatEndPoint.setDisplayStyle(Utils::StringAspect::LineEditDisplay);
chatModelName.setSettingsKey(Constants::CHAT_MODEL_NAME);
chatModelName.setLabelText(Tr::tr("Model name:"));
chatModelName.setDisplayStyle(Utils::StringAspect::LineEditDisplay);
chatSelectModels.m_buttonText = Tr::tr("Select Chat Model");
chatPrompts.setDisplayName(Tr::tr("Chat Prompt"));
chatPrompts.setSettingsKey(Constants::CHAT_PROMPTS);
chatPrompts.setDisplayStyle(Utils::SelectionAspect::DisplayStyle::ComboBox);
chatTokensThreshold.setSettingsKey(Constants::CHAT_TOKENS_THRESHOLD);
chatTokensThreshold.setLabelText(Tr::tr("Chat History Token Limit"));
chatTokensThreshold.setToolTip(Tr::tr("Maximum number of tokens in chat history. When "
"exceeded, oldest messages will be removed."));
chatTokensThreshold.setRange(1000, 16000);
chatTokensThreshold.setDefaultValue(8000);
loadProviders();
loadPrompts();
llmProviders.setDefaultValue(llmProviders.indexForDisplay("Ollama"));
chatLlmProviders.setDefaultValue(chatLlmProviders.indexForDisplay("Ollama"));
fimPrompts.setDefaultValue(fimPrompts.indexForDisplay("CodeLlama FIM"));
chatPrompts.setDefaultValue(chatPrompts.indexForDisplay("CodeLlama Chat"));
auto fimProviderName = llmProviders.displayForIndex(llmProviders.value());
setCurrentFimProvider(fimProviderName);
auto chatProviderName = chatLlmProviders.displayForIndex(chatLlmProviders.value());
setCurrentChatProvider(chatProviderName);
readSettings();
auto nameFimPromts = fimPrompts.displayForIndex(fimPrompts.value());
LLMCore::PromptTemplateManager::instance().setCurrentFimTemplate(nameFimPromts);
auto nameChatPromts = chatPrompts.displayForIndex(chatPrompts.value());
LLMCore::PromptTemplateManager::instance().setCurrentChatTemplate(nameChatPromts);
Logger::instance().setLoggingEnabled(enableLogging());
setupConnections();
setLayouter([this]() {
using namespace Layouting;
auto rootLayout
= Column{Row{enableQodeAssist, Stretch{1}, resetToDefaults},
Row{enableLogging, Stretch{1}},
Space{8},
Group{title(Tr::tr("AI Suggestions")),
Column{enableAutoComplete,
multiLineCompletion,
Row{autoCompletionCharThreshold,
autoCompletionTypingInterval,
startSuggestionTimer,
Stretch{1}},
Row{llmProviders, Stretch{1}},
Row{url, endPoint, fimUrlIndicator},
Row{selectModels, modelName, fimModelIndicator},
Row{fimPrompts, Stretch{1}}}},
Space{16},
Group{title(Tr::tr("AI Chat")),
Column{Row{chatLlmProviders, Stretch{1}},
Row{chatUrl, chatEndPoint, chatUrlIndicator},
Row{chatSelectModels, chatModelName, chatModelIndicator},
Row{chatPrompts, Stretch{1}},
Row{chatTokensThreshold, Stretch{1}}}},
Stretch{1}};
return rootLayout;
});
updateStatusIndicators();
}
void GeneralSettings::setupConnections()
{
connect(&llmProviders, &Utils::SelectionAspect::volatileValueChanged, this, [this]() {
auto providerName = llmProviders.displayForIndex(llmProviders.volatileValue());
setCurrentFimProvider(providerName);
modelName.setVolatileValue("");
});
connect(&chatLlmProviders, &Utils::SelectionAspect::volatileValueChanged, this, [this]() {
auto providerName = chatLlmProviders.displayForIndex(chatLlmProviders.volatileValue());
setCurrentChatProvider(providerName);
chatModelName.setVolatileValue("");
});
connect(&fimPrompts, &Utils::SelectionAspect::volatileValueChanged, this, [this]() {
int index = fimPrompts.volatileValue();
LLMCore::PromptTemplateManager::instance().setCurrentFimTemplate(
fimPrompts.displayForIndex(index));
});
connect(&chatPrompts, &Utils::SelectionAspect::volatileValueChanged, this, [this]() {
int index = chatPrompts.volatileValue();
LLMCore::PromptTemplateManager::instance().setCurrentChatTemplate(
chatPrompts.displayForIndex(index));
});
connect(&selectModels, &ButtonAspect::clicked, this, [this]() {
auto *provider = LLMCore::ProvidersManager::instance().getCurrentFimProvider();
showModelSelectionDialog(&modelName, provider);
});
connect(&chatSelectModels, &ButtonAspect::clicked, this, [this]() {
auto *provider = LLMCore::ProvidersManager::instance().getCurrentChatProvider();
showModelSelectionDialog(&chatModelName, provider);
});
connect(&enableLogging, &Utils::BoolAspect::volatileValueChanged, this, [this]() {
Logger::instance().setLoggingEnabled(enableLogging.volatileValue());
});
connect(&resetToDefaults, &ButtonAspect::clicked, this, &GeneralSettings::resetPageToDefaults);
connect(&url,
&Utils::StringAspect::volatileValueChanged,
this,
&GeneralSettings::updateStatusIndicators);
connect(&modelName,
&Utils::StringAspect::volatileValueChanged,
this,
&GeneralSettings::updateStatusIndicators);
connect(&chatUrl,
&Utils::StringAspect::volatileValueChanged,
this,
&GeneralSettings::updateStatusIndicators);
connect(&chatModelName,
&Utils::StringAspect::volatileValueChanged,
this,
&GeneralSettings::updateStatusIndicators);
}
void GeneralSettings::showModelSelectionDialog(Utils::StringAspect *modelNameObj,
LLMCore::Provider *provider)
{
Utils::Environment env = Utils::Environment::systemEnvironment();
QString providerUrl = (modelNameObj == &modelName) ? url() : chatUrl();
if (provider) {
QStringList models = provider->getInstalledModels(env, providerUrl);
bool ok;
QString selectedModel = QInputDialog::getItem(Core::ICore::dialogParent(),
Tr::tr("Select LLM Model"),
Tr::tr("Choose a model:"),
models,
0,
false,
&ok);
if (ok && !selectedModel.isEmpty()) {
modelNameObj->setVolatileValue(selectedModel);
writeSettings();
}
}
}
void GeneralSettings::resetPageToDefaults()
{
QMessageBox::StandardButton reply;
reply = QMessageBox::question(
Core::ICore::dialogParent(),
Tr::tr("Reset Settings"),
Tr::tr("Are you sure you want to reset all settings to default values?"),
QMessageBox::Yes | QMessageBox::No);
if (reply == QMessageBox::Yes) {
resetAspect(enableQodeAssist);
resetAspect(enableAutoComplete);
resetAspect(enableLogging);
resetAspect(startSuggestionTimer);
resetAspect(autoCompletionTypingInterval);
resetAspect(autoCompletionCharThreshold);
resetAspect(llmProviders);
resetAspect(chatLlmProviders);
resetAspect(fimPrompts);
resetAspect(chatPrompts);
resetAspect(chatTokensThreshold);
}
modelName.setVolatileValue("");
chatModelName.setVolatileValue("");
updateStatusIndicators();
}
void GeneralSettings::updateStatusIndicators()
{
bool fimUrlValid = !url.volatileValue().isEmpty() && !endPoint.volatileValue().isEmpty();
bool fimModelValid = !modelName.volatileValue().isEmpty();
bool chatUrlValid = !chatUrl.volatileValue().isEmpty()
&& !chatEndPoint.volatileValue().isEmpty();
bool chatModelValid = !chatModelName.volatileValue().isEmpty();
bool fimPingSuccessful = false;
if (fimUrlValid) {
QUrl pingUrl(url.volatileValue());
fimPingSuccessful = Settings::pingUrl(pingUrl);
}
bool chatPingSuccessful = false;
if (chatUrlValid) {
QUrl pingUrl(chatUrl.volatileValue());
chatPingSuccessful = Settings::pingUrl(pingUrl);
}
setIndicatorStatus(fimModelIndicator,
fimModelValid ? tr("Model is properly configured")
: tr("No model selected or model name is invalid"),
fimModelValid);
setIndicatorStatus(fimUrlIndicator,
fimPingSuccessful ? tr("Server is reachable")
: tr("Server is not reachable or URL is invalid"),
fimPingSuccessful);
setIndicatorStatus(chatModelIndicator,
chatModelValid ? tr("Model is properly configured")
: tr("No model selected or model name is invalid"),
chatModelValid);
setIndicatorStatus(chatUrlIndicator,
chatPingSuccessful ? tr("Server is reachable")
: tr("Server is not reachable or URL is invalid"),
chatPingSuccessful);
}
void GeneralSettings::setIndicatorStatus(Utils::StringAspect &indicator,
const QString &tooltip,
bool isValid)
{
const Utils::Icon &icon = isValid ? Utils::Icons::OK : Utils::Icons::WARNING;
indicator.setLabelPixmap(icon.pixmap());
indicator.setToolTip(tooltip);
}
void GeneralSettings::setCurrentFimProvider(const QString &name)
{
const auto provider = LLMCore::ProvidersManager::instance().setCurrentFimProvider(name);
if (!provider)
return;
url.setValue(provider->url());
endPoint.setValue(provider->completionEndpoint());
}
void GeneralSettings::setCurrentChatProvider(const QString &name)
{
const auto provider = LLMCore::ProvidersManager::instance().setCurrentChatProvider(name);
if (!provider)
return;
chatUrl.setValue(provider->url());
chatEndPoint.setValue(provider->chatEndpoint());
}
void GeneralSettings::loadProviders()
{
for (const auto &name : LLMCore::ProvidersManager::instance().providersNames()) {
llmProviders.addOption(name);
chatLlmProviders.addOption(name);
}
}
void GeneralSettings::loadPrompts()
{
for (const auto &name : LLMCore::PromptTemplateManager::instance().fimTemplatesNames()) {
fimPrompts.addOption(name);
}
for (const auto &name : LLMCore::PromptTemplateManager::instance().chatTemplatesNames()) {
chatPrompts.addOption(name);
}
}
class GeneralSettingsPage : public Core::IOptionsPage
{
public:
GeneralSettingsPage()
{
setId(Constants::QODE_ASSIST_GENERAL_SETTINGS_PAGE_ID);
setDisplayName(Tr::tr("General"));
setCategory(Constants::QODE_ASSIST_GENERAL_OPTIONS_CATEGORY);
setSettingsProvider([] { return &generalSettings(); });
}
};
const GeneralSettingsPage generalSettingsPage;
} // namespace QodeAssist::Settings

View File

@ -0,0 +1,85 @@
/*
* Copyright (C) 2024 Petr Mironychev
*
* This file is part of QodeAssist.
*
* QodeAssist is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* QodeAssist is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <utils/aspects.h>
#include "SettingsUtils.hpp"
namespace QodeAssist::LLMCore {
class Provider;
}
namespace QodeAssist::Settings {
class GeneralSettings : public Utils::AspectContainer
{
public:
GeneralSettings();
Utils::BoolAspect enableQodeAssist{this};
Utils::BoolAspect enableAutoComplete{this};
Utils::BoolAspect multiLineCompletion{this};
Utils::BoolAspect enableLogging{this};
Utils::IntegerAspect startSuggestionTimer{this};
Utils::IntegerAspect autoCompletionCharThreshold{this};
Utils::IntegerAspect autoCompletionTypingInterval{this};
Utils::SelectionAspect llmProviders{this};
Utils::StringAspect url{this};
Utils::StringAspect endPoint{this};
Utils::StringAspect modelName{this};
ButtonAspect selectModels{this};
Utils::SelectionAspect fimPrompts{this};
ButtonAspect resetToDefaults{this};
Utils::SelectionAspect chatLlmProviders{this};
Utils::StringAspect chatUrl{this};
Utils::StringAspect chatEndPoint{this};
Utils::StringAspect chatModelName{this};
ButtonAspect chatSelectModels{this};
Utils::SelectionAspect chatPrompts{this};
Utils::StringAspect fimModelIndicator{this};
Utils::StringAspect fimUrlIndicator{this};
Utils::StringAspect chatModelIndicator{this};
Utils::StringAspect chatUrlIndicator{this};
Utils::IntegerAspect chatTokensThreshold{this};
private:
void setupConnections();
void showModelSelectionDialog(Utils::StringAspect *modelNameObj, LLMCore::Provider *provider);
void resetPageToDefaults();
void updateStatusIndicators();
void setIndicatorStatus(Utils::StringAspect &indicator, const QString &tooltip, bool isValid);
void setCurrentFimProvider(const QString &name);
void setCurrentChatProvider(const QString &name);
void loadProviders();
void loadPrompts();
};
GeneralSettings &generalSettings();
} // namespace QodeAssist::Settings

View File

@ -0,0 +1,283 @@
/*
* Copyright (C) 2024 Petr Mironychev
*
* This file is part of QodeAssist.
*
* QodeAssist is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* QodeAssist is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
*/
#include "PresetPromptsSettings.hpp"
#include <QMessageBox>
#include <coreplugin/dialogs/ioptionspage.h>
#include <coreplugin/icore.h>
#include <utils/layoutbuilder.h>
#include "RequestType.hpp"
#include "SettingsConstants.hpp"
namespace QodeAssist::Settings {
PresetPromptsSettings &presetPromptsSettings()
{
static PresetPromptsSettings settings;
return settings;
}
PresetPromptsSettings::PresetPromptsSettings()
{
setAutoApply(false);
setDisplayName(Tr::tr("Preset Prompts Params"));
fimTemperature.setSettingsKey(Constants::FIM_TEMPERATURE);
fimTemperature.setLabelText(Tr::tr("Temperature:"));
fimTemperature.setDefaultValue(0.2);
fimTemperature.setRange(0.0, 10.0);
fimTemperature.setSingleStep(0.1);
chatTemperature.setSettingsKey(Constants::CHAT_TEMPERATURE);
chatTemperature.setLabelText(Tr::tr("Temperature:"));
chatTemperature.setDefaultValue(0.5);
chatTemperature.setRange(0.0, 10.0);
chatTemperature.setSingleStep(0.1);
fimOllamaLivetime.setSettingsKey(Constants::FIM_OLLAMA_LIVETIME);
fimOllamaLivetime.setLabelText(
Tr::tr("Time to suspend Ollama after completion request (in minutes), "
"Only Ollama, -1 to disable"));
fimOllamaLivetime.setDefaultValue("5m");
fimOllamaLivetime.setDisplayStyle(Utils::StringAspect::LineEditDisplay);
chatOllamaLivetime.setSettingsKey(Constants::CHAT_OLLAMA_LIVETIME);
chatOllamaLivetime.setLabelText(
Tr::tr("Time to suspend Ollama after completion request (in minutes), "
"Only Ollama, -1 to disable"));
chatOllamaLivetime.setDefaultValue("5m");
chatOllamaLivetime.setDisplayStyle(Utils::StringAspect::LineEditDisplay);
fimMaxTokens.setSettingsKey(Constants::FIM_MAX_TOKENS);
fimMaxTokens.setLabelText(Tr::tr("Max Tokens"));
fimMaxTokens.setRange(-1, 10000);
fimMaxTokens.setDefaultValue(50);
chatMaxTokens.setSettingsKey(Constants::CHAT_MAX_TOKENS);
chatMaxTokens.setLabelText(Tr::tr("Max Tokens"));
chatMaxTokens.setRange(-1, 10000);
chatMaxTokens.setDefaultValue(2000);
fimUseTopP.setSettingsKey(Constants::FIM_USE_TOP_P);
fimUseTopP.setDefaultValue(false);
fimTopP.setSettingsKey(Constants::FIM_TOP_P);
fimTopP.setLabelText(Tr::tr("use top_p"));
fimTopP.setDefaultValue(0.9);
fimTopP.setRange(0.0, 1.0);
fimTopP.setSingleStep(0.1);
chatUseTopP.setSettingsKey(Constants::CHAT_USE_TOP_P);
chatUseTopP.setDefaultValue(false);
chatTopP.setSettingsKey(Constants::CHAT_TOP_P);
chatTopP.setLabelText(Tr::tr("use top_p"));
chatTopP.setDefaultValue(0.9);
chatTopP.setRange(0.0, 1.0);
chatTopP.setSingleStep(0.1);
fimUseTopK.setSettingsKey(Constants::FIM_USE_TOP_K);
fimUseTopK.setDefaultValue(false);
fimTopK.setSettingsKey(Constants::FIM_TOP_K);
fimTopK.setLabelText(Tr::tr("use top_k"));
fimTopK.setDefaultValue(50);
fimTopK.setRange(1, 1000);
chatUseTopK.setSettingsKey(Constants::CHAT_USE_TOP_K);
chatUseTopK.setDefaultValue(false);
chatTopK.setSettingsKey(Constants::CHAT_TOP_K);
chatTopK.setLabelText(Tr::tr("use top_k"));
chatTopK.setDefaultValue(50);
chatTopK.setRange(1, 1000);
fimUsePresencePenalty.setSettingsKey(Constants::FIM_USE_PRESENCE_PENALTY);
fimUsePresencePenalty.setDefaultValue(false);
fimPresencePenalty.setSettingsKey(Constants::FIM_PRESENCE_PENALTY);
fimPresencePenalty.setLabelText(Tr::tr("use presence_penalty"));
fimPresencePenalty.setDefaultValue(0.0);
fimPresencePenalty.setRange(-2.0, 2.0);
fimPresencePenalty.setSingleStep(0.1);
chatUsePresencePenalty.setSettingsKey(Constants::CHAT_USE_PRESENCE_PENALTY);
chatUsePresencePenalty.setDefaultValue(false);
chatPresencePenalty.setSettingsKey(Constants::CHAT_PRESENCE_PENALTY);
chatPresencePenalty.setLabelText(Tr::tr("use presence_penalty"));
chatPresencePenalty.setDefaultValue(0.0);
chatPresencePenalty.setRange(-2.0, 2.0);
chatPresencePenalty.setSingleStep(0.1);
fimUseFrequencyPenalty.setSettingsKey(Constants::FIM_USE_FREQUENCY_PENALTY);
fimUseFrequencyPenalty.setDefaultValue(false);
fimFrequencyPenalty.setSettingsKey(Constants::FIM_FREQUENCY_PENALTY);
fimFrequencyPenalty.setLabelText(Tr::tr("use frequency_penalty"));
fimFrequencyPenalty.setDefaultValue(0.0);
fimFrequencyPenalty.setRange(-2.0, 2.0);
fimFrequencyPenalty.setSingleStep(0.1);
chatUseFrequencyPenalty.setSettingsKey(Constants::CHAT_USE_FREQUENCY_PENALTY);
chatUseFrequencyPenalty.setDefaultValue(false);
chatFrequencyPenalty.setSettingsKey(Constants::CHAT_FREQUENCY_PENALTY);
chatFrequencyPenalty.setLabelText(Tr::tr("use frequency_penalty"));
chatFrequencyPenalty.setDefaultValue(0.0);
chatFrequencyPenalty.setRange(-2.0, 2.0);
chatFrequencyPenalty.setSingleStep(0.1);
fimApiKey.setSettingsKey(Constants::FIM_API_KEY);
fimApiKey.setLabelText(Tr::tr("API Key:"));
fimApiKey.setDisplayStyle(Utils::StringAspect::LineEditDisplay);
fimApiKey.setPlaceHolderText(Tr::tr("Enter your API key here"));
chatApiKey.setSettingsKey(Constants::CHAT_API_KEY);
chatApiKey.setLabelText(Tr::tr("API Key:"));
chatApiKey.setDisplayStyle(Utils::StringAspect::LineEditDisplay);
chatApiKey.setPlaceHolderText(Tr::tr("Enter your API key here"));
resetToDefaults.m_buttonText = Tr::tr("Reset Page to Defaults");
readSettings();
setupConnections();
setLayouter([this]() {
using namespace Layouting;
return Column{Row{Stretch{1}, resetToDefaults},
Group{title(Tr::tr("Prompt settings for FIM")),
Column{Row{fimTemperature, Stretch{1}},
Row{fimMaxTokens, Stretch{1}},
Row{fimUseTopP, fimTopP, Stretch{1}},
Row{fimUseTopK, fimTopK, Stretch{1}},
Row{fimUsePresencePenalty, fimPresencePenalty, Stretch{1}},
Row{fimUseFrequencyPenalty, fimFrequencyPenalty, Stretch{1}},
Row{fimOllamaLivetime, Stretch{1}},
fimApiKey}},
Space{16},
Group{title(Tr::tr("Prompt settings for Chat")),
Column{Row{chatTemperature, Stretch{1}},
Row{chatMaxTokens, Stretch{1}},
Row{chatUseTopP, chatTopP, Stretch{1}},
Row{chatUseTopK, chatTopK, Stretch{1}},
Row{fimUsePresencePenalty, fimPresencePenalty, Stretch{1}},
Row{fimUseFrequencyPenalty, fimFrequencyPenalty, Stretch{1}},
Row{chatOllamaLivetime, Stretch{1}},
chatApiKey}},
Stretch{1}};
});
}
PresetPromptsSettings::PromptSettings PresetPromptsSettings::getSettings(int type) const
{
auto reqtype = static_cast<LLMCore::RequestType>(type);
PromptSettings settings;
if (reqtype == LLMCore::RequestType::Fim) {
settings.temperature = fimTemperature();
settings.maxTokens = fimMaxTokens();
settings.useTopP = fimUseTopP();
settings.topP = fimTopP();
settings.useTopK = fimUseTopK();
settings.topK = fimTopK();
settings.usePresencePenalty = fimUsePresencePenalty();
settings.presencePenalty = fimPresencePenalty();
settings.useFrequencyPenalty = fimUseFrequencyPenalty();
settings.frequencyPenalty = fimFrequencyPenalty();
settings.ollamaLivetime = fimOllamaLivetime();
settings.apiKey = fimApiKey();
} else if (reqtype == LLMCore::RequestType::Chat) {
settings.temperature = chatTemperature();
settings.maxTokens = chatMaxTokens();
settings.useTopP = chatUseTopP();
settings.topP = chatTopP();
settings.useTopK = chatUseTopK();
settings.topK = chatTopK();
settings.usePresencePenalty = chatUsePresencePenalty();
settings.presencePenalty = chatPresencePenalty();
settings.useFrequencyPenalty = chatUseFrequencyPenalty();
settings.frequencyPenalty = chatFrequencyPenalty();
settings.ollamaLivetime = chatOllamaLivetime();
settings.apiKey = chatApiKey();
}
return settings;
}
void PresetPromptsSettings::setupConnections()
{
connect(&resetToDefaults,
&ButtonAspect::clicked,
this,
&PresetPromptsSettings::resetSettingsToDefaults);
}
void PresetPromptsSettings::resetSettingsToDefaults()
{
QMessageBox::StandardButton reply;
reply = QMessageBox::question(
Core::ICore::dialogParent(),
Tr::tr("Reset Settings"),
Tr::tr("Are you sure you want to reset all settings to default values?"),
QMessageBox::Yes | QMessageBox::No);
if (reply == QMessageBox::Yes) {
resetAspect(fimTemperature);
resetAspect(fimMaxTokens);
resetAspect(fimOllamaLivetime);
resetAspect(fimUseTopP);
resetAspect(fimTopP);
resetAspect(fimUseTopK);
resetAspect(fimTopK);
resetAspect(fimUsePresencePenalty);
resetAspect(fimPresencePenalty);
resetAspect(fimUseFrequencyPenalty);
resetAspect(fimFrequencyPenalty);
resetAspect(chatTemperature);
resetAspect(chatMaxTokens);
resetAspect(chatUseTopP);
resetAspect(chatTopP);
resetAspect(chatUseTopK);
resetAspect(chatTopK);
resetAspect(chatUsePresencePenalty);
resetAspect(chatPresencePenalty);
resetAspect(chatUseFrequencyPenalty);
resetAspect(chatFrequencyPenalty);
resetAspect(chatOllamaLivetime);
}
}
class PresetPromptsSettingsPage : public Core::IOptionsPage
{
public:
PresetPromptsSettingsPage()
{
setId(Constants::QODE_ASSIST_PRESET_PROMPTS_SETTINGS_PAGE_ID);
setDisplayName(Tr::tr("Preset Prompts Params"));
setCategory(Constants::QODE_ASSIST_GENERAL_OPTIONS_CATEGORY);
setSettingsProvider([] { return &presetPromptsSettings(); });
}
};
const PresetPromptsSettingsPage presetPromptsSettingsPage;
} // namespace QodeAssist::Settings

View File

@ -0,0 +1,94 @@
/*
* 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 "SettingsUtils.hpp"
#include <utils/aspects.h>
namespace QodeAssist::Settings {
class PresetPromptsSettings : public Utils::AspectContainer
{
public:
struct PromptSettings
{
double temperature;
int maxTokens;
bool useTopP;
double topP;
bool useTopK;
int topK;
bool usePresencePenalty;
double presencePenalty;
bool useFrequencyPenalty;
double frequencyPenalty;
QString ollamaLivetime;
QString apiKey;
};
PresetPromptsSettings();
Utils::DoubleAspect fimTemperature{this};
Utils::IntegerAspect fimMaxTokens{this};
Utils::DoubleAspect chatTemperature{this};
Utils::IntegerAspect chatMaxTokens{this};
Utils::BoolAspect fimUseTopP{this};
Utils::DoubleAspect fimTopP{this};
Utils::BoolAspect chatUseTopP{this};
Utils::DoubleAspect chatTopP{this};
Utils::BoolAspect fimUseTopK{this};
Utils::IntegerAspect fimTopK{this};
Utils::BoolAspect chatUseTopK{this};
Utils::IntegerAspect chatTopK{this};
Utils::BoolAspect fimUsePresencePenalty{this};
Utils::DoubleAspect fimPresencePenalty{this};
Utils::BoolAspect chatUsePresencePenalty{this};
Utils::DoubleAspect chatPresencePenalty{this};
Utils::BoolAspect fimUseFrequencyPenalty{this};
Utils::DoubleAspect fimFrequencyPenalty{this};
Utils::BoolAspect chatUseFrequencyPenalty{this};
Utils::DoubleAspect chatFrequencyPenalty{this};
Utils::StringAspect fimOllamaLivetime{this};
Utils::StringAspect chatOllamaLivetime{this};
Utils::StringAspect fimApiKey{this};
Utils::StringAspect chatApiKey{this};
ButtonAspect resetToDefaults{this};
PromptSettings getSettings(int type) const;
private:
void setupConnections();
void resetSettingsToDefaults();
};
PresetPromptsSettings &presetPromptsSettings();
} // namespace QodeAssist::Settings

View File

@ -0,0 +1,102 @@
/*
* 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
namespace QodeAssist::Constants {
const char ACTION_ID[] = "QodeAssist.Action";
const char MENU_ID[] = "QodeAssist.Menu";
// settings
const char ENABLE_QODE_ASSIST[] = "QodeAssist.enableQodeAssist";
const char ENABLE_AUTO_COMPLETE[] = "QodeAssist.enableAutoComplete";
const char ENABLE_LOGGING[] = "QodeAssist.enableLogging";
const char LLM_PROVIDERS[] = "QodeAssist.llmProviders";
const char URL[] = "QodeAssist.url";
const char END_POINT[] = "QodeAssist.endPoint";
const char MODEL_NAME[] = "QodeAssist.modelName";
const char SELECT_MODELS[] = "QodeAssist.selectModels";
const char FIM_PROMPTS[] = "QodeAssist.fimPrompts";
const char PROVIDER_PATHS[] = "QodeAssist.providerPaths";
const char START_SUGGESTION_TIMER[] = "QodeAssist.startSuggestionTimer";
const char AUTO_COMPLETION_CHAR_THRESHOLD[] = "QodeAssist.autoCompletionCharThreshold";
const char AUTO_COMPLETION_TYPING_INTERVAL[] = "QodeAssist.autoCompletionTypingInterval";
const char MAX_FILE_THRESHOLD[] = "QodeAssist.maxFileThreshold";
const char MULTILINE_COMPLETION[] = "QodeAssist.multilineCompletion";
const char CUSTOM_JSON_TEMPLATE[] = "QodeAssist.customJsonTemplate";
const char CHAT_LLM_PROVIDERS[] = "QodeAssist.chatLlmProviders";
const char CHAT_URL[] = "QodeAssist.chatUrl";
const char CHAT_END_POINT[] = "QodeAssist.chatEndPoint";
const char CHAT_MODEL_NAME[] = "QodeAssist.chatModelName";
const char CHAT_SELECT_MODELS[] = "QodeAssist.chatSelectModels";
const char CHAT_PROMPTS[] = "QodeAssist.chatPrompts";
const char CHAT_TOKENS_THRESHOLD[] = "QodeAssist.chatTokensThreshold";
const char QODE_ASSIST_GENERAL_OPTIONS_ID[] = "QodeAssist.GeneralOptions";
const char QODE_ASSIST_GENERAL_SETTINGS_PAGE_ID[] = "QodeAssist.1GeneralSettingsPageId";
const char QODE_ASSIST_CONTEXT_SETTINGS_PAGE_ID[] = "QodeAssist.2ContextSettingsPageId";
const char QODE_ASSIST_PRESET_PROMPTS_SETTINGS_PAGE_ID[]
= "QodeAssist.3PresetPromptsSettingsPageId";
const char QODE_ASSIST_CUSTOM_PROMPT_SETTINGS_PAGE_ID[] = "QodeAssist.4CustomPromptSettingsPageId";
const char QODE_ASSIST_GENERAL_OPTIONS_CATEGORY[] = "QodeAssist.Category";
const char QODE_ASSIST_GENERAL_OPTIONS_DISPLAY_CATEGORY[] = "Qode Assist";
const char QODE_ASSIST_REQUEST_SUGGESTION[] = "QodeAssist.RequestSuggestion";
// context settings
const char READ_FULL_FILE[] = "QodeAssist.readFullFile";
const char READ_STRINGS_BEFORE_CURSOR[] = "QodeAssist.readStringsBeforeCursor";
const char READ_STRINGS_AFTER_CURSOR[] = "QodeAssist.readStringsAfterCursor";
const char USE_SYSTEM_PROMPT[] = "QodeAssist.useSystemPrompt";
const char USE_FILE_PATH_IN_CONTEXT[] = "QodeAssist.useFilePathInContext";
const char SYSTEM_PROMPT[] = "QodeAssist.systemPrompt";
const char USE_PROJECT_CHANGES_CACHE[] = "QodeAssist.useProjectChangesCache";
const char MAX_CHANGES_CACHE_SIZE[] = "QodeAssist.maxChangesCacheSize";
const char USE_CHAT_SYSTEM_PROMPT[] = "QodeAssist.useChatSystemPrompt";
const char CHAT_SYSTEM_PROMPT[] = "QodeAssist.chatSystemPrompt";
// preset prompt settings
const char FIM_TEMPERATURE[] = "QodeAssist.fimTemperature";
const char FIM_MAX_TOKENS[] = "QodeAssist.fimMaxTokens";
const char FIM_USE_TOP_P[] = "QodeAssist.fimUseTopP";
const char FIM_TOP_P[] = "QodeAssist.fimTopP";
const char FIM_USE_TOP_K[] = "QodeAssist.fimUseTopK";
const char FIM_TOP_K[] = "QodeAssist.fimTopK";
const char FIM_USE_PRESENCE_PENALTY[] = "QodeAssist.fimUsePresencePenalty";
const char FIM_PRESENCE_PENALTY[] = "QodeAssist.fimPresencePenalty";
const char FIM_USE_FREQUENCY_PENALTY[] = "QodeAssist.fimUseFrequencyPenalty";
const char FIM_FREQUENCY_PENALTY[] = "QodeAssist.fimFrequencyPenalty";
const char FIM_OLLAMA_LIVETIME[] = "QodeAssist.fimOllamaLivetime";
const char FIM_API_KEY[] = "QodeAssist.apiKey";
const char CHAT_TEMPERATURE[] = "QodeAssist.chatTemperature";
const char CHAT_MAX_TOKENS[] = "QodeAssist.chatMaxTokens";
const char CHAT_USE_TOP_P[] = "QodeAssist.chatUseTopP";
const char CHAT_TOP_P[] = "QodeAssist.chatTopP";
const char CHAT_USE_TOP_K[] = "QodeAssist.chatUseTopK";
const char CHAT_TOP_K[] = "QodeAssist.chatTopK";
const char CHAT_USE_PRESENCE_PENALTY[] = "QodeAssist.chatUsePresencePenalty";
const char CHAT_PRESENCE_PENALTY[] = "QodeAssist.chatPresencePenalty";
const char CHAT_USE_FREQUENCY_PENALTY[] = "QodeAssist.chatUseFrequencyPenalty";
const char CHAT_FREQUENCY_PENALTY[] = "QodeAssist.chatFrequencyPenalty";
const char CHAT_OLLAMA_LIVETIME[] = "QodeAssist.chatOllamaLivetime";
const char CHAT_API_KEY[] = "QodeAssist.chatApiKey";
} // namespace QodeAssist::Constants

View File

@ -0,0 +1,96 @@
/*
* 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 <QCoreApplication>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QPushButton>
#include <QtCore/qtimer.h>
#include <utils/aspects.h>
#include <utils/layoutbuilder.h>
namespace QodeAssist::Settings {
struct Tr
{
Q_DECLARE_TR_FUNCTIONS(QtC::QodeAssist)
};
inline bool pingUrl(const QUrl &url, int timeout = 5000)
{
if (!url.isValid()) {
return false;
}
QNetworkAccessManager manager;
QNetworkRequest request(url);
request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, true);
QScopedPointer<QNetworkReply> reply(manager.get(request));
QTimer timer;
timer.setSingleShot(true);
QEventLoop loop;
QObject::connect(reply.data(), &QNetworkReply::finished, &loop, &QEventLoop::quit);
QObject::connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit);
timer.start(timeout);
loop.exec();
if (timer.isActive()) {
timer.stop();
return (reply->error() == QNetworkReply::NoError);
} else {
QObject::disconnect(reply.data(), &QNetworkReply::finished, &loop, &QEventLoop::quit);
reply->abort();
return false;
}
}
template<typename AspectType>
void resetAspect(AspectType &aspect)
{
aspect.setVolatileValue(aspect.defaultValue());
}
class ButtonAspect : public Utils::BaseAspect
{
Q_OBJECT
public:
ButtonAspect(Utils::AspectContainer *container = nullptr)
: Utils::BaseAspect(container)
{}
void addToLayout(Layouting::Layout &parent) override
{
auto button = new QPushButton(m_buttonText);
connect(button, &QPushButton::clicked, this, &ButtonAspect::clicked);
parent.addItem(button);
}
QString m_buttonText;
signals:
void clicked();
};
} // namespace QodeAssist::Settings

View File

@ -0,0 +1,55 @@
/*
* Copyright (C) 2024 Petr Mironychev
*
* This file is part of QodeAssist.
*
* QodeAssist is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* QodeAssist is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <QtCore/qjsonarray.h>
#include "llmcore/PromptTemplate.hpp"
namespace QodeAssist::Templates {
class CodeLlamaChat : public LLMCore::PromptTemplate
{
public:
LLMCore::TemplateType type() const override { return LLMCore::TemplateType::Chat; }
QString name() const override { return "CodeLlama Chat"; }
QString promptTemplate() const override { return "[INST] %1 [/INST]"; }
QStringList stopWords() const override { return QStringList() << "[INST]" << "[/INST]"; }
void prepareRequest(QJsonObject &request, const LLMCore::ContextData &context) const override
{
QString formattedPrompt = promptTemplate().arg(context.prefix);
QJsonArray messages = request["messages"].toArray();
QJsonObject newMessage;
newMessage["role"] = "user";
newMessage["content"] = formattedPrompt;
messages.append(newMessage);
request["messages"] = messages;
}
};
class LlamaChat : public CodeLlamaChat
{
public:
QString name() const override { return "Llama Chat"; }
};
} // namespace QodeAssist::Templates

View File

@ -19,23 +19,24 @@
#pragma once
#include "PromptTemplate.hpp"
#include "llmcore/PromptTemplate.hpp"
namespace QodeAssist::Templates {
class CodeLLamaTemplate : public PromptTemplate
class CodeLlamaFim : public LLMCore::PromptTemplate
{
public:
QString name() const override { return "CodeLlama"; }
LLMCore::TemplateType type() const override { return LLMCore::TemplateType::Fim; }
QString name() const override { return "CodeLlama FIM"; }
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 ContextData &context) const override
void prepareRequest(QJsonObject &request, const LLMCore::ContextData &context) const override
{
QString formattedPrompt = promptTemplate().arg(context.instriuctions,
QString formattedPrompt = promptTemplate().arg(context.systemPrompt,
context.prefix,
context.suffix);
request["prompt"] = formattedPrompt;

View File

@ -0,0 +1,91 @@
/*
* 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 "llmcore/PromptTemplate.hpp"
#include <QJsonArray>
#include <QJsonDocument>
#include "logger/Logger.hpp"
#include "settings/CustomPromptSettings.hpp"
namespace QodeAssist::Templates {
class CustomTemplate : public LLMCore::PromptTemplate
{
public:
LLMCore::TemplateType type() const override { return LLMCore::TemplateType::Fim; }
QString name() const override { return "Custom FIM Template"; }
QString promptTemplate() const override
{
return Settings::customPromptSettings().customJsonTemplate();
}
QStringList stopWords() const override { return QStringList(); }
void prepareRequest(QJsonObject &request, const LLMCore::ContextData &context) const override
{
QJsonDocument doc = QJsonDocument::fromJson(promptTemplate().toUtf8());
if (doc.isNull() || !doc.isObject()) {
LOG_MESSAGE(QString("Invalid JSON template in settings"));
return;
}
QJsonObject templateObj = doc.object();
QJsonObject processedObj = processJsonTemplate(templateObj, context);
for (auto it = processedObj.begin(); it != processedObj.end(); ++it) {
request[it.key()] = it.value();
}
}
private:
QJsonValue processJsonValue(const QJsonValue &value, const LLMCore::ContextData &context) const
{
if (value.isString()) {
QString str = value.toString();
str.replace("{{QODE_INSTRUCTIONS}}", context.systemPrompt);
str.replace("{{QODE_PREFIX}}", context.prefix);
str.replace("{{QODE_SUFFIX}}", context.suffix);
return str;
} else if (value.isObject()) {
return processJsonTemplate(value.toObject(), context);
} else if (value.isArray()) {
QJsonArray newArray;
for (const QJsonValue &arrayValue : value.toArray()) {
newArray.append(processJsonValue(arrayValue, context));
}
return newArray;
}
return value;
}
QJsonObject processJsonTemplate(const QJsonObject &templateObj,
const LLMCore::ContextData &context) const
{
QJsonObject result;
for (auto it = templateObj.begin(); it != templateObj.end(); ++it) {
result[it.key()] = processJsonValue(it.value(), context);
}
return result;
}
};
} // namespace QodeAssist::Templates

View File

@ -0,0 +1,54 @@
/*
* 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 <QJsonArray>
#include "llmcore/PromptTemplate.hpp"
namespace QodeAssist::Templates {
class DeepSeekCoderChat : public LLMCore::PromptTemplate
{
public:
QString name() const override { return "DeepSeekCoder Chat"; }
LLMCore::TemplateType type() const override { return LLMCore::TemplateType::Chat; }
QString promptTemplate() const override { return "### Instruction:\n%1\n### Response:\n"; }
QStringList stopWords() const override
{
return QStringList() << "### Instruction:" << "### Response:" << "\n\n### " << "<|EOT|>";
}
void prepareRequest(QJsonObject &request, const LLMCore::ContextData &context) const override
{
QString formattedPrompt = promptTemplate().arg(context.prefix);
QJsonArray messages = request["messages"].toArray();
QJsonObject newMessage;
newMessage["role"] = "user";
newMessage["content"] = formattedPrompt;
messages.append(newMessage);
request["messages"] = messages;
}
};
} // namespace QodeAssist::Templates

View File

@ -19,22 +19,23 @@
#pragma once
#include "PromptTemplate.hpp"
#include "llmcore/PromptTemplate.hpp"
namespace QodeAssist::Templates {
class DeepSeekCoderV2Template : public PromptTemplate
class DeepSeekCoderFim : public LLMCore::PromptTemplate
{
public:
QString name() const override { return "DeepSeekCoderV2"; }
LLMCore::TemplateType type() const override { return LLMCore::TemplateType::Fim; }
QString name() const override { return "DeepSeekCoder FIM"; }
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
void prepareRequest(QJsonObject &request, const LLMCore::ContextData &context) const override
{
QString formattedPrompt = promptTemplate().arg(context.instriuctions,
QString formattedPrompt = promptTemplate().arg(context.systemPrompt,
context.prefix,
context.suffix);
request["prompt"] = formattedPrompt;

View File

@ -19,25 +19,35 @@
#pragma once
#include "PromptTemplate.hpp"
#include <QJsonArray>
#include "llmcore/PromptTemplate.hpp"
namespace QodeAssist::Templates {
class CodeQwenChatTemplate : public PromptTemplate
class QwenChat : public LLMCore::PromptTemplate
{
public:
QString name() const override { return "CodeQwenChat (experimental)"; }
QString promptTemplate() const override { return "%1\n### Instruction:%2%3 ### Response:\n"; }
QString name() const override { return "Qwen Chat"; }
LLMCore::TemplateType type() const override { return LLMCore::TemplateType::Chat; }
QString promptTemplate() const override { return "### Instruction:\n%1\n### Response:\n"; }
QStringList stopWords() const override
{
return QStringList() << "### Instruction:" << "### Response:" << "\n\n### ";
return QStringList() << "### Instruction:" << "### Response:" << "\n\n### " << "<|EOT|>";
}
void prepareRequest(QJsonObject &request, const ContextData &context) const override
void prepareRequest(QJsonObject &request, const LLMCore::ContextData &context) const override
{
QString formattedPrompt = promptTemplate().arg(context.instriuctions,
context.prefix,
context.suffix);
request["prompt"] = formattedPrompt;
QString formattedPrompt = promptTemplate().arg(context.prefix);
QJsonArray messages = request["messages"].toArray();
QJsonObject newMessage;
newMessage["role"] = "user";
newMessage["content"] = formattedPrompt;
messages.append(newMessage);
request["messages"] = messages;
}
};

View File

@ -19,23 +19,24 @@
#pragma once
#include "PromptTemplate.hpp"
#include "llmcore/PromptTemplate.hpp"
namespace QodeAssist::Templates {
class StarCoder2Template : public PromptTemplate
class StarCoder2Fim : public LLMCore::PromptTemplate
{
public:
QString name() const override { return "StarCoder2"; }
LLMCore::TemplateType type() const override { return LLMCore::TemplateType::Fim; }
QString name() const override { return "StarCoder2 FIM"; }
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 ContextData &context) const override
void prepareRequest(QJsonObject &request, const LLMCore::ContextData &context) const override
{
QString formattedPrompt = promptTemplate().arg(context.instriuctions,
QString formattedPrompt = promptTemplate().arg(context.systemPrompt,
context.prefix,
context.suffix);
request["prompt"] = formattedPrompt;

View File

@ -0,0 +1,51 @@
/*
* 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 <QJsonArray>
#include "llmcore/PromptTemplate.hpp"
namespace QodeAssist::Templates {
class StarCoderChat : public LLMCore::PromptTemplate
{
public:
QString name() const override { return "StarCoder Chat"; }
LLMCore::TemplateType type() const override { return LLMCore::TemplateType::Chat; }
QString promptTemplate() const override { return "### Instruction:\n%1\n### Response:\n"; }
QStringList stopWords() const override
{
return QStringList() << "###"
<< "<|endoftext|>" << "<file_sep>";
}
void prepareRequest(QJsonObject &request, const LLMCore::ContextData &context) const override
{
QString formattedPrompt = promptTemplate().arg(context.prefix);
QJsonArray messages = request["messages"].toArray();
QJsonObject newMessage;
newMessage["role"] = "user";
newMessage["content"] = formattedPrompt;
messages.append(newMessage);
request["messages"] = messages;
}
};
} // namespace QodeAssist::Templates