mirror of
https://github.com/Palm1r/QodeAssist.git
synced 2026-02-12 10:10:44 -05:00
Compare commits
33 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6fea300825 | |||
| 14bf0e6c94 | |||
| 0c045e65df | |||
| 15138b4644 | |||
| 5c98de7440 | |||
| b808d0ec10 | |||
| 30fcd7e019 | |||
| 7442256bab | |||
| 8be279a5fd | |||
| d77e13cddb | |||
| 162c068431 | |||
| 4e8ff55355 | |||
| 8df21e96bd | |||
| 57bec94ee4 | |||
| 1760a2d5ff | |||
| 1649a246e1 | |||
| 0ab4b51520 | |||
| d235d0fcdf | |||
| 1cbde3d55b | |||
| 9903ac8f7b | |||
| fbe363689f | |||
| d7f0cc92e6 | |||
| 8311db5b08 | |||
| cd6dd94cd2 | |||
| b559a17407 | |||
| 9745997952 | |||
| aac58d6933 | |||
| 93c69df1b9 | |||
| 4b0b589d0e | |||
| 04c44f5916 | |||
| 6e56646b4c | |||
| c89fe1451b | |||
| 938636ab48 |
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
@ -3,7 +3,7 @@
|
||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: petrmdev
|
||||
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
|
||||
|
||||
6
.github/workflows/build_cmake.yml
vendored
6
.github/workflows/build_cmake.yml
vendored
@ -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)
|
||||
|
||||
@ -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,29 +35,25 @@ 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/DeepSeekCoderV2.hpp
|
||||
templates/CustomTemplate.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
|
||||
providers/OllamaProvider.hpp providers/OllamaProvider.cpp
|
||||
providers/LMStudioProvider.hpp providers/LMStudioProvider.cpp
|
||||
providers/OpenAICompatProvider.hpp providers/OpenAICompatProvider.cpp
|
||||
LLMProvidersManager.hpp LLMProvidersManager.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
|
||||
settings/GeneralSettings.hpp settings/GeneralSettings.cpp
|
||||
settings/ContextSettings.hpp settings/ContextSettings.cpp
|
||||
settings/CustomPromptSettings.hpp settings/CustomPromptSettings.cpp
|
||||
settings/PresetPromptsSettings.hpp settings/PresetPromptsSettings.cpp
|
||||
settings/SettingsUtils.hpp
|
||||
core/ChangesManager.h core/ChangesManager.cpp
|
||||
chat/ChatOutputPane.h chat/ChatOutputPane.cpp
|
||||
chat/NavigationPanel.hpp chat/NavigationPanel.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(QodeAssist PRIVATE QodeAssistChatViewplugin)
|
||||
|
||||
@ -23,6 +23,7 @@
|
||||
#include <QTextBlock>
|
||||
#include <languageserverprotocol/lsptypes.h>
|
||||
|
||||
#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::contextSettings().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
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -23,22 +23,25 @@
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
|
||||
#include <llmcore/RequestConfig.hpp>
|
||||
#include <texteditor/textdocument.h>
|
||||
|
||||
#include "DocumentContextReader.hpp"
|
||||
#include "LLMProvidersManager.hpp"
|
||||
#include "PromptTemplateManager.hpp"
|
||||
#include "QodeAssistUtils.hpp"
|
||||
#include "core/ChangesManager.h"
|
||||
#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
|
||||
@ -53,8 +56,6 @@ void LLMClientInterface::startImpl()
|
||||
|
||||
void LLMClientInterface::sendData(const QByteArray &data)
|
||||
{
|
||||
updateProvider();
|
||||
|
||||
QJsonDocument doc = QJsonDocument::fromJson(data);
|
||||
if (!doc.isObject())
|
||||
return;
|
||||
@ -79,94 +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 ©right = 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::contextSettings().readFullFile()) {
|
||||
contextBefore = reader.readWholeFileBefore(lineNumber, cursorPosition);
|
||||
} else {
|
||||
contextBefore
|
||||
= reader.getContextBefore(lineNumber,
|
||||
cursorPosition,
|
||||
Settings::contextSettings().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::contextSettings().readFullFile()) {
|
||||
contextAfter = reader.readWholeFileAfter(lineNumber, cursorPosition);
|
||||
} else {
|
||||
contextAfter = reader.getContextAfter(lineNumber,
|
||||
cursorPosition,
|
||||
Settings::contextSettings().readStringsAfterCursor());
|
||||
}
|
||||
|
||||
return contextAfter;
|
||||
}
|
||||
|
||||
void LLMClientInterface::handleInitialize(const QJsonObject &request)
|
||||
{
|
||||
QJsonObject response;
|
||||
@ -217,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::generalSettings().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 QStringView &accumulatedCompletion)
|
||||
{
|
||||
auto updatedContext = prepareContext(request, accumulatedCompletion);
|
||||
sendLLMRequest(request, updatedContext);
|
||||
}
|
||||
|
||||
ContextData LLMClientInterface::prepareContext(const QJsonObject &request,
|
||||
const QStringView &accumulatedCompletion)
|
||||
LLMCore::ContextData LLMClientInterface::prepareContext(const QJsonObject &request,
|
||||
const QStringView &accumulatedCompletion)
|
||||
{
|
||||
QJsonObject params = request["params"].toObject();
|
||||
QJsonObject doc = params["doc"].toObject();
|
||||
@ -266,46 +183,23 @@ ContextData LLMClientInterface::prepareContext(const QJsonObject &request,
|
||||
filePath);
|
||||
|
||||
if (!textDocument) {
|
||||
logMessage("Error: Document is not available for" + filePath.toString());
|
||||
return ContextData{};
|
||||
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 recentChanges = ChangesManager::instance().getRecentChangesContext(textDocument);
|
||||
|
||||
QString contextBefore = сontextBefore(widget, lineNumber, cursorPosition);
|
||||
QString contextAfter = сontextAfter(widget, lineNumber, cursorPosition);
|
||||
QString instructions
|
||||
= QString("%1%2%3").arg(Settings::contextSettings().useSpecificInstructions()
|
||||
? reader.getSpecificInstructions()
|
||||
: QString(),
|
||||
Settings::contextSettings().useFilePathInContext()
|
||||
? reader.getLanguageAndFileInfo()
|
||||
: QString(),
|
||||
Settings::contextSettings().useProjectChangesCache() ? recentChanges
|
||||
: QString());
|
||||
|
||||
return {QString("%1%2").arg(contextBefore, accumulatedCompletion), contextAfter, instructions};
|
||||
}
|
||||
|
||||
void LLMClientInterface::updateProvider()
|
||||
{
|
||||
m_serverUrl = QUrl(QString("%1%2").arg(Settings::generalSettings().url(),
|
||||
Settings::generalSettings().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"];
|
||||
@ -325,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();
|
||||
@ -337,69 +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::generalSettings().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 QStringView &completion)
|
||||
{
|
||||
QString filteredCompletion = completion.toString();
|
||||
|
||||
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();
|
||||
@ -420,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() {}
|
||||
|
||||
@ -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 QStringView &accumulatedCompletion = QString());
|
||||
void sendLLMRequest(const QJsonObject &request, const ContextData &prompt);
|
||||
void handleLLMResponse(QNetworkReply *reply, const QJsonObject &request);
|
||||
|
||||
ContextData prepareContext(const QJsonObject &request,
|
||||
const QStringView &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 QStringView &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;
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"Name" : "QodeAssist",
|
||||
"Version" : "0.1.2",
|
||||
"Version" : "0.3.0",
|
||||
"CompatVersion" : "${IDE_VERSION_COMPAT}",
|
||||
"Vendor" : "Petr Mironychev",
|
||||
"Copyright" : "(C) ${IDE_COPYRIGHT_YEAR} Petr Mironychev, (C) ${IDE_COPYRIGHT_YEAR} The Qt Company Ltd",
|
||||
|
||||
@ -24,54 +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 AUTO_COMPLETION_CHAR_THRESHOLD[] = "QodeAssist.autoCompletionCharThreshold";
|
||||
const char AUTO_COMPLETION_TYPING_INTERVAL[] = "QodeAssist.autoCompletionTypingInterval";
|
||||
const char MAX_FILE_THRESHOLD[] = "QodeAssist.maxFileThreshold";
|
||||
const char OLLAMA_LIVETIME[] = "QodeAssist.ollamaLivetime";
|
||||
const char SPECIFIC_INSTRUCTIONS[] = "QodeAssist.specificInstractions";
|
||||
const char MULTILINE_COMPLETION[] = "QodeAssist.multilineCompletion";
|
||||
const char API_KEY[] = "QodeAssist.apiKey";
|
||||
const char USE_SPECIFIC_INSTRUCTIONS[] = "QodeAssist.useSpecificInstructions";
|
||||
const char USE_FILE_PATH_IN_CONTEXT[] = "QodeAssist.useFilePathInContext";
|
||||
const char CUSTOM_JSON_TEMPLATE[] = "QodeAssist.customJsonTemplate";
|
||||
const char USE_PROJECT_CHANGES_CACHE[] = "QodeAssist.useProjectChangesCache";
|
||||
const char MAX_CHANGES_CACHE_SIZE[] = "QodeAssist.maxChangesCacheSize";
|
||||
|
||||
const char QODE_ASSIST_GENERAL_OPTIONS_ID[] = "QodeAssist.GeneralOptions";
|
||||
const char QODE_ASSIST_GENERAL_SETTINGS_PAGE_ID[] = "QodeAssist.1GeneralSettingsPageId";
|
||||
const char QODE_ASSIST_CONTEXT_SETTINGS_PAGE_ID[] = "QodeAssist.2ContextSettingsPageId";
|
||||
const char QODE_ASSIST_PRESET_PROMPTS_SETTINGS_PAGE_ID[]
|
||||
= "QodeAssist.3PresetPromptsSettingsPageId";
|
||||
const char QODE_ASSIST_CUSTOM_PROMPT_SETTINGS_PAGE_ID[] = "QodeAssist.4CustomPromptSettingsPageId";
|
||||
|
||||
const char QODE_ASSIST_GENERAL_OPTIONS_CATEGORY[] = "QodeAssist.Category";
|
||||
const char QODE_ASSIST_GENERAL_OPTIONS_DISPLAY_CATEGORY[] = "Qode Assist";
|
||||
|
||||
const char QODE_ASSIST_REQUEST_SUGGESTION[] = "QodeAssist.RequestSuggestion";
|
||||
|
||||
} // namespace QodeAssist::Constants
|
||||
|
||||
@ -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
|
||||
16
README.md
16
README.md
@ -3,6 +3,7 @@
|
||||
|
||||
QodeAssist is an AI-powered coding assistant plugin for Qt Creator. It provides intelligent code completion and suggestions for C++ and QML, leveraging large language models through local providers like Ollama. Enhance your coding productivity with context-aware AI assistance directly in your Qt development environment.
|
||||
|
||||
|
||||
<img src="https://github.com/user-attachments/assets/255a52f1-5cc0-4ca3-b05c-c4cf9cdbe25a" width="600" alt="QodeAssistPreview">
|
||||
|
||||
## Supported LLM Providers
|
||||
@ -11,6 +12,11 @@ QodeAssist currently supports the following LLM (Large Language Model) providers
|
||||
- [LM Studio](https://lmstudio.ai)
|
||||
- OpenAI compatible providers
|
||||
|
||||
## QtCreator Version Compatibility
|
||||
|
||||
- Since version 0.2.3: Compatible with QtCreator 14.0.2
|
||||
- QtCreator 14.0.1 and below are not supported anymore (latest compatible version: 0.2.2)
|
||||
|
||||
## Supported Models
|
||||
|
||||
QodeAssist has been thoroughly tested and optimized for use with the following language models, all of which are specifically trained for Fill-in-the-Middle (FIM) tasks:
|
||||
@ -65,9 +71,15 @@ If you've successfully used a model that's not listed here, please let us know b
|
||||
|
||||
1. Install QtCreator 14.0
|
||||
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. For example, you can run:
|
||||
|
||||
For suggestions:
|
||||
```
|
||||
ollama run starcoder2:7b
|
||||
ollama run codellama:7b-code
|
||||
```
|
||||
For chat:
|
||||
```
|
||||
ollama run codellama:7b-instruct
|
||||
```
|
||||
4. Download the QodeAssist plugin.
|
||||
5. Launch Qt Creator and install the plugin:
|
||||
|
||||
95
chat/ChatOutputPane.cpp
Normal file
95
chat/ChatOutputPane.cpp
Normal 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
52
chat/ChatOutputPane.h
Normal 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
|
||||
@ -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
37
chat/NavigationPanel.hpp
Normal 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
35
chatview/CMakeLists.txt
Normal 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}
|
||||
)
|
||||
179
chatview/ChatModel.cpp
Normal file
179
chatview/ChatModel.cpp
Normal 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 "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;
|
||||
}
|
||||
|
||||
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::addMessage(const QString &content, ChatRole role)
|
||||
{
|
||||
int tokenCount = estimateTokenCount(content);
|
||||
beginInsertRows(QModelIndex(), m_messages.size(), m_messages.size());
|
||||
m_messages.append({role, content, tokenCount});
|
||||
m_totalTokens += tokenCount;
|
||||
endInsertRows();
|
||||
trim();
|
||||
emit totalTokensChanged();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Chat
|
||||
83
chatview/ChatModel.hpp
Normal file
83
chatview/ChatModel.hpp
Normal 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/>.
|
||||
*/
|
||||
|
||||
#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;
|
||||
};
|
||||
|
||||
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);
|
||||
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;
|
||||
|
||||
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)
|
||||
127
chatview/ChatRootView.cpp
Normal file
127
chatview/ChatRootView.cpp
Normal file
@ -0,0 +1,127 @@
|
||||
/*
|
||||
* 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::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
|
||||
75
chatview/ChatRootView.hpp
Normal file
75
chatview/ChatRootView.hpp
Normal file
@ -0,0 +1,75 @@
|
||||
/*
|
||||
* 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);
|
||||
|
||||
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
|
||||
14
chatview/ChatUtils.cpp
Normal file
14
chatview/ChatUtils.cpp
Normal file
@ -0,0 +1,14 @@
|
||||
#include "ChatUtils.h"
|
||||
|
||||
#include <QClipboard>
|
||||
#include <QGuiApplication>
|
||||
|
||||
namespace QodeAssist::Chat {
|
||||
|
||||
void ChatUtils::copyToClipboard(const QString &text)
|
||||
{
|
||||
qDebug() << "call clipboard" << text;
|
||||
QGuiApplication::clipboard()->setText(text);
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Chat
|
||||
21
chatview/ChatUtils.h
Normal file
21
chatview/ChatUtils.h
Normal 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
43
chatview/ChatWidget.cpp
Normal 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
41
chatview/ChatWidget.hpp
Normal 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();
|
||||
};
|
||||
|
||||
}
|
||||
117
chatview/ClientInterface.cpp
Normal file
117
chatview/ClientInterface.cpp
Normal file
@ -0,0 +1,117 @@
|
||||
/*
|
||||
* 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 &, bool isComplete) {
|
||||
handleLLMResponse(completion, 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)
|
||||
{
|
||||
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);
|
||||
|
||||
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_accumulatedResponse.clear();
|
||||
m_chatModel->addMessage(message, ChatModel::ChatRole::User);
|
||||
m_requestHandler->sendLLMRequest(config, request);
|
||||
}
|
||||
|
||||
void ClientInterface::clearMessages()
|
||||
{
|
||||
m_chatModel->clear();
|
||||
m_accumulatedResponse.clear();
|
||||
LOG_MESSAGE("Chat history cleared");
|
||||
}
|
||||
|
||||
void ClientInterface::handleLLMResponse(const QString &response, bool isComplete)
|
||||
{
|
||||
m_accumulatedResponse += response;
|
||||
|
||||
if (isComplete) {
|
||||
LOG_MESSAGE("Message completed. Final response: " + m_accumulatedResponse);
|
||||
emit messageReceived(m_accumulatedResponse.trimmed());
|
||||
|
||||
m_chatModel->addMessage(m_accumulatedResponse.trimmed(), ChatModel::ChatRole::Assistant);
|
||||
m_accumulatedResponse.clear();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Chat
|
||||
54
chatview/ClientInterface.hpp
Normal file
54
chatview/ClientInterface.hpp
Normal 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 <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();
|
||||
|
||||
signals:
|
||||
void messageReceived(const QString &message);
|
||||
void errorOccurred(const QString &error);
|
||||
|
||||
private:
|
||||
void handleLLMResponse(const QString &response, bool isComplete);
|
||||
|
||||
LLMCore::RequestHandler *m_requestHandler;
|
||||
QString m_accumulatedResponse;
|
||||
ChatModel *m_chatModel;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Chat
|
||||
51
chatview/MessagePart.hpp
Normal file
51
chatview/MessagePart.hpp
Normal 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
40
chatview/qml/Badge.qml
Normal 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
91
chatview/qml/ChatItem.qml
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
161
chatview/qml/RootItem.qml
Normal file
161
chatview/qml/RootItem.qml
Normal file
@ -0,0 +1,161 @@
|
||||
/*
|
||||
* 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 5
|
||||
|
||||
QQC.TextField {
|
||||
id: messageInput
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.minimumWidth: 60
|
||||
Layout.minimumHeight: 30
|
||||
rightInset: -(parent.width - sendButton.width - clearButton.width)
|
||||
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
|
||||
}
|
||||
|
||||
onAccepted: sendButton.clicked()
|
||||
}
|
||||
|
||||
Button {
|
||||
id: sendButton
|
||||
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.minimumHeight: 30
|
||||
text: qsTr("Send")
|
||||
onClicked: {
|
||||
if (messageInput.text.trim() !== "") {
|
||||
root.sendMessage(messageInput.text);
|
||||
messageInput.text = ""
|
||||
scrollToBottom()
|
||||
}
|
||||
}
|
||||
}
|
||||
Button {
|
||||
id: clearButton
|
||||
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.minimumHeight: 30
|
||||
text: qsTr("Clear")
|
||||
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)
|
||||
}
|
||||
}
|
||||
86
chatview/qml/dialog/CodeBlock.qml
Normal file
86
chatview/qml/dialog/CodeBlock.qml
Normal 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/>.
|
||||
*/
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import ChatView
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
property string code: ""
|
||||
property string language: ""
|
||||
property color selectionColor
|
||||
|
||||
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: "monospace"
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
29
chatview/qml/dialog/TextBlock.qml
Normal file
29
chatview/qml/dialog/TextBlock.qml
Normal 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
|
||||
}
|
||||
@ -18,7 +18,7 @@
|
||||
*/
|
||||
|
||||
#include "ChangesManager.h"
|
||||
#include "QodeAssistUtils.hpp"
|
||||
#include "logger/Logger.hpp"
|
||||
#include "settings/ContextSettings.hpp"
|
||||
|
||||
namespace QodeAssist {
|
||||
@ -65,12 +65,6 @@ void ChangesManager::addChange(TextEditor::TextDocument *document,
|
||||
documentQueue.dequeue();
|
||||
}
|
||||
}
|
||||
|
||||
logMessage(QString("ChangesManager: Updated %1 line %2: '%3'")
|
||||
.arg(fileName)
|
||||
.arg(lineNumber)
|
||||
.arg(lineContent));
|
||||
logMessage(QString("ChangesManager: Document queue size %1").arg(documentQueue.size()));
|
||||
}
|
||||
|
||||
QString ChangesManager::getRecentChangesContext(const TextEditor::TextDocument *currentDocument) const
|
||||
|
||||
23
llmcore/CMakeLists.txt
Normal file
23
llmcore/CMakeLists.txt
Normal 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})
|
||||
@ -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
|
||||
@ -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
|
||||
88
llmcore/PromptTemplateManager.cpp
Normal file
88
llmcore/PromptTemplateManager.cpp
Normal file
@ -0,0 +1,88 @@
|
||||
/*
|
||||
* 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 nullptr;
|
||||
}
|
||||
|
||||
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 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
|
||||
@ -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
|
||||
@ -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
|
||||
86
llmcore/ProvidersManager.cpp
Normal file
86
llmcore/ProvidersManager.cpp
Normal 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 nullptr;
|
||||
}
|
||||
|
||||
return m_currentFimProvider;
|
||||
}
|
||||
|
||||
Provider *ProvidersManager::getCurrentChatProvider()
|
||||
{
|
||||
if (m_currentChatProvider == nullptr) {
|
||||
LOG_MESSAGE("Current chat provider is null");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return m_currentChatProvider;
|
||||
}
|
||||
|
||||
QStringList ProvidersManager::providersNames() const
|
||||
{
|
||||
return m_providers.keys();
|
||||
}
|
||||
|
||||
ProvidersManager::~ProvidersManager()
|
||||
{
|
||||
qDeleteAll(m_providers);
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::LLMCore
|
||||
@ -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
40
llmcore/RequestConfig.hpp
Normal 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
|
||||
157
llmcore/RequestHandler.cpp
Normal file
157
llmcore/RequestHandler.cpp
Normal file
@ -0,0 +1,157 @@
|
||||
/*
|
||||
* 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 || reply->isFinished()) {
|
||||
if (isComplete) {
|
||||
if (config.requestType == RequestType::Fim) {
|
||||
auto cleanedCompletion = removeStopWords(accumulatedResponse,
|
||||
config.promptTemplate->stopWords());
|
||||
emit completionReceived(cleanedCompletion, request, true);
|
||||
} else {
|
||||
emit completionReceived(accumulatedResponse, request, true);
|
||||
}
|
||||
} else {
|
||||
emit completionReceived(accumulatedResponse, request, false);
|
||||
}
|
||||
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
|
||||
63
llmcore/RequestHandler.hpp
Normal file
63
llmcore/RequestHandler.hpp
Normal 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
6
llmcore/RequestType.hpp
Normal file
@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
namespace QodeAssist::LLMCore {
|
||||
|
||||
enum RequestType { Fim, Chat };
|
||||
}
|
||||
14
logger/CMakeLists.txt
Normal file
14
logger/CMakeLists.txt
Normal 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
55
logger/Logger.cpp
Normal 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
33
logger/Logger.hpp
Normal 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
|
||||
@ -25,8 +25,7 @@
|
||||
#include <QJsonObject>
|
||||
#include <QNetworkReply>
|
||||
|
||||
#include "PromptTemplateManager.hpp"
|
||||
#include "QodeAssistUtils.hpp"
|
||||
#include "logger/Logger.hpp"
|
||||
#include "settings/PresetPromptsSettings.hpp"
|
||||
|
||||
namespace QodeAssist::Providers {
|
||||
@ -48,29 +47,43 @@ QString LMStudioProvider::completionEndpoint() const
|
||||
return "/v1/chat/completions";
|
||||
}
|
||||
|
||||
void LMStudioProvider::prepareRequest(QJsonObject &request)
|
||||
QString LMStudioProvider::chatEndpoint() const
|
||||
{
|
||||
auto &settings = Settings::presetPromptsSettings();
|
||||
const auto ¤tTemplate = PromptTemplateManager::instance().getCurrentTemplate();
|
||||
if (currentTemplate->name() == "Custom Template")
|
||||
return;
|
||||
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)
|
||||
@ -114,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);
|
||||
|
||||
@ -138,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();
|
||||
|
||||
@ -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
|
||||
|
||||
@ -25,8 +25,8 @@
|
||||
#include <QNetworkReply>
|
||||
#include <QtCore/qeventloop.h>
|
||||
|
||||
#include "PromptTemplateManager.hpp"
|
||||
#include "QodeAssistUtils.hpp"
|
||||
#include "llmcore/PromptTemplateManager.hpp"
|
||||
#include "logger/Logger.hpp"
|
||||
#include "settings/PresetPromptsSettings.hpp"
|
||||
|
||||
namespace QodeAssist::Providers {
|
||||
@ -48,61 +48,87 @@ QString OllamaProvider::completionEndpoint() const
|
||||
return "/api/generate";
|
||||
}
|
||||
|
||||
void OllamaProvider::prepareRequest(QJsonObject &request)
|
||||
QString OllamaProvider::chatEndpoint() const
|
||||
{
|
||||
auto &settings = Settings::presetPromptsSettings();
|
||||
auto currentTemplate = PromptTemplateManager::instance().getCurrentTemplate();
|
||||
if (currentTemplate->name() == "Custom Template")
|
||||
return;
|
||||
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;
|
||||
@ -121,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();
|
||||
|
||||
@ -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
|
||||
|
||||
@ -24,7 +24,6 @@
|
||||
#include <QJsonObject>
|
||||
#include <QNetworkReply>
|
||||
|
||||
#include "PromptTemplateManager.hpp"
|
||||
#include "settings/PresetPromptsSettings.hpp"
|
||||
|
||||
namespace QodeAssist::Providers {
|
||||
@ -46,32 +45,44 @@ QString OpenAICompatProvider::completionEndpoint() const
|
||||
return "/v1/chat/completions";
|
||||
}
|
||||
|
||||
void OpenAICompatProvider::prepareRequest(QJsonObject &request)
|
||||
QString OpenAICompatProvider::chatEndpoint() const
|
||||
{
|
||||
auto &settings = Settings::presetPromptsSettings();
|
||||
const auto ¤tTemplate = PromptTemplateManager::instance().getCurrentTemplate();
|
||||
if (currentTemplate->name() == "Custom Template")
|
||||
return;
|
||||
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;
|
||||
}
|
||||
@ -118,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();
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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,22 @@
|
||||
#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/CustomTemplate.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"
|
||||
|
||||
using namespace Utils;
|
||||
using namespace Core;
|
||||
@ -67,21 +74,26 @@ 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::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>();
|
||||
|
||||
Utils::Icon QCODEASSIST_ICON(
|
||||
{{":/resources/images/qoderassist-icon.png", Utils::Theme::IconsBaseColor}});
|
||||
@ -106,6 +118,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 +154,8 @@ public:
|
||||
|
||||
private:
|
||||
QPointer<QodeAssistClient> m_qodeAssistClient;
|
||||
QPointer<Chat::ChatOutputPane> m_chatOutputPane;
|
||||
QPointer<Chat::NavigationPanel> m_navigationPanel;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Internal
|
||||
|
||||
@ -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
20
settings/CMakeLists.txt
Normal 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})
|
||||
@ -17,8 +17,6 @@
|
||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ContextSettings.hpp"
|
||||
|
||||
#include <QMessageBox>
|
||||
@ -26,8 +24,7 @@
|
||||
#include <coreplugin/icore.h>
|
||||
#include <utils/layoutbuilder.h>
|
||||
|
||||
#include "QodeAssistConstants.hpp"
|
||||
#include "QodeAssisttr.h"
|
||||
#include "SettingsConstants.hpp"
|
||||
|
||||
namespace QodeAssist::Settings {
|
||||
ContextSettings &contextSettings()
|
||||
@ -60,17 +57,37 @@ ContextSettings::ContextSettings()
|
||||
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"));
|
||||
useSystemPrompt.setSettingsKey(Constants::USE_SYSTEM_PROMPT);
|
||||
useSystemPrompt.setDefaultValue(true);
|
||||
useSystemPrompt.setLabelText(Tr::tr("Use System Prompt"));
|
||||
|
||||
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");
|
||||
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");
|
||||
|
||||
@ -87,21 +104,26 @@ ContextSettings::ContextSettings()
|
||||
|
||||
readStringsAfterCursor.setEnabled(!readFullFile());
|
||||
readStringsBeforeCursor.setEnabled(!readFullFile());
|
||||
specificInstractions.setEnabled(useSpecificInstructions());
|
||||
systemPrompt.setEnabled(useSystemPrompt());
|
||||
|
||||
setupConnection();
|
||||
|
||||
setLayouter([this]() {
|
||||
using namespace Layouting;
|
||||
return Column{Row{readFullFile, Stretch{1}, resetToDefaults},
|
||||
Row{readStringsBeforeCursor, Stretch{1}},
|
||||
Row{readStringsAfterCursor, Stretch{1}},
|
||||
useFilePathInContext,
|
||||
useSpecificInstructions,
|
||||
specificInstractions,
|
||||
useProjectChangesCache,
|
||||
Row{maxChangesCacheSize, Stretch{1}},
|
||||
Stretch{1}};
|
||||
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}}};
|
||||
});
|
||||
}
|
||||
|
||||
@ -111,8 +133,8 @@ void ContextSettings::setupConnection()
|
||||
readStringsAfterCursor.setEnabled(!readFullFile.volatileValue());
|
||||
readStringsBeforeCursor.setEnabled(!readFullFile.volatileValue());
|
||||
});
|
||||
connect(&useSpecificInstructions, &Utils::BoolAspect::volatileValueChanged, this, [this]() {
|
||||
specificInstractions.setEnabled(useSpecificInstructions.volatileValue());
|
||||
connect(&useSystemPrompt, &Utils::BoolAspect::volatileValueChanged, this, [this]() {
|
||||
systemPrompt.setEnabled(useSystemPrompt.volatileValue());
|
||||
});
|
||||
connect(&resetToDefaults, &ButtonAspect::clicked, this, &ContextSettings::resetPageToDefaults);
|
||||
}
|
||||
@ -131,13 +153,11 @@ void ContextSettings::resetPageToDefaults()
|
||||
resetAspect(readStringsBeforeCursor);
|
||||
resetAspect(readStringsAfterCursor);
|
||||
resetAspect(useFilePathInContext);
|
||||
resetAspect(useSpecificInstructions);
|
||||
resetAspect(specificInstractions);
|
||||
resetAspect(useSystemPrompt);
|
||||
resetAspect(systemPrompt);
|
||||
resetAspect(useChatSystemPrompt);
|
||||
resetAspect(chatSystemPrompt);
|
||||
}
|
||||
|
||||
QMessageBox::information(Core::ICore::dialogParent(),
|
||||
Tr::tr("Settings Reset"),
|
||||
Tr::tr("All settings have been reset to their default values."));
|
||||
}
|
||||
|
||||
class ContextSettingsPage : public Core::IOptionsPage
|
||||
|
||||
@ -34,11 +34,13 @@ public:
|
||||
Utils::IntegerAspect readStringsBeforeCursor{this};
|
||||
Utils::IntegerAspect readStringsAfterCursor{this};
|
||||
|
||||
Utils::StringAspect specificInstractions{this};
|
||||
Utils::BoolAspect useSpecificInstructions{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};
|
||||
|
||||
|
||||
@ -26,8 +26,7 @@
|
||||
#include <coreplugin/icore.h>
|
||||
#include <utils/layoutbuilder.h>
|
||||
|
||||
#include "QodeAssistConstants.hpp"
|
||||
#include "QodeAssisttr.h"
|
||||
#include "SettingsConstants.hpp"
|
||||
|
||||
namespace QodeAssist::Settings {
|
||||
|
||||
@ -85,12 +84,13 @@ CustomPromptSettings::CustomPromptSettings()
|
||||
|
||||
setLayouter([this]() {
|
||||
using namespace Layouting;
|
||||
return Column{Row{customJsonLabel, Stretch{1}, resetToDefaults},
|
||||
Row{customJsonTemplate,
|
||||
Column{saveCustomTemplateButton,
|
||||
loadCustomTemplateButton,
|
||||
customJsonLegend,
|
||||
Stretch{1}}}};
|
||||
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}}}}}};
|
||||
});
|
||||
}
|
||||
|
||||
@ -122,10 +122,6 @@ void CustomPromptSettings::resetSettingsToDefaults()
|
||||
if (reply == QMessageBox::Yes) {
|
||||
resetAspect(customJsonTemplate);
|
||||
}
|
||||
|
||||
QMessageBox::information(Core::ICore::dialogParent(),
|
||||
Tr::tr("Settings Reset"),
|
||||
Tr::tr("All settings have been reset to their default values."));
|
||||
}
|
||||
|
||||
void CustomPromptSettings::saveCustomTemplate()
|
||||
|
||||
@ -19,7 +19,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "settings/SettingsUtils.hpp"
|
||||
#include "SettingsUtils.hpp"
|
||||
#include <utils/aspects.h>
|
||||
|
||||
namespace QodeAssist::Settings {
|
||||
|
||||
@ -24,12 +24,14 @@
|
||||
#include <coreplugin/dialogs/ioptionspage.h>
|
||||
#include <coreplugin/icore.h>
|
||||
#include <utils/layoutbuilder.h>
|
||||
#include <utils/utilsicons.h>
|
||||
|
||||
#include "LLMProvidersManager.hpp"
|
||||
#include "Logger.hpp"
|
||||
#include "PromptTemplateManager.hpp"
|
||||
#include "QodeAssistConstants.hpp"
|
||||
#include "QodeAssistUtils.hpp"
|
||||
#include "QodeAssisttr.h"
|
||||
#include "Provider.hpp"
|
||||
#include "ProvidersManager.hpp"
|
||||
#include "SettingsConstants.hpp"
|
||||
#include "SettingsUtils.hpp"
|
||||
|
||||
namespace QodeAssist::Settings {
|
||||
|
||||
@ -58,8 +60,8 @@ GeneralSettings::GeneralSettings()
|
||||
enableLogging.setDefaultValue(false);
|
||||
|
||||
multiLineCompletion.setSettingsKey(Constants::MULTILINE_COMPLETION);
|
||||
multiLineCompletion.setDefaultValue(true);
|
||||
multiLineCompletion.setLabelText(Tr::tr("Enable 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)"));
|
||||
@ -83,9 +85,8 @@ GeneralSettings::GeneralSettings()
|
||||
autoCompletionTypingInterval.setDefaultValue(2000);
|
||||
|
||||
llmProviders.setSettingsKey(Constants::LLM_PROVIDERS);
|
||||
llmProviders.setDisplayName(Tr::tr("FIM Provider:"));
|
||||
llmProviders.setDisplayName(Tr::tr("AI Suggest Provider:"));
|
||||
llmProviders.setDisplayStyle(Utils::SelectionAspect::DisplayStyle::ComboBox);
|
||||
llmProviders.setDefaultValue(0);
|
||||
|
||||
url.setSettingsKey(Constants::URL);
|
||||
url.setLabelText(Tr::tr("URL:"));
|
||||
@ -96,102 +97,165 @@ GeneralSettings::GeneralSettings()
|
||||
endPoint.setDisplayStyle(Utils::StringAspect::LineEditDisplay);
|
||||
|
||||
modelName.setSettingsKey(Constants::MODEL_NAME);
|
||||
modelName.setLabelText(Tr::tr("LLM 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.setDefaultValue(0);
|
||||
fimPrompts.setDisplayStyle(Utils::SelectionAspect::DisplayStyle::ComboBox);
|
||||
resetToDefaults.m_buttonText = Tr::tr("Reset Page to Defaults");
|
||||
|
||||
const auto &manager = LLMProvidersManager::instance();
|
||||
if (!manager.getProviderNames().isEmpty()) {
|
||||
const auto providerNames = manager.getProviderNames();
|
||||
for (const QString &name : providerNames) {
|
||||
llmProviders.addOption(name);
|
||||
}
|
||||
}
|
||||
chatLlmProviders.setSettingsKey(Constants::CHAT_LLM_PROVIDERS);
|
||||
chatLlmProviders.setDisplayName(Tr::tr("AI Chat Provider:"));
|
||||
chatLlmProviders.setDisplayStyle(Utils::SelectionAspect::DisplayStyle::ComboBox);
|
||||
|
||||
const auto &promptManager = PromptTemplateManager::instance();
|
||||
if (!promptManager.getTemplateNames().isEmpty()) {
|
||||
const auto promptNames = promptManager.getTemplateNames();
|
||||
for (const QString &name : promptNames) {
|
||||
fimPrompts.addOption(name);
|
||||
}
|
||||
}
|
||||
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(4000);
|
||||
|
||||
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();
|
||||
|
||||
LLMProvidersManager::instance().setCurrentProvider(llmProviders.stringValue());
|
||||
PromptTemplateManager::instance().setCurrentTemplate(fimPrompts.stringValue());
|
||||
setLoggingEnabled(enableLogging());
|
||||
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},
|
||||
enableAutoComplete,
|
||||
multiLineCompletion,
|
||||
Row{autoCompletionCharThreshold,
|
||||
autoCompletionTypingInterval,
|
||||
startSuggestionTimer,
|
||||
Stretch{1}},
|
||||
Space{8},
|
||||
enableLogging,
|
||||
Space{8},
|
||||
llmProviders,
|
||||
Row{url, endPoint},
|
||||
Space{8},
|
||||
Row{selectModels, modelName},
|
||||
Space{8},
|
||||
fimPrompts,
|
||||
Stretch{1}};
|
||||
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]() {
|
||||
int index = llmProviders.volatileValue();
|
||||
logMessage(QString("currentProvider %1").arg(llmProviders.displayForIndex(index)));
|
||||
LLMProvidersManager::instance().setCurrentProvider(llmProviders.displayForIndex(index));
|
||||
updateProviderSettings();
|
||||
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();
|
||||
logMessage(QString("currentPrompt %1").arg(fimPrompts.displayForIndex(index)));
|
||||
PromptTemplateManager::instance().setCurrentTemplate(fimPrompts.displayForIndex(index));
|
||||
LLMCore::PromptTemplateManager::instance().setCurrentFimTemplate(
|
||||
fimPrompts.displayForIndex(index));
|
||||
});
|
||||
connect(&selectModels, &ButtonAspect::clicked, this, [this]() { showModelSelectionDialog(); });
|
||||
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]() {
|
||||
setLoggingEnabled(enableLogging.volatileValue());
|
||||
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::updateProviderSettings()
|
||||
void GeneralSettings::showModelSelectionDialog(Utils::StringAspect *modelNameObj,
|
||||
LLMCore::Provider *provider)
|
||||
{
|
||||
const auto provider = LLMProvidersManager::instance().getCurrentProvider();
|
||||
|
||||
if (provider) {
|
||||
url.setVolatileValue(provider->url());
|
||||
endPoint.setVolatileValue(provider->completionEndpoint());
|
||||
}
|
||||
}
|
||||
|
||||
void GeneralSettings::showModelSelectionDialog()
|
||||
{
|
||||
auto *provider = LLMProvidersManager::instance().getCurrentProvider();
|
||||
Utils::Environment env = Utils::Environment::systemEnvironment();
|
||||
QString providerUrl = (modelNameObj == &modelName) ? url() : chatUrl();
|
||||
|
||||
if (provider) {
|
||||
QStringList models = provider->getInstalledModels(env);
|
||||
QStringList models = provider->getInstalledModels(env, providerUrl);
|
||||
bool ok;
|
||||
QString selectedModel = QInputDialog::getItem(Core::ICore::dialogParent(),
|
||||
Tr::tr("Select LLM Model"),
|
||||
@ -202,7 +266,7 @@ void GeneralSettings::showModelSelectionDialog()
|
||||
&ok);
|
||||
|
||||
if (ok && !selectedModel.isEmpty()) {
|
||||
modelName.setVolatileValue(selectedModel);
|
||||
modelNameObj->setVolatileValue(selectedModel);
|
||||
writeSettings();
|
||||
}
|
||||
}
|
||||
@ -220,23 +284,106 @@ void GeneralSettings::resetPageToDefaults()
|
||||
if (reply == QMessageBox::Yes) {
|
||||
resetAspect(enableQodeAssist);
|
||||
resetAspect(enableAutoComplete);
|
||||
resetAspect(llmProviders);
|
||||
resetAspect(url);
|
||||
resetAspect(endPoint);
|
||||
resetAspect(modelName);
|
||||
resetAspect(fimPrompts);
|
||||
resetAspect(enableLogging);
|
||||
resetAspect(startSuggestionTimer);
|
||||
resetAspect(autoCompletionTypingInterval);
|
||||
resetAspect(autoCompletionCharThreshold);
|
||||
resetAspect(llmProviders);
|
||||
resetAspect(chatLlmProviders);
|
||||
resetAspect(fimPrompts);
|
||||
resetAspect(chatPrompts);
|
||||
resetAspect(chatTokensThreshold);
|
||||
}
|
||||
|
||||
fimPrompts.setStringValue("StarCoder2");
|
||||
llmProviders.setStringValue("Ollama");
|
||||
modelName.setVolatileValue("");
|
||||
chatModelName.setVolatileValue("");
|
||||
|
||||
QMessageBox::information(Core::ICore::dialogParent(),
|
||||
Tr::tr("Settings Reset"),
|
||||
Tr::tr("All settings have been reset to their default values."));
|
||||
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
|
||||
|
||||
@ -21,8 +21,11 @@
|
||||
|
||||
#include <utils/aspects.h>
|
||||
|
||||
#include "settings/SettingsUtils.hpp"
|
||||
#include "SettingsUtils.hpp"
|
||||
|
||||
namespace QodeAssist::LLMCore {
|
||||
class Provider;
|
||||
}
|
||||
namespace QodeAssist::Settings {
|
||||
|
||||
class GeneralSettings : public Utils::AspectContainer
|
||||
@ -47,11 +50,34 @@ public:
|
||||
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 updateProviderSettings();
|
||||
void showModelSelectionDialog();
|
||||
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();
|
||||
|
||||
@ -24,8 +24,8 @@
|
||||
#include <coreplugin/icore.h>
|
||||
#include <utils/layoutbuilder.h>
|
||||
|
||||
#include "QodeAssistConstants.hpp"
|
||||
#include "QodeAssisttr.h"
|
||||
#include "RequestType.hpp"
|
||||
#include "SettingsConstants.hpp"
|
||||
|
||||
namespace QodeAssist::Settings {
|
||||
|
||||
@ -41,99 +41,190 @@ PresetPromptsSettings::PresetPromptsSettings()
|
||||
|
||||
setDisplayName(Tr::tr("Preset Prompts Params"));
|
||||
|
||||
temperature.setSettingsKey(Constants::TEMPERATURE);
|
||||
temperature.setLabelText(Tr::tr("Temperature:"));
|
||||
temperature.setDefaultValue(0.2);
|
||||
temperature.setRange(0.0, 10.0);
|
||||
fimTemperature.setSettingsKey(Constants::FIM_TEMPERATURE);
|
||||
fimTemperature.setLabelText(Tr::tr("Temperature:"));
|
||||
fimTemperature.setDefaultValue(0.2);
|
||||
fimTemperature.setRange(0.0, 10.0);
|
||||
fimTemperature.setSingleStep(0.1);
|
||||
|
||||
ollamaLivetime.setSettingsKey(Constants::OLLAMA_LIVETIME);
|
||||
ollamaLivetime.setLabelText(
|
||||
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"));
|
||||
ollamaLivetime.setDefaultValue("5m");
|
||||
ollamaLivetime.setDisplayStyle(Utils::StringAspect::LineEditDisplay);
|
||||
fimOllamaLivetime.setDefaultValue("5m");
|
||||
fimOllamaLivetime.setDisplayStyle(Utils::StringAspect::LineEditDisplay);
|
||||
|
||||
maxTokens.setSettingsKey(Constants::MAX_TOKENS);
|
||||
maxTokens.setLabelText(Tr::tr("Max Tokens"));
|
||||
maxTokens.setRange(-1, 10000);
|
||||
maxTokens.setDefaultValue(150);
|
||||
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);
|
||||
|
||||
useTopP.setSettingsKey(Constants::USE_TOP_P);
|
||||
useTopP.setDefaultValue(false);
|
||||
fimMaxTokens.setSettingsKey(Constants::FIM_MAX_TOKENS);
|
||||
fimMaxTokens.setLabelText(Tr::tr("Max Tokens"));
|
||||
fimMaxTokens.setRange(-1, 10000);
|
||||
fimMaxTokens.setDefaultValue(50);
|
||||
|
||||
topP.setSettingsKey(Constants::TOP_P);
|
||||
topP.setLabelText(Tr::tr("top_p"));
|
||||
topP.setDefaultValue(0.9);
|
||||
topP.setRange(0.0, 1.0);
|
||||
chatMaxTokens.setSettingsKey(Constants::CHAT_MAX_TOKENS);
|
||||
chatMaxTokens.setLabelText(Tr::tr("Max Tokens"));
|
||||
chatMaxTokens.setRange(-1, 10000);
|
||||
chatMaxTokens.setDefaultValue(2000);
|
||||
|
||||
useTopK.setSettingsKey(Constants::USE_TOP_K);
|
||||
useTopK.setDefaultValue(false);
|
||||
fimUseTopP.setSettingsKey(Constants::FIM_USE_TOP_P);
|
||||
fimUseTopP.setDefaultValue(false);
|
||||
|
||||
topK.setSettingsKey(Constants::TOP_K);
|
||||
topK.setLabelText(Tr::tr("top_k"));
|
||||
topK.setDefaultValue(50);
|
||||
topK.setRange(1, 1000);
|
||||
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);
|
||||
|
||||
usePresencePenalty.setSettingsKey(Constants::USE_PRESENCE_PENALTY);
|
||||
usePresencePenalty.setDefaultValue(false);
|
||||
chatUseTopP.setSettingsKey(Constants::CHAT_USE_TOP_P);
|
||||
chatUseTopP.setDefaultValue(false);
|
||||
|
||||
presencePenalty.setSettingsKey(Constants::PRESENCE_PENALTY);
|
||||
presencePenalty.setLabelText(Tr::tr("presence_penalty"));
|
||||
presencePenalty.setDefaultValue(0.0);
|
||||
presencePenalty.setRange(-2.0, 2.0);
|
||||
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);
|
||||
|
||||
useFrequencyPenalty.setSettingsKey(Constants::USE_FREQUENCY_PENALTY);
|
||||
useFrequencyPenalty.setDefaultValue(false);
|
||||
fimUseTopK.setSettingsKey(Constants::FIM_USE_TOP_K);
|
||||
fimUseTopK.setDefaultValue(false);
|
||||
|
||||
frequencyPenalty.setSettingsKey(Constants::FREQUENCY_PENALTY);
|
||||
frequencyPenalty.setLabelText(Tr::tr("frequency_penalty"));
|
||||
frequencyPenalty.setDefaultValue(0.0);
|
||||
frequencyPenalty.setRange(-2.0, 2.0);
|
||||
fimTopK.setSettingsKey(Constants::FIM_TOP_K);
|
||||
fimTopK.setLabelText(Tr::tr("use top_k"));
|
||||
fimTopK.setDefaultValue(50);
|
||||
fimTopK.setRange(1, 1000);
|
||||
|
||||
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"));
|
||||
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();
|
||||
|
||||
topK.setEnabled(useTopK());
|
||||
topP.setEnabled(useTopP());
|
||||
presencePenalty.setEnabled(usePresencePenalty());
|
||||
frequencyPenalty.setEnabled(useFrequencyPenalty());
|
||||
|
||||
setupConnections();
|
||||
|
||||
setLayouter([this]() {
|
||||
using namespace Layouting;
|
||||
return Column{Row{temperature, Stretch{1}, resetToDefaults},
|
||||
maxTokens,
|
||||
Row{useTopP, topP, Stretch{1}},
|
||||
Row{useTopK, topK, Stretch{1}},
|
||||
Row{usePresencePenalty, presencePenalty, Stretch{1}},
|
||||
Row{useFrequencyPenalty, frequencyPenalty, Stretch{1}},
|
||||
apiKey,
|
||||
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(&useTopP, &Utils::BoolAspect::volatileValueChanged, this, [this]() {
|
||||
topP.setEnabled(useTopP.volatileValue());
|
||||
});
|
||||
connect(&useTopK, &Utils::BoolAspect::volatileValueChanged, this, [this]() {
|
||||
topK.setEnabled(useTopK.volatileValue());
|
||||
});
|
||||
connect(&usePresencePenalty, &Utils::BoolAspect::volatileValueChanged, this, [this]() {
|
||||
presencePenalty.setEnabled(usePresencePenalty.volatileValue());
|
||||
});
|
||||
connect(&useFrequencyPenalty, &Utils::BoolAspect::volatileValueChanged, this, [this]() {
|
||||
frequencyPenalty.setEnabled(useFrequencyPenalty.volatileValue());
|
||||
});
|
||||
|
||||
connect(&resetToDefaults,
|
||||
&ButtonAspect::clicked,
|
||||
this,
|
||||
@ -150,22 +241,29 @@ void PresetPromptsSettings::resetSettingsToDefaults()
|
||||
QMessageBox::Yes | QMessageBox::No);
|
||||
|
||||
if (reply == QMessageBox::Yes) {
|
||||
resetAspect(temperature);
|
||||
resetAspect(maxTokens);
|
||||
resetAspect(ollamaLivetime);
|
||||
resetAspect(useTopP);
|
||||
resetAspect(topP);
|
||||
resetAspect(useTopK);
|
||||
resetAspect(topK);
|
||||
resetAspect(usePresencePenalty);
|
||||
resetAspect(presencePenalty);
|
||||
resetAspect(useFrequencyPenalty);
|
||||
resetAspect(frequencyPenalty);
|
||||
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);
|
||||
}
|
||||
|
||||
QMessageBox::information(Core::ICore::dialogParent(),
|
||||
Tr::tr("Settings Reset"),
|
||||
Tr::tr("All settings have been reset to their default values."));
|
||||
}
|
||||
|
||||
class PresetPromptsSettingsPage : public Core::IOptionsPage
|
||||
|
||||
@ -19,7 +19,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "settings/SettingsUtils.hpp"
|
||||
#include "SettingsUtils.hpp"
|
||||
#include <utils/aspects.h>
|
||||
|
||||
namespace QodeAssist::Settings {
|
||||
@ -27,28 +27,63 @@ 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 temperature{this};
|
||||
Utils::IntegerAspect maxTokens{this};
|
||||
Utils::DoubleAspect fimTemperature{this};
|
||||
Utils::IntegerAspect fimMaxTokens{this};
|
||||
|
||||
Utils::BoolAspect useTopP{this};
|
||||
Utils::DoubleAspect topP{this};
|
||||
Utils::DoubleAspect chatTemperature{this};
|
||||
Utils::IntegerAspect chatMaxTokens{this};
|
||||
|
||||
Utils::BoolAspect useTopK{this};
|
||||
Utils::IntegerAspect topK{this};
|
||||
Utils::BoolAspect fimUseTopP{this};
|
||||
Utils::DoubleAspect fimTopP{this};
|
||||
|
||||
Utils::BoolAspect usePresencePenalty{this};
|
||||
Utils::DoubleAspect presencePenalty{this};
|
||||
Utils::BoolAspect chatUseTopP{this};
|
||||
Utils::DoubleAspect chatTopP{this};
|
||||
|
||||
Utils::BoolAspect useFrequencyPenalty{this};
|
||||
Utils::DoubleAspect frequencyPenalty{this};
|
||||
Utils::BoolAspect fimUseTopK{this};
|
||||
Utils::IntegerAspect fimTopK{this};
|
||||
|
||||
Utils::StringAspect ollamaLivetime{this};
|
||||
Utils::StringAspect apiKey{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();
|
||||
|
||||
102
settings/SettingsConstants.hpp
Normal file
102
settings/SettingsConstants.hpp
Normal 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
|
||||
@ -19,12 +19,53 @@
|
||||
|
||||
#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)
|
||||
{
|
||||
|
||||
49
templates/CodeLlamaChat.hpp
Normal file
49
templates/CodeLlamaChat.hpp
Normal file
@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Templates
|
||||
@ -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;
|
||||
@ -19,31 +19,32 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "PromptTemplate.hpp"
|
||||
#include "llmcore/PromptTemplate.hpp"
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
|
||||
#include "QodeAssistUtils.hpp"
|
||||
#include "logger/Logger.hpp"
|
||||
#include "settings/CustomPromptSettings.hpp"
|
||||
|
||||
namespace QodeAssist::Templates {
|
||||
|
||||
class CustomTemplate : public PromptTemplate
|
||||
class CustomTemplate : public LLMCore::PromptTemplate
|
||||
{
|
||||
public:
|
||||
QString name() const override { return "Custom Template"; }
|
||||
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 ContextData &context) const override
|
||||
void prepareRequest(QJsonObject &request, const LLMCore::ContextData &context) const override
|
||||
{
|
||||
QJsonDocument doc = QJsonDocument::fromJson(promptTemplate().toUtf8());
|
||||
if (doc.isNull() || !doc.isObject()) {
|
||||
logMessage(QString("Invalid JSON template in settings"));
|
||||
LOG_MESSAGE(QString("Invalid JSON template in settings"));
|
||||
|
||||
return;
|
||||
}
|
||||
@ -57,11 +58,11 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
QJsonValue processJsonValue(const QJsonValue &value, const ContextData &context) const
|
||||
QJsonValue processJsonValue(const QJsonValue &value, const LLMCore::ContextData &context) const
|
||||
{
|
||||
if (value.isString()) {
|
||||
QString str = value.toString();
|
||||
str.replace("{{QODE_INSTRUCTIONS}}", context.instriuctions);
|
||||
str.replace("{{QODE_INSTRUCTIONS}}", context.systemPrompt);
|
||||
str.replace("{{QODE_PREFIX}}", context.prefix);
|
||||
str.replace("{{QODE_SUFFIX}}", context.suffix);
|
||||
return str;
|
||||
@ -77,7 +78,8 @@ private:
|
||||
return value;
|
||||
}
|
||||
|
||||
QJsonObject processJsonTemplate(const QJsonObject &templateObj, const ContextData &context) const
|
||||
QJsonObject processJsonTemplate(const QJsonObject &templateObj,
|
||||
const LLMCore::ContextData &context) const
|
||||
{
|
||||
QJsonObject result;
|
||||
for (auto it = templateObj.begin(); it != templateObj.end(); ++it) {
|
||||
54
templates/DeepSeekCoderChat.hpp
Normal file
54
templates/DeepSeekCoderChat.hpp
Normal 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
|
||||
@ -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;
|
||||
54
templates/QwenChat.hpp
Normal file
54
templates/QwenChat.hpp
Normal 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 QwenChat : public LLMCore::PromptTemplate
|
||||
{
|
||||
public:
|
||||
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### " << "<|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
|
||||
@ -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;
|
||||
Reference in New Issue
Block a user