mirror of
https://github.com/Palm1r/QodeAssist.git
synced 2026-06-17 03:39:19 -04:00
Compare commits
8 Commits
v0.5.0
...
dev-rag-ba
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
142afa725f | ||
|
|
f36db033e6 | ||
|
|
5dfcf74128 | ||
|
|
02101665ca | ||
|
|
77a03d42ed | ||
|
|
09c38c8b0e | ||
|
|
7b73d7af7b | ||
|
|
5a426b4d9f |
@@ -8,7 +8,6 @@ set(CMAKE_AUTOUIC ON)
|
|||||||
set(CMAKE_CXX_STANDARD 20)
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||||
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
|
||||||
|
|
||||||
find_package(QtCreator REQUIRED COMPONENTS Core)
|
find_package(QtCreator REQUIRED COMPONENTS Core)
|
||||||
find_package(Qt6 COMPONENTS Core Gui Quick Widgets Network REQUIRED)
|
find_package(Qt6 COMPONENTS Core Gui Quick Widgets Network REQUIRED)
|
||||||
@@ -44,30 +43,26 @@ add_qtc_plugin(QodeAssist
|
|||||||
LLMClientInterface.hpp LLMClientInterface.cpp
|
LLMClientInterface.hpp LLMClientInterface.cpp
|
||||||
templates/Templates.hpp
|
templates/Templates.hpp
|
||||||
templates/CodeLlamaFim.hpp
|
templates/CodeLlamaFim.hpp
|
||||||
templates/Ollama.hpp
|
|
||||||
templates/Claude.hpp
|
|
||||||
templates/OpenAI.hpp
|
|
||||||
templates/MistralAI.hpp
|
|
||||||
templates/StarCoder2Fim.hpp
|
templates/StarCoder2Fim.hpp
|
||||||
# templates/DeepSeekCoderFim.hpp
|
templates/DeepSeekCoderFim.hpp
|
||||||
# templates/CustomFimTemplate.hpp
|
templates/CustomFimTemplate.hpp
|
||||||
templates/Qwen.hpp
|
templates/Qwen.hpp
|
||||||
templates/OpenAICompatible.hpp
|
templates/Ollama.hpp
|
||||||
|
templates/BasicChat.hpp
|
||||||
templates/Llama3.hpp
|
templates/Llama3.hpp
|
||||||
templates/ChatML.hpp
|
templates/ChatML.hpp
|
||||||
templates/Alpaca.hpp
|
templates/Alpaca.hpp
|
||||||
templates/Llama2.hpp
|
templates/Llama2.hpp
|
||||||
|
templates/Claude.hpp
|
||||||
|
templates/OpenAI.hpp
|
||||||
templates/CodeLlamaQMLFim.hpp
|
templates/CodeLlamaQMLFim.hpp
|
||||||
templates/GoogleAI.hpp
|
|
||||||
providers/Providers.hpp
|
providers/Providers.hpp
|
||||||
providers/OllamaProvider.hpp providers/OllamaProvider.cpp
|
providers/OllamaProvider.hpp providers/OllamaProvider.cpp
|
||||||
providers/ClaudeProvider.hpp providers/ClaudeProvider.cpp
|
|
||||||
providers/OpenAIProvider.hpp providers/OpenAIProvider.cpp
|
|
||||||
providers/MistralAIProvider.hpp providers/MistralAIProvider.cpp
|
|
||||||
providers/LMStudioProvider.hpp providers/LMStudioProvider.cpp
|
providers/LMStudioProvider.hpp providers/LMStudioProvider.cpp
|
||||||
providers/OpenAICompatProvider.hpp providers/OpenAICompatProvider.cpp
|
providers/OpenAICompatProvider.hpp providers/OpenAICompatProvider.cpp
|
||||||
providers/OpenRouterAIProvider.hpp providers/OpenRouterAIProvider.cpp
|
providers/OpenRouterAIProvider.hpp providers/OpenRouterAIProvider.cpp
|
||||||
providers/GoogleAIProvider.hpp providers/GoogleAIProvider.cpp
|
providers/ClaudeProvider.hpp providers/ClaudeProvider.cpp
|
||||||
|
providers/OpenAIProvider.hpp providers/OpenAIProvider.cpp
|
||||||
QodeAssist.qrc
|
QodeAssist.qrc
|
||||||
LSPCompletion.hpp
|
LSPCompletion.hpp
|
||||||
LLMSuggestion.hpp LLMSuggestion.cpp
|
LLMSuggestion.hpp LLMSuggestion.cpp
|
||||||
|
|||||||
@@ -24,21 +24,24 @@
|
|||||||
#include <QFileDialog>
|
#include <QFileDialog>
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
|
|
||||||
|
#include <coreplugin/editormanager/editormanager.h>
|
||||||
#include <coreplugin/icore.h>
|
#include <coreplugin/icore.h>
|
||||||
#include <projectexplorer/project.h>
|
#include <projectexplorer/project.h>
|
||||||
#include <projectexplorer/projectexplorer.h>
|
#include <projectexplorer/projectexplorer.h>
|
||||||
#include <projectexplorer/projectmanager.h>
|
#include <projectexplorer/projectmanager.h>
|
||||||
|
#include <projectexplorer/projecttree.h>
|
||||||
#include <utils/theme/theme.h>
|
#include <utils/theme/theme.h>
|
||||||
#include <utils/utilsicons.h>
|
#include <utils/utilsicons.h>
|
||||||
#include <coreplugin/editormanager/editormanager.h>
|
|
||||||
|
|
||||||
#include "ChatAssistantSettings.hpp"
|
#include "ChatAssistantSettings.hpp"
|
||||||
#include "ChatSerializer.hpp"
|
#include "ChatSerializer.hpp"
|
||||||
#include "GeneralSettings.hpp"
|
#include "GeneralSettings.hpp"
|
||||||
#include "Logger.hpp"
|
#include "Logger.hpp"
|
||||||
#include "ProjectSettings.hpp"
|
#include "ProjectSettings.hpp"
|
||||||
#include "context/TokenUtils.hpp"
|
|
||||||
#include "context/ContextManager.hpp"
|
#include "context/ContextManager.hpp"
|
||||||
|
#include "context/FileChunker.hpp"
|
||||||
|
#include "context/RAGManager.hpp"
|
||||||
|
#include "context/TokenUtils.hpp"
|
||||||
|
|
||||||
namespace QodeAssist::Chat {
|
namespace QodeAssist::Chat {
|
||||||
|
|
||||||
@@ -447,6 +450,69 @@ void ChatRootView::openChatHistoryFolder()
|
|||||||
QDesktopServices::openUrl(url);
|
QDesktopServices::openUrl(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ChatRootView.cpp
|
||||||
|
|
||||||
|
void ChatRootView::testRAG(const QString &message)
|
||||||
|
{
|
||||||
|
auto project = ProjectExplorer::ProjectTree::currentProject();
|
||||||
|
if (!project) {
|
||||||
|
qDebug() << "No active project found";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString TEST_QUERY = message;
|
||||||
|
|
||||||
|
qDebug() << "Starting RAG test with query:";
|
||||||
|
qDebug() << TEST_QUERY;
|
||||||
|
qDebug() << "\nFirst, processing project files...";
|
||||||
|
|
||||||
|
auto files = Context::ContextManager::instance().getProjectSourceFiles(project);
|
||||||
|
// Было: auto future = Context::RAGManager::instance().processFiles(project, files);
|
||||||
|
// Стало:
|
||||||
|
auto future = Context::RAGManager::instance().processProjectFiles(project, files);
|
||||||
|
|
||||||
|
connect(
|
||||||
|
&Context::RAGManager::instance(),
|
||||||
|
&Context::RAGManager::vectorizationProgress,
|
||||||
|
this,
|
||||||
|
[](int processed, int total) {
|
||||||
|
qDebug() << QString("Vectorization progress: %1 of %2 files").arg(processed).arg(total);
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(
|
||||||
|
&Context::RAGManager::instance(),
|
||||||
|
&Context::RAGManager::vectorizationFinished,
|
||||||
|
this,
|
||||||
|
[this, project, TEST_QUERY]() {
|
||||||
|
qDebug() << "\nVectorization completed. Starting similarity search...\n";
|
||||||
|
// Было: Context::RAGManager::instance().searchSimilarDocuments(TEST_QUERY, project, 5);
|
||||||
|
// Стало:
|
||||||
|
auto future = Context::RAGManager::instance().findRelevantChunks(TEST_QUERY, project, 5);
|
||||||
|
future.then([](const QList<Context::RAGManager::ChunkSearchResult> &results) {
|
||||||
|
qDebug() << "Found" << results.size() << "relevant chunks:";
|
||||||
|
for (const auto &result : results) {
|
||||||
|
qDebug() << "File:" << result.filePath;
|
||||||
|
qDebug() << "Lines:" << result.startLine << "-" << result.endLine;
|
||||||
|
qDebug() << "Score:" << result.combinedScore;
|
||||||
|
qDebug() << "Content:" << result.content;
|
||||||
|
qDebug() << "---";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatRootView::testChunking()
|
||||||
|
{
|
||||||
|
auto project = ProjectExplorer::ProjectTree::currentProject();
|
||||||
|
if (!project) {
|
||||||
|
qDebug() << "No active project found";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Context::FileChunker::ChunkingConfig config;
|
||||||
|
Context::ContextManager::instance().testProjectChunks(project, config);
|
||||||
|
}
|
||||||
|
|
||||||
void ChatRootView::updateInputTokensCount()
|
void ChatRootView::updateInputTokensCount()
|
||||||
{
|
{
|
||||||
int inputTokens = m_messageTokensCount;
|
int inputTokens = m_messageTokensCount;
|
||||||
|
|||||||
@@ -65,6 +65,8 @@ public:
|
|||||||
Q_INVOKABLE void calculateMessageTokensCount(const QString &message);
|
Q_INVOKABLE void calculateMessageTokensCount(const QString &message);
|
||||||
Q_INVOKABLE void setIsSyncOpenFiles(bool state);
|
Q_INVOKABLE void setIsSyncOpenFiles(bool state);
|
||||||
Q_INVOKABLE void openChatHistoryFolder();
|
Q_INVOKABLE void openChatHistoryFolder();
|
||||||
|
Q_INVOKABLE void testRAG(const QString &message);
|
||||||
|
Q_INVOKABLE void testChunking();
|
||||||
|
|
||||||
Q_INVOKABLE void updateInputTokensCount();
|
Q_INVOKABLE void updateInputTokensCount();
|
||||||
int inputTokensCount() const;
|
int inputTokensCount() const;
|
||||||
|
|||||||
@@ -93,47 +93,51 @@ void ClientInterface::sendMessage(
|
|||||||
}
|
}
|
||||||
|
|
||||||
LLMCore::ContextData context;
|
LLMCore::ContextData context;
|
||||||
|
context.prefix = message;
|
||||||
|
context.suffix = "";
|
||||||
|
|
||||||
|
QString systemPrompt;
|
||||||
|
if (chatAssistantSettings.useSystemPrompt())
|
||||||
|
systemPrompt = chatAssistantSettings.systemPrompt();
|
||||||
|
|
||||||
if (chatAssistantSettings.useSystemPrompt()) {
|
|
||||||
QString systemPrompt = chatAssistantSettings.systemPrompt();
|
|
||||||
if (!linkedFiles.isEmpty()) {
|
if (!linkedFiles.isEmpty()) {
|
||||||
systemPrompt = getSystemPromptWithLinkedFiles(systemPrompt, linkedFiles);
|
systemPrompt = getSystemPromptWithLinkedFiles(systemPrompt, linkedFiles);
|
||||||
}
|
}
|
||||||
context.systemPrompt = systemPrompt;
|
|
||||||
}
|
|
||||||
|
|
||||||
QVector<LLMCore::Message> messages;
|
QJsonObject providerRequest;
|
||||||
for (const auto &msg : m_chatModel->getChatHistory()) {
|
providerRequest["model"] = Settings::generalSettings().caModel();
|
||||||
messages.append({msg.role == ChatModel::ChatRole::User ? "user" : "assistant", msg.content});
|
providerRequest["stream"] = chatAssistantSettings.stream();
|
||||||
}
|
providerRequest["messages"] = m_chatModel->prepareMessagesForRequest(systemPrompt);
|
||||||
context.history = messages;
|
|
||||||
|
if (promptTemplate)
|
||||||
|
promptTemplate->prepareRequest(providerRequest, context);
|
||||||
|
else
|
||||||
|
qWarning("No prompt template found");
|
||||||
|
|
||||||
|
if (provider)
|
||||||
|
provider->prepareRequest(providerRequest, LLMCore::RequestType::Chat);
|
||||||
|
else
|
||||||
|
qWarning("No provider found");
|
||||||
|
|
||||||
LLMCore::LLMConfig config;
|
LLMCore::LLMConfig config;
|
||||||
config.requestType = LLMCore::RequestType::Chat;
|
config.requestType = LLMCore::RequestType::Chat;
|
||||||
config.provider = provider;
|
config.provider = provider;
|
||||||
config.promptTemplate = promptTemplate;
|
config.promptTemplate = promptTemplate;
|
||||||
if (provider->providerID() == LLMCore::ProviderID::GoogleAI) {
|
config.url = QString("%1%2").arg(Settings::generalSettings().caUrl(), provider->chatEndpoint());
|
||||||
QString stream = chatAssistantSettings.stream() ? QString{"streamGenerateContent?alt=sse"}
|
config.providerRequest = providerRequest;
|
||||||
: QString{"generateContent?"};
|
config.multiLineCompletion = false;
|
||||||
config.url = QUrl(QString("%1/models/%2:%3")
|
|
||||||
.arg(
|
|
||||||
Settings::generalSettings().caUrl(),
|
|
||||||
Settings::generalSettings().caModel(),
|
|
||||||
stream));
|
|
||||||
} else {
|
|
||||||
config.url
|
|
||||||
= QString("%1%2").arg(Settings::generalSettings().caUrl(), provider->chatEndpoint());
|
|
||||||
config.providerRequest
|
|
||||||
= {{"model", Settings::generalSettings().caModel()},
|
|
||||||
{"stream", chatAssistantSettings.stream()}};
|
|
||||||
}
|
|
||||||
|
|
||||||
config.apiKey = provider->apiKey();
|
config.apiKey = provider->apiKey();
|
||||||
|
|
||||||
config.provider
|
QJsonObject request;
|
||||||
->prepareRequest(config.providerRequest, promptTemplate, context, LLMCore::RequestType::Chat);
|
request["id"] = QUuid::createUuid().toString();
|
||||||
|
|
||||||
|
auto errors = config.provider->validateRequest(config.providerRequest, promptTemplate->type());
|
||||||
|
if (!errors.isEmpty()) {
|
||||||
|
LOG_MESSAGE("Validate errors for chat request:");
|
||||||
|
LOG_MESSAGES(errors);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
QJsonObject request{{"id", QUuid::createUuid().toString()}};
|
|
||||||
m_requestHandler->sendLLMRequest(config, request);
|
m_requestHandler->sendLLMRequest(config, request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,18 +27,14 @@ Rectangle {
|
|||||||
|
|
||||||
property alias msgModel: msgCreator.model
|
property alias msgModel: msgCreator.model
|
||||||
property alias messageAttachments: attachmentsModel.model
|
property alias messageAttachments: attachmentsModel.model
|
||||||
property bool isUserMessage: false
|
|
||||||
|
|
||||||
height: msgColumn.implicitHeight + 10
|
height: msgColumn.implicitHeight + 10
|
||||||
radius: 8
|
radius: 8
|
||||||
color: isUserMessage ? palette.alternateBase
|
|
||||||
: palette.base
|
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
id: msgColumn
|
id: msgColumn
|
||||||
|
|
||||||
x: 5
|
width: parent.width
|
||||||
width: parent.width - x
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
spacing: 5
|
spacing: 5
|
||||||
|
|
||||||
@@ -117,17 +113,6 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: userMessageMarker
|
|
||||||
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
width: 3
|
|
||||||
height: root.height - root.radius
|
|
||||||
color: "#92BD6C"
|
|
||||||
radius: root.radius
|
|
||||||
visible: root.isUserMessage
|
|
||||||
}
|
|
||||||
|
|
||||||
component TextComponent : TextBlock {
|
component TextComponent : TextBlock {
|
||||||
required property var itemData
|
required property var itemData
|
||||||
height: implicitHeight + 10
|
height: implicitHeight + 10
|
||||||
|
|||||||
@@ -96,7 +96,8 @@ ChatRootView {
|
|||||||
width: ListView.view.width - scroll.width
|
width: ListView.view.width - scroll.width
|
||||||
msgModel: root.chatModel.processMessageContent(model.content)
|
msgModel: root.chatModel.processMessageContent(model.content)
|
||||||
messageAttachments: model.attachments
|
messageAttachments: model.attachments
|
||||||
isUserMessage: model.roleType === ChatModel.User
|
color: model.roleType === ChatModel.User ? palette.alternateBase
|
||||||
|
: palette.base
|
||||||
}
|
}
|
||||||
|
|
||||||
header: Item {
|
header: Item {
|
||||||
@@ -197,6 +198,8 @@ ChatRootView {
|
|||||||
}
|
}
|
||||||
attachFiles.onClicked: root.showAttachFilesDialog()
|
attachFiles.onClicked: root.showAttachFilesDialog()
|
||||||
linkFiles.onClicked: root.showLinkFilesDialog()
|
linkFiles.onClicked: root.showLinkFilesDialog()
|
||||||
|
testRag.onClicked: root.testRAG(messageInput.text)
|
||||||
|
testChunks.onClicked: root.testChunking()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ Rectangle {
|
|||||||
property alias syncOpenFiles: syncOpenFilesId
|
property alias syncOpenFiles: syncOpenFilesId
|
||||||
property alias attachFiles: attachFilesId
|
property alias attachFiles: attachFilesId
|
||||||
property alias linkFiles: linkFilesId
|
property alias linkFiles: linkFilesId
|
||||||
|
property alias testRag: testRagId
|
||||||
|
property alias testChunks: testChunksId
|
||||||
|
|
||||||
color: palette.window.hslLightness > 0.5 ?
|
color: palette.window.hslLightness > 0.5 ?
|
||||||
Qt.darker(palette.window, 1.1) :
|
Qt.darker(palette.window, 1.1) :
|
||||||
@@ -91,6 +93,18 @@ Rectangle {
|
|||||||
ToolTip.text: qsTr("Automatically synchronize currently opened files with the model context")
|
ToolTip.text: qsTr("Automatically synchronize currently opened files with the model context")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QoAButton {
|
||||||
|
id: testRagId
|
||||||
|
|
||||||
|
text: qsTr("Test RAG")
|
||||||
|
}
|
||||||
|
|
||||||
|
QoAButton {
|
||||||
|
id: testChunksId
|
||||||
|
|
||||||
|
text: qsTr("Test Chunks")
|
||||||
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,28 +35,6 @@ ConfigurationManager &ConfigurationManager::instance()
|
|||||||
void ConfigurationManager::init()
|
void ConfigurationManager::init()
|
||||||
{
|
{
|
||||||
setupConnections();
|
setupConnections();
|
||||||
updateAllTemplateDescriptions();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ConfigurationManager::updateTemplateDescription(const Utils::StringAspect &templateAspect)
|
|
||||||
{
|
|
||||||
LLMCore::PromptTemplate *templ = m_templateManger.getFimTemplateByName(templateAspect.value());
|
|
||||||
|
|
||||||
if (!templ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (&templateAspect == &m_generalSettings.ccTemplate) {
|
|
||||||
m_generalSettings.ccTemplateDescription.setValue(templ->description());
|
|
||||||
} else if (&templateAspect == &m_generalSettings.caTemplate) {
|
|
||||||
m_generalSettings.caTemplateDescription.setValue(templ->description());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ConfigurationManager::updateAllTemplateDescriptions()
|
|
||||||
{
|
|
||||||
updateTemplateDescription(m_generalSettings.ccTemplate);
|
|
||||||
updateTemplateDescription(m_generalSettings.caTemplate);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ConfigurationManager::ConfigurationManager(QObject *parent)
|
ConfigurationManager::ConfigurationManager(QObject *parent)
|
||||||
@@ -86,14 +64,6 @@ void ConfigurationManager::setupConnections()
|
|||||||
connect(&m_generalSettings.ccPreset1SelectModel, &Button::clicked, this, &Config::selectModel);
|
connect(&m_generalSettings.ccPreset1SelectModel, &Button::clicked, this, &Config::selectModel);
|
||||||
connect(
|
connect(
|
||||||
&m_generalSettings.ccPreset1SelectTemplate, &Button::clicked, this, &Config::selectTemplate);
|
&m_generalSettings.ccPreset1SelectTemplate, &Button::clicked, this, &Config::selectTemplate);
|
||||||
|
|
||||||
connect(&m_generalSettings.ccTemplate, &Utils::StringAspect::changed, this, [this]() {
|
|
||||||
updateTemplateDescription(m_generalSettings.ccTemplate);
|
|
||||||
});
|
|
||||||
|
|
||||||
connect(&m_generalSettings.caTemplate, &Utils::StringAspect::changed, this, [this]() {
|
|
||||||
updateTemplateDescription(m_generalSettings.caTemplate);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConfigurationManager::selectProvider()
|
void ConfigurationManager::selectProvider()
|
||||||
@@ -167,14 +137,9 @@ void ConfigurationManager::selectTemplate()
|
|||||||
|
|
||||||
const bool isCodeCompletion = (settingsButton == &m_generalSettings.ccSelectTemplate);
|
const bool isCodeCompletion = (settingsButton == &m_generalSettings.ccSelectTemplate);
|
||||||
const bool isPreset1 = (settingsButton == &m_generalSettings.ccPreset1SelectTemplate);
|
const bool isPreset1 = (settingsButton == &m_generalSettings.ccPreset1SelectTemplate);
|
||||||
const QString providerName = isCodeCompletion ? m_generalSettings.ccProvider.volatileValue()
|
|
||||||
: isPreset1 ? m_generalSettings.ccPreset1Provider.volatileValue()
|
|
||||||
: m_generalSettings.caProvider.volatileValue();
|
|
||||||
auto providerID = m_providersManager.getProviderByName(providerName)->providerID();
|
|
||||||
|
|
||||||
const auto templateList = isCodeCompletion || isPreset1
|
const auto templateList = isCodeCompletion || isPreset1 ? m_templateManger.fimTemplatesNames()
|
||||||
? m_templateManger.getFimTemplatesForProvider(providerID)
|
: m_templateManger.chatTemplatesNames();
|
||||||
: m_templateManger.getChatTemplatesForProvider(providerID);
|
|
||||||
|
|
||||||
auto &targetSettings = isCodeCompletion ? m_generalSettings.ccTemplate
|
auto &targetSettings = isCodeCompletion ? m_generalSettings.ccTemplate
|
||||||
: isPreset1 ? m_generalSettings.ccPreset1Template
|
: isPreset1 ? m_generalSettings.ccPreset1Template
|
||||||
|
|||||||
@@ -36,9 +36,6 @@ public:
|
|||||||
|
|
||||||
void init();
|
void init();
|
||||||
|
|
||||||
void updateTemplateDescription(const Utils::StringAspect &templateAspect);
|
|
||||||
void updateAllTemplateDescriptions();
|
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void selectProvider();
|
void selectProvider();
|
||||||
void selectModel();
|
void selectModel();
|
||||||
|
|||||||
@@ -27,8 +27,8 @@
|
|||||||
#include <texteditor/textdocument.h>
|
#include <texteditor/textdocument.h>
|
||||||
|
|
||||||
#include "CodeHandler.hpp"
|
#include "CodeHandler.hpp"
|
||||||
#include "context/ContextManager.hpp"
|
|
||||||
#include "context/DocumentContextReader.hpp"
|
#include "context/DocumentContextReader.hpp"
|
||||||
|
#include "llmcore/MessageBuilder.hpp"
|
||||||
#include "llmcore/PromptTemplateManager.hpp"
|
#include "llmcore/PromptTemplateManager.hpp"
|
||||||
#include "llmcore/ProvidersManager.hpp"
|
#include "llmcore/ProvidersManager.hpp"
|
||||||
#include "logger/Logger.hpp"
|
#include "logger/Logger.hpp"
|
||||||
@@ -146,13 +146,24 @@ void LLMClientInterface::handleExit(const QJsonObject &request)
|
|||||||
emit finished();
|
emit finished();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool QodeAssist::LLMClientInterface::isSpecifyCompletion(const QJsonObject &request)
|
||||||
|
{
|
||||||
|
auto &generalSettings = Settings::generalSettings();
|
||||||
|
|
||||||
|
Context::ProgrammingLanguage documentLanguage = getDocumentLanguage(request);
|
||||||
|
Context::ProgrammingLanguage preset1Language = Context::ProgrammingLanguageUtils::fromString(
|
||||||
|
generalSettings.preset1Language.displayForIndex(generalSettings.preset1Language()));
|
||||||
|
|
||||||
|
return generalSettings.specifyPreset1() && documentLanguage == preset1Language;
|
||||||
|
}
|
||||||
|
|
||||||
void LLMClientInterface::handleCompletion(const QJsonObject &request)
|
void LLMClientInterface::handleCompletion(const QJsonObject &request)
|
||||||
{
|
{
|
||||||
auto updatedContext = prepareContext(request);
|
const auto updatedContext = prepareContext(request);
|
||||||
auto &completeSettings = Settings::codeCompletionSettings();
|
auto &completeSettings = Settings::codeCompletionSettings();
|
||||||
auto &generalSettings = Settings::generalSettings();
|
auto &generalSettings = Settings::generalSettings();
|
||||||
|
|
||||||
bool isPreset1Active = Context::ContextManager::instance().isSpecifyCompletion(request);
|
bool isPreset1Active = isSpecifyCompletion(request);
|
||||||
|
|
||||||
const auto providerName = !isPreset1Active ? generalSettings.ccProvider()
|
const auto providerName = !isPreset1Active ? generalSettings.ccProvider()
|
||||||
: generalSettings.ccPreset1Provider();
|
: generalSettings.ccPreset1Provider();
|
||||||
@@ -183,19 +194,14 @@ void LLMClientInterface::handleCompletion(const QJsonObject &request)
|
|||||||
config.requestType = LLMCore::RequestType::CodeCompletion;
|
config.requestType = LLMCore::RequestType::CodeCompletion;
|
||||||
config.provider = provider;
|
config.provider = provider;
|
||||||
config.promptTemplate = promptTemplate;
|
config.promptTemplate = promptTemplate;
|
||||||
// TODO refactor networking
|
|
||||||
if (provider->providerID() == LLMCore::ProviderID::GoogleAI) {
|
|
||||||
QString stream = completeSettings.stream() ? QString{"streamGenerateContent?alt=sse"}
|
|
||||||
: QString{"generateContent?"};
|
|
||||||
config.url = QUrl(QString("%1/models/%2:%3").arg(url, modelName, stream));
|
|
||||||
} else {
|
|
||||||
config.url = QUrl(QString("%1%2").arg(
|
config.url = QUrl(QString("%1%2").arg(
|
||||||
url,
|
url,
|
||||||
promptTemplate->type() == LLMCore::TemplateType::FIM ? provider->completionEndpoint()
|
promptTemplate->type() == LLMCore::TemplateType::Fim ? provider->completionEndpoint()
|
||||||
: provider->chatEndpoint()));
|
: provider->chatEndpoint()));
|
||||||
config.providerRequest = {{"model", modelName}, {"stream", completeSettings.stream()}};
|
|
||||||
}
|
|
||||||
config.apiKey = provider->apiKey();
|
config.apiKey = provider->apiKey();
|
||||||
|
|
||||||
|
config.providerRequest = {{"model", modelName}, {"stream", completeSettings.stream()}};
|
||||||
|
|
||||||
config.multiLineCompletion = completeSettings.multiLineCompletion();
|
config.multiLineCompletion = completeSettings.multiLineCompletion();
|
||||||
|
|
||||||
const auto stopWords = QJsonArray::fromStringList(config.promptTemplate->stopWords());
|
const auto stopWords = QJsonArray::fromStringList(config.promptTemplate->stopWords());
|
||||||
@@ -204,35 +210,28 @@ void LLMClientInterface::handleCompletion(const QJsonObject &request)
|
|||||||
|
|
||||||
QString systemPrompt;
|
QString systemPrompt;
|
||||||
if (completeSettings.useSystemPrompt())
|
if (completeSettings.useSystemPrompt())
|
||||||
systemPrompt.append(completeSettings.useUserMessageTemplateForCC()
|
systemPrompt.append(completeSettings.systemPrompt());
|
||||||
&& promptTemplate->type() == LLMCore::TemplateType::Chat
|
if (!updatedContext.fileContext.isEmpty())
|
||||||
? completeSettings.systemPromptForNonFimModels()
|
systemPrompt.append(updatedContext.fileContext);
|
||||||
: completeSettings.systemPrompt());
|
|
||||||
if (updatedContext.fileContext.has_value())
|
|
||||||
systemPrompt.append(updatedContext.fileContext.value());
|
|
||||||
|
|
||||||
updatedContext.systemPrompt = systemPrompt;
|
|
||||||
|
|
||||||
if (promptTemplate->type() == LLMCore::TemplateType::Chat) {
|
|
||||||
QString userMessage;
|
QString userMessage;
|
||||||
if (completeSettings.useUserMessageTemplateForCC()) {
|
if (completeSettings.useUserMessageTemplateForCC() && promptTemplate->type() == LLMCore::TemplateType::Chat) {
|
||||||
userMessage = completeSettings.processMessageToFIM(
|
userMessage = completeSettings.userMessageTemplateForCC().arg(updatedContext.prefix, updatedContext.suffix);
|
||||||
updatedContext.prefix.value_or(""), updatedContext.suffix.value_or(""));
|
|
||||||
} else {
|
} else {
|
||||||
userMessage = updatedContext.prefix.value_or("") + updatedContext.suffix.value_or("");
|
userMessage = updatedContext.prefix;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO refactor add message
|
auto message = LLMCore::MessageBuilder()
|
||||||
QVector<LLMCore::Message> messages;
|
.addSystemMessage(systemPrompt)
|
||||||
messages.append({"user", userMessage});
|
.addUserMessage(userMessage)
|
||||||
updatedContext.history = messages;
|
.addSuffix(updatedContext.suffix)
|
||||||
}
|
.addTokenizer(promptTemplate);
|
||||||
|
|
||||||
config.provider->prepareRequest(
|
message.saveTo(
|
||||||
config.providerRequest,
|
config.providerRequest,
|
||||||
promptTemplate,
|
providerName == "Ollama" ? LLMCore::ProvidersApi::Ollama : LLMCore::ProvidersApi::OpenAI);
|
||||||
updatedContext,
|
|
||||||
LLMCore::RequestType::CodeCompletion);
|
config.provider->prepareRequest(config.providerRequest, LLMCore::RequestType::CodeCompletion);
|
||||||
|
|
||||||
auto errors = config.provider->validateRequest(config.providerRequest, promptTemplate->type());
|
auto errors = config.provider->validateRequest(config.providerRequest, promptTemplate->type());
|
||||||
if (!errors.isEmpty()) {
|
if (!errors.isEmpty()) {
|
||||||
@@ -267,11 +266,29 @@ LLMCore::ContextData LLMClientInterface::prepareContext(const QJsonObject &reque
|
|||||||
return reader.prepareContext(lineNumber, cursorPosition);
|
return reader.prepareContext(lineNumber, cursorPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Context::ProgrammingLanguage LLMClientInterface::getDocumentLanguage(const QJsonObject &request) const
|
||||||
|
{
|
||||||
|
QJsonObject params = request["params"].toObject();
|
||||||
|
QJsonObject doc = params["doc"].toObject();
|
||||||
|
QString uri = doc["uri"].toString();
|
||||||
|
|
||||||
|
Utils::FilePath filePath = Utils::FilePath::fromString(QUrl(uri).toLocalFile());
|
||||||
|
TextEditor::TextDocument *textDocument = TextEditor::TextDocument::textDocumentForFilePath(
|
||||||
|
filePath);
|
||||||
|
|
||||||
|
if (!textDocument) {
|
||||||
|
LOG_MESSAGE("Error: Document is not available for" + filePath.toString());
|
||||||
|
return Context::ProgrammingLanguage::Unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Context::ProgrammingLanguageUtils::fromMimeType(textDocument->mimeType());
|
||||||
|
}
|
||||||
|
|
||||||
void LLMClientInterface::sendCompletionToClient(const QString &completion,
|
void LLMClientInterface::sendCompletionToClient(const QString &completion,
|
||||||
const QJsonObject &request,
|
const QJsonObject &request,
|
||||||
bool isComplete)
|
bool isComplete)
|
||||||
{
|
{
|
||||||
bool isPreset1Active = Context::ContextManager::instance().isSpecifyCompletion(request);
|
bool isPreset1Active = isSpecifyCompletion(request);
|
||||||
|
|
||||||
auto templateName = !isPreset1Active ? Settings::generalSettings().ccTemplate()
|
auto templateName = !isPreset1Active ? Settings::generalSettings().ccTemplate()
|
||||||
: Settings::generalSettings().ccPreset1Template();
|
: Settings::generalSettings().ccPreset1Template();
|
||||||
@@ -289,8 +306,6 @@ void LLMClientInterface::sendCompletionToClient(const QString &completion,
|
|||||||
QJsonArray completions;
|
QJsonArray completions;
|
||||||
QJsonObject completionItem;
|
QJsonObject completionItem;
|
||||||
|
|
||||||
LOG_MESSAGE(QString("Completions before filter: \n%1").arg(completion));
|
|
||||||
|
|
||||||
QString processedCompletion
|
QString processedCompletion
|
||||||
= promptTemplate->type() == LLMCore::TemplateType::Chat
|
= promptTemplate->type() == LLMCore::TemplateType::Chat
|
||||||
&& Settings::codeCompletionSettings().smartProcessInstuctText()
|
&& Settings::codeCompletionSettings().smartProcessInstuctText()
|
||||||
@@ -300,7 +315,9 @@ void LLMClientInterface::sendCompletionToClient(const QString &completion,
|
|||||||
completionItem[LanguageServerProtocol::textKey] = processedCompletion;
|
completionItem[LanguageServerProtocol::textKey] = processedCompletion;
|
||||||
QJsonObject range;
|
QJsonObject range;
|
||||||
range["start"] = position;
|
range["start"] = position;
|
||||||
range["end"] = position;
|
QJsonObject end = position;
|
||||||
|
end["character"] = position["character"].toInt() + processedCompletion.length();
|
||||||
|
range["end"] = end;
|
||||||
completionItem[LanguageServerProtocol::rangeKey] = range;
|
completionItem[LanguageServerProtocol::rangeKey] = range;
|
||||||
completionItem[LanguageServerProtocol::positionKey] = position;
|
completionItem[LanguageServerProtocol::positionKey] = position;
|
||||||
completions.append(completionItem);
|
completions.append(completionItem);
|
||||||
|
|||||||
@@ -61,6 +61,8 @@ private:
|
|||||||
|
|
||||||
LLMCore::ContextData prepareContext(
|
LLMCore::ContextData prepareContext(
|
||||||
const QJsonObject &request, const QStringView &accumulatedCompletion = QString{});
|
const QJsonObject &request, const QStringView &accumulatedCompletion = QString{});
|
||||||
|
Context::ProgrammingLanguage getDocumentLanguage(const QJsonObject &request) const;
|
||||||
|
bool isSpecifyCompletion(const QJsonObject &request);
|
||||||
|
|
||||||
LLMCore::RequestHandler m_requestHandler;
|
LLMCore::RequestHandler m_requestHandler;
|
||||||
QElapsedTimer m_completionTimer;
|
QElapsedTimer m_completionTimer;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"Id" : "qodeassist",
|
"Id" : "qodeassist",
|
||||||
"Name" : "QodeAssist",
|
"Name" : "QodeAssist",
|
||||||
"Version" : "0.5.0",
|
"Version" : "0.4.13",
|
||||||
"Vendor" : "Petr Mironychev",
|
"Vendor" : "Petr Mironychev",
|
||||||
"VendorId" : "petrmironychev",
|
"VendorId" : "petrmironychev",
|
||||||
"Copyright" : "(C) ${IDE_COPYRIGHT_YEAR} Petr Mironychev, (C) ${IDE_COPYRIGHT_YEAR} The Qt Company Ltd",
|
"Copyright" : "(C) ${IDE_COPYRIGHT_YEAR} Petr Mironychev, (C) ${IDE_COPYRIGHT_YEAR} The Qt Company Ltd",
|
||||||
|
|||||||
@@ -5,11 +5,20 @@ add_library(Context STATIC
|
|||||||
ContentFile.hpp
|
ContentFile.hpp
|
||||||
TokenUtils.hpp TokenUtils.cpp
|
TokenUtils.hpp TokenUtils.cpp
|
||||||
ProgrammingLanguage.hpp ProgrammingLanguage.cpp
|
ProgrammingLanguage.hpp ProgrammingLanguage.cpp
|
||||||
|
RAGManager.hpp RAGManager.cpp
|
||||||
|
RAGStorage.hpp RAGStorage.cpp
|
||||||
|
RAGData.hpp
|
||||||
|
RAGVectorizer.hpp RAGVectorizer.cpp
|
||||||
|
RAGSimilaritySearch.hpp RAGSimilaritySearch.cpp
|
||||||
|
RAGPreprocessor.hpp RAGPreprocessor.cpp
|
||||||
|
EnhancedRAGSimilaritySearch.hpp EnhancedRAGSimilaritySearch.cpp
|
||||||
|
FileChunker.hpp FileChunker.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(Context
|
target_link_libraries(Context
|
||||||
PUBLIC
|
PUBLIC
|
||||||
Qt::Core
|
Qt::Core
|
||||||
|
Qt::Sql
|
||||||
QtCreator::Core
|
QtCreator::Core
|
||||||
QtCreator::TextEditor
|
QtCreator::TextEditor
|
||||||
QtCreator::Utils
|
QtCreator::Utils
|
||||||
|
|||||||
@@ -21,13 +21,12 @@
|
|||||||
|
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QJsonObject>
|
|
||||||
#include <QTextStream>
|
#include <QTextStream>
|
||||||
|
|
||||||
#include "GeneralSettings.hpp"
|
#include <projectexplorer/project.h>
|
||||||
#include "Logger.hpp"
|
#include <projectexplorer/projectnodes.h>
|
||||||
#include <texteditor/textdocument.h>
|
|
||||||
#include <utils/filepath.h>
|
#include "FileChunker.hpp"
|
||||||
|
|
||||||
namespace QodeAssist::Context {
|
namespace QodeAssist::Context {
|
||||||
|
|
||||||
@@ -70,33 +69,108 @@ ContentFile ContextManager::createContentFile(const QString &filePath) const
|
|||||||
return contentFile;
|
return contentFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
ProgrammingLanguage ContextManager::getDocumentLanguage(const QJsonObject &request) const
|
bool ContextManager::isInBuildDirectory(const QString &filePath) const
|
||||||
{
|
{
|
||||||
QJsonObject params = request["params"].toObject();
|
static const QStringList buildDirPatterns
|
||||||
QJsonObject doc = params["doc"].toObject();
|
= {QString(QDir::separator()) + QLatin1String("build") + QDir::separator(),
|
||||||
QString uri = doc["uri"].toString();
|
QString(QDir::separator()) + QLatin1String("Build") + QDir::separator(),
|
||||||
|
QString(QDir::separator()) + QLatin1String("BUILD") + QDir::separator(),
|
||||||
|
QString(QDir::separator()) + QLatin1String("debug") + QDir::separator(),
|
||||||
|
QString(QDir::separator()) + QLatin1String("Debug") + QDir::separator(),
|
||||||
|
QString(QDir::separator()) + QLatin1String("DEBUG") + QDir::separator(),
|
||||||
|
QString(QDir::separator()) + QLatin1String("release") + QDir::separator(),
|
||||||
|
QString(QDir::separator()) + QLatin1String("Release") + QDir::separator(),
|
||||||
|
QString(QDir::separator()) + QLatin1String("RELEASE") + QDir::separator(),
|
||||||
|
QString(QDir::separator()) + QLatin1String("builds") + QDir::separator()};
|
||||||
|
|
||||||
Utils::FilePath filePath = Utils::FilePath::fromString(QUrl(uri).toLocalFile());
|
// Нормализуем путь
|
||||||
TextEditor::TextDocument *textDocument = TextEditor::TextDocument::textDocumentForFilePath(
|
QString normalizedPath = QDir::fromNativeSeparators(filePath);
|
||||||
filePath);
|
|
||||||
|
|
||||||
if (!textDocument) {
|
// Проверяем, содержит ли путь паттерны build-директории
|
||||||
LOG_MESSAGE("Error: Document is not available for" + filePath.toString());
|
for (const QString &pattern : buildDirPatterns) {
|
||||||
return Context::ProgrammingLanguage::Unknown;
|
// Сравниваем с нормализованным паттерном
|
||||||
|
QString normalizedPattern = QDir::fromNativeSeparators(pattern);
|
||||||
|
if (normalizedPath.contains(normalizedPattern)) {
|
||||||
|
qDebug() << "Skipping build file:" << filePath;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Context::ProgrammingLanguageUtils::fromMimeType(textDocument->mimeType());
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ContextManager::isSpecifyCompletion(const QJsonObject &request)
|
QStringList ContextManager::getProjectSourceFiles(ProjectExplorer::Project *project) const
|
||||||
{
|
{
|
||||||
auto &generalSettings = Settings::generalSettings();
|
QStringList sourceFiles;
|
||||||
|
if (!project)
|
||||||
|
return sourceFiles;
|
||||||
|
|
||||||
Context::ProgrammingLanguage documentLanguage = getDocumentLanguage(request);
|
auto projectNode = project->rootProjectNode();
|
||||||
Context::ProgrammingLanguage preset1Language = Context::ProgrammingLanguageUtils::fromString(
|
if (!projectNode)
|
||||||
generalSettings.preset1Language.displayForIndex(generalSettings.preset1Language()));
|
return sourceFiles;
|
||||||
|
|
||||||
return generalSettings.specifyPreset1() && documentLanguage == preset1Language;
|
projectNode->forEachNode(
|
||||||
|
[&sourceFiles, this](ProjectExplorer::FileNode *fileNode) {
|
||||||
|
if (fileNode) {
|
||||||
|
QString filePath = fileNode->filePath().toString();
|
||||||
|
if (shouldProcessFile(filePath) && !isInBuildDirectory(filePath)) {
|
||||||
|
sourceFiles.append(filePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
nullptr);
|
||||||
|
|
||||||
|
return sourceFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ContextManager::shouldProcessFile(const QString &filePath) const
|
||||||
|
{
|
||||||
|
static const QStringList supportedExtensions
|
||||||
|
= {"cpp", "hpp", "c", "h", "cc", "hh", "cxx", "hxx", "qml", "js", "py"};
|
||||||
|
|
||||||
|
QFileInfo fileInfo(filePath);
|
||||||
|
return supportedExtensions.contains(fileInfo.suffix().toLower());
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContextManager::testProjectChunks(
|
||||||
|
ProjectExplorer::Project *project, const FileChunker::ChunkingConfig &config)
|
||||||
|
{
|
||||||
|
if (!project) {
|
||||||
|
qDebug() << "No project provided";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "\nStarting test chunking for project:" << project->displayName();
|
||||||
|
|
||||||
|
// Get source files
|
||||||
|
QStringList sourceFiles = getProjectSourceFiles(project);
|
||||||
|
qDebug() << "Found" << sourceFiles.size() << "source files";
|
||||||
|
|
||||||
|
// Create chunker
|
||||||
|
auto chunker = new FileChunker(config, this);
|
||||||
|
|
||||||
|
// Connect progress and error signals
|
||||||
|
connect(chunker, &FileChunker::progressUpdated, this, [](int processed, int total) {
|
||||||
|
qDebug() << "Progress:" << processed << "/" << total << "files";
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(chunker, &FileChunker::error, this, [](const QString &error) {
|
||||||
|
qDebug() << "Error:" << error;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Start chunking and handle results
|
||||||
|
auto future = chunker->chunkFiles(sourceFiles);
|
||||||
|
|
||||||
|
// Используем QFutureWatcher для обработки результатов
|
||||||
|
auto watcher = new QFutureWatcher<QList<FileChunk>>(this);
|
||||||
|
|
||||||
|
connect(watcher, &QFutureWatcher<QList<FileChunk>>::finished, this, [watcher, chunker]() {
|
||||||
|
// Очистка
|
||||||
|
watcher->deleteLater();
|
||||||
|
chunker->deleteLater();
|
||||||
|
});
|
||||||
|
|
||||||
|
watcher->setFuture(future);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace QodeAssist::Context
|
} // namespace QodeAssist::Context
|
||||||
|
|||||||
@@ -19,11 +19,16 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "ContentFile.hpp"
|
||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
|
||||||
#include "ContentFile.hpp"
|
#include "FileChunker.hpp"
|
||||||
#include "ProgrammingLanguage.hpp"
|
|
||||||
|
namespace ProjectExplorer {
|
||||||
|
class Project;
|
||||||
|
}
|
||||||
|
|
||||||
namespace QodeAssist::Context {
|
namespace QodeAssist::Context {
|
||||||
|
|
||||||
@@ -33,17 +38,23 @@ class ContextManager : public QObject
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
static ContextManager &instance();
|
static ContextManager &instance();
|
||||||
|
|
||||||
QString readFile(const QString &filePath) const;
|
QString readFile(const QString &filePath) const;
|
||||||
QList<ContentFile> getContentFiles(const QStringList &filePaths) const;
|
QList<ContentFile> getContentFiles(const QStringList &filePaths) const;
|
||||||
ProgrammingLanguage getDocumentLanguage(const QJsonObject &request) const;
|
QStringList getProjectSourceFiles(ProjectExplorer::Project *project) const;
|
||||||
bool isSpecifyCompletion(const QJsonObject &request);
|
|
||||||
|
void testProjectChunks(
|
||||||
|
ProjectExplorer::Project *project, const FileChunker::ChunkingConfig &config);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
explicit ContextManager(QObject *parent = nullptr);
|
explicit ContextManager(QObject *parent = nullptr);
|
||||||
~ContextManager() = default;
|
~ContextManager() = default;
|
||||||
ContextManager(const ContextManager &) = delete;
|
ContextManager(const ContextManager &) = delete;
|
||||||
ContextManager &operator=(const ContextManager &) = delete;
|
ContextManager &operator=(const ContextManager &) = delete;
|
||||||
|
|
||||||
ContentFile createContentFile(const QString &filePath) const;
|
ContentFile createContentFile(const QString &filePath) const;
|
||||||
|
bool shouldProcessFile(const QString &filePath) const;
|
||||||
|
bool isInBuildDirectory(const QString &filePath) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace QodeAssist::Context
|
} // namespace QodeAssist::Context
|
||||||
|
|||||||
@@ -210,13 +210,14 @@ LLMCore::ContextData DocumentContextReader::prepareContext(int lineNumber, int c
|
|||||||
QString contextAfter = getContextAfter(lineNumber, cursorPosition);
|
QString contextAfter = getContextAfter(lineNumber, cursorPosition);
|
||||||
|
|
||||||
QString fileContext;
|
QString fileContext;
|
||||||
|
if (Settings::codeCompletionSettings().useFilePathInContext())
|
||||||
fileContext.append("\n ").append(getLanguageAndFileInfo());
|
fileContext.append("\n ").append(getLanguageAndFileInfo());
|
||||||
|
|
||||||
if (Settings::codeCompletionSettings().useProjectChangesCache())
|
if (Settings::codeCompletionSettings().useProjectChangesCache())
|
||||||
fileContext.append("\n ").append(
|
fileContext.append("\n ").append(
|
||||||
ChangesManager::instance().getRecentChangesContext(m_textDocument));
|
ChangesManager::instance().getRecentChangesContext(m_textDocument));
|
||||||
|
|
||||||
return {.prefix = contextBefore, .suffix = contextAfter, .fileContext = fileContext};
|
return {contextBefore, contextAfter, fileContext};
|
||||||
}
|
}
|
||||||
|
|
||||||
QString DocumentContextReader::getContextBefore(int lineNumber, int cursorPosition) const
|
QString DocumentContextReader::getContextBefore(int lineNumber, int cursorPosition) const
|
||||||
|
|||||||
265
context/EnhancedRAGSimilaritySearch.cpp
Normal file
265
context/EnhancedRAGSimilaritySearch.cpp
Normal file
@@ -0,0 +1,265 @@
|
|||||||
|
#include "EnhancedRAGSimilaritySearch.hpp"
|
||||||
|
|
||||||
|
#include <QSet>
|
||||||
|
|
||||||
|
namespace QodeAssist::Context {
|
||||||
|
|
||||||
|
// Static regex getters
|
||||||
|
const QRegularExpression &EnhancedRAGSimilaritySearch::getNamespaceRegex()
|
||||||
|
{
|
||||||
|
static const QRegularExpression regex(R"(namespace\s+(?:\w+\s*::\s*)*\w+\s*\{)");
|
||||||
|
return regex;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QRegularExpression &EnhancedRAGSimilaritySearch::getClassRegex()
|
||||||
|
{
|
||||||
|
static const QRegularExpression regex(
|
||||||
|
R"((?:template\s*<[^>]*>\s*)?(?:class|struct)\s+(\w+)\s*(?:final\s*)?(?::\s*(?:public|protected|private)\s+\w+(?:\s*,\s*(?:public|protected|private)\s+\w+)*\s*)?{)");
|
||||||
|
return regex;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QRegularExpression &EnhancedRAGSimilaritySearch::getFunctionRegex()
|
||||||
|
{
|
||||||
|
static const QRegularExpression regex(
|
||||||
|
R"((?:virtual\s+)?(?:static\s+)?(?:inline\s+)?(?:explicit\s+)?(?:constexpr\s+)?(?:[\w:]+\s+)?(?:\w+\s*::\s*)*\w+\s*\([^)]*\)\s*(?:const\s*)?(?:noexcept\s*)?(?:override\s*)?(?:final\s*)?(?:=\s*0\s*)?(?:=\s*default\s*)?(?:=\s*delete\s*)?(?:\s*->.*?)?\s*{)");
|
||||||
|
return regex;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QRegularExpression &EnhancedRAGSimilaritySearch::getTemplateRegex()
|
||||||
|
{
|
||||||
|
static const QRegularExpression regex(R"(template\s*<[^>]*>\s*(?:class|struct|typename)\s+\w+)");
|
||||||
|
return regex;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache getters
|
||||||
|
QCache<QString, EnhancedRAGSimilaritySearch::SimilarityScore> &
|
||||||
|
EnhancedRAGSimilaritySearch::getScoreCache()
|
||||||
|
{
|
||||||
|
static QCache<QString, SimilarityScore> cache(1000); // Cache size of 1000 entries
|
||||||
|
return cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
QCache<QString, QStringList> &EnhancedRAGSimilaritySearch::getStructureCache()
|
||||||
|
{
|
||||||
|
static QCache<QString, QStringList> cache(500); // Cache size of 500 entries
|
||||||
|
return cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main public interface
|
||||||
|
EnhancedRAGSimilaritySearch::SimilarityScore EnhancedRAGSimilaritySearch::calculateSimilarity(
|
||||||
|
const RAGVector &v1, const RAGVector &v2, const QString &code1, const QString &code2)
|
||||||
|
{
|
||||||
|
// Generate cache key based on content hashes
|
||||||
|
QString cacheKey = QString("%1_%2").arg(qHash(code1)).arg(qHash(code2));
|
||||||
|
|
||||||
|
// Check cache first
|
||||||
|
auto &scoreCache = getScoreCache();
|
||||||
|
if (auto *cached = scoreCache.object(cacheKey)) {
|
||||||
|
return *cached;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate new similarity score
|
||||||
|
SimilarityScore score = calculateSimilarityInternal(v1, v2, code1, code2);
|
||||||
|
|
||||||
|
// Cache the result
|
||||||
|
scoreCache.insert(cacheKey, new SimilarityScore(score));
|
||||||
|
|
||||||
|
return score;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal implementation
|
||||||
|
EnhancedRAGSimilaritySearch::SimilarityScore EnhancedRAGSimilaritySearch::calculateSimilarityInternal(
|
||||||
|
const RAGVector &v1, const RAGVector &v2, const QString &code1, const QString &code2)
|
||||||
|
{
|
||||||
|
if (v1.empty() || v2.empty()) {
|
||||||
|
LOG_MESSAGE("Warning: Empty vectors in similarity calculation");
|
||||||
|
return SimilarityScore(0.0f, 0.0f, 0.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (v1.size() != v2.size()) {
|
||||||
|
LOG_MESSAGE(QString("Vector size mismatch: %1 vs %2").arg(v1.size()).arg(v2.size()));
|
||||||
|
return SimilarityScore(0.0f, 0.0f, 0.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate semantic similarity using vector embeddings
|
||||||
|
float semantic_similarity = 0.0f;
|
||||||
|
|
||||||
|
#if defined(__SSE__) || defined(_M_X64) || defined(_M_AMD64)
|
||||||
|
if (v1.size() >= 4) { // Use SSE for vectors of 4 or more elements
|
||||||
|
semantic_similarity = calculateCosineSimilaritySSE(v1, v2);
|
||||||
|
} else {
|
||||||
|
semantic_similarity = calculateCosineSimilarity(v1, v2);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
semantic_similarity = calculateCosineSimilarity(v1, v2);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// If semantic similarity is very low, skip structural analysis
|
||||||
|
if (semantic_similarity < 0.0001f) {
|
||||||
|
return SimilarityScore(0.0f, 0.0f, 0.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate structural similarity
|
||||||
|
float structural_similarity = calculateStructuralSimilarity(code1, code2);
|
||||||
|
|
||||||
|
// Calculate combined score with dynamic weights
|
||||||
|
float semantic_weight = 0.7f;
|
||||||
|
const int large_file_threshold = 10000;
|
||||||
|
|
||||||
|
if (code1.size() > large_file_threshold || code2.size() > large_file_threshold) {
|
||||||
|
semantic_weight = 0.8f; // Increase semantic weight for large files
|
||||||
|
}
|
||||||
|
|
||||||
|
float combined_score = semantic_weight * semantic_similarity
|
||||||
|
+ (1.0f - semantic_weight) * structural_similarity;
|
||||||
|
|
||||||
|
return SimilarityScore(semantic_similarity, structural_similarity, combined_score);
|
||||||
|
}
|
||||||
|
|
||||||
|
float EnhancedRAGSimilaritySearch::calculateCosineSimilarity(const RAGVector &v1, const RAGVector &v2)
|
||||||
|
{
|
||||||
|
float dotProduct = 0.0f;
|
||||||
|
float norm1 = 0.0f;
|
||||||
|
float norm2 = 0.0f;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < v1.size(); ++i) {
|
||||||
|
dotProduct += v1[i] * v2[i];
|
||||||
|
norm1 += v1[i] * v1[i];
|
||||||
|
norm2 += v2[i] * v2[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
norm1 = std::sqrt(norm1);
|
||||||
|
norm2 = std::sqrt(norm2);
|
||||||
|
|
||||||
|
if (norm1 == 0.0f || norm2 == 0.0f) {
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
return dotProduct / (norm1 * norm2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(__SSE__) || defined(_M_X64) || defined(_M_AMD64)
|
||||||
|
float EnhancedRAGSimilaritySearch::calculateCosineSimilaritySSE(
|
||||||
|
const RAGVector &v1, const RAGVector &v2)
|
||||||
|
{
|
||||||
|
const float *p1 = v1.data();
|
||||||
|
const float *p2 = v2.data();
|
||||||
|
const size_t size = v1.size();
|
||||||
|
const size_t alignedSize = size & ~3ULL; // Round down to multiple of 4
|
||||||
|
|
||||||
|
__m128 sum = _mm_setzero_ps();
|
||||||
|
__m128 norm1 = _mm_setzero_ps();
|
||||||
|
__m128 norm2 = _mm_setzero_ps();
|
||||||
|
|
||||||
|
// Process 4 elements at a time using SSE
|
||||||
|
for (size_t i = 0; i < alignedSize; i += 4) {
|
||||||
|
__m128 v1_vec = _mm_loadu_ps(p1 + i); // Use unaligned load for safety
|
||||||
|
__m128 v2_vec = _mm_loadu_ps(p2 + i);
|
||||||
|
|
||||||
|
sum = _mm_add_ps(sum, _mm_mul_ps(v1_vec, v2_vec));
|
||||||
|
norm1 = _mm_add_ps(norm1, _mm_mul_ps(v1_vec, v1_vec));
|
||||||
|
norm2 = _mm_add_ps(norm2, _mm_mul_ps(v2_vec, v2_vec));
|
||||||
|
}
|
||||||
|
|
||||||
|
float dotProduct = horizontalSum(sum);
|
||||||
|
float n1 = std::sqrt(horizontalSum(norm1));
|
||||||
|
float n2 = std::sqrt(horizontalSum(norm2));
|
||||||
|
|
||||||
|
// Process remaining elements
|
||||||
|
for (size_t i = alignedSize; i < size; ++i) {
|
||||||
|
dotProduct += v1[i] * v2[i];
|
||||||
|
n1 += v1[i] * v1[i];
|
||||||
|
n2 += v2[i] * v2[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (n1 == 0.0f || n2 == 0.0f) {
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
return dotProduct / (std::sqrt(n1) * std::sqrt(n2));
|
||||||
|
}
|
||||||
|
|
||||||
|
float EnhancedRAGSimilaritySearch::horizontalSum(__m128 x)
|
||||||
|
{
|
||||||
|
__m128 shuf = _mm_shuffle_ps(x, x, _MM_SHUFFLE(2, 3, 0, 1));
|
||||||
|
__m128 sums = _mm_add_ps(x, shuf);
|
||||||
|
shuf = _mm_movehl_ps(shuf, sums);
|
||||||
|
sums = _mm_add_ss(sums, shuf);
|
||||||
|
return _mm_cvtss_f32(sums);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
float EnhancedRAGSimilaritySearch::calculateStructuralSimilarity(
|
||||||
|
const QString &code1, const QString &code2)
|
||||||
|
{
|
||||||
|
QStringList structures1 = extractStructures(code1);
|
||||||
|
QStringList structures2 = extractStructures(code2);
|
||||||
|
|
||||||
|
return calculateJaccardSimilarity(structures1, structures2);
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList EnhancedRAGSimilaritySearch::extractStructures(const QString &code)
|
||||||
|
{
|
||||||
|
// Check cache first
|
||||||
|
auto &structureCache = getStructureCache();
|
||||||
|
QString cacheKey = QString::number(qHash(code));
|
||||||
|
|
||||||
|
if (auto *cached = structureCache.object(cacheKey)) {
|
||||||
|
return *cached;
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList structures;
|
||||||
|
structures.reserve(100); // Reserve space for typical file
|
||||||
|
|
||||||
|
// Extract namespaces
|
||||||
|
auto namespaceMatches = getNamespaceRegex().globalMatch(code);
|
||||||
|
while (namespaceMatches.hasNext()) {
|
||||||
|
structures.append(namespaceMatches.next().captured().trimmed());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract classes
|
||||||
|
auto classMatches = getClassRegex().globalMatch(code);
|
||||||
|
while (classMatches.hasNext()) {
|
||||||
|
structures.append(classMatches.next().captured().trimmed());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract functions
|
||||||
|
auto functionMatches = getFunctionRegex().globalMatch(code);
|
||||||
|
while (functionMatches.hasNext()) {
|
||||||
|
structures.append(functionMatches.next().captured().trimmed());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract templates
|
||||||
|
auto templateMatches = getTemplateRegex().globalMatch(code);
|
||||||
|
while (templateMatches.hasNext()) {
|
||||||
|
structures.append(templateMatches.next().captured().trimmed());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache the result
|
||||||
|
structureCache.insert(cacheKey, new QStringList(structures));
|
||||||
|
|
||||||
|
return structures;
|
||||||
|
}
|
||||||
|
|
||||||
|
float EnhancedRAGSimilaritySearch::calculateJaccardSimilarity(
|
||||||
|
const QStringList &set1, const QStringList &set2)
|
||||||
|
{
|
||||||
|
if (set1.isEmpty() && set2.isEmpty()) {
|
||||||
|
return 1.0f; // Пустые множества считаем идентичными
|
||||||
|
}
|
||||||
|
if (set1.isEmpty() || set2.isEmpty()) {
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
QSet<QString> set1Unique = QSet<QString>(set1.begin(), set1.end());
|
||||||
|
QSet<QString> set2Unique = QSet<QString>(set2.begin(), set2.end());
|
||||||
|
|
||||||
|
QSet<QString> intersection = set1Unique;
|
||||||
|
intersection.intersect(set2Unique);
|
||||||
|
|
||||||
|
QSet<QString> union_set = set1Unique;
|
||||||
|
union_set.unite(set2Unique);
|
||||||
|
|
||||||
|
return static_cast<float>(intersection.size()) / union_set.size();
|
||||||
|
}
|
||||||
|
} // namespace QodeAssist::Context
|
||||||
74
context/EnhancedRAGSimilaritySearch.hpp
Normal file
74
context/EnhancedRAGSimilaritySearch.hpp
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QCache>
|
||||||
|
#include <QHash>
|
||||||
|
#include <QRegularExpression>
|
||||||
|
#include <QString>
|
||||||
|
#include <QStringList>
|
||||||
|
#include <QtGlobal>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
#include <optional>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#if defined(__SSE__) || defined(_M_X64) || defined(_M_AMD64)
|
||||||
|
#include <emmintrin.h>
|
||||||
|
#include <xmmintrin.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "RAGData.hpp"
|
||||||
|
#include "logger/Logger.hpp"
|
||||||
|
|
||||||
|
namespace QodeAssist::Context {
|
||||||
|
|
||||||
|
class EnhancedRAGSimilaritySearch
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
struct SimilarityScore
|
||||||
|
{
|
||||||
|
float semantic_similarity{0.0f};
|
||||||
|
float structural_similarity{0.0f};
|
||||||
|
float combined_score{0.0f};
|
||||||
|
|
||||||
|
SimilarityScore() = default;
|
||||||
|
SimilarityScore(float sem, float str, float comb)
|
||||||
|
: semantic_similarity(sem)
|
||||||
|
, structural_similarity(str)
|
||||||
|
, combined_score(comb)
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
|
||||||
|
static SimilarityScore calculateSimilarity(
|
||||||
|
const RAGVector &v1, const RAGVector &v2, const QString &code1, const QString &code2);
|
||||||
|
|
||||||
|
private:
|
||||||
|
static SimilarityScore calculateSimilarityInternal(
|
||||||
|
const RAGVector &v1, const RAGVector &v2, const QString &code1, const QString &code2);
|
||||||
|
|
||||||
|
static float calculateCosineSimilarity(const RAGVector &v1, const RAGVector &v2);
|
||||||
|
|
||||||
|
#if defined(__SSE__) || defined(_M_X64) || defined(_M_AMD64)
|
||||||
|
static float calculateCosineSimilaritySSE(const RAGVector &v1, const RAGVector &v2);
|
||||||
|
static float horizontalSum(__m128 x);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static float calculateStructuralSimilarity(const QString &code1, const QString &code2);
|
||||||
|
static QStringList extractStructures(const QString &code);
|
||||||
|
static float calculateJaccardSimilarity(const QStringList &set1, const QStringList &set2);
|
||||||
|
|
||||||
|
static const QRegularExpression &getNamespaceRegex();
|
||||||
|
static const QRegularExpression &getClassRegex();
|
||||||
|
static const QRegularExpression &getFunctionRegex();
|
||||||
|
static const QRegularExpression &getTemplateRegex();
|
||||||
|
|
||||||
|
// Cache for similarity scores
|
||||||
|
static QCache<QString, SimilarityScore> &getScoreCache();
|
||||||
|
|
||||||
|
// Cache for extracted structures
|
||||||
|
static QCache<QString, QStringList> &getStructureCache();
|
||||||
|
|
||||||
|
EnhancedRAGSimilaritySearch() = delete; // Prevent instantiation
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace QodeAssist::Context
|
||||||
198
context/FileChunker.cpp
Normal file
198
context/FileChunker.cpp
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
// FileChunker.cpp
|
||||||
|
#include "FileChunker.hpp"
|
||||||
|
|
||||||
|
#include <coreplugin/idocument.h>
|
||||||
|
#include <texteditor/syntaxhighlighter.h>
|
||||||
|
#include <texteditor/textdocument.h>
|
||||||
|
#include <texteditor/texteditorconstants.h>
|
||||||
|
|
||||||
|
#include <QFutureWatcher>
|
||||||
|
#include <QTimer>
|
||||||
|
|
||||||
|
namespace QodeAssist::Context {
|
||||||
|
|
||||||
|
FileChunker::FileChunker(QObject *parent)
|
||||||
|
: QObject(parent)
|
||||||
|
{}
|
||||||
|
|
||||||
|
FileChunker::FileChunker(const ChunkingConfig &config, QObject *parent)
|
||||||
|
: QObject(parent)
|
||||||
|
, m_config(config)
|
||||||
|
{}
|
||||||
|
|
||||||
|
QFuture<QList<FileChunk>> FileChunker::chunkFiles(const QStringList &filePaths)
|
||||||
|
{
|
||||||
|
qDebug() << "\nStarting chunking process for" << filePaths.size() << "files";
|
||||||
|
qDebug() << "Configuration:"
|
||||||
|
<< "\n Max lines per chunk:" << m_config.maxLinesPerChunk
|
||||||
|
<< "\n Overlap lines:" << m_config.overlapLines
|
||||||
|
<< "\n Skip empty lines:" << m_config.skipEmptyLines
|
||||||
|
<< "\n Preserve functions:" << m_config.preserveFunctions
|
||||||
|
<< "\n Preserve classes:" << m_config.preserveClasses
|
||||||
|
<< "\n Batch size:" << m_config.batchSize;
|
||||||
|
|
||||||
|
auto promise = std::make_shared<QPromise<QList<FileChunk>>>();
|
||||||
|
promise->start();
|
||||||
|
|
||||||
|
if (filePaths.isEmpty()) {
|
||||||
|
qDebug() << "No files to process";
|
||||||
|
promise->addResult({});
|
||||||
|
promise->finish();
|
||||||
|
return promise->future();
|
||||||
|
}
|
||||||
|
|
||||||
|
processNextBatch(promise, filePaths, 0);
|
||||||
|
return promise->future();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileChunker::processNextBatch(
|
||||||
|
std::shared_ptr<QPromise<QList<FileChunk>>> promise, const QStringList &files, int startIndex)
|
||||||
|
{
|
||||||
|
if (startIndex >= files.size()) {
|
||||||
|
emit chunkingComplete();
|
||||||
|
promise->finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int endIndex = qMin(startIndex + m_config.batchSize, files.size());
|
||||||
|
QList<FileChunk> batchChunks;
|
||||||
|
|
||||||
|
for (int i = startIndex; i < endIndex; ++i) {
|
||||||
|
try {
|
||||||
|
auto chunks = processFile(files[i]);
|
||||||
|
batchChunks.append(chunks);
|
||||||
|
} catch (const std::exception &e) {
|
||||||
|
emit error(QString("Error processing file %1: %2").arg(files[i], e.what()));
|
||||||
|
}
|
||||||
|
emit progressUpdated(i + 1, files.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
promise->addResult(batchChunks);
|
||||||
|
|
||||||
|
// Планируем обработку следующего батча
|
||||||
|
QTimer::singleShot(0, this, [this, promise, files, endIndex]() {
|
||||||
|
processNextBatch(promise, files, endIndex);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<FileChunk> FileChunker::processFile(const QString &filePath)
|
||||||
|
{
|
||||||
|
qDebug() << "\nProcessing file:" << filePath;
|
||||||
|
|
||||||
|
auto document = new TextEditor::TextDocument;
|
||||||
|
auto filePathObj = Utils::FilePath::fromString(filePath);
|
||||||
|
auto result = document->open(&m_error, filePathObj, filePathObj);
|
||||||
|
if (result != Core::IDocument::OpenResult::Success) {
|
||||||
|
qDebug() << "Failed to open document:" << filePath << "-" << m_error;
|
||||||
|
emit error(QString("Failed to open document: %1 - %2").arg(filePath, m_error));
|
||||||
|
delete document;
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "Document opened successfully. Line count:" << document->document()->blockCount();
|
||||||
|
|
||||||
|
auto chunks = createChunksForDocument(document);
|
||||||
|
qDebug() << "Created" << chunks.size() << "chunks for file";
|
||||||
|
|
||||||
|
delete document;
|
||||||
|
return chunks;
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<FileChunk> FileChunker::createChunksForDocument(TextEditor::TextDocument *document)
|
||||||
|
{
|
||||||
|
QList<FileChunk> chunks;
|
||||||
|
QString filePath = document->filePath().toString();
|
||||||
|
qDebug() << "\nCreating chunks for document:" << filePath << "\nConfiguration:"
|
||||||
|
<< "\n Max lines per chunk:" << m_config.maxLinesPerChunk
|
||||||
|
<< "\n Min lines per chunk:" << m_config.minLinesPerChunk
|
||||||
|
<< "\n Overlap lines:" << m_config.overlapLines;
|
||||||
|
// Если файл меньше минимального размера чанка, создаем один чанк
|
||||||
|
if (document->document()->blockCount() <= m_config.minLinesPerChunk) {
|
||||||
|
FileChunk chunk;
|
||||||
|
chunk.filePath = filePath;
|
||||||
|
chunk.startLine = 0;
|
||||||
|
chunk.endLine = document->document()->blockCount() - 1;
|
||||||
|
chunk.createdAt = QDateTime::currentDateTime();
|
||||||
|
chunk.updatedAt = chunk.createdAt;
|
||||||
|
|
||||||
|
QString content;
|
||||||
|
QTextBlock block = document->document()->firstBlock();
|
||||||
|
while (block.isValid()) {
|
||||||
|
content += block.text() + "\n";
|
||||||
|
block = block.next();
|
||||||
|
}
|
||||||
|
chunk.content = content;
|
||||||
|
|
||||||
|
qDebug() << "File is smaller than minimum chunk size. Creating single chunk:"
|
||||||
|
<< "\n Lines:" << chunk.lineCount() << "\n Content size:" << chunk.content.size()
|
||||||
|
<< "bytes";
|
||||||
|
|
||||||
|
chunks.append(chunk);
|
||||||
|
return chunks;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Для больших файлов создаем чанки фиксированного размера с перекрытием
|
||||||
|
int currentStartLine = 0;
|
||||||
|
int lineCount = 0;
|
||||||
|
QString content;
|
||||||
|
QTextBlock block = document->document()->firstBlock();
|
||||||
|
|
||||||
|
while (block.isValid()) {
|
||||||
|
content += block.text() + "\n";
|
||||||
|
lineCount++;
|
||||||
|
|
||||||
|
// Если достигли размера чанка или это последний блок
|
||||||
|
if (lineCount >= m_config.maxLinesPerChunk || !block.next().isValid()) {
|
||||||
|
FileChunk chunk;
|
||||||
|
chunk.filePath = filePath;
|
||||||
|
chunk.startLine = currentStartLine;
|
||||||
|
chunk.endLine = currentStartLine + lineCount - 1;
|
||||||
|
chunk.content = content;
|
||||||
|
chunk.createdAt = QDateTime::currentDateTime();
|
||||||
|
chunk.updatedAt = chunk.createdAt;
|
||||||
|
|
||||||
|
qDebug() << "Creating chunk:"
|
||||||
|
<< "\n Start line:" << chunk.startLine << "\n End line:" << chunk.endLine
|
||||||
|
<< "\n Lines:" << chunk.lineCount()
|
||||||
|
<< "\n Content size:" << chunk.content.size() << "bytes";
|
||||||
|
|
||||||
|
chunks.append(chunk);
|
||||||
|
|
||||||
|
// Начинаем новый чанк с учетом перекрытия
|
||||||
|
if (block.next().isValid()) {
|
||||||
|
// Отступаем назад на размер перекрытия
|
||||||
|
int overlapLines = qMin(m_config.overlapLines, lineCount);
|
||||||
|
currentStartLine = chunk.endLine - overlapLines + 1;
|
||||||
|
|
||||||
|
// Сбрасываем контент, но добавляем перекрывающиеся строки
|
||||||
|
content.clear();
|
||||||
|
QTextBlock overlapBlock = document->document()->findBlockByLineNumber(
|
||||||
|
currentStartLine);
|
||||||
|
while (overlapBlock.isValid() && overlapBlock.blockNumber() <= chunk.endLine) {
|
||||||
|
content += overlapBlock.text() + "\n";
|
||||||
|
overlapBlock = overlapBlock.next();
|
||||||
|
}
|
||||||
|
lineCount = overlapLines;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
block = block.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "Finished creating chunks for file:" << filePath
|
||||||
|
<< "\nTotal chunks:" << chunks.size();
|
||||||
|
|
||||||
|
return chunks;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileChunker::setConfig(const ChunkingConfig &config)
|
||||||
|
{
|
||||||
|
m_config = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
FileChunker::ChunkingConfig FileChunker::config() const
|
||||||
|
{
|
||||||
|
return m_config;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace QodeAssist::Context
|
||||||
68
context/FileChunker.hpp
Normal file
68
context/FileChunker.hpp
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
// FileChunker.hpp
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <texteditor/textdocument.h>
|
||||||
|
#include <QDateTime>
|
||||||
|
#include <QFuture>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
namespace QodeAssist::Context {
|
||||||
|
|
||||||
|
struct FileChunk
|
||||||
|
{
|
||||||
|
QString filePath; // Path to the source file
|
||||||
|
int startLine; // Starting line of the chunk
|
||||||
|
int endLine; // Ending line of the chunk
|
||||||
|
QDateTime createdAt; // When the chunk was created
|
||||||
|
QDateTime updatedAt; // When the chunk was last updated
|
||||||
|
QString content; // Content of the chunk
|
||||||
|
|
||||||
|
// Helper methods
|
||||||
|
int lineCount() const { return endLine - startLine + 1; }
|
||||||
|
bool isValid() const { return !filePath.isEmpty() && startLine >= 0 && endLine >= startLine; }
|
||||||
|
};
|
||||||
|
|
||||||
|
class FileChunker : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
struct ChunkingConfig
|
||||||
|
{
|
||||||
|
int maxLinesPerChunk = 80;
|
||||||
|
int minLinesPerChunk = 40;
|
||||||
|
int overlapLines = 20;
|
||||||
|
bool skipEmptyLines = true;
|
||||||
|
bool preserveFunctions = true;
|
||||||
|
bool preserveClasses = true;
|
||||||
|
int batchSize = 10;
|
||||||
|
};
|
||||||
|
|
||||||
|
explicit FileChunker(QObject *parent = nullptr);
|
||||||
|
explicit FileChunker(const ChunkingConfig &config, QObject *parent = nullptr);
|
||||||
|
|
||||||
|
// Main chunking method
|
||||||
|
QFuture<QList<FileChunk>> chunkFiles(const QStringList &filePaths);
|
||||||
|
|
||||||
|
// Configuration
|
||||||
|
void setConfig(const ChunkingConfig &config);
|
||||||
|
ChunkingConfig config() const;
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void progressUpdated(int processedFiles, int totalFiles);
|
||||||
|
void chunkingComplete();
|
||||||
|
void error(const QString &errorMessage);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QList<FileChunk> processFile(const QString &filePath);
|
||||||
|
QList<FileChunk> createChunksForDocument(TextEditor::TextDocument *document);
|
||||||
|
void processNextBatch(
|
||||||
|
std::shared_ptr<QPromise<QList<FileChunk>>> promise,
|
||||||
|
const QStringList &files,
|
||||||
|
int startIndex);
|
||||||
|
|
||||||
|
ChunkingConfig m_config;
|
||||||
|
QString m_error;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace QodeAssist::Context
|
||||||
@@ -24,8 +24,8 @@
|
|||||||
namespace QodeAssist::Context {
|
namespace QodeAssist::Context {
|
||||||
|
|
||||||
enum class ProgrammingLanguage {
|
enum class ProgrammingLanguage {
|
||||||
QML, // QML/JavaScript
|
QML,
|
||||||
Cpp, // C/C++
|
Cpp,
|
||||||
Python,
|
Python,
|
||||||
Unknown,
|
Unknown,
|
||||||
};
|
};
|
||||||
|
|||||||
7
context/RAGData.hpp
Normal file
7
context/RAGData.hpp
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace QodeAssist::Context {
|
||||||
|
using RAGVector = std::vector<float>;
|
||||||
|
}
|
||||||
443
context/RAGManager.cpp
Normal file
443
context/RAGManager.cpp
Normal file
@@ -0,0 +1,443 @@
|
|||||||
|
/*
|
||||||
|
* 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 "RAGManager.hpp"
|
||||||
|
#include "EnhancedRAGSimilaritySearch.hpp"
|
||||||
|
#include "RAGPreprocessor.hpp"
|
||||||
|
#include "RAGSimilaritySearch.hpp"
|
||||||
|
#include "logger/Logger.hpp"
|
||||||
|
|
||||||
|
#include <coreplugin/icore.h>
|
||||||
|
#include <projectexplorer/project.h>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QtConcurrent>
|
||||||
|
|
||||||
|
namespace QodeAssist::Context {
|
||||||
|
|
||||||
|
RAGManager &RAGManager::instance()
|
||||||
|
{
|
||||||
|
static RAGManager manager;
|
||||||
|
return manager;
|
||||||
|
}
|
||||||
|
|
||||||
|
RAGManager::RAGManager(QObject *parent)
|
||||||
|
: QObject(parent)
|
||||||
|
, m_vectorizer(std::make_unique<RAGVectorizer>())
|
||||||
|
{}
|
||||||
|
|
||||||
|
RAGManager::~RAGManager() {}
|
||||||
|
|
||||||
|
QString RAGManager::getStoragePath(ProjectExplorer::Project *project) const
|
||||||
|
{
|
||||||
|
return QString("%1/qodeassist/%2/rag/vectors.db")
|
||||||
|
.arg(Core::ICore::userResourcePath().toString(), project->displayName());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<QString> RAGManager::loadFileContent(const QString &filePath)
|
||||||
|
{
|
||||||
|
QFile file(filePath);
|
||||||
|
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||||
|
qDebug() << "ERROR: Failed to open file for reading:" << filePath
|
||||||
|
<< "Error:" << file.errorString();
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
QFileInfo fileInfo(filePath);
|
||||||
|
qDebug() << "Loading content from file:" << fileInfo.fileName() << "Size:" << fileInfo.size()
|
||||||
|
<< "bytes";
|
||||||
|
|
||||||
|
QString content = QString::fromUtf8(file.readAll());
|
||||||
|
if (content.isEmpty()) {
|
||||||
|
qDebug() << "WARNING: Empty content read from file:" << filePath;
|
||||||
|
}
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RAGManager::ensureStorageForProject(ProjectExplorer::Project *project) const
|
||||||
|
{
|
||||||
|
qDebug() << "Ensuring storage for project:" << project->displayName();
|
||||||
|
|
||||||
|
if (m_currentProject == project && m_currentStorage) {
|
||||||
|
qDebug() << "Using existing storage";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "Creating new storage";
|
||||||
|
m_currentStorage.reset();
|
||||||
|
m_currentProject = project;
|
||||||
|
|
||||||
|
if (project) {
|
||||||
|
QString storagePath = getStoragePath(project);
|
||||||
|
qDebug() << "Storage path:" << storagePath;
|
||||||
|
|
||||||
|
StorageOptions options;
|
||||||
|
m_currentStorage = std::make_unique<RAGStorage>(storagePath, options);
|
||||||
|
|
||||||
|
qDebug() << "Initializing storage...";
|
||||||
|
if (!m_currentStorage->init()) {
|
||||||
|
qDebug() << "Failed to initialize storage";
|
||||||
|
m_currentStorage.reset();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
qDebug() << "Storage initialized successfully";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QFuture<void> RAGManager::processProjectFiles(
|
||||||
|
ProjectExplorer::Project *project,
|
||||||
|
const QStringList &filePaths,
|
||||||
|
const FileChunker::ChunkingConfig &config)
|
||||||
|
{
|
||||||
|
qDebug() << "\nStarting batch processing of" << filePaths.size()
|
||||||
|
<< "files for project:" << project->displayName();
|
||||||
|
|
||||||
|
auto promise = std::make_shared<QPromise<void>>();
|
||||||
|
promise->start();
|
||||||
|
|
||||||
|
qDebug() << "Initializing storage...";
|
||||||
|
ensureStorageForProject(project);
|
||||||
|
|
||||||
|
if (!m_currentStorage) {
|
||||||
|
qDebug() << "Failed to initialize storage for project:" << project->displayName();
|
||||||
|
promise->finish();
|
||||||
|
return promise->future();
|
||||||
|
}
|
||||||
|
qDebug() << "Storage initialized successfully";
|
||||||
|
|
||||||
|
qDebug() << "Checking files for processing...";
|
||||||
|
QSet<QString> uniqueFiles;
|
||||||
|
for (const QString &filePath : filePaths) {
|
||||||
|
qDebug() << "Checking file:" << filePath;
|
||||||
|
if (isFileStorageOutdated(project, filePath)) {
|
||||||
|
qDebug() << "File needs processing:" << filePath;
|
||||||
|
uniqueFiles.insert(filePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList filesToProcess = uniqueFiles.values();
|
||||||
|
|
||||||
|
if (filesToProcess.isEmpty()) {
|
||||||
|
qDebug() << "No files need processing";
|
||||||
|
emit vectorizationFinished();
|
||||||
|
promise->finish();
|
||||||
|
return promise->future();
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "Starting to process" << filesToProcess.size() << "files";
|
||||||
|
const int batchSize = 10;
|
||||||
|
processNextFileBatch(promise, project, filesToProcess, config, 0, batchSize);
|
||||||
|
|
||||||
|
return promise->future();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RAGManager::processNextFileBatch(
|
||||||
|
std::shared_ptr<QPromise<void>> promise,
|
||||||
|
ProjectExplorer::Project *project,
|
||||||
|
const QStringList &files,
|
||||||
|
const FileChunker::ChunkingConfig &config,
|
||||||
|
int startIndex,
|
||||||
|
int batchSize)
|
||||||
|
{
|
||||||
|
if (startIndex >= files.size()) {
|
||||||
|
qDebug() << "All batches processed successfully";
|
||||||
|
emit vectorizationFinished();
|
||||||
|
promise->finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int endIndex = qMin(startIndex + batchSize, files.size());
|
||||||
|
auto currentBatch = files.mid(startIndex, endIndex - startIndex);
|
||||||
|
|
||||||
|
qDebug() << "\nProcessing batch" << (startIndex / batchSize + 1) << "(" << currentBatch.size()
|
||||||
|
<< "files)"
|
||||||
|
<< "\nProgress:" << startIndex << "to" << endIndex << "of" << files.size();
|
||||||
|
|
||||||
|
for (const QString &filePath : currentBatch) {
|
||||||
|
qDebug() << "Starting processing file:" << filePath;
|
||||||
|
auto future = processFileWithChunks(project, filePath, config);
|
||||||
|
auto watcher = new QFutureWatcher<bool>;
|
||||||
|
watcher->setFuture(future);
|
||||||
|
|
||||||
|
connect(
|
||||||
|
watcher,
|
||||||
|
&QFutureWatcher<bool>::finished,
|
||||||
|
this,
|
||||||
|
[this,
|
||||||
|
watcher,
|
||||||
|
promise,
|
||||||
|
project,
|
||||||
|
files,
|
||||||
|
startIndex,
|
||||||
|
endIndex,
|
||||||
|
batchSize,
|
||||||
|
config,
|
||||||
|
filePath]() {
|
||||||
|
bool success = watcher->result();
|
||||||
|
qDebug() << "File processed:" << filePath << "success:" << success;
|
||||||
|
|
||||||
|
bool isLastFileInBatch = (filePath == files[endIndex - 1]);
|
||||||
|
if (isLastFileInBatch) {
|
||||||
|
qDebug() << "Batch completed, moving to next batch";
|
||||||
|
emit vectorizationProgress(endIndex, files.size());
|
||||||
|
processNextFileBatch(promise, project, files, config, endIndex, batchSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
watcher->deleteLater();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QFuture<bool> RAGManager::processFileWithChunks(
|
||||||
|
ProjectExplorer::Project *project,
|
||||||
|
const QString &filePath,
|
||||||
|
const FileChunker::ChunkingConfig &config)
|
||||||
|
{
|
||||||
|
auto promise = std::make_shared<QPromise<bool>>();
|
||||||
|
promise->start();
|
||||||
|
|
||||||
|
ensureStorageForProject(project);
|
||||||
|
if (!m_currentStorage) {
|
||||||
|
qDebug() << "Storage not initialized for file:" << filePath;
|
||||||
|
promise->addResult(false);
|
||||||
|
promise->finish();
|
||||||
|
return promise->future();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto fileContent = loadFileContent(filePath);
|
||||||
|
if (!fileContent) {
|
||||||
|
qDebug() << "Failed to load content for file:" << filePath;
|
||||||
|
promise->addResult(false);
|
||||||
|
promise->finish();
|
||||||
|
return promise->future();
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "Creating chunks for file:" << filePath;
|
||||||
|
auto chunksFuture = m_chunker.chunkFiles({filePath});
|
||||||
|
auto chunks = chunksFuture.result();
|
||||||
|
|
||||||
|
if (chunks.isEmpty()) {
|
||||||
|
qDebug() << "No chunks created for file:" << filePath;
|
||||||
|
promise->addResult(false);
|
||||||
|
promise->finish();
|
||||||
|
return promise->future();
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "Created" << chunks.size() << "chunks for file:" << filePath;
|
||||||
|
|
||||||
|
// Преобразуем FileChunk в FileChunkData
|
||||||
|
QList<FileChunkData> chunkData;
|
||||||
|
for (const auto &chunk : chunks) {
|
||||||
|
FileChunkData data;
|
||||||
|
data.filePath = chunk.filePath;
|
||||||
|
data.startLine = chunk.startLine;
|
||||||
|
data.endLine = chunk.endLine;
|
||||||
|
data.content = chunk.content;
|
||||||
|
chunkData.append(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "Deleting old chunks for file:" << filePath;
|
||||||
|
m_currentStorage->deleteChunksForFile(filePath);
|
||||||
|
|
||||||
|
auto vectorizeFuture = vectorizeAndStoreChunks(filePath, chunkData);
|
||||||
|
auto watcher = new QFutureWatcher<void>;
|
||||||
|
watcher->setFuture(vectorizeFuture);
|
||||||
|
|
||||||
|
connect(watcher, &QFutureWatcher<void>::finished, this, [promise, watcher, filePath]() {
|
||||||
|
qDebug() << "Completed processing file:" << filePath;
|
||||||
|
promise->addResult(true);
|
||||||
|
promise->finish();
|
||||||
|
watcher->deleteLater();
|
||||||
|
});
|
||||||
|
|
||||||
|
return promise->future();
|
||||||
|
}
|
||||||
|
|
||||||
|
QFuture<void> RAGManager::vectorizeAndStoreChunks(
|
||||||
|
const QString &filePath, const QList<FileChunkData> &chunks)
|
||||||
|
{
|
||||||
|
qDebug() << "Vectorizing and storing" << chunks.size() << "chunks for file:" << filePath;
|
||||||
|
|
||||||
|
auto promise = std::make_shared<QPromise<void>>();
|
||||||
|
promise->start();
|
||||||
|
|
||||||
|
// Обрабатываем чанки последовательно
|
||||||
|
processNextChunk(promise, chunks, 0);
|
||||||
|
|
||||||
|
return promise->future();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RAGManager::processNextChunk(
|
||||||
|
std::shared_ptr<QPromise<void>> promise, const QList<FileChunkData> &chunks, int currentIndex)
|
||||||
|
{
|
||||||
|
if (currentIndex >= chunks.size()) {
|
||||||
|
promise->finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto &chunk = chunks[currentIndex];
|
||||||
|
QString processedContent = RAGPreprocessor::preprocessCode(chunk.content);
|
||||||
|
qDebug() << "Processing chunk" << currentIndex + 1 << "of" << chunks.size();
|
||||||
|
|
||||||
|
auto vectorFuture = m_vectorizer->vectorizeText(processedContent);
|
||||||
|
auto watcher = new QFutureWatcher<RAGVector>;
|
||||||
|
watcher->setFuture(vectorFuture);
|
||||||
|
|
||||||
|
connect(
|
||||||
|
watcher,
|
||||||
|
&QFutureWatcher<RAGVector>::finished,
|
||||||
|
this,
|
||||||
|
[this, watcher, promise, chunks, currentIndex, chunk]() {
|
||||||
|
auto vector = watcher->result();
|
||||||
|
|
||||||
|
if (!vector.empty()) {
|
||||||
|
qDebug() << "Storing vector and chunk for file:" << chunk.filePath;
|
||||||
|
bool vectorStored = m_currentStorage->storeVector(chunk.filePath, vector);
|
||||||
|
bool chunkStored = m_currentStorage->storeChunk(chunk);
|
||||||
|
qDebug() << "Storage results - Vector:" << vectorStored << "Chunk:" << chunkStored;
|
||||||
|
} else {
|
||||||
|
qDebug() << "Failed to vectorize chunk content";
|
||||||
|
}
|
||||||
|
|
||||||
|
processNextChunk(promise, chunks, currentIndex + 1);
|
||||||
|
|
||||||
|
watcher->deleteLater();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QFuture<QList<RAGManager::ChunkSearchResult>> RAGManager::findRelevantChunks(
|
||||||
|
const QString &query, ProjectExplorer::Project *project, int topK)
|
||||||
|
{
|
||||||
|
auto promise = std::make_shared<QPromise<QList<ChunkSearchResult>>>();
|
||||||
|
promise->start();
|
||||||
|
|
||||||
|
ensureStorageForProject(project);
|
||||||
|
if (!m_currentStorage) {
|
||||||
|
qDebug() << "Storage not initialized for project:" << project->displayName();
|
||||||
|
promise->addResult({});
|
||||||
|
promise->finish();
|
||||||
|
return promise->future();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString processedQuery = RAGPreprocessor::preprocessCode(query);
|
||||||
|
|
||||||
|
auto vectorFuture = m_vectorizer->vectorizeText(processedQuery);
|
||||||
|
vectorFuture.then([this, promise, project, processedQuery, topK](const RAGVector &queryVector) {
|
||||||
|
if (queryVector.empty()) {
|
||||||
|
qDebug() << "Failed to vectorize query";
|
||||||
|
promise->addResult({});
|
||||||
|
promise->finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto files = m_currentStorage->getFilesWithChunks();
|
||||||
|
QList<FileChunkData> allChunks;
|
||||||
|
|
||||||
|
for (const auto &filePath : files) {
|
||||||
|
auto fileChunks = m_currentStorage->getChunksForFile(filePath);
|
||||||
|
allChunks.append(fileChunks);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto results = rankChunks(queryVector, processedQuery, allChunks);
|
||||||
|
|
||||||
|
if (results.size() > topK) {
|
||||||
|
results = results.mid(0, topK);
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "Found" << results.size() << "relevant chunks";
|
||||||
|
promise->addResult(results);
|
||||||
|
promise->finish();
|
||||||
|
|
||||||
|
closeStorage();
|
||||||
|
});
|
||||||
|
|
||||||
|
return promise->future();
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<RAGManager::ChunkSearchResult> RAGManager::rankChunks(
|
||||||
|
const RAGVector &queryVector, const QString &queryText, const QList<FileChunkData> &chunks)
|
||||||
|
{
|
||||||
|
QList<ChunkSearchResult> results;
|
||||||
|
results.reserve(chunks.size());
|
||||||
|
|
||||||
|
for (const auto &chunk : chunks) {
|
||||||
|
auto chunkVector = m_currentStorage->getVector(chunk.filePath);
|
||||||
|
if (!chunkVector.has_value()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString processedChunk = RAGPreprocessor::preprocessCode(chunk.content);
|
||||||
|
|
||||||
|
auto similarity = EnhancedRAGSimilaritySearch::calculateSimilarity(
|
||||||
|
queryVector, chunkVector.value(), queryText, processedChunk);
|
||||||
|
|
||||||
|
results.append(ChunkSearchResult{
|
||||||
|
chunk.filePath,
|
||||||
|
chunk.startLine,
|
||||||
|
chunk.endLine,
|
||||||
|
chunk.content,
|
||||||
|
similarity.semantic_similarity,
|
||||||
|
similarity.structural_similarity,
|
||||||
|
similarity.combined_score});
|
||||||
|
}
|
||||||
|
|
||||||
|
std::sort(results.begin(), results.end());
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList RAGManager::getStoredFiles(ProjectExplorer::Project *project) const
|
||||||
|
{
|
||||||
|
ensureStorageForProject(project);
|
||||||
|
if (!m_currentStorage) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return m_currentStorage->getAllFiles();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RAGManager::isFileStorageOutdated(
|
||||||
|
ProjectExplorer::Project *project, const QString &filePath) const
|
||||||
|
{
|
||||||
|
ensureStorageForProject(project);
|
||||||
|
if (!m_currentStorage) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return m_currentStorage->needsUpdate(filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<RAGVector> RAGManager::loadVectorFromStorage(
|
||||||
|
ProjectExplorer::Project *project, const QString &filePath)
|
||||||
|
{
|
||||||
|
ensureStorageForProject(project);
|
||||||
|
if (!m_currentStorage) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
return m_currentStorage->getVector(filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RAGManager::closeStorage()
|
||||||
|
{
|
||||||
|
qDebug() << "Closing storage...";
|
||||||
|
if (m_currentStorage) {
|
||||||
|
m_currentStorage.reset();
|
||||||
|
m_currentProject = nullptr;
|
||||||
|
qDebug() << "Storage closed";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace QodeAssist::Context
|
||||||
119
context/RAGManager.hpp
Normal file
119
context/RAGManager.hpp
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
/*
|
||||||
|
* 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 <memory>
|
||||||
|
#include <optional>
|
||||||
|
#include <QFuture>
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
|
#include "FileChunker.hpp"
|
||||||
|
#include "RAGData.hpp"
|
||||||
|
#include "RAGStorage.hpp"
|
||||||
|
#include "RAGVectorizer.hpp"
|
||||||
|
|
||||||
|
namespace ProjectExplorer {
|
||||||
|
class Project;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace QodeAssist::Context {
|
||||||
|
|
||||||
|
class RAGManager : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
struct ChunkSearchResult
|
||||||
|
{
|
||||||
|
QString filePath;
|
||||||
|
int startLine;
|
||||||
|
int endLine;
|
||||||
|
QString content;
|
||||||
|
float semanticScore;
|
||||||
|
float structuralScore;
|
||||||
|
float combinedScore;
|
||||||
|
|
||||||
|
bool operator<(const ChunkSearchResult &other) const
|
||||||
|
{
|
||||||
|
return combinedScore > other.combinedScore;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static RAGManager &instance();
|
||||||
|
|
||||||
|
QFuture<void> processProjectFiles(
|
||||||
|
ProjectExplorer::Project *project,
|
||||||
|
const QStringList &filePaths,
|
||||||
|
const FileChunker::ChunkingConfig &config = FileChunker::ChunkingConfig());
|
||||||
|
|
||||||
|
QFuture<QList<ChunkSearchResult>> findRelevantChunks(
|
||||||
|
const QString &query, ProjectExplorer::Project *project, int topK = 5);
|
||||||
|
|
||||||
|
QStringList getStoredFiles(ProjectExplorer::Project *project) const;
|
||||||
|
bool isFileStorageOutdated(ProjectExplorer::Project *project, const QString &filePath) const;
|
||||||
|
|
||||||
|
void processNextChunk(
|
||||||
|
std::shared_ptr<QPromise<void>> promise,
|
||||||
|
const QList<FileChunkData> &chunks,
|
||||||
|
int currentIndex);
|
||||||
|
void closeStorage();
|
||||||
|
signals:
|
||||||
|
void vectorizationProgress(int processed, int total);
|
||||||
|
void vectorizationFinished();
|
||||||
|
|
||||||
|
private:
|
||||||
|
explicit RAGManager(QObject *parent = nullptr);
|
||||||
|
~RAGManager();
|
||||||
|
RAGManager(const RAGManager &) = delete;
|
||||||
|
RAGManager &operator=(const RAGManager &) = delete;
|
||||||
|
|
||||||
|
QString getStoragePath(ProjectExplorer::Project *project) const;
|
||||||
|
void ensureStorageForProject(ProjectExplorer::Project *project) const;
|
||||||
|
std::optional<QString> loadFileContent(const QString &filePath);
|
||||||
|
std::optional<RAGVector> loadVectorFromStorage(
|
||||||
|
ProjectExplorer::Project *project, const QString &filePath);
|
||||||
|
|
||||||
|
void processNextFileBatch(
|
||||||
|
std::shared_ptr<QPromise<void>> promise,
|
||||||
|
ProjectExplorer::Project *project,
|
||||||
|
const QStringList &files,
|
||||||
|
const FileChunker::ChunkingConfig &config,
|
||||||
|
int startIndex,
|
||||||
|
int batchSize);
|
||||||
|
|
||||||
|
QFuture<bool> processFileWithChunks(
|
||||||
|
ProjectExplorer::Project *project,
|
||||||
|
const QString &filePath,
|
||||||
|
const FileChunker::ChunkingConfig &config);
|
||||||
|
|
||||||
|
QFuture<void> vectorizeAndStoreChunks(
|
||||||
|
const QString &filePath, const QList<FileChunkData> &chunks);
|
||||||
|
|
||||||
|
QList<ChunkSearchResult> rankChunks(
|
||||||
|
const RAGVector &queryVector, const QString &queryText, const QList<FileChunkData> &chunks);
|
||||||
|
|
||||||
|
private:
|
||||||
|
mutable std::unique_ptr<RAGVectorizer> m_vectorizer;
|
||||||
|
mutable std::unique_ptr<RAGStorage> m_currentStorage;
|
||||||
|
mutable ProjectExplorer::Project *m_currentProject{nullptr};
|
||||||
|
FileChunker m_chunker;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace QodeAssist::Context
|
||||||
2
context/RAGPreprocessor.cpp
Normal file
2
context/RAGPreprocessor.cpp
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
#include "RAGPreprocessor.hpp"
|
||||||
|
|
||||||
64
context/RAGPreprocessor.hpp
Normal file
64
context/RAGPreprocessor.hpp
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
#include <QRegularExpression>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
#include "Logger.hpp"
|
||||||
|
|
||||||
|
namespace QodeAssist::Context {
|
||||||
|
|
||||||
|
class RAGPreprocessor
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static const QRegularExpression &getLicenseRegex()
|
||||||
|
{
|
||||||
|
static const QRegularExpression regex(
|
||||||
|
R"((/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)|//[^\n]*(?:\n|$))",
|
||||||
|
QRegularExpression::MultilineOption);
|
||||||
|
return regex;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const QRegularExpression &getClassRegex()
|
||||||
|
{
|
||||||
|
static const QRegularExpression regex(
|
||||||
|
R"((?:template\s*<[^>]*>\s*)?(?:class|struct)\s+(\w+)\s*(?:final\s*)?(?::\s*(?:public|protected|private)\s+\w+(?:\s*,\s*(?:public|protected|private)\s+\w+)*\s*)?{)");
|
||||||
|
return regex;
|
||||||
|
}
|
||||||
|
|
||||||
|
static QString preprocessCode(const QString &code)
|
||||||
|
{
|
||||||
|
if (code.isEmpty()) {
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
QStringList lines = code.split('\n', Qt::SkipEmptyParts);
|
||||||
|
return processLines(lines);
|
||||||
|
} catch (const std::exception &e) {
|
||||||
|
LOG_MESSAGE(QString("Error preprocessing code: %1").arg(e.what()));
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static QString processLines(const QStringList &lines)
|
||||||
|
{
|
||||||
|
const int estimatedAvgLength = 80;
|
||||||
|
QString result;
|
||||||
|
result.reserve(lines.size() * estimatedAvgLength);
|
||||||
|
|
||||||
|
for (const QString &line : lines) {
|
||||||
|
const QString trimmed = line.trimmed();
|
||||||
|
if (!trimmed.isEmpty()) {
|
||||||
|
result += trimmed;
|
||||||
|
result += QLatin1Char('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.endsWith('\n')) {
|
||||||
|
result.chop(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace QodeAssist::Context
|
||||||
67
context/RAGSimilaritySearch.cpp
Normal file
67
context/RAGSimilaritySearch.cpp
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
* 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 "RAGSimilaritySearch.hpp"
|
||||||
|
#include "logger/Logger.hpp"
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
namespace QodeAssist::Context {
|
||||||
|
|
||||||
|
float RAGSimilaritySearch::l2Distance(const RAGVector &v1, const RAGVector &v2)
|
||||||
|
{
|
||||||
|
if (v1.size() != v2.size()) {
|
||||||
|
LOG_MESSAGE(QString("Vector size mismatch: %1 vs %2").arg(v1.size()).arg(v2.size()));
|
||||||
|
return std::numeric_limits<float>::max();
|
||||||
|
}
|
||||||
|
|
||||||
|
float sum = 0.0f;
|
||||||
|
for (size_t i = 0; i < v1.size(); ++i) {
|
||||||
|
float diff = v1[i] - v2[i];
|
||||||
|
sum += diff * diff;
|
||||||
|
}
|
||||||
|
return std::sqrt(sum);
|
||||||
|
}
|
||||||
|
|
||||||
|
float RAGSimilaritySearch::cosineSimilarity(const RAGVector &v1, const RAGVector &v2)
|
||||||
|
{
|
||||||
|
if (v1.size() != v2.size()) {
|
||||||
|
LOG_MESSAGE(QString("Vector size mismatch: %1 vs %2").arg(v1.size()).arg(v2.size()));
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
float dotProduct = 0.0f;
|
||||||
|
float norm1 = 0.0f;
|
||||||
|
float norm2 = 0.0f;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < v1.size(); ++i) {
|
||||||
|
dotProduct += v1[i] * v2[i];
|
||||||
|
norm1 += v1[i] * v1[i];
|
||||||
|
norm2 += v2[i] * v2[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
norm1 = std::sqrt(norm1);
|
||||||
|
norm2 = std::sqrt(norm2);
|
||||||
|
|
||||||
|
if (norm1 == 0.0f || norm2 == 0.0f)
|
||||||
|
return 0.0f;
|
||||||
|
return dotProduct / (norm1 * norm2);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace QodeAssist::Context
|
||||||
@@ -17,17 +17,21 @@
|
|||||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace QodeAssist::LLMCore {
|
#pragma once
|
||||||
|
|
||||||
enum class ProviderID {
|
#include "RAGData.hpp"
|
||||||
Any,
|
|
||||||
Ollama,
|
namespace QodeAssist::Context {
|
||||||
LMStudio,
|
|
||||||
Claude,
|
class RAGSimilaritySearch
|
||||||
OpenAI,
|
{
|
||||||
OpenAICompatible,
|
public:
|
||||||
MistralAI,
|
static float l2Distance(const RAGVector &v1, const RAGVector &v2);
|
||||||
OpenRouter,
|
|
||||||
GoogleAI
|
static float cosineSimilarity(const RAGVector &v1, const RAGVector &v2);
|
||||||
|
|
||||||
|
private:
|
||||||
|
RAGSimilaritySearch() = delete;
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
} // namespace QodeAssist::Context
|
||||||
1047
context/RAGStorage.cpp
Normal file
1047
context/RAGStorage.cpp
Normal file
File diff suppressed because it is too large
Load Diff
174
context/RAGStorage.hpp
Normal file
174
context/RAGStorage.hpp
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
/*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// RAGStorage.hpp
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
#include <QDateTime>
|
||||||
|
#include <QMutex>
|
||||||
|
#include <QObject>
|
||||||
|
#include <QSqlDatabase>
|
||||||
|
#include <QString>
|
||||||
|
#include <qsqlquery.h>
|
||||||
|
|
||||||
|
#include <RAGData.hpp>
|
||||||
|
|
||||||
|
namespace QodeAssist::Context {
|
||||||
|
|
||||||
|
struct FileChunkData
|
||||||
|
{
|
||||||
|
QString filePath;
|
||||||
|
int startLine;
|
||||||
|
int endLine;
|
||||||
|
QString content;
|
||||||
|
QDateTime createdAt;
|
||||||
|
QDateTime updatedAt;
|
||||||
|
|
||||||
|
bool isValid() const
|
||||||
|
{
|
||||||
|
return !filePath.isEmpty() && startLine >= 0 && endLine >= startLine && !content.isEmpty();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct StorageOptions
|
||||||
|
{
|
||||||
|
int maxChunkSize = 1024 * 1024;
|
||||||
|
int maxVectorSize = 1024;
|
||||||
|
bool useCompression = false;
|
||||||
|
bool enableLogging = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct StorageStatistics
|
||||||
|
{
|
||||||
|
int totalChunks;
|
||||||
|
int totalVectors;
|
||||||
|
int totalFiles;
|
||||||
|
qint64 totalSize;
|
||||||
|
QDateTime lastUpdate;
|
||||||
|
};
|
||||||
|
|
||||||
|
class RAGStorage : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
static constexpr int CURRENT_VERSION = 1;
|
||||||
|
|
||||||
|
enum class Status { Ok, DatabaseError, ValidationError, VersionError, ConnectionError };
|
||||||
|
|
||||||
|
struct ValidationResult
|
||||||
|
{
|
||||||
|
bool isValid;
|
||||||
|
QString errorMessage;
|
||||||
|
Status errorStatus;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Error
|
||||||
|
{
|
||||||
|
QString message;
|
||||||
|
QString sqlError;
|
||||||
|
QString query;
|
||||||
|
Status status;
|
||||||
|
};
|
||||||
|
|
||||||
|
explicit RAGStorage(
|
||||||
|
const QString &dbPath,
|
||||||
|
const StorageOptions &options = StorageOptions(),
|
||||||
|
QObject *parent = nullptr);
|
||||||
|
~RAGStorage();
|
||||||
|
|
||||||
|
bool init();
|
||||||
|
Status status() const;
|
||||||
|
Error lastError() const;
|
||||||
|
bool isReady() const;
|
||||||
|
QString dbPath() const;
|
||||||
|
|
||||||
|
bool beginTransaction();
|
||||||
|
bool commitTransaction();
|
||||||
|
bool rollbackTransaction();
|
||||||
|
|
||||||
|
bool storeVector(const QString &filePath, const RAGVector &vector);
|
||||||
|
bool updateVector(const QString &filePath, const RAGVector &vector);
|
||||||
|
std::optional<RAGVector> getVector(const QString &filePath);
|
||||||
|
bool needsUpdate(const QString &filePath);
|
||||||
|
QStringList getAllFiles();
|
||||||
|
|
||||||
|
bool storeChunk(const FileChunkData &chunk);
|
||||||
|
bool storeChunks(const QList<FileChunkData> &chunks);
|
||||||
|
bool updateChunk(const FileChunkData &chunk);
|
||||||
|
bool updateChunks(const QList<FileChunkData> &chunks);
|
||||||
|
bool deleteChunksForFile(const QString &filePath);
|
||||||
|
std::optional<FileChunkData> getChunk(const QString &filePath, int startLine, int endLine);
|
||||||
|
QList<FileChunkData> getChunksForFile(const QString &filePath);
|
||||||
|
bool chunkExists(const QString &filePath, int startLine, int endLine);
|
||||||
|
|
||||||
|
int getChunkCount(const QString &filePath);
|
||||||
|
bool deleteOldChunks(const QString &filePath, const QDateTime &olderThan);
|
||||||
|
bool deleteAllChunks();
|
||||||
|
QStringList getFilesWithChunks();
|
||||||
|
bool vacuum();
|
||||||
|
bool backup(const QString &backupPath);
|
||||||
|
bool restore(const QString &backupPath);
|
||||||
|
StorageStatistics getStatistics() const;
|
||||||
|
|
||||||
|
int getStorageVersion() const;
|
||||||
|
bool isVersionCompatible() const;
|
||||||
|
|
||||||
|
bool applyMigration(int version);
|
||||||
|
signals:
|
||||||
|
void errorOccurred(const Error &error);
|
||||||
|
void operationCompleted(const QString &operation);
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool createTables();
|
||||||
|
bool createIndices();
|
||||||
|
bool createVersionTable();
|
||||||
|
bool createChunksTable();
|
||||||
|
bool createVectorsTable();
|
||||||
|
bool openDatabase();
|
||||||
|
bool initializeNewStorage();
|
||||||
|
bool upgradeStorage(int fromVersion);
|
||||||
|
bool validateSchema() const;
|
||||||
|
|
||||||
|
QDateTime getFileLastModified(const QString &filePath);
|
||||||
|
RAGVector blobToVector(const QByteArray &blob);
|
||||||
|
QByteArray vectorToBlob(const RAGVector &vector);
|
||||||
|
|
||||||
|
void setError(const QString &message, Status status = Status::DatabaseError);
|
||||||
|
void clearError();
|
||||||
|
bool prepareStatements();
|
||||||
|
ValidationResult validateChunk(const FileChunkData &chunk) const;
|
||||||
|
ValidationResult validateVector(const RAGVector &vector) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
QSqlDatabase m_db;
|
||||||
|
QString m_dbPath;
|
||||||
|
StorageOptions m_options;
|
||||||
|
mutable QMutex m_mutex;
|
||||||
|
Error m_lastError;
|
||||||
|
Status m_status;
|
||||||
|
|
||||||
|
QSqlQuery m_insertChunkQuery;
|
||||||
|
QSqlQuery m_updateChunkQuery;
|
||||||
|
QSqlQuery m_insertVectorQuery;
|
||||||
|
QSqlQuery m_updateVectorQuery;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace QodeAssist::Context
|
||||||
116
context/RAGVectorizer.cpp
Normal file
116
context/RAGVectorizer.cpp
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
/*
|
||||||
|
* 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 "RAGVectorizer.hpp"
|
||||||
|
|
||||||
|
#include <QJsonArray>
|
||||||
|
#include <QJsonDocument>
|
||||||
|
#include <QJsonObject>
|
||||||
|
#include <QNetworkReply>
|
||||||
|
|
||||||
|
namespace QodeAssist::Context {
|
||||||
|
|
||||||
|
RAGVectorizer::RAGVectorizer(const QString &providerUrl,
|
||||||
|
const QString &modelName,
|
||||||
|
QObject *parent)
|
||||||
|
: QObject(parent)
|
||||||
|
, m_network(new QNetworkAccessManager(this))
|
||||||
|
, m_embedProviderUrl(providerUrl)
|
||||||
|
, m_model(modelName)
|
||||||
|
{}
|
||||||
|
|
||||||
|
RAGVectorizer::~RAGVectorizer() {}
|
||||||
|
|
||||||
|
QJsonObject RAGVectorizer::prepareEmbeddingRequest(const QString &text) const
|
||||||
|
{
|
||||||
|
return QJsonObject{{"model", m_model}, {"prompt", text}};
|
||||||
|
}
|
||||||
|
|
||||||
|
RAGVector RAGVectorizer::parseEmbeddingResponse(const QByteArray &response) const
|
||||||
|
{
|
||||||
|
QJsonDocument doc = QJsonDocument::fromJson(response);
|
||||||
|
if (doc.isNull()) {
|
||||||
|
qDebug() << "Failed to parse JSON response";
|
||||||
|
return RAGVector();
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject obj = doc.object();
|
||||||
|
if (!obj.contains("embedding")) {
|
||||||
|
qDebug() << "Response does not contain 'embedding' field";
|
||||||
|
// qDebug() << "Response content:" << response;
|
||||||
|
return RAGVector();
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonArray array = obj["embedding"].toArray();
|
||||||
|
if (array.isEmpty()) {
|
||||||
|
qDebug() << "Embedding array is empty";
|
||||||
|
return RAGVector();
|
||||||
|
}
|
||||||
|
|
||||||
|
RAGVector result;
|
||||||
|
result.reserve(array.size());
|
||||||
|
for (const auto &value : array) {
|
||||||
|
result.push_back(value.toDouble());
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "Successfully parsed vector with size:" << result.size();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
QFuture<RAGVector> RAGVectorizer::vectorizeText(const QString &text)
|
||||||
|
{
|
||||||
|
qDebug() << "Vectorizing text, length:" << text.length();
|
||||||
|
qDebug() << "Using embedding provider:" << m_embedProviderUrl;
|
||||||
|
|
||||||
|
auto promise = std::make_shared<QPromise<RAGVector>>();
|
||||||
|
promise->start();
|
||||||
|
|
||||||
|
QNetworkRequest request(QUrl(m_embedProviderUrl + "/api/embeddings"));
|
||||||
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||||
|
|
||||||
|
QJsonObject requestData = prepareEmbeddingRequest(text);
|
||||||
|
QByteArray jsonData = QJsonDocument(requestData).toJson();
|
||||||
|
qDebug() << "Sending request to embeddings API:" << jsonData;
|
||||||
|
|
||||||
|
auto reply = m_network->post(request, jsonData);
|
||||||
|
|
||||||
|
connect(reply, &QNetworkReply::finished, this, [promise, reply, this]() {
|
||||||
|
if (reply->error() == QNetworkReply::NoError) {
|
||||||
|
QByteArray response = reply->readAll();
|
||||||
|
// qDebug() << "Received response from embeddings API:" << response;
|
||||||
|
|
||||||
|
auto vector = parseEmbeddingResponse(response);
|
||||||
|
qDebug() << "Parsed vector size:" << vector.size();
|
||||||
|
promise->addResult(vector);
|
||||||
|
} else {
|
||||||
|
qDebug() << "Network error:" << reply->errorString();
|
||||||
|
qDebug() << "HTTP status code:"
|
||||||
|
<< reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||||
|
qDebug() << "Response:" << reply->readAll();
|
||||||
|
|
||||||
|
promise->addResult(RAGVector());
|
||||||
|
}
|
||||||
|
promise->finish();
|
||||||
|
reply->deleteLater();
|
||||||
|
});
|
||||||
|
|
||||||
|
return promise->future();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace QodeAssist::Context
|
||||||
51
context/RAGVectorizer.hpp
Normal file
51
context/RAGVectorizer.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 <QFuture>
|
||||||
|
#include <QNetworkAccessManager>
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
|
#include <RAGData.hpp>
|
||||||
|
|
||||||
|
namespace QodeAssist::Context {
|
||||||
|
|
||||||
|
class RAGVectorizer : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit RAGVectorizer(
|
||||||
|
const QString &providerUrl = "http://localhost:11434",
|
||||||
|
const QString &modelName = "all-minilm:33m-l12-v2-fp16",
|
||||||
|
QObject *parent = nullptr);
|
||||||
|
~RAGVectorizer();
|
||||||
|
|
||||||
|
QFuture<RAGVector> vectorizeText(const QString &text);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QJsonObject prepareEmbeddingRequest(const QString &text) const;
|
||||||
|
RAGVector parseEmbeddingResponse(const QByteArray &response) const;
|
||||||
|
|
||||||
|
QNetworkAccessManager *m_network;
|
||||||
|
QString m_embedProviderUrl;
|
||||||
|
QString m_model;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace QodeAssist::Context
|
||||||
@@ -10,7 +10,7 @@ add_library(LLMCore STATIC
|
|||||||
OllamaMessage.hpp OllamaMessage.cpp
|
OllamaMessage.hpp OllamaMessage.cpp
|
||||||
OpenAIMessage.hpp OpenAIMessage.cpp
|
OpenAIMessage.hpp OpenAIMessage.cpp
|
||||||
ValidationUtils.hpp ValidationUtils.cpp
|
ValidationUtils.hpp ValidationUtils.cpp
|
||||||
ProviderID.hpp
|
MessageBuilder.hpp MessageBuilder.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(LLMCore
|
target_link_libraries(LLMCore
|
||||||
|
|||||||
@@ -20,23 +20,14 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QVector>
|
|
||||||
|
|
||||||
namespace QodeAssist::LLMCore {
|
namespace QodeAssist::LLMCore {
|
||||||
|
|
||||||
struct Message
|
|
||||||
{
|
|
||||||
QString role;
|
|
||||||
QString content;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ContextData
|
struct ContextData
|
||||||
{
|
{
|
||||||
std::optional<QString> systemPrompt;
|
QString prefix;
|
||||||
std::optional<QString> prefix;
|
QString suffix;
|
||||||
std::optional<QString> suffix;
|
QString fileContext;
|
||||||
std::optional<QString> fileContext;
|
|
||||||
std::optional<QVector<Message>> history;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace QodeAssist::LLMCore
|
} // namespace QodeAssist::LLMCore
|
||||||
|
|||||||
93
llmcore/MessageBuilder.cpp
Normal file
93
llmcore/MessageBuilder.cpp
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
/*
|
||||||
|
* 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 "MessageBuilder.hpp"
|
||||||
|
|
||||||
|
QodeAssist::LLMCore::MessageBuilder &QodeAssist::LLMCore::MessageBuilder::addSystemMessage(
|
||||||
|
const QString &content)
|
||||||
|
{
|
||||||
|
m_systemMessage = content;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
QodeAssist::LLMCore::MessageBuilder &QodeAssist::LLMCore::MessageBuilder::addUserMessage(
|
||||||
|
const QString &content)
|
||||||
|
{
|
||||||
|
m_messages.append({MessageRole::User, content});
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
QodeAssist::LLMCore::MessageBuilder &QodeAssist::LLMCore::MessageBuilder::addSuffix(
|
||||||
|
const QString &content)
|
||||||
|
{
|
||||||
|
m_suffix = content;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
QodeAssist::LLMCore::MessageBuilder &QodeAssist::LLMCore::MessageBuilder::addTokenizer(
|
||||||
|
PromptTemplate *promptTemplate)
|
||||||
|
{
|
||||||
|
m_promptTemplate = promptTemplate;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString QodeAssist::LLMCore::MessageBuilder::roleToString(MessageRole role) const
|
||||||
|
{
|
||||||
|
switch (role) {
|
||||||
|
case MessageRole::System:
|
||||||
|
return ROLE_SYSTEM;
|
||||||
|
case MessageRole::User:
|
||||||
|
return ROLE_USER;
|
||||||
|
case MessageRole::Assistant:
|
||||||
|
return ROLE_ASSISTANT;
|
||||||
|
default:
|
||||||
|
return ROLE_USER;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void QodeAssist::LLMCore::MessageBuilder::saveTo(QJsonObject &request, ProvidersApi api)
|
||||||
|
{
|
||||||
|
if (!m_promptTemplate) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ContextData context{
|
||||||
|
m_messages.isEmpty() ? QString() : m_messages.last().content, m_suffix, m_systemMessage};
|
||||||
|
|
||||||
|
if (api == ProvidersApi::Ollama) {
|
||||||
|
if (m_promptTemplate->type() == TemplateType::Fim) {
|
||||||
|
request["system"] = m_systemMessage;
|
||||||
|
m_promptTemplate->prepareRequest(request, context);
|
||||||
|
} else {
|
||||||
|
QJsonArray messages;
|
||||||
|
|
||||||
|
messages.append(QJsonObject{{"role", "system"}, {"content", m_systemMessage}});
|
||||||
|
messages.append(QJsonObject{{"role", "user"}, {"content", m_messages.last().content}});
|
||||||
|
request["messages"] = messages;
|
||||||
|
m_promptTemplate->prepareRequest(request, context);
|
||||||
|
}
|
||||||
|
} else if (api == ProvidersApi::OpenAI) {
|
||||||
|
QJsonArray messages;
|
||||||
|
|
||||||
|
messages.append(QJsonObject{{"role", "system"}, {"content", m_systemMessage}});
|
||||||
|
messages.append(QJsonObject{{"role", "user"}, {"content", m_messages.last().content}});
|
||||||
|
request["messages"] = messages;
|
||||||
|
m_promptTemplate->prepareRequest(request, context);
|
||||||
|
}
|
||||||
|
}
|
||||||
68
llmcore/MessageBuilder.hpp
Normal file
68
llmcore/MessageBuilder.hpp
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
/*
|
||||||
|
* 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 <QJsonObject>
|
||||||
|
#include <QString>
|
||||||
|
#include <QVector>
|
||||||
|
|
||||||
|
#include "PromptTemplate.hpp"
|
||||||
|
|
||||||
|
namespace QodeAssist::LLMCore {
|
||||||
|
|
||||||
|
enum class MessageRole { System, User, Assistant };
|
||||||
|
|
||||||
|
enum class OllamaFormat { Messages, Completions };
|
||||||
|
|
||||||
|
enum class ProvidersApi { Ollama, OpenAI, Claude };
|
||||||
|
|
||||||
|
static const QString ROLE_SYSTEM = "system";
|
||||||
|
static const QString ROLE_USER = "user";
|
||||||
|
static const QString ROLE_ASSISTANT = "assistant";
|
||||||
|
|
||||||
|
struct Message
|
||||||
|
{
|
||||||
|
MessageRole role;
|
||||||
|
QString content;
|
||||||
|
};
|
||||||
|
|
||||||
|
class MessageBuilder
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
MessageBuilder &addSystemMessage(const QString &content);
|
||||||
|
|
||||||
|
MessageBuilder &addUserMessage(const QString &content);
|
||||||
|
|
||||||
|
MessageBuilder &addSuffix(const QString &content);
|
||||||
|
|
||||||
|
MessageBuilder &addTokenizer(PromptTemplate *promptTemplate);
|
||||||
|
|
||||||
|
QString roleToString(MessageRole role) const;
|
||||||
|
|
||||||
|
void saveTo(QJsonObject &request, ProvidersApi api);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QString m_systemMessage;
|
||||||
|
QString m_suffix;
|
||||||
|
QVector<Message> m_messages;
|
||||||
|
PromptTemplate *m_promptTemplate;
|
||||||
|
};
|
||||||
|
} // namespace QodeAssist::LLMCore
|
||||||
@@ -24,11 +24,10 @@
|
|||||||
#include <QString>
|
#include <QString>
|
||||||
|
|
||||||
#include "ContextData.hpp"
|
#include "ContextData.hpp"
|
||||||
#include "ProviderID.hpp"
|
|
||||||
|
|
||||||
namespace QodeAssist::LLMCore {
|
namespace QodeAssist::LLMCore {
|
||||||
|
|
||||||
enum class TemplateType { Chat, FIM };
|
enum class TemplateType { Chat, Fim };
|
||||||
|
|
||||||
class PromptTemplate
|
class PromptTemplate
|
||||||
{
|
{
|
||||||
@@ -36,9 +35,9 @@ public:
|
|||||||
virtual ~PromptTemplate() = default;
|
virtual ~PromptTemplate() = default;
|
||||||
virtual TemplateType type() const = 0;
|
virtual TemplateType type() const = 0;
|
||||||
virtual QString name() const = 0;
|
virtual QString name() const = 0;
|
||||||
|
virtual QString promptTemplate() const = 0;
|
||||||
virtual QStringList stopWords() const = 0;
|
virtual QStringList stopWords() const = 0;
|
||||||
virtual void prepareRequest(QJsonObject &request, const ContextData &context) const = 0;
|
virtual void prepareRequest(QJsonObject &request, const ContextData &context) const = 0;
|
||||||
virtual QString description() const = 0;
|
virtual QString description() const = 0;
|
||||||
virtual bool isSupportProvider(ProviderID id) const = 0;
|
|
||||||
};
|
};
|
||||||
} // namespace QodeAssist::LLMCore
|
} // namespace QodeAssist::LLMCore
|
||||||
|
|||||||
@@ -37,32 +37,6 @@ QStringList PromptTemplateManager::chatTemplatesNames() const
|
|||||||
return m_chatTemplates.keys();
|
return m_chatTemplates.keys();
|
||||||
}
|
}
|
||||||
|
|
||||||
QStringList PromptTemplateManager::getFimTemplatesForProvider(ProviderID id)
|
|
||||||
{
|
|
||||||
QStringList templateList;
|
|
||||||
|
|
||||||
for (const auto tmpl : m_fimTemplates) {
|
|
||||||
if (tmpl->isSupportProvider(id)) {
|
|
||||||
templateList.append(tmpl->name());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return templateList;
|
|
||||||
}
|
|
||||||
|
|
||||||
QStringList PromptTemplateManager::getChatTemplatesForProvider(ProviderID id)
|
|
||||||
{
|
|
||||||
QStringList templateList;
|
|
||||||
|
|
||||||
for (const auto tmpl : m_chatTemplates) {
|
|
||||||
if (tmpl->isSupportProvider(id)) {
|
|
||||||
templateList.append(tmpl->name());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return templateList;
|
|
||||||
}
|
|
||||||
|
|
||||||
PromptTemplateManager::~PromptTemplateManager()
|
PromptTemplateManager::~PromptTemplateManager()
|
||||||
{
|
{
|
||||||
qDeleteAll(m_fimTemplates);
|
qDeleteAll(m_fimTemplates);
|
||||||
@@ -70,15 +44,11 @@ PromptTemplateManager::~PromptTemplateManager()
|
|||||||
|
|
||||||
PromptTemplate *PromptTemplateManager::getFimTemplateByName(const QString &templateName)
|
PromptTemplate *PromptTemplateManager::getFimTemplateByName(const QString &templateName)
|
||||||
{
|
{
|
||||||
if (!m_fimTemplates.contains(templateName))
|
|
||||||
return m_fimTemplates.first();
|
|
||||||
return m_fimTemplates[templateName];
|
return m_fimTemplates[templateName];
|
||||||
}
|
}
|
||||||
|
|
||||||
PromptTemplate *PromptTemplateManager::getChatTemplateByName(const QString &templateName)
|
PromptTemplate *PromptTemplateManager::getChatTemplateByName(const QString &templateName)
|
||||||
{
|
{
|
||||||
if (!m_chatTemplates.contains(templateName))
|
|
||||||
return m_chatTemplates.first();
|
|
||||||
return m_chatTemplates[templateName];
|
return m_chatTemplates[templateName];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -51,9 +51,6 @@ public:
|
|||||||
QStringList fimTemplatesNames() const;
|
QStringList fimTemplatesNames() const;
|
||||||
QStringList chatTemplatesNames() const;
|
QStringList chatTemplatesNames() const;
|
||||||
|
|
||||||
QStringList getFimTemplatesForProvider(ProviderID id);
|
|
||||||
QStringList getChatTemplatesForProvider(ProviderID id);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
PromptTemplateManager() = default;
|
PromptTemplateManager() = default;
|
||||||
PromptTemplateManager(const PromptTemplateManager &) = delete;
|
PromptTemplateManager(const PromptTemplateManager &) = delete;
|
||||||
|
|||||||
@@ -23,7 +23,6 @@
|
|||||||
#include <QNetworkRequest>
|
#include <QNetworkRequest>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
|
||||||
#include "ContextData.hpp"
|
|
||||||
#include "PromptTemplate.hpp"
|
#include "PromptTemplate.hpp"
|
||||||
#include "RequestType.hpp"
|
#include "RequestType.hpp"
|
||||||
|
|
||||||
@@ -42,18 +41,13 @@ public:
|
|||||||
virtual QString completionEndpoint() const = 0;
|
virtual QString completionEndpoint() const = 0;
|
||||||
virtual QString chatEndpoint() const = 0;
|
virtual QString chatEndpoint() const = 0;
|
||||||
virtual bool supportsModelListing() const = 0;
|
virtual bool supportsModelListing() const = 0;
|
||||||
virtual void prepareRequest(
|
|
||||||
QJsonObject &request,
|
virtual void prepareRequest(QJsonObject &request, RequestType type) = 0;
|
||||||
LLMCore::PromptTemplate *prompt,
|
|
||||||
LLMCore::ContextData context,
|
|
||||||
LLMCore::RequestType type)
|
|
||||||
= 0;
|
|
||||||
virtual bool handleResponse(QNetworkReply *reply, QString &accumulatedResponse) = 0;
|
virtual bool handleResponse(QNetworkReply *reply, QString &accumulatedResponse) = 0;
|
||||||
virtual QList<QString> getInstalledModels(const QString &url) = 0;
|
virtual QList<QString> getInstalledModels(const QString &url) = 0;
|
||||||
virtual QList<QString> validateRequest(const QJsonObject &request, TemplateType type) = 0;
|
virtual QList<QString> validateRequest(const QJsonObject &request, TemplateType type) = 0;
|
||||||
virtual QString apiKey() const = 0;
|
virtual QString apiKey() const = 0;
|
||||||
virtual void prepareNetworkRequest(QNetworkRequest &networkRequest) const = 0;
|
virtual void prepareNetworkRequest(QNetworkRequest &networkRequest) const = 0;
|
||||||
virtual ProviderID providerID() const = 0;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace QodeAssist::LLMCore
|
} // namespace QodeAssist::LLMCore
|
||||||
|
|||||||
@@ -39,8 +39,6 @@ ProvidersManager::~ProvidersManager()
|
|||||||
|
|
||||||
Provider *ProvidersManager::getProviderByName(const QString &providerName)
|
Provider *ProvidersManager::getProviderByName(const QString &providerName)
|
||||||
{
|
{
|
||||||
if (!m_providers.contains(providerName))
|
|
||||||
return m_providers.first();
|
|
||||||
return m_providers[providerName];
|
return m_providers[providerName];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -58,10 +58,7 @@ void RequestHandler::sendLLMRequest(const LLMConfig &config, const QJsonObject &
|
|||||||
reply->deleteLater();
|
reply->deleteLater();
|
||||||
m_activeRequests.remove(requestId);
|
m_activeRequests.remove(requestId);
|
||||||
if (reply->error() != QNetworkReply::NoError) {
|
if (reply->error() != QNetworkReply::NoError) {
|
||||||
LOG_MESSAGE(QString("Error details: %1\nStatus code: %2\nResponse: %3")
|
LOG_MESSAGE(QString("Error in QodeAssist request: %1").arg(reply->errorString()));
|
||||||
.arg(reply->errorString())
|
|
||||||
.arg(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt())
|
|
||||||
.arg(QString(reply->readAll())));
|
|
||||||
emit requestFinished(requestId, false, reply->errorString());
|
emit requestFinished(requestId, false, reply->errorString());
|
||||||
} else {
|
} else {
|
||||||
LOG_MESSAGE("Request finished successfully");
|
LOG_MESSAGE("Request finished successfully");
|
||||||
|
|||||||
@@ -21,5 +21,5 @@
|
|||||||
|
|
||||||
namespace QodeAssist::LLMCore {
|
namespace QodeAssist::LLMCore {
|
||||||
|
|
||||||
enum RequestType { CodeCompletion, Chat, Embedding };
|
enum RequestType { CodeCompletion, Chat };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,10 +30,13 @@
|
|||||||
#include "logger/Logger.hpp"
|
#include "logger/Logger.hpp"
|
||||||
#include "settings/ChatAssistantSettings.hpp"
|
#include "settings/ChatAssistantSettings.hpp"
|
||||||
#include "settings/CodeCompletionSettings.hpp"
|
#include "settings/CodeCompletionSettings.hpp"
|
||||||
|
#include "settings/GeneralSettings.hpp"
|
||||||
#include "settings/ProviderSettings.hpp"
|
#include "settings/ProviderSettings.hpp"
|
||||||
|
|
||||||
namespace QodeAssist::Providers {
|
namespace QodeAssist::Providers {
|
||||||
|
|
||||||
|
ClaudeProvider::ClaudeProvider() {}
|
||||||
|
|
||||||
QString ClaudeProvider::name() const
|
QString ClaudeProvider::name() const
|
||||||
{
|
{
|
||||||
return "Claude";
|
return "Claude";
|
||||||
@@ -59,17 +62,31 @@ bool ClaudeProvider::supportsModelListing() const
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClaudeProvider::prepareRequest(
|
void ClaudeProvider::prepareRequest(QJsonObject &request, LLMCore::RequestType type)
|
||||||
QJsonObject &request,
|
|
||||||
LLMCore::PromptTemplate *prompt,
|
|
||||||
LLMCore::ContextData context,
|
|
||||||
LLMCore::RequestType type)
|
|
||||||
{
|
{
|
||||||
if (!prompt->isSupportProvider(providerID())) {
|
auto prepareMessages = [](QJsonObject &req) -> QJsonArray {
|
||||||
LOG_MESSAGE(QString("Template %1 doesn't support %2 provider").arg(name(), prompt->name()));
|
QJsonArray messages;
|
||||||
|
if (req.contains("messages")) {
|
||||||
|
QJsonArray origMessages = req["messages"].toArray();
|
||||||
|
for (const auto &msg : origMessages) {
|
||||||
|
QJsonObject message = msg.toObject();
|
||||||
|
if (message["role"].toString() == "system") {
|
||||||
|
req["system"] = message["content"];
|
||||||
|
} else {
|
||||||
|
messages.append(message);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
prompt->prepareRequest(request, context);
|
} else {
|
||||||
|
if (req.contains("system")) {
|
||||||
|
req["system"] = req["system"].toString();
|
||||||
|
}
|
||||||
|
if (req.contains("prompt")) {
|
||||||
|
messages.append(
|
||||||
|
QJsonObject{{"role", "user"}, {"content", req.take("prompt").toString()}});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return messages;
|
||||||
|
};
|
||||||
|
|
||||||
auto applyModelParams = [&request](const auto &settings) {
|
auto applyModelParams = [&request](const auto &settings) {
|
||||||
request["max_tokens"] = settings.maxTokens();
|
request["max_tokens"] = settings.maxTokens();
|
||||||
@@ -81,6 +98,11 @@ void ClaudeProvider::prepareRequest(
|
|||||||
request["stream"] = true;
|
request["stream"] = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
QJsonArray messages = prepareMessages(request);
|
||||||
|
if (!messages.isEmpty()) {
|
||||||
|
request["messages"] = std::move(messages);
|
||||||
|
}
|
||||||
|
|
||||||
if (type == LLMCore::RequestType::CodeCompletion) {
|
if (type == LLMCore::RequestType::CodeCompletion) {
|
||||||
applyModelParams(Settings::codeCompletionSettings());
|
applyModelParams(Settings::codeCompletionSettings());
|
||||||
} else {
|
} else {
|
||||||
@@ -213,9 +235,4 @@ void ClaudeProvider::prepareNetworkRequest(QNetworkRequest &networkRequest) cons
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LLMCore::ProviderID ClaudeProvider::providerID() const
|
|
||||||
{
|
|
||||||
return LLMCore::ProviderID::Claude;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace QodeAssist::Providers
|
} // namespace QodeAssist::Providers
|
||||||
|
|||||||
@@ -26,22 +26,19 @@ namespace QodeAssist::Providers {
|
|||||||
class ClaudeProvider : public LLMCore::Provider
|
class ClaudeProvider : public LLMCore::Provider
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
ClaudeProvider();
|
||||||
|
|
||||||
QString name() const override;
|
QString name() const override;
|
||||||
QString url() const override;
|
QString url() const override;
|
||||||
QString completionEndpoint() const override;
|
QString completionEndpoint() const override;
|
||||||
QString chatEndpoint() const override;
|
QString chatEndpoint() const override;
|
||||||
bool supportsModelListing() const override;
|
bool supportsModelListing() const override;
|
||||||
void prepareRequest(
|
void prepareRequest(QJsonObject &request, LLMCore::RequestType type) override;
|
||||||
QJsonObject &request,
|
|
||||||
LLMCore::PromptTemplate *prompt,
|
|
||||||
LLMCore::ContextData context,
|
|
||||||
LLMCore::RequestType type) override;
|
|
||||||
bool handleResponse(QNetworkReply *reply, QString &accumulatedResponse) override;
|
bool handleResponse(QNetworkReply *reply, QString &accumulatedResponse) override;
|
||||||
QList<QString> getInstalledModels(const QString &url) override;
|
QList<QString> getInstalledModels(const QString &url) override;
|
||||||
QList<QString> validateRequest(const QJsonObject &request, LLMCore::TemplateType type) override;
|
QList<QString> validateRequest(const QJsonObject &request, LLMCore::TemplateType type) override;
|
||||||
QString apiKey() const override;
|
QString apiKey() const override;
|
||||||
void prepareNetworkRequest(QNetworkRequest &networkRequest) const override;
|
void prepareNetworkRequest(QNetworkRequest &networkRequest) const override;
|
||||||
LLMCore::ProviderID providerID() const override;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace QodeAssist::Providers
|
} // namespace QodeAssist::Providers
|
||||||
|
|||||||
@@ -1,303 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2024 Petr Mironychev
|
|
||||||
*
|
|
||||||
* This file is part of QodeAssist.
|
|
||||||
*
|
|
||||||
* QodeAssist is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* QodeAssist is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "GoogleAIProvider.hpp"
|
|
||||||
|
|
||||||
#include <QEventLoop>
|
|
||||||
#include <QJsonArray>
|
|
||||||
#include <QJsonDocument>
|
|
||||||
#include <QJsonObject>
|
|
||||||
#include <QNetworkReply>
|
|
||||||
#include <QtCore/qurlquery.h>
|
|
||||||
|
|
||||||
#include "llmcore/ValidationUtils.hpp"
|
|
||||||
#include "logger/Logger.hpp"
|
|
||||||
#include "settings/ChatAssistantSettings.hpp"
|
|
||||||
#include "settings/CodeCompletionSettings.hpp"
|
|
||||||
#include "settings/GeneralSettings.hpp"
|
|
||||||
#include "settings/ProviderSettings.hpp"
|
|
||||||
|
|
||||||
namespace QodeAssist::Providers {
|
|
||||||
|
|
||||||
QString GoogleAIProvider::name() const
|
|
||||||
{
|
|
||||||
return "Google AI";
|
|
||||||
}
|
|
||||||
|
|
||||||
QString GoogleAIProvider::url() const
|
|
||||||
{
|
|
||||||
return "https://generativelanguage.googleapis.com/v1beta";
|
|
||||||
}
|
|
||||||
|
|
||||||
QString GoogleAIProvider::completionEndpoint() const
|
|
||||||
{
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
QString GoogleAIProvider::chatEndpoint() const
|
|
||||||
{
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
bool GoogleAIProvider::supportsModelListing() const
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void GoogleAIProvider::prepareRequest(
|
|
||||||
QJsonObject &request,
|
|
||||||
LLMCore::PromptTemplate *prompt,
|
|
||||||
LLMCore::ContextData context,
|
|
||||||
LLMCore::RequestType type)
|
|
||||||
{
|
|
||||||
if (!prompt->isSupportProvider(providerID())) {
|
|
||||||
LOG_MESSAGE(QString("Template %1 doesn't support %2 provider").arg(name(), prompt->name()));
|
|
||||||
}
|
|
||||||
|
|
||||||
prompt->prepareRequest(request, context);
|
|
||||||
|
|
||||||
auto applyModelParams = [&request](const auto &settings) {
|
|
||||||
QJsonObject generationConfig;
|
|
||||||
generationConfig["maxOutputTokens"] = settings.maxTokens();
|
|
||||||
generationConfig["temperature"] = settings.temperature();
|
|
||||||
|
|
||||||
if (settings.useTopP())
|
|
||||||
generationConfig["topP"] = settings.topP();
|
|
||||||
if (settings.useTopK())
|
|
||||||
generationConfig["topK"] = settings.topK();
|
|
||||||
|
|
||||||
request["generationConfig"] = generationConfig;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (type == LLMCore::RequestType::CodeCompletion) {
|
|
||||||
applyModelParams(Settings::codeCompletionSettings());
|
|
||||||
} else {
|
|
||||||
applyModelParams(Settings::chatAssistantSettings());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool GoogleAIProvider::handleResponse(QNetworkReply *reply, QString &accumulatedResponse)
|
|
||||||
{
|
|
||||||
if (reply->isFinished()) {
|
|
||||||
if (reply->bytesAvailable() > 0) {
|
|
||||||
QByteArray data = reply->readAll();
|
|
||||||
|
|
||||||
if (data.startsWith("data: ")) {
|
|
||||||
return handleStreamResponse(data, accumulatedResponse);
|
|
||||||
} else {
|
|
||||||
return handleRegularResponse(data, accumulatedResponse);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
QByteArray data = reply->readAll();
|
|
||||||
if (data.isEmpty()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.startsWith("data: ")) {
|
|
||||||
return handleStreamResponse(data, accumulatedResponse);
|
|
||||||
} else {
|
|
||||||
return handleRegularResponse(data, accumulatedResponse);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QList<QString> GoogleAIProvider::getInstalledModels(const QString &url)
|
|
||||||
{
|
|
||||||
QList<QString> models;
|
|
||||||
|
|
||||||
QNetworkAccessManager manager;
|
|
||||||
QNetworkRequest request(QString("%1/models?key=%2").arg(url, apiKey()));
|
|
||||||
|
|
||||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
|
||||||
|
|
||||||
QNetworkReply *reply = manager.get(request);
|
|
||||||
QEventLoop loop;
|
|
||||||
QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
|
|
||||||
loop.exec();
|
|
||||||
|
|
||||||
if (reply->error() == QNetworkReply::NoError) {
|
|
||||||
QByteArray responseData = reply->readAll();
|
|
||||||
QJsonDocument jsonResponse = QJsonDocument::fromJson(responseData);
|
|
||||||
QJsonObject jsonObject = jsonResponse.object();
|
|
||||||
|
|
||||||
if (jsonObject.contains("models")) {
|
|
||||||
QJsonArray modelArray = jsonObject["models"].toArray();
|
|
||||||
models.clear();
|
|
||||||
|
|
||||||
for (const QJsonValue &value : modelArray) {
|
|
||||||
QJsonObject modelObject = value.toObject();
|
|
||||||
if (modelObject.contains("name")) {
|
|
||||||
QString modelName = modelObject["name"].toString();
|
|
||||||
if (modelName.contains("/")) {
|
|
||||||
modelName = modelName.split("/").last();
|
|
||||||
}
|
|
||||||
models.append(modelName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
LOG_MESSAGE(QString("Error fetching Google AI models: %1").arg(reply->errorString()));
|
|
||||||
}
|
|
||||||
|
|
||||||
reply->deleteLater();
|
|
||||||
return models;
|
|
||||||
}
|
|
||||||
|
|
||||||
QList<QString> GoogleAIProvider::validateRequest(
|
|
||||||
const QJsonObject &request, LLMCore::TemplateType type)
|
|
||||||
{
|
|
||||||
QJsonObject templateReq;
|
|
||||||
|
|
||||||
templateReq = QJsonObject{
|
|
||||||
{"contents", QJsonArray{}},
|
|
||||||
{"system_instruction", QJsonArray{}},
|
|
||||||
{"generationConfig",
|
|
||||||
QJsonObject{{"temperature", {}}, {"maxOutputTokens", {}}, {"topP", {}}, {"topK", {}}}},
|
|
||||||
{"safetySettings", QJsonArray{}}};
|
|
||||||
|
|
||||||
return LLMCore::ValidationUtils::validateRequestFields(request, templateReq);
|
|
||||||
}
|
|
||||||
|
|
||||||
QString GoogleAIProvider::apiKey() const
|
|
||||||
{
|
|
||||||
return Settings::providerSettings().googleAiApiKey();
|
|
||||||
}
|
|
||||||
|
|
||||||
void GoogleAIProvider::prepareNetworkRequest(QNetworkRequest &networkRequest) const
|
|
||||||
{
|
|
||||||
networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
|
||||||
|
|
||||||
QUrl url = networkRequest.url();
|
|
||||||
QUrlQuery query(url.query());
|
|
||||||
query.addQueryItem("key", apiKey());
|
|
||||||
url.setQuery(query);
|
|
||||||
networkRequest.setUrl(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
LLMCore::ProviderID GoogleAIProvider::providerID() const
|
|
||||||
{
|
|
||||||
return LLMCore::ProviderID::GoogleAI;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool GoogleAIProvider::handleStreamResponse(const QByteArray &data, QString &accumulatedResponse)
|
|
||||||
{
|
|
||||||
QByteArrayList lines = data.split('\n');
|
|
||||||
bool isDone = false;
|
|
||||||
|
|
||||||
for (const QByteArray &line : lines) {
|
|
||||||
QByteArray trimmedLine = line.trimmed();
|
|
||||||
if (trimmedLine.isEmpty()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (trimmedLine == "data: [DONE]") {
|
|
||||||
isDone = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (trimmedLine.startsWith("data: ")) {
|
|
||||||
QByteArray jsonData = trimmedLine.mid(6); // Remove "data: " prefix
|
|
||||||
QJsonDocument doc = QJsonDocument::fromJson(jsonData);
|
|
||||||
if (doc.isNull() || !doc.isObject()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
QJsonObject responseObj = doc.object();
|
|
||||||
|
|
||||||
if (responseObj.contains("error")) {
|
|
||||||
QJsonObject error = responseObj["error"].toObject();
|
|
||||||
LOG_MESSAGE("Error in Google AI stream response: " + error["message"].toString());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (responseObj.contains("candidates")) {
|
|
||||||
QJsonArray candidates = responseObj["candidates"].toArray();
|
|
||||||
if (!candidates.isEmpty()) {
|
|
||||||
QJsonObject candidate = candidates.first().toObject();
|
|
||||||
|
|
||||||
if (candidate.contains("finishReason")
|
|
||||||
&& !candidate["finishReason"].toString().isEmpty()) {
|
|
||||||
isDone = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (candidate.contains("content")) {
|
|
||||||
QJsonObject content = candidate["content"].toObject();
|
|
||||||
if (content.contains("parts")) {
|
|
||||||
QJsonArray parts = content["parts"].toArray();
|
|
||||||
for (const auto &part : parts) {
|
|
||||||
QJsonObject partObj = part.toObject();
|
|
||||||
if (partObj.contains("text")) {
|
|
||||||
accumulatedResponse += partObj["text"].toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return isDone;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool GoogleAIProvider::handleRegularResponse(const QByteArray &data, QString &accumulatedResponse)
|
|
||||||
{
|
|
||||||
QJsonDocument doc = QJsonDocument::fromJson(data);
|
|
||||||
if (doc.isNull() || !doc.isObject()) {
|
|
||||||
LOG_MESSAGE("Invalid JSON response from Google AI API");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
QJsonObject response = doc.object();
|
|
||||||
|
|
||||||
if (response.contains("error")) {
|
|
||||||
QJsonObject error = response["error"].toObject();
|
|
||||||
LOG_MESSAGE("Error in Google AI response: " + error["message"].toString());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!response.contains("candidates") || response["candidates"].toArray().isEmpty()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
QJsonObject candidate = response["candidates"].toArray().first().toObject();
|
|
||||||
if (!candidate.contains("content")) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
QJsonObject content = candidate["content"].toObject();
|
|
||||||
if (!content.contains("parts")) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
QJsonArray parts = content["parts"].toArray();
|
|
||||||
for (const auto &part : parts) {
|
|
||||||
QJsonObject partObj = part.toObject();
|
|
||||||
if (partObj.contains("text")) {
|
|
||||||
accumulatedResponse += partObj["text"].toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace QodeAssist::Providers
|
|
||||||
@@ -1,51 +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 "llmcore/Provider.hpp"
|
|
||||||
|
|
||||||
namespace QodeAssist::Providers {
|
|
||||||
|
|
||||||
class GoogleAIProvider : public LLMCore::Provider
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
QString name() const override;
|
|
||||||
QString url() const override;
|
|
||||||
QString completionEndpoint() const override;
|
|
||||||
QString chatEndpoint() const override;
|
|
||||||
bool supportsModelListing() const override;
|
|
||||||
void prepareRequest(
|
|
||||||
QJsonObject &request,
|
|
||||||
LLMCore::PromptTemplate *prompt,
|
|
||||||
LLMCore::ContextData context,
|
|
||||||
LLMCore::RequestType type) override;
|
|
||||||
bool handleResponse(QNetworkReply *reply, QString &accumulatedResponse) override;
|
|
||||||
QList<QString> getInstalledModels(const QString &url) override;
|
|
||||||
QList<QString> validateRequest(const QJsonObject &request, LLMCore::TemplateType type) override;
|
|
||||||
QString apiKey() const override;
|
|
||||||
void prepareNetworkRequest(QNetworkRequest &networkRequest) const override;
|
|
||||||
LLMCore::ProviderID providerID() const override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
bool handleStreamResponse(const QByteArray &data, QString &accumulatedResponse);
|
|
||||||
bool handleRegularResponse(const QByteArray &data, QString &accumulatedResponse);
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace QodeAssist::Providers
|
|
||||||
@@ -33,6 +33,8 @@
|
|||||||
|
|
||||||
namespace QodeAssist::Providers {
|
namespace QodeAssist::Providers {
|
||||||
|
|
||||||
|
LMStudioProvider::LMStudioProvider() {}
|
||||||
|
|
||||||
QString LMStudioProvider::name() const
|
QString LMStudioProvider::name() const
|
||||||
{
|
{
|
||||||
return "LM Studio";
|
return "LM Studio";
|
||||||
@@ -58,6 +60,47 @@ bool LMStudioProvider::supportsModelListing() const
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void LMStudioProvider::prepareRequest(QJsonObject &request, LLMCore::RequestType type)
|
||||||
|
{
|
||||||
|
auto prepareMessages = [](QJsonObject &req) -> QJsonArray {
|
||||||
|
QJsonArray messages;
|
||||||
|
if (req.contains("system")) {
|
||||||
|
messages.append(
|
||||||
|
QJsonObject{{"role", "system"}, {"content", req.take("system").toString()}});
|
||||||
|
}
|
||||||
|
if (req.contains("prompt")) {
|
||||||
|
messages.append(
|
||||||
|
QJsonObject{{"role", "user"}, {"content", req.take("prompt").toString()}});
|
||||||
|
}
|
||||||
|
return messages;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto applyModelParams = [&request](const auto &settings) {
|
||||||
|
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();
|
||||||
|
};
|
||||||
|
|
||||||
|
QJsonArray messages = prepareMessages(request);
|
||||||
|
if (!messages.isEmpty()) {
|
||||||
|
request["messages"] = std::move(messages);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type == LLMCore::RequestType::CodeCompletion) {
|
||||||
|
applyModelParams(Settings::codeCompletionSettings());
|
||||||
|
} else {
|
||||||
|
applyModelParams(Settings::chatAssistantSettings());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool LMStudioProvider::handleResponse(QNetworkReply *reply, QString &accumulatedResponse)
|
bool LMStudioProvider::handleResponse(QNetworkReply *reply, QString &accumulatedResponse)
|
||||||
{
|
{
|
||||||
QByteArray data = reply->readAll();
|
QByteArray data = reply->readAll();
|
||||||
@@ -168,42 +211,4 @@ void LMStudioProvider::prepareNetworkRequest(QNetworkRequest &networkRequest) co
|
|||||||
networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||||
}
|
}
|
||||||
|
|
||||||
LLMCore::ProviderID LMStudioProvider::providerID() const
|
|
||||||
{
|
|
||||||
return LLMCore::ProviderID::LMStudio;
|
|
||||||
}
|
|
||||||
|
|
||||||
void QodeAssist::Providers::LMStudioProvider::prepareRequest(
|
|
||||||
QJsonObject &request,
|
|
||||||
LLMCore::PromptTemplate *prompt,
|
|
||||||
LLMCore::ContextData context,
|
|
||||||
LLMCore::RequestType type)
|
|
||||||
{
|
|
||||||
if (!prompt->isSupportProvider(providerID())) {
|
|
||||||
LOG_MESSAGE(QString("Template %1 doesn't support %2 provider").arg(name(), prompt->name()));
|
|
||||||
}
|
|
||||||
|
|
||||||
prompt->prepareRequest(request, context);
|
|
||||||
|
|
||||||
auto applyModelParams = [&request](const auto &settings) {
|
|
||||||
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();
|
|
||||||
};
|
|
||||||
|
|
||||||
if (type == LLMCore::RequestType::CodeCompletion) {
|
|
||||||
applyModelParams(Settings::codeCompletionSettings());
|
|
||||||
} else {
|
|
||||||
applyModelParams(Settings::chatAssistantSettings());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace QodeAssist::Providers
|
} // namespace QodeAssist::Providers
|
||||||
|
|||||||
@@ -26,22 +26,19 @@ namespace QodeAssist::Providers {
|
|||||||
class LMStudioProvider : public LLMCore::Provider
|
class LMStudioProvider : public LLMCore::Provider
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
LMStudioProvider();
|
||||||
|
|
||||||
QString name() const override;
|
QString name() const override;
|
||||||
QString url() const override;
|
QString url() const override;
|
||||||
QString completionEndpoint() const override;
|
QString completionEndpoint() const override;
|
||||||
QString chatEndpoint() const override;
|
QString chatEndpoint() const override;
|
||||||
bool supportsModelListing() const override;
|
bool supportsModelListing() const override;
|
||||||
void prepareRequest(
|
void prepareRequest(QJsonObject &request, LLMCore::RequestType type) override;
|
||||||
QJsonObject &request,
|
|
||||||
LLMCore::PromptTemplate *prompt,
|
|
||||||
LLMCore::ContextData context,
|
|
||||||
LLMCore::RequestType type) override;
|
|
||||||
bool handleResponse(QNetworkReply *reply, QString &accumulatedResponse) override;
|
bool handleResponse(QNetworkReply *reply, QString &accumulatedResponse) override;
|
||||||
QList<QString> getInstalledModels(const QString &url) override;
|
QList<QString> getInstalledModels(const QString &url) override;
|
||||||
QList<QString> validateRequest(const QJsonObject &request, LLMCore::TemplateType type) override;
|
QList<QString> validateRequest(const QJsonObject &request, LLMCore::TemplateType type) override;
|
||||||
QString apiKey() const override;
|
QString apiKey() const override;
|
||||||
void prepareNetworkRequest(QNetworkRequest &networkRequest) const override;
|
void prepareNetworkRequest(QNetworkRequest &networkRequest) const override;
|
||||||
LLMCore::ProviderID providerID() const override;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace QodeAssist::Providers
|
} // namespace QodeAssist::Providers
|
||||||
|
|||||||
@@ -1,220 +0,0 @@
|
|||||||
#include "MistralAIProvider.hpp"
|
|
||||||
|
|
||||||
#include "settings/ChatAssistantSettings.hpp"
|
|
||||||
#include "settings/CodeCompletionSettings.hpp"
|
|
||||||
#include "settings/ProviderSettings.hpp"
|
|
||||||
|
|
||||||
#include <QJsonArray>
|
|
||||||
#include <QJsonDocument>
|
|
||||||
#include <QJsonObject>
|
|
||||||
#include <QNetworkReply>
|
|
||||||
#include <QtCore/qeventloop.h>
|
|
||||||
|
|
||||||
#include "llmcore/OpenAIMessage.hpp"
|
|
||||||
#include "llmcore/ValidationUtils.hpp"
|
|
||||||
#include "logger/Logger.hpp"
|
|
||||||
|
|
||||||
namespace QodeAssist::Providers {
|
|
||||||
|
|
||||||
QString MistralAIProvider::name() const
|
|
||||||
{
|
|
||||||
return "Mistral AI";
|
|
||||||
}
|
|
||||||
|
|
||||||
QString MistralAIProvider::url() const
|
|
||||||
{
|
|
||||||
return "https://api.mistral.ai";
|
|
||||||
}
|
|
||||||
|
|
||||||
QString MistralAIProvider::completionEndpoint() const
|
|
||||||
{
|
|
||||||
return "/v1/fim/completions";
|
|
||||||
}
|
|
||||||
|
|
||||||
QString MistralAIProvider::chatEndpoint() const
|
|
||||||
{
|
|
||||||
return "/v1/chat/completions";
|
|
||||||
}
|
|
||||||
|
|
||||||
bool MistralAIProvider::supportsModelListing() const
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool MistralAIProvider::handleResponse(QNetworkReply *reply, QString &accumulatedResponse)
|
|
||||||
{
|
|
||||||
QByteArray data = reply->readAll();
|
|
||||||
if (data.isEmpty()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isDone = false;
|
|
||||||
QByteArrayList lines = data.split('\n');
|
|
||||||
|
|
||||||
for (const QByteArray &line : lines) {
|
|
||||||
if (line.trimmed().isEmpty()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (line == "data: [DONE]") {
|
|
||||||
isDone = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
QByteArray jsonData = line;
|
|
||||||
if (line.startsWith("data: ")) {
|
|
||||||
jsonData = line.mid(6);
|
|
||||||
}
|
|
||||||
|
|
||||||
QJsonParseError error;
|
|
||||||
QJsonDocument doc = QJsonDocument::fromJson(jsonData, &error);
|
|
||||||
|
|
||||||
if (doc.isNull()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto message = LLMCore::OpenAIMessage::fromJson(doc.object());
|
|
||||||
if (message.hasError()) {
|
|
||||||
LOG_MESSAGE("Error in OpenAI response: " + message.error);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString content = message.getContent();
|
|
||||||
if (!content.isEmpty()) {
|
|
||||||
accumulatedResponse += content;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (message.isDone()) {
|
|
||||||
isDone = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return isDone;
|
|
||||||
}
|
|
||||||
|
|
||||||
QList<QString> MistralAIProvider::getInstalledModels(const QString &url)
|
|
||||||
{
|
|
||||||
QList<QString> models;
|
|
||||||
QNetworkAccessManager manager;
|
|
||||||
QNetworkRequest request(QString("%1/v1/models").arg(url));
|
|
||||||
|
|
||||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
|
||||||
if (!apiKey().isEmpty()) {
|
|
||||||
request.setRawHeader("Authorization", QString("Bearer %1").arg(apiKey()).toUtf8());
|
|
||||||
}
|
|
||||||
|
|
||||||
QNetworkReply *reply = manager.get(request);
|
|
||||||
QEventLoop loop;
|
|
||||||
QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
|
|
||||||
loop.exec();
|
|
||||||
|
|
||||||
if (reply->error() == QNetworkReply::NoError) {
|
|
||||||
QByteArray responseData = reply->readAll();
|
|
||||||
QJsonDocument jsonResponse = QJsonDocument::fromJson(responseData);
|
|
||||||
QJsonObject jsonObject = jsonResponse.object();
|
|
||||||
|
|
||||||
if (jsonObject.contains("data") && jsonObject["object"].toString() == "list") {
|
|
||||||
QJsonArray modelArray = jsonObject["data"].toArray();
|
|
||||||
for (const QJsonValue &value : modelArray) {
|
|
||||||
QJsonObject modelObject = value.toObject();
|
|
||||||
if (modelObject.contains("id")) {
|
|
||||||
QString modelId = modelObject["id"].toString();
|
|
||||||
models.append(modelId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
LOG_MESSAGE(QString("Error fetching Mistral AI models: %1").arg(reply->errorString()));
|
|
||||||
}
|
|
||||||
|
|
||||||
reply->deleteLater();
|
|
||||||
return models;
|
|
||||||
}
|
|
||||||
|
|
||||||
QList<QString> MistralAIProvider::validateRequest(
|
|
||||||
const QJsonObject &request, LLMCore::TemplateType type)
|
|
||||||
{
|
|
||||||
const auto fimReq = QJsonObject{
|
|
||||||
{"model", {}},
|
|
||||||
{"max_tokens", {}},
|
|
||||||
{"stream", {}},
|
|
||||||
{"temperature", {}},
|
|
||||||
{"prompt", {}},
|
|
||||||
{"suffix", {}}};
|
|
||||||
|
|
||||||
const auto templateReq = QJsonObject{
|
|
||||||
{"model", {}},
|
|
||||||
{"messages", QJsonArray{{QJsonObject{{"role", {}}, {"content", {}}}}}},
|
|
||||||
{"temperature", {}},
|
|
||||||
{"max_tokens", {}},
|
|
||||||
{"top_p", {}},
|
|
||||||
{"frequency_penalty", {}},
|
|
||||||
{"presence_penalty", {}},
|
|
||||||
{"stop", QJsonArray{}},
|
|
||||||
{"stream", {}}};
|
|
||||||
|
|
||||||
return LLMCore::ValidationUtils::validateRequestFields(
|
|
||||||
request, type == LLMCore::TemplateType::FIM ? fimReq : templateReq);
|
|
||||||
}
|
|
||||||
|
|
||||||
QString MistralAIProvider::apiKey() const
|
|
||||||
{
|
|
||||||
return Settings::providerSettings().mistralAiApiKey();
|
|
||||||
}
|
|
||||||
|
|
||||||
void MistralAIProvider::prepareNetworkRequest(QNetworkRequest &networkRequest) const
|
|
||||||
{
|
|
||||||
networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
|
||||||
|
|
||||||
if (!apiKey().isEmpty()) {
|
|
||||||
networkRequest.setRawHeader("Authorization", QString("Bearer %1").arg(apiKey()).toUtf8());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LLMCore::ProviderID MistralAIProvider::providerID() const
|
|
||||||
{
|
|
||||||
return LLMCore::ProviderID::MistralAI;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MistralAIProvider::prepareRequest(
|
|
||||||
QJsonObject &request,
|
|
||||||
LLMCore::PromptTemplate *prompt,
|
|
||||||
LLMCore::ContextData context,
|
|
||||||
LLMCore::RequestType type)
|
|
||||||
{
|
|
||||||
if (!prompt->isSupportProvider(providerID())) {
|
|
||||||
LOG_MESSAGE(QString("Template %1 doesn't support %2 provider").arg(name(), prompt->name()));
|
|
||||||
}
|
|
||||||
|
|
||||||
prompt->prepareRequest(request, context);
|
|
||||||
|
|
||||||
if (type == LLMCore::RequestType::Chat) {
|
|
||||||
auto &settings = Settings::chatAssistantSettings();
|
|
||||||
|
|
||||||
request["max_tokens"] = settings.maxTokens();
|
|
||||||
request["temperature"] = settings.temperature();
|
|
||||||
|
|
||||||
if (settings.useTopP())
|
|
||||||
request["top_p"] = settings.topP();
|
|
||||||
|
|
||||||
// request["random_seed"] = "";
|
|
||||||
|
|
||||||
if (settings.useFrequencyPenalty())
|
|
||||||
request["frequency_penalty"] = settings.frequencyPenalty();
|
|
||||||
if (settings.usePresencePenalty())
|
|
||||||
request["presence_penalty"] = settings.presencePenalty();
|
|
||||||
|
|
||||||
} else {
|
|
||||||
auto &settings = Settings::codeCompletionSettings();
|
|
||||||
|
|
||||||
request["max_tokens"] = settings.maxTokens();
|
|
||||||
request["temperature"] = settings.temperature();
|
|
||||||
|
|
||||||
if (settings.useTopP())
|
|
||||||
request["top_p"] = settings.topP();
|
|
||||||
|
|
||||||
// request["random_seed"] = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace QodeAssist::Providers
|
|
||||||
@@ -1,47 +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 "llmcore/Provider.hpp"
|
|
||||||
|
|
||||||
namespace QodeAssist::Providers {
|
|
||||||
|
|
||||||
class MistralAIProvider : public LLMCore::Provider
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
QString name() const override;
|
|
||||||
QString url() const override;
|
|
||||||
QString completionEndpoint() const override;
|
|
||||||
QString chatEndpoint() const override;
|
|
||||||
bool supportsModelListing() const override;
|
|
||||||
void prepareRequest(
|
|
||||||
QJsonObject &request,
|
|
||||||
LLMCore::PromptTemplate *prompt,
|
|
||||||
LLMCore::ContextData context,
|
|
||||||
LLMCore::RequestType type) override;
|
|
||||||
bool handleResponse(QNetworkReply *reply, QString &accumulatedResponse) override;
|
|
||||||
QList<QString> getInstalledModels(const QString &url) override;
|
|
||||||
QList<QString> validateRequest(const QJsonObject &request, LLMCore::TemplateType type) override;
|
|
||||||
QString apiKey() const override;
|
|
||||||
void prepareNetworkRequest(QNetworkRequest &networkRequest) const override;
|
|
||||||
LLMCore::ProviderID providerID() const override;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace QodeAssist::Providers
|
|
||||||
@@ -33,6 +33,8 @@
|
|||||||
|
|
||||||
namespace QodeAssist::Providers {
|
namespace QodeAssist::Providers {
|
||||||
|
|
||||||
|
OllamaProvider::OllamaProvider() {}
|
||||||
|
|
||||||
QString OllamaProvider::name() const
|
QString OllamaProvider::name() const
|
||||||
{
|
{
|
||||||
return "Ollama";
|
return "Ollama";
|
||||||
@@ -58,18 +60,8 @@ bool OllamaProvider::supportsModelListing() const
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void OllamaProvider::prepareRequest(
|
void OllamaProvider::prepareRequest(QJsonObject &request, LLMCore::RequestType type)
|
||||||
QJsonObject &request,
|
|
||||||
LLMCore::PromptTemplate *prompt,
|
|
||||||
LLMCore::ContextData context,
|
|
||||||
LLMCore::RequestType type)
|
|
||||||
{
|
{
|
||||||
if (!prompt->isSupportProvider(providerID())) {
|
|
||||||
LOG_MESSAGE(QString("Template %1 doesn't support %2 provider").arg(name(), prompt->name()));
|
|
||||||
}
|
|
||||||
|
|
||||||
prompt->prepareRequest(request, context);
|
|
||||||
|
|
||||||
auto applySettings = [&request](const auto &settings) {
|
auto applySettings = [&request](const auto &settings) {
|
||||||
QJsonObject options;
|
QJsonObject options;
|
||||||
options["num_predict"] = settings.maxTokens();
|
options["num_predict"] = settings.maxTokens();
|
||||||
@@ -200,7 +192,7 @@ QList<QString> OllamaProvider::validateRequest(const QJsonObject &request, LLMCo
|
|||||||
{"presence_penalty", {}}}}};
|
{"presence_penalty", {}}}}};
|
||||||
|
|
||||||
return LLMCore::ValidationUtils::validateRequestFields(
|
return LLMCore::ValidationUtils::validateRequestFields(
|
||||||
request, type == LLMCore::TemplateType::FIM ? fimReq : messageReq);
|
request, type == LLMCore::TemplateType::Fim ? fimReq : messageReq);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString OllamaProvider::apiKey() const
|
QString OllamaProvider::apiKey() const
|
||||||
@@ -211,11 +203,6 @@ QString OllamaProvider::apiKey() const
|
|||||||
void OllamaProvider::prepareNetworkRequest(QNetworkRequest &networkRequest) const
|
void OllamaProvider::prepareNetworkRequest(QNetworkRequest &networkRequest) const
|
||||||
{
|
{
|
||||||
networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||||
}
|
};
|
||||||
|
|
||||||
LLMCore::ProviderID OllamaProvider::providerID() const
|
|
||||||
{
|
|
||||||
return LLMCore::ProviderID::Ollama;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace QodeAssist::Providers
|
} // namespace QodeAssist::Providers
|
||||||
|
|||||||
@@ -26,22 +26,19 @@ namespace QodeAssist::Providers {
|
|||||||
class OllamaProvider : public LLMCore::Provider
|
class OllamaProvider : public LLMCore::Provider
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
OllamaProvider();
|
||||||
|
|
||||||
QString name() const override;
|
QString name() const override;
|
||||||
QString url() const override;
|
QString url() const override;
|
||||||
QString completionEndpoint() const override;
|
QString completionEndpoint() const override;
|
||||||
QString chatEndpoint() const override;
|
QString chatEndpoint() const override;
|
||||||
bool supportsModelListing() const override;
|
bool supportsModelListing() const override;
|
||||||
void prepareRequest(
|
void prepareRequest(QJsonObject &request, LLMCore::RequestType type) override;
|
||||||
QJsonObject &request,
|
|
||||||
LLMCore::PromptTemplate *prompt,
|
|
||||||
LLMCore::ContextData context,
|
|
||||||
LLMCore::RequestType type) override;
|
|
||||||
bool handleResponse(QNetworkReply *reply, QString &accumulatedResponse) override;
|
bool handleResponse(QNetworkReply *reply, QString &accumulatedResponse) override;
|
||||||
QList<QString> getInstalledModels(const QString &url) override;
|
QList<QString> getInstalledModels(const QString &url) override;
|
||||||
QList<QString> validateRequest(const QJsonObject &request, LLMCore::TemplateType type) override;
|
QList<QString> validateRequest(const QJsonObject &request, LLMCore::TemplateType type) override;
|
||||||
QString apiKey() const override;
|
QString apiKey() const override;
|
||||||
void prepareNetworkRequest(QNetworkRequest &networkRequest) const override;
|
void prepareNetworkRequest(QNetworkRequest &networkRequest) const override;
|
||||||
LLMCore::ProviderID providerID() const override;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace QodeAssist::Providers
|
} // namespace QodeAssist::Providers
|
||||||
|
|||||||
@@ -34,6 +34,8 @@
|
|||||||
|
|
||||||
namespace QodeAssist::Providers {
|
namespace QodeAssist::Providers {
|
||||||
|
|
||||||
|
OpenAICompatProvider::OpenAICompatProvider() {}
|
||||||
|
|
||||||
QString OpenAICompatProvider::name() const
|
QString OpenAICompatProvider::name() const
|
||||||
{
|
{
|
||||||
return "OpenAI Compatible";
|
return "OpenAI Compatible";
|
||||||
@@ -59,17 +61,20 @@ bool OpenAICompatProvider::supportsModelListing() const
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void OpenAICompatProvider::prepareRequest(
|
void OpenAICompatProvider::prepareRequest(QJsonObject &request, LLMCore::RequestType type)
|
||||||
QJsonObject &request,
|
|
||||||
LLMCore::PromptTemplate *prompt,
|
|
||||||
LLMCore::ContextData context,
|
|
||||||
LLMCore::RequestType type)
|
|
||||||
{
|
{
|
||||||
if (!prompt->isSupportProvider(providerID())) {
|
auto prepareMessages = [](QJsonObject &req) -> QJsonArray {
|
||||||
LOG_MESSAGE(QString("Template %1 doesn't support %2 provider").arg(name(), prompt->name()));
|
QJsonArray messages;
|
||||||
|
if (req.contains("system")) {
|
||||||
|
messages.append(
|
||||||
|
QJsonObject{{"role", "system"}, {"content", req.take("system").toString()}});
|
||||||
}
|
}
|
||||||
|
if (req.contains("prompt")) {
|
||||||
prompt->prepareRequest(request, context);
|
messages.append(
|
||||||
|
QJsonObject{{"role", "user"}, {"content", req.take("prompt").toString()}});
|
||||||
|
}
|
||||||
|
return messages;
|
||||||
|
};
|
||||||
|
|
||||||
auto applyModelParams = [&request](const auto &settings) {
|
auto applyModelParams = [&request](const auto &settings) {
|
||||||
request["max_tokens"] = settings.maxTokens();
|
request["max_tokens"] = settings.maxTokens();
|
||||||
@@ -85,6 +90,11 @@ void OpenAICompatProvider::prepareRequest(
|
|||||||
request["presence_penalty"] = settings.presencePenalty();
|
request["presence_penalty"] = settings.presencePenalty();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
QJsonArray messages = prepareMessages(request);
|
||||||
|
if (!messages.isEmpty()) {
|
||||||
|
request["messages"] = std::move(messages);
|
||||||
|
}
|
||||||
|
|
||||||
if (type == LLMCore::RequestType::CodeCompletion) {
|
if (type == LLMCore::RequestType::CodeCompletion) {
|
||||||
applyModelParams(Settings::codeCompletionSettings());
|
applyModelParams(Settings::codeCompletionSettings());
|
||||||
} else {
|
} else {
|
||||||
@@ -180,9 +190,4 @@ void OpenAICompatProvider::prepareNetworkRequest(QNetworkRequest &networkRequest
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LLMCore::ProviderID OpenAICompatProvider::providerID() const
|
|
||||||
{
|
|
||||||
return LLMCore::ProviderID::OpenAICompatible;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace QodeAssist::Providers
|
} // namespace QodeAssist::Providers
|
||||||
|
|||||||
@@ -26,22 +26,19 @@ namespace QodeAssist::Providers {
|
|||||||
class OpenAICompatProvider : public LLMCore::Provider
|
class OpenAICompatProvider : public LLMCore::Provider
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
OpenAICompatProvider();
|
||||||
|
|
||||||
QString name() const override;
|
QString name() const override;
|
||||||
QString url() const override;
|
QString url() const override;
|
||||||
QString completionEndpoint() const override;
|
QString completionEndpoint() const override;
|
||||||
QString chatEndpoint() const override;
|
QString chatEndpoint() const override;
|
||||||
bool supportsModelListing() const override;
|
bool supportsModelListing() const override;
|
||||||
void prepareRequest(
|
void prepareRequest(QJsonObject &request, LLMCore::RequestType type) override;
|
||||||
QJsonObject &request,
|
|
||||||
LLMCore::PromptTemplate *prompt,
|
|
||||||
LLMCore::ContextData context,
|
|
||||||
LLMCore::RequestType type) override;
|
|
||||||
bool handleResponse(QNetworkReply *reply, QString &accumulatedResponse) override;
|
bool handleResponse(QNetworkReply *reply, QString &accumulatedResponse) override;
|
||||||
QList<QString> getInstalledModels(const QString &url) override;
|
QList<QString> getInstalledModels(const QString &url) override;
|
||||||
QList<QString> validateRequest(const QJsonObject &request, LLMCore::TemplateType type) override;
|
QList<QString> validateRequest(const QJsonObject &request, LLMCore::TemplateType type) override;
|
||||||
QString apiKey() const override;
|
QString apiKey() const override;
|
||||||
void prepareNetworkRequest(QNetworkRequest &networkRequest) const override;
|
void prepareNetworkRequest(QNetworkRequest &networkRequest) const override;
|
||||||
LLMCore::ProviderID providerID() const override;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace QodeAssist::Providers
|
} // namespace QodeAssist::Providers
|
||||||
|
|||||||
@@ -35,6 +35,8 @@
|
|||||||
|
|
||||||
namespace QodeAssist::Providers {
|
namespace QodeAssist::Providers {
|
||||||
|
|
||||||
|
OpenAIProvider::OpenAIProvider() {}
|
||||||
|
|
||||||
QString OpenAIProvider::name() const
|
QString OpenAIProvider::name() const
|
||||||
{
|
{
|
||||||
return "OpenAI";
|
return "OpenAI";
|
||||||
@@ -60,17 +62,20 @@ bool OpenAIProvider::supportsModelListing() const
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void OpenAIProvider::prepareRequest(
|
void OpenAIProvider::prepareRequest(QJsonObject &request, LLMCore::RequestType type)
|
||||||
QJsonObject &request,
|
|
||||||
LLMCore::PromptTemplate *prompt,
|
|
||||||
LLMCore::ContextData context,
|
|
||||||
LLMCore::RequestType type)
|
|
||||||
{
|
{
|
||||||
if (!prompt->isSupportProvider(providerID())) {
|
auto prepareMessages = [](QJsonObject &req) -> QJsonArray {
|
||||||
LOG_MESSAGE(QString("Template %1 doesn't support %2 provider").arg(name(), prompt->name()));
|
QJsonArray messages;
|
||||||
|
if (req.contains("system")) {
|
||||||
|
messages.append(
|
||||||
|
QJsonObject{{"role", "system"}, {"content", req.take("system").toString()}});
|
||||||
}
|
}
|
||||||
|
if (req.contains("prompt")) {
|
||||||
prompt->prepareRequest(request, context);
|
messages.append(
|
||||||
|
QJsonObject{{"role", "user"}, {"content", req.take("prompt").toString()}});
|
||||||
|
}
|
||||||
|
return messages;
|
||||||
|
};
|
||||||
|
|
||||||
auto applyModelParams = [&request](const auto &settings) {
|
auto applyModelParams = [&request](const auto &settings) {
|
||||||
request["max_tokens"] = settings.maxTokens();
|
request["max_tokens"] = settings.maxTokens();
|
||||||
@@ -86,6 +91,11 @@ void OpenAIProvider::prepareRequest(
|
|||||||
request["presence_penalty"] = settings.presencePenalty();
|
request["presence_penalty"] = settings.presencePenalty();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
QJsonArray messages = prepareMessages(request);
|
||||||
|
if (!messages.isEmpty()) {
|
||||||
|
request["messages"] = std::move(messages);
|
||||||
|
}
|
||||||
|
|
||||||
if (type == LLMCore::RequestType::CodeCompletion) {
|
if (type == LLMCore::RequestType::CodeCompletion) {
|
||||||
applyModelParams(Settings::codeCompletionSettings());
|
applyModelParams(Settings::codeCompletionSettings());
|
||||||
} else {
|
} else {
|
||||||
@@ -216,9 +226,4 @@ void OpenAIProvider::prepareNetworkRequest(QNetworkRequest &networkRequest) cons
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LLMCore::ProviderID OpenAIProvider::providerID() const
|
|
||||||
{
|
|
||||||
return LLMCore::ProviderID::OpenAI;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace QodeAssist::Providers
|
} // namespace QodeAssist::Providers
|
||||||
|
|||||||
@@ -26,22 +26,19 @@ namespace QodeAssist::Providers {
|
|||||||
class OpenAIProvider : public LLMCore::Provider
|
class OpenAIProvider : public LLMCore::Provider
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
OpenAIProvider();
|
||||||
|
|
||||||
QString name() const override;
|
QString name() const override;
|
||||||
QString url() const override;
|
QString url() const override;
|
||||||
QString completionEndpoint() const override;
|
QString completionEndpoint() const override;
|
||||||
QString chatEndpoint() const override;
|
QString chatEndpoint() const override;
|
||||||
bool supportsModelListing() const override;
|
bool supportsModelListing() const override;
|
||||||
void prepareRequest(
|
void prepareRequest(QJsonObject &request, LLMCore::RequestType type) override;
|
||||||
QJsonObject &request,
|
|
||||||
LLMCore::PromptTemplate *prompt,
|
|
||||||
LLMCore::ContextData context,
|
|
||||||
LLMCore::RequestType type) override;
|
|
||||||
bool handleResponse(QNetworkReply *reply, QString &accumulatedResponse) override;
|
bool handleResponse(QNetworkReply *reply, QString &accumulatedResponse) override;
|
||||||
QList<QString> getInstalledModels(const QString &url) override;
|
QList<QString> getInstalledModels(const QString &url) override;
|
||||||
QList<QString> validateRequest(const QJsonObject &request, LLMCore::TemplateType type) override;
|
QList<QString> validateRequest(const QJsonObject &request, LLMCore::TemplateType type) override;
|
||||||
QString apiKey() const override;
|
QString apiKey() const override;
|
||||||
void prepareNetworkRequest(QNetworkRequest &networkRequest) const override;
|
void prepareNetworkRequest(QNetworkRequest &networkRequest) const override;
|
||||||
LLMCore::ProviderID providerID() const override;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace QodeAssist::Providers
|
} // namespace QodeAssist::Providers
|
||||||
|
|||||||
@@ -19,6 +19,8 @@
|
|||||||
|
|
||||||
#include "OpenRouterAIProvider.hpp"
|
#include "OpenRouterAIProvider.hpp"
|
||||||
|
|
||||||
|
#include "settings/ChatAssistantSettings.hpp"
|
||||||
|
#include "settings/CodeCompletionSettings.hpp"
|
||||||
#include "settings/ProviderSettings.hpp"
|
#include "settings/ProviderSettings.hpp"
|
||||||
|
|
||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
@@ -31,6 +33,8 @@
|
|||||||
|
|
||||||
namespace QodeAssist::Providers {
|
namespace QodeAssist::Providers {
|
||||||
|
|
||||||
|
OpenRouterProvider::OpenRouterProvider() {}
|
||||||
|
|
||||||
QString OpenRouterProvider::name() const
|
QString OpenRouterProvider::name() const
|
||||||
{
|
{
|
||||||
return "OpenRouter";
|
return "OpenRouter";
|
||||||
@@ -41,6 +45,47 @@ QString OpenRouterProvider::url() const
|
|||||||
return "https://openrouter.ai/api";
|
return "https://openrouter.ai/api";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void OpenRouterProvider::prepareRequest(QJsonObject &request, LLMCore::RequestType type)
|
||||||
|
{
|
||||||
|
auto prepareMessages = [](QJsonObject &req) -> QJsonArray {
|
||||||
|
QJsonArray messages;
|
||||||
|
if (req.contains("system")) {
|
||||||
|
messages.append(
|
||||||
|
QJsonObject{{"role", "system"}, {"content", req.take("system").toString()}});
|
||||||
|
}
|
||||||
|
if (req.contains("prompt")) {
|
||||||
|
messages.append(
|
||||||
|
QJsonObject{{"role", "user"}, {"content", req.take("prompt").toString()}});
|
||||||
|
}
|
||||||
|
return messages;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto applyModelParams = [&request](const auto &settings) {
|
||||||
|
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();
|
||||||
|
};
|
||||||
|
|
||||||
|
QJsonArray messages = prepareMessages(request);
|
||||||
|
if (!messages.isEmpty()) {
|
||||||
|
request["messages"] = std::move(messages);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type == LLMCore::RequestType::CodeCompletion) {
|
||||||
|
applyModelParams(Settings::codeCompletionSettings());
|
||||||
|
} else {
|
||||||
|
applyModelParams(Settings::chatAssistantSettings());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool OpenRouterProvider::handleResponse(QNetworkReply *reply, QString &accumulatedResponse)
|
bool OpenRouterProvider::handleResponse(QNetworkReply *reply, QString &accumulatedResponse)
|
||||||
{
|
{
|
||||||
QByteArray data = reply->readAll();
|
QByteArray data = reply->readAll();
|
||||||
@@ -97,9 +142,4 @@ QString OpenRouterProvider::apiKey() const
|
|||||||
return Settings::providerSettings().openRouterApiKey();
|
return Settings::providerSettings().openRouterApiKey();
|
||||||
}
|
}
|
||||||
|
|
||||||
LLMCore::ProviderID OpenRouterProvider::providerID() const
|
|
||||||
{
|
|
||||||
return LLMCore::ProviderID::OpenRouter;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace QodeAssist::Providers
|
} // namespace QodeAssist::Providers
|
||||||
|
|||||||
@@ -27,11 +27,13 @@ namespace QodeAssist::Providers {
|
|||||||
class OpenRouterProvider : public OpenAICompatProvider
|
class OpenRouterProvider : public OpenAICompatProvider
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
OpenRouterProvider();
|
||||||
|
|
||||||
QString name() const override;
|
QString name() const override;
|
||||||
QString url() const override;
|
QString url() const override;
|
||||||
|
void prepareRequest(QJsonObject &request, LLMCore::RequestType type) override;
|
||||||
bool handleResponse(QNetworkReply *reply, QString &accumulatedResponse) override;
|
bool handleResponse(QNetworkReply *reply, QString &accumulatedResponse) override;
|
||||||
QString apiKey() const override;
|
QString apiKey() const override;
|
||||||
LLMCore::ProviderID providerID() const override;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace QodeAssist::Providers
|
} // namespace QodeAssist::Providers
|
||||||
|
|||||||
@@ -21,9 +21,7 @@
|
|||||||
|
|
||||||
#include "llmcore/ProvidersManager.hpp"
|
#include "llmcore/ProvidersManager.hpp"
|
||||||
#include "providers/ClaudeProvider.hpp"
|
#include "providers/ClaudeProvider.hpp"
|
||||||
#include "providers/GoogleAIProvider.hpp"
|
|
||||||
#include "providers/LMStudioProvider.hpp"
|
#include "providers/LMStudioProvider.hpp"
|
||||||
#include "providers/MistralAIProvider.hpp"
|
|
||||||
#include "providers/OllamaProvider.hpp"
|
#include "providers/OllamaProvider.hpp"
|
||||||
#include "providers/OpenAICompatProvider.hpp"
|
#include "providers/OpenAICompatProvider.hpp"
|
||||||
#include "providers/OpenAIProvider.hpp"
|
#include "providers/OpenAIProvider.hpp"
|
||||||
@@ -35,13 +33,11 @@ inline void registerProviders()
|
|||||||
{
|
{
|
||||||
auto &providerManager = LLMCore::ProvidersManager::instance();
|
auto &providerManager = LLMCore::ProvidersManager::instance();
|
||||||
providerManager.registerProvider<OllamaProvider>();
|
providerManager.registerProvider<OllamaProvider>();
|
||||||
|
providerManager.registerProvider<LMStudioProvider>();
|
||||||
|
providerManager.registerProvider<OpenAICompatProvider>();
|
||||||
|
providerManager.registerProvider<OpenRouterProvider>();
|
||||||
providerManager.registerProvider<ClaudeProvider>();
|
providerManager.registerProvider<ClaudeProvider>();
|
||||||
providerManager.registerProvider<OpenAIProvider>();
|
providerManager.registerProvider<OpenAIProvider>();
|
||||||
providerManager.registerProvider<OpenAICompatProvider>();
|
|
||||||
providerManager.registerProvider<LMStudioProvider>();
|
|
||||||
providerManager.registerProvider<OpenRouterProvider>();
|
|
||||||
providerManager.registerProvider<MistralAIProvider>();
|
|
||||||
providerManager.registerProvider<GoogleAIProvider>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace QodeAssist::Providers
|
} // namespace QodeAssist::Providers
|
||||||
|
|||||||
@@ -108,10 +108,8 @@ public:
|
|||||||
UpdateDialog::checkForUpdatesAndShow(Core::ICore::mainWindow());
|
UpdateDialog::checkForUpdatesAndShow(Core::ICore::mainWindow());
|
||||||
});
|
});
|
||||||
|
|
||||||
if (Settings::generalSettings().enableChat()) {
|
|
||||||
m_chatOutputPane = new Chat::ChatOutputPane(this);
|
m_chatOutputPane = new Chat::ChatOutputPane(this);
|
||||||
m_navigationPanel = new Chat::NavigationPanel();
|
m_navigationPanel = new Chat::NavigationPanel();
|
||||||
}
|
|
||||||
|
|
||||||
Settings::setupProjectPanel();
|
Settings::setupProjectPanel();
|
||||||
ConfigurationManager::instance().init();
|
ConfigurationManager::instance().init();
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ CodeCompletionSettings::CodeCompletionSettings()
|
|||||||
maxTokens.setSettingsKey(Constants::CC_MAX_TOKENS);
|
maxTokens.setSettingsKey(Constants::CC_MAX_TOKENS);
|
||||||
maxTokens.setLabelText(Tr::tr("Max Tokens:"));
|
maxTokens.setLabelText(Tr::tr("Max Tokens:"));
|
||||||
maxTokens.setRange(-1, 900000);
|
maxTokens.setRange(-1, 900000);
|
||||||
maxTokens.setDefaultValue(100);
|
maxTokens.setDefaultValue(50);
|
||||||
|
|
||||||
// Advanced Parameters
|
// Advanced Parameters
|
||||||
useTopP.setSettingsKey(Constants::CC_USE_TOP_P);
|
useTopP.setSettingsKey(Constants::CC_USE_TOP_P);
|
||||||
@@ -151,47 +151,31 @@ CodeCompletionSettings::CodeCompletionSettings()
|
|||||||
|
|
||||||
systemPrompt.setSettingsKey(Constants::CC_SYSTEM_PROMPT);
|
systemPrompt.setSettingsKey(Constants::CC_SYSTEM_PROMPT);
|
||||||
systemPrompt.setDisplayStyle(Utils::StringAspect::TextEditDisplay);
|
systemPrompt.setDisplayStyle(Utils::StringAspect::TextEditDisplay);
|
||||||
systemPromptForNonFimModels.setLabelText(Tr::tr("System prompt for FIM models:"));
|
|
||||||
systemPrompt.setDefaultValue(
|
systemPrompt.setDefaultValue(
|
||||||
"You are an expert C++, Qt, and QML code completion assistant. Your task is to provide "
|
"You are an expert in C++, Qt, and QML programming. Your task is to provide code "
|
||||||
"precise and contextually appropriate code completions.\n\n");
|
"suggestions that seamlessly integrate with existing code. Do not repeat code from position "
|
||||||
|
"before or after <cursor>. You will receive a code context with specified insertion points. "
|
||||||
|
"Your goal is to complete only one code block."
|
||||||
|
"Here is the code context with insertion points:<code_context>Before: {{variable}}After: "
|
||||||
|
"{{variable}}</code_context> Instructions: 1. Carefully analyze the provided code context. "
|
||||||
|
"2. Consider the existing code and the specified insertion points.3. Generate a code "
|
||||||
|
"suggestion that completes one logic expression between the 'Before' and 'After' points. "
|
||||||
|
"4. Ensure your suggestion does not repeat any existing code. 5. Format your suggestion as "
|
||||||
|
"a code block using triple backticks. 6. Do not include any comments or descriptions with "
|
||||||
|
"your code suggestion.");
|
||||||
|
|
||||||
useUserMessageTemplateForCC.setSettingsKey(Constants::CC_USE_USER_TEMPLATE);
|
useUserMessageTemplateForCC.setSettingsKey(Constants::CC_USE_USER_TEMPLATE);
|
||||||
useUserMessageTemplateForCC.setDefaultValue(true);
|
useUserMessageTemplateForCC.setDefaultValue(true);
|
||||||
useUserMessageTemplateForCC.setLabelText(
|
useUserMessageTemplateForCC.setLabelText(Tr::tr("Use User Template for code completion message for non-FIM models"));
|
||||||
Tr::tr("Use special system prompt and user message for non FIM models"));
|
|
||||||
|
|
||||||
systemPromptForNonFimModels.setSettingsKey(Constants::CC_SYSTEM_PROMPT_FOR_NON_FIM);
|
|
||||||
systemPromptForNonFimModels.setDisplayStyle(Utils::StringAspect::TextEditDisplay);
|
|
||||||
systemPromptForNonFimModels.setLabelText(Tr::tr("System prompt for non FIM models:"));
|
|
||||||
systemPromptForNonFimModels.setDefaultValue(
|
|
||||||
"You are an expert C++, Qt, and QML code completion assistant. Your task is to provide "
|
|
||||||
"precise and contextually appropriate code completions.\n\n"
|
|
||||||
"Core Requirements:\n"
|
|
||||||
"1. Continue code exactly from the cursor position, ensuring it properly connects with any "
|
|
||||||
"existing code after the cursor\n"
|
|
||||||
"2. Never repeat existing code before or after the cursor\n"
|
|
||||||
"Specific Guidelines:\n"
|
|
||||||
"- For function calls: Complete parameters with appropriate types and names\n"
|
|
||||||
"- For class members: Respect access modifiers and class conventions\n"
|
|
||||||
"- Respect existing indentation and formatting\n"
|
|
||||||
"- Consider scope and visibility of referenced symbols\n"
|
|
||||||
"- Ensure seamless integration with code both before and after the cursor\n\n"
|
|
||||||
"Context Format:\n"
|
|
||||||
"<code_context>\n"
|
|
||||||
"{{code before cursor}}<cursor>{{code after cursor}}\n"
|
|
||||||
"</code_context>\n\n"
|
|
||||||
"Response Format:\n"
|
|
||||||
"- No explanations or comments\n"
|
|
||||||
"- Only include new characters needed to create valid code\n"
|
|
||||||
"- Should be codeblock with language\n");
|
|
||||||
|
|
||||||
userMessageTemplateForCC.setSettingsKey(Constants::CC_USER_TEMPLATE);
|
userMessageTemplateForCC.setSettingsKey(Constants::CC_USER_TEMPLATE);
|
||||||
userMessageTemplateForCC.setDisplayStyle(Utils::StringAspect::TextEditDisplay);
|
userMessageTemplateForCC.setDisplayStyle(Utils::StringAspect::TextEditDisplay);
|
||||||
userMessageTemplateForCC.setLabelText(Tr::tr("User message for non FIM models:"));
|
userMessageTemplateForCC.setDefaultValue("Here is the code context with insertion points: <code_context>"
|
||||||
userMessageTemplateForCC.setDefaultValue(
|
"\nBefore: %1After: %2\n </code_context>");
|
||||||
"Here is the code context with insertion points:\n"
|
|
||||||
"<code_context>\n${prefix}<cursor>${suffix}\n</code_context>\n\n");
|
useFilePathInContext.setSettingsKey(Constants::CC_USE_FILE_PATH_IN_CONTEXT);
|
||||||
|
useFilePathInContext.setDefaultValue(true);
|
||||||
|
useFilePathInContext.setLabelText(Tr::tr("Use File Path in Context"));
|
||||||
|
|
||||||
useProjectChangesCache.setSettingsKey(Constants::CC_USE_PROJECT_CHANGES_CACHE);
|
useProjectChangesCache.setSettingsKey(Constants::CC_USE_PROJECT_CHANGES_CACHE);
|
||||||
useProjectChangesCache.setDefaultValue(true);
|
useProjectChangesCache.setDefaultValue(true);
|
||||||
@@ -252,14 +236,10 @@ CodeCompletionSettings::CodeCompletionSettings()
|
|||||||
|
|
||||||
auto contextItem = Column{Row{contextGrid, Stretch{1}},
|
auto contextItem = Column{Row{contextGrid, Stretch{1}},
|
||||||
Row{useSystemPrompt, Stretch{1}},
|
Row{useSystemPrompt, Stretch{1}},
|
||||||
Group{title(Tr::tr("Prompts for FIM models")),
|
systemPrompt,
|
||||||
Column{systemPrompt}},
|
|
||||||
Group{title(Tr::tr("Prompts for Non FIM models")),
|
|
||||||
Column{
|
|
||||||
Row{useUserMessageTemplateForCC, Stretch{1}},
|
Row{useUserMessageTemplateForCC, Stretch{1}},
|
||||||
systemPromptForNonFimModels,
|
|
||||||
userMessageTemplateForCC,
|
userMessageTemplateForCC,
|
||||||
}},
|
Row{useFilePathInContext, Stretch{1}},
|
||||||
Row{useProjectChangesCache, maxChangesCacheSize, Stretch{1}}};
|
Row{useProjectChangesCache, maxChangesCacheSize, Stretch{1}}};
|
||||||
|
|
||||||
return Column{
|
return Column{
|
||||||
@@ -347,24 +327,14 @@ void CodeCompletionSettings::resetSettingsToDefaults()
|
|||||||
resetAspect(readStringsAfterCursor);
|
resetAspect(readStringsAfterCursor);
|
||||||
resetAspect(useSystemPrompt);
|
resetAspect(useSystemPrompt);
|
||||||
resetAspect(systemPrompt);
|
resetAspect(systemPrompt);
|
||||||
|
resetAspect(useFilePathInContext);
|
||||||
resetAspect(useProjectChangesCache);
|
resetAspect(useProjectChangesCache);
|
||||||
resetAspect(maxChangesCacheSize);
|
resetAspect(maxChangesCacheSize);
|
||||||
resetAspect(ollamaLivetime);
|
resetAspect(ollamaLivetime);
|
||||||
resetAspect(contextWindow);
|
resetAspect(contextWindow);
|
||||||
resetAspect(useUserMessageTemplateForCC);
|
|
||||||
resetAspect(userMessageTemplateForCC);
|
|
||||||
resetAspect(systemPromptForNonFimModels);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QString CodeCompletionSettings::processMessageToFIM(const QString &prefix, const QString &suffix)
|
|
||||||
{
|
|
||||||
QString result = userMessageTemplateForCC();
|
|
||||||
result.replace("${prefix}", prefix);
|
|
||||||
result.replace("${suffix}", suffix);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
class CodeCompletionSettingsPage : public Core::IOptionsPage
|
class CodeCompletionSettingsPage : public Core::IOptionsPage
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|||||||
@@ -67,8 +67,8 @@ public:
|
|||||||
Utils::BoolAspect useSystemPrompt{this};
|
Utils::BoolAspect useSystemPrompt{this};
|
||||||
Utils::StringAspect systemPrompt{this};
|
Utils::StringAspect systemPrompt{this};
|
||||||
Utils::BoolAspect useUserMessageTemplateForCC{this};
|
Utils::BoolAspect useUserMessageTemplateForCC{this};
|
||||||
Utils::StringAspect systemPromptForNonFimModels{this};
|
|
||||||
Utils::StringAspect userMessageTemplateForCC{this};
|
Utils::StringAspect userMessageTemplateForCC{this};
|
||||||
|
Utils::BoolAspect useFilePathInContext{this};
|
||||||
Utils::BoolAspect useProjectChangesCache{this};
|
Utils::BoolAspect useProjectChangesCache{this};
|
||||||
Utils::IntegerAspect maxChangesCacheSize{this};
|
Utils::IntegerAspect maxChangesCacheSize{this};
|
||||||
|
|
||||||
@@ -79,8 +79,6 @@ public:
|
|||||||
// API Configuration Settings
|
// API Configuration Settings
|
||||||
Utils::StringAspect apiKey{this};
|
Utils::StringAspect apiKey{this};
|
||||||
|
|
||||||
QString processMessageToFIM(const QString &prefix, const QString &suffix);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void setupConnections();
|
void setupConnections();
|
||||||
void resetSettingsToDefaults();
|
void resetSettingsToDefaults();
|
||||||
|
|||||||
@@ -21,7 +21,6 @@
|
|||||||
|
|
||||||
#include <coreplugin/dialogs/ioptionspage.h>
|
#include <coreplugin/dialogs/ioptionspage.h>
|
||||||
#include <coreplugin/icore.h>
|
#include <coreplugin/icore.h>
|
||||||
#include <utils/detailswidget.h>
|
|
||||||
#include <utils/layoutbuilder.h>
|
#include <utils/layoutbuilder.h>
|
||||||
#include <utils/utilsicons.h>
|
#include <utils/utilsicons.h>
|
||||||
#include <QInputDialog>
|
#include <QInputDialog>
|
||||||
@@ -66,10 +65,6 @@ GeneralSettings::GeneralSettings()
|
|||||||
enableCheckUpdate.setLabelText(TrConstants::ENABLE_CHECK_UPDATE_ON_START);
|
enableCheckUpdate.setLabelText(TrConstants::ENABLE_CHECK_UPDATE_ON_START);
|
||||||
enableCheckUpdate.setDefaultValue(true);
|
enableCheckUpdate.setDefaultValue(true);
|
||||||
|
|
||||||
enableChat.setSettingsKey(Constants::ENABLE_CHAT);
|
|
||||||
enableChat.setLabelText(TrConstants::ENABLE_CHAT);
|
|
||||||
enableChat.setDefaultValue(true);
|
|
||||||
|
|
||||||
resetToDefaults.m_buttonText = TrConstants::RESET_TO_DEFAULTS;
|
resetToDefaults.m_buttonText = TrConstants::RESET_TO_DEFAULTS;
|
||||||
checkUpdate.m_buttonText = TrConstants::CHECK_UPDATE;
|
checkUpdate.m_buttonText = TrConstants::CHECK_UPDATE;
|
||||||
|
|
||||||
@@ -94,11 +89,6 @@ GeneralSettings::GeneralSettings()
|
|||||||
ccStatus.setDefaultValue("");
|
ccStatus.setDefaultValue("");
|
||||||
ccTest.m_buttonText = TrConstants::TEST;
|
ccTest.m_buttonText = TrConstants::TEST;
|
||||||
|
|
||||||
ccTemplateDescription.setDisplayStyle(Utils::StringAspect::TextEditDisplay);
|
|
||||||
ccTemplateDescription.setReadOnly(true);
|
|
||||||
ccTemplateDescription.setDefaultValue("");
|
|
||||||
ccTemplateDescription.setLabelText(TrConstants::CURRENT_TEMPLATE_DESCRIPTION);
|
|
||||||
|
|
||||||
// preset1
|
// preset1
|
||||||
specifyPreset1.setSettingsKey(Constants::CC_SPECIFY_PRESET1);
|
specifyPreset1.setSettingsKey(Constants::CC_SPECIFY_PRESET1);
|
||||||
specifyPreset1.setLabelText(TrConstants::ADD_NEW_PRESET);
|
specifyPreset1.setLabelText(TrConstants::ADD_NEW_PRESET);
|
||||||
@@ -154,11 +144,6 @@ GeneralSettings::GeneralSettings()
|
|||||||
caStatus.setDefaultValue("");
|
caStatus.setDefaultValue("");
|
||||||
caTest.m_buttonText = TrConstants::TEST;
|
caTest.m_buttonText = TrConstants::TEST;
|
||||||
|
|
||||||
caTemplateDescription.setDisplayStyle(Utils::StringAspect::TextEditDisplay);
|
|
||||||
caTemplateDescription.setReadOnly(true);
|
|
||||||
caTemplateDescription.setDefaultValue("");
|
|
||||||
caTemplateDescription.setLabelText(TrConstants::CURRENT_TEMPLATE_DESCRIPTION);
|
|
||||||
|
|
||||||
readSettings();
|
readSettings();
|
||||||
|
|
||||||
Logger::instance().setLoggingEnabled(enableLogging());
|
Logger::instance().setLoggingEnabled(enableLogging());
|
||||||
@@ -190,19 +175,13 @@ GeneralSettings::GeneralSettings()
|
|||||||
|
|
||||||
auto ccGroup = Group{
|
auto ccGroup = Group{
|
||||||
title(TrConstants::CODE_COMPLETION),
|
title(TrConstants::CODE_COMPLETION),
|
||||||
Column{
|
Column{ccGrid, Row{specifyPreset1, preset1Language, Stretch{1}}, ccPreset1Grid}};
|
||||||
ccGrid,
|
auto caGroup = Group{title(TrConstants::CHAT_ASSISTANT), caGrid};
|
||||||
ccTemplateDescription,
|
|
||||||
Row{specifyPreset1, preset1Language, Stretch{1}},
|
|
||||||
ccPreset1Grid}};
|
|
||||||
auto caGroup
|
|
||||||
= Group{title(TrConstants::CHAT_ASSISTANT), Column{caGrid, caTemplateDescription}};
|
|
||||||
|
|
||||||
auto rootLayout = Column{
|
auto rootLayout = Column{
|
||||||
Row{enableQodeAssist, Stretch{1}, Row{checkUpdate, resetToDefaults}},
|
Row{enableQodeAssist, Stretch{1}, Row{checkUpdate, resetToDefaults}},
|
||||||
Row{enableLogging, Stretch{1}},
|
Row{enableLogging, Stretch{1}},
|
||||||
Row{enableCheckUpdate, Stretch{1}},
|
Row{enableCheckUpdate, Stretch{1}},
|
||||||
Row{enableChat, Stretch{1}},
|
|
||||||
Space{8},
|
Space{8},
|
||||||
ccGroup,
|
ccGroup,
|
||||||
Space{8},
|
Space{8},
|
||||||
@@ -213,8 +192,10 @@ GeneralSettings::GeneralSettings()
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void GeneralSettings::showSelectionDialog(
|
void GeneralSettings::showSelectionDialog(const QStringList &data,
|
||||||
const QStringList &data, Utils::StringAspect &aspect, const QString &title, const QString &text)
|
Utils::StringAspect &aspect,
|
||||||
|
const QString &title,
|
||||||
|
const QString &text)
|
||||||
{
|
{
|
||||||
if (data.isEmpty())
|
if (data.isEmpty())
|
||||||
return;
|
return;
|
||||||
@@ -258,7 +239,6 @@ void GeneralSettings::showModelsNotFoundDialog(Utils::StringAspect &aspect)
|
|||||||
auto selectProviderBtn = new QPushButton(TrConstants::SELECT_PROVIDER);
|
auto selectProviderBtn = new QPushButton(TrConstants::SELECT_PROVIDER);
|
||||||
auto selectUrlBtn = new QPushButton(TrConstants::SELECT_URL);
|
auto selectUrlBtn = new QPushButton(TrConstants::SELECT_URL);
|
||||||
auto enterManuallyBtn = new QPushButton(TrConstants::ENTER_MODEL_MANUALLY);
|
auto enterManuallyBtn = new QPushButton(TrConstants::ENTER_MODEL_MANUALLY);
|
||||||
auto configureApiKeyBtn = new QPushButton(TrConstants::CONFIGURE_API_KEY);
|
|
||||||
|
|
||||||
connect(selectProviderBtn, &QPushButton::clicked, &dialog, [this, providerButton, &dialog]() {
|
connect(selectProviderBtn, &QPushButton::clicked, &dialog, [this, providerButton, &dialog]() {
|
||||||
dialog.close();
|
dialog.close();
|
||||||
@@ -275,15 +255,9 @@ void GeneralSettings::showModelsNotFoundDialog(Utils::StringAspect &aspect)
|
|||||||
showModelsNotSupportedDialog(aspect);
|
showModelsNotSupportedDialog(aspect);
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(configureApiKeyBtn, &QPushButton::clicked, &dialog, [&dialog]() {
|
|
||||||
dialog.close();
|
|
||||||
Core::ICore::showOptionsDialog(Constants::QODE_ASSIST_PROVIDER_SETTINGS_PAGE_ID);
|
|
||||||
});
|
|
||||||
|
|
||||||
dialog.buttonLayout()->addWidget(selectProviderBtn);
|
dialog.buttonLayout()->addWidget(selectProviderBtn);
|
||||||
dialog.buttonLayout()->addWidget(selectUrlBtn);
|
dialog.buttonLayout()->addWidget(selectUrlBtn);
|
||||||
dialog.buttonLayout()->addWidget(enterManuallyBtn);
|
dialog.buttonLayout()->addWidget(enterManuallyBtn);
|
||||||
dialog.buttonLayout()->addWidget(configureApiKeyBtn);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto closeBtn = new QPushButton(TrConstants::CLOSE);
|
auto closeBtn = new QPushButton(TrConstants::CLOSE);
|
||||||
|
|||||||
@@ -36,8 +36,6 @@ public:
|
|||||||
Utils::BoolAspect enableQodeAssist{this};
|
Utils::BoolAspect enableQodeAssist{this};
|
||||||
Utils::BoolAspect enableLogging{this};
|
Utils::BoolAspect enableLogging{this};
|
||||||
Utils::BoolAspect enableCheckUpdate{this};
|
Utils::BoolAspect enableCheckUpdate{this};
|
||||||
Utils::BoolAspect enableChat{this};
|
|
||||||
|
|
||||||
ButtonAspect checkUpdate{this};
|
ButtonAspect checkUpdate{this};
|
||||||
ButtonAspect resetToDefaults{this};
|
ButtonAspect resetToDefaults{this};
|
||||||
|
|
||||||
@@ -57,8 +55,6 @@ public:
|
|||||||
Utils::StringAspect ccStatus{this};
|
Utils::StringAspect ccStatus{this};
|
||||||
ButtonAspect ccTest{this};
|
ButtonAspect ccTest{this};
|
||||||
|
|
||||||
Utils::StringAspect ccTemplateDescription{this};
|
|
||||||
|
|
||||||
// TODO create dynamic presets system
|
// TODO create dynamic presets system
|
||||||
// preset1 for code completion settings
|
// preset1 for code completion settings
|
||||||
Utils::BoolAspect specifyPreset1{this};
|
Utils::BoolAspect specifyPreset1{this};
|
||||||
@@ -92,8 +88,6 @@ public:
|
|||||||
Utils::StringAspect caStatus{this};
|
Utils::StringAspect caStatus{this};
|
||||||
ButtonAspect caTest{this};
|
ButtonAspect caTest{this};
|
||||||
|
|
||||||
Utils::StringAspect caTemplateDescription{this};
|
|
||||||
|
|
||||||
void showSelectionDialog(const QStringList &data,
|
void showSelectionDialog(const QStringList &data,
|
||||||
Utils::StringAspect &aspect,
|
Utils::StringAspect &aspect,
|
||||||
const QString &title = {},
|
const QString &title = {},
|
||||||
|
|||||||
@@ -69,7 +69,6 @@ ProviderSettings::ProviderSettings()
|
|||||||
claudeApiKey.setDefaultValue("");
|
claudeApiKey.setDefaultValue("");
|
||||||
claudeApiKey.setAutoApply(true);
|
claudeApiKey.setAutoApply(true);
|
||||||
|
|
||||||
// OpenAI Settings
|
|
||||||
openAiApiKey.setSettingsKey(Constants::OPEN_AI_API_KEY);
|
openAiApiKey.setSettingsKey(Constants::OPEN_AI_API_KEY);
|
||||||
openAiApiKey.setLabelText(Tr::tr("OpenAI API Key:"));
|
openAiApiKey.setLabelText(Tr::tr("OpenAI API Key:"));
|
||||||
openAiApiKey.setDisplayStyle(Utils::StringAspect::LineEditDisplay);
|
openAiApiKey.setDisplayStyle(Utils::StringAspect::LineEditDisplay);
|
||||||
@@ -78,24 +77,6 @@ ProviderSettings::ProviderSettings()
|
|||||||
openAiApiKey.setDefaultValue("");
|
openAiApiKey.setDefaultValue("");
|
||||||
openAiApiKey.setAutoApply(true);
|
openAiApiKey.setAutoApply(true);
|
||||||
|
|
||||||
// MistralAI Settings
|
|
||||||
mistralAiApiKey.setSettingsKey(Constants::MISTRAL_AI_API_KEY);
|
|
||||||
mistralAiApiKey.setLabelText(Tr::tr("Mistral AI API Key:"));
|
|
||||||
mistralAiApiKey.setDisplayStyle(Utils::StringAspect::LineEditDisplay);
|
|
||||||
mistralAiApiKey.setPlaceHolderText(Tr::tr("Enter your API key here"));
|
|
||||||
mistralAiApiKey.setHistoryCompleter(Constants::MISTRAL_AI_API_KEY_HISTORY);
|
|
||||||
mistralAiApiKey.setDefaultValue("");
|
|
||||||
mistralAiApiKey.setAutoApply(true);
|
|
||||||
|
|
||||||
// GoogleAI Settings
|
|
||||||
googleAiApiKey.setSettingsKey(Constants::GOOGLE_AI_API_KEY);
|
|
||||||
googleAiApiKey.setLabelText(Tr::tr("Google AI API Key:"));
|
|
||||||
googleAiApiKey.setDisplayStyle(Utils::StringAspect::LineEditDisplay);
|
|
||||||
googleAiApiKey.setPlaceHolderText(Tr::tr("Enter your API key here"));
|
|
||||||
googleAiApiKey.setHistoryCompleter(Constants::GOOGLE_AI_API_KEY_HISTORY);
|
|
||||||
googleAiApiKey.setDefaultValue("");
|
|
||||||
googleAiApiKey.setAutoApply(true);
|
|
||||||
|
|
||||||
resetToDefaults.m_buttonText = Tr::tr("Reset Page to Defaults");
|
resetToDefaults.m_buttonText = Tr::tr("Reset Page to Defaults");
|
||||||
|
|
||||||
readSettings();
|
readSettings();
|
||||||
@@ -115,10 +96,6 @@ ProviderSettings::ProviderSettings()
|
|||||||
Group{title(Tr::tr("OpenAI Compatible Settings")), Column{openAiCompatApiKey}},
|
Group{title(Tr::tr("OpenAI Compatible Settings")), Column{openAiCompatApiKey}},
|
||||||
Space{8},
|
Space{8},
|
||||||
Group{title(Tr::tr("Claude Settings")), Column{claudeApiKey}},
|
Group{title(Tr::tr("Claude Settings")), Column{claudeApiKey}},
|
||||||
Space{8},
|
|
||||||
Group{title(Tr::tr("Mistral AI Settings")), Column{mistralAiApiKey}},
|
|
||||||
Space{8},
|
|
||||||
Group{title(Tr::tr("Google AI Settings")), Column{googleAiApiKey}},
|
|
||||||
Stretch{1}};
|
Stretch{1}};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -135,12 +112,6 @@ void ProviderSettings::setupConnections()
|
|||||||
});
|
});
|
||||||
connect(&claudeApiKey, &ButtonAspect::changed, this, [this]() { claudeApiKey.writeSettings(); });
|
connect(&claudeApiKey, &ButtonAspect::changed, this, [this]() { claudeApiKey.writeSettings(); });
|
||||||
connect(&openAiApiKey, &ButtonAspect::changed, this, [this]() { openAiApiKey.writeSettings(); });
|
connect(&openAiApiKey, &ButtonAspect::changed, this, [this]() { openAiApiKey.writeSettings(); });
|
||||||
connect(&mistralAiApiKey, &ButtonAspect::changed, this, [this]() {
|
|
||||||
mistralAiApiKey.writeSettings();
|
|
||||||
});
|
|
||||||
connect(&googleAiApiKey, &ButtonAspect::changed, this, [this]() {
|
|
||||||
googleAiApiKey.writeSettings();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProviderSettings::resetSettingsToDefaults()
|
void ProviderSettings::resetSettingsToDefaults()
|
||||||
@@ -157,8 +128,6 @@ void ProviderSettings::resetSettingsToDefaults()
|
|||||||
resetAspect(openAiCompatApiKey);
|
resetAspect(openAiCompatApiKey);
|
||||||
resetAspect(claudeApiKey);
|
resetAspect(claudeApiKey);
|
||||||
resetAspect(openAiApiKey);
|
resetAspect(openAiApiKey);
|
||||||
resetAspect(mistralAiApiKey);
|
|
||||||
resetAspect(googleAiApiKey);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -37,8 +37,6 @@ public:
|
|||||||
Utils::StringAspect openAiCompatApiKey{this};
|
Utils::StringAspect openAiCompatApiKey{this};
|
||||||
Utils::StringAspect claudeApiKey{this};
|
Utils::StringAspect claudeApiKey{this};
|
||||||
Utils::StringAspect openAiApiKey{this};
|
Utils::StringAspect openAiApiKey{this};
|
||||||
Utils::StringAspect mistralAiApiKey{this};
|
|
||||||
Utils::StringAspect googleAiApiKey{this};
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void setupConnections();
|
void setupConnections();
|
||||||
|
|||||||
@@ -60,7 +60,6 @@ const char ENABLE_QODE_ASSIST[] = "QodeAssist.enableQodeAssist";
|
|||||||
const char CC_AUTO_COMPLETION[] = "QodeAssist.ccAutoCompletion";
|
const char CC_AUTO_COMPLETION[] = "QodeAssist.ccAutoCompletion";
|
||||||
const char ENABLE_LOGGING[] = "QodeAssist.enableLogging";
|
const char ENABLE_LOGGING[] = "QodeAssist.enableLogging";
|
||||||
const char ENABLE_CHECK_UPDATE[] = "QodeAssist.enableCheckUpdate";
|
const char ENABLE_CHECK_UPDATE[] = "QodeAssist.enableCheckUpdate";
|
||||||
const char ENABLE_CHAT[] = "QodeAssist.enableChat";
|
|
||||||
|
|
||||||
const char PROVIDER_PATHS[] = "QodeAssist.providerPaths";
|
const char PROVIDER_PATHS[] = "QodeAssist.providerPaths";
|
||||||
const char СС_START_SUGGESTION_TIMER[] = "QodeAssist.startSuggestionTimer";
|
const char СС_START_SUGGESTION_TIMER[] = "QodeAssist.startSuggestionTimer";
|
||||||
@@ -101,18 +100,14 @@ const char CLAUDE_API_KEY[] = "QodeAssist.claudeApiKey";
|
|||||||
const char CLAUDE_API_KEY_HISTORY[] = "QodeAssist.claudeApiKeyHistory";
|
const char CLAUDE_API_KEY_HISTORY[] = "QodeAssist.claudeApiKeyHistory";
|
||||||
const char OPEN_AI_API_KEY[] = "QodeAssist.openAiApiKey";
|
const char OPEN_AI_API_KEY[] = "QodeAssist.openAiApiKey";
|
||||||
const char OPEN_AI_API_KEY_HISTORY[] = "QodeAssist.openAiApiKeyHistory";
|
const char OPEN_AI_API_KEY_HISTORY[] = "QodeAssist.openAiApiKeyHistory";
|
||||||
const char MISTRAL_AI_API_KEY[] = "QodeAssist.mistralAiApiKey";
|
|
||||||
const char MISTRAL_AI_API_KEY_HISTORY[] = "QodeAssist.mistralAiApiKeyHistory";
|
|
||||||
const char GOOGLE_AI_API_KEY[] = "QodeAssist.googleAiApiKey";
|
|
||||||
const char GOOGLE_AI_API_KEY_HISTORY[] = "QodeAssist.googleAiApiKeyHistory";
|
|
||||||
|
|
||||||
// context settings
|
// context settings
|
||||||
const char CC_READ_FULL_FILE[] = "QodeAssist.ccReadFullFile";
|
const char CC_READ_FULL_FILE[] = "QodeAssist.ccReadFullFile";
|
||||||
const char CC_READ_STRINGS_BEFORE_CURSOR[] = "QodeAssist.ccReadStringsBeforeCursor";
|
const char CC_READ_STRINGS_BEFORE_CURSOR[] = "QodeAssist.ccReadStringsBeforeCursor";
|
||||||
const char CC_READ_STRINGS_AFTER_CURSOR[] = "QodeAssist.ccReadStringsAfterCursor";
|
const char CC_READ_STRINGS_AFTER_CURSOR[] = "QodeAssist.ccReadStringsAfterCursor";
|
||||||
const char CC_USE_SYSTEM_PROMPT[] = "QodeAssist.ccUseSystemPrompt";
|
const char CC_USE_SYSTEM_PROMPT[] = "QodeAssist.ccUseSystemPrompt";
|
||||||
|
const char CC_USE_FILE_PATH_IN_CONTEXT[] = "QodeAssist.ccUseFilePathInContext";
|
||||||
const char CC_SYSTEM_PROMPT[] = "QodeAssist.ccSystemPrompt";
|
const char CC_SYSTEM_PROMPT[] = "QodeAssist.ccSystemPrompt";
|
||||||
const char CC_SYSTEM_PROMPT_FOR_NON_FIM[] = "QodeAssist.ccSystemPromptForNonFim";
|
|
||||||
const char CC_USE_USER_TEMPLATE[] = "QodeAssist.ccUseUserTemplate";
|
const char CC_USE_USER_TEMPLATE[] = "QodeAssist.ccUseUserTemplate";
|
||||||
const char CC_USER_TEMPLATE[] = "QodeAssist.ccUserTemplate";
|
const char CC_USER_TEMPLATE[] = "QodeAssist.ccUserTemplate";
|
||||||
const char CC_USE_PROJECT_CHANGES_CACHE[] = "QodeAssist.ccUseProjectChangesCache";
|
const char CC_USE_PROJECT_CHANGES_CACHE[] = "QodeAssist.ccUseProjectChangesCache";
|
||||||
|
|||||||
@@ -39,9 +39,6 @@ inline const char *TEST = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Test");
|
|||||||
inline const char *ENABLE_LOG = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Enable Logging");
|
inline const char *ENABLE_LOG = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Enable Logging");
|
||||||
inline const char *ENABLE_CHECK_UPDATE_ON_START
|
inline const char *ENABLE_CHECK_UPDATE_ON_START
|
||||||
= QT_TRANSLATE_NOOP("QtC::QodeAssist", "Check for updates when Qt Creator starts");
|
= QT_TRANSLATE_NOOP("QtC::QodeAssist", "Check for updates when Qt Creator starts");
|
||||||
inline const char *ENABLE_CHAT = QT_TRANSLATE_NOOP(
|
|
||||||
"QtC::QodeAssist",
|
|
||||||
"Enable Chat(If you have performance issues try disabling this, need restart QtC)");
|
|
||||||
|
|
||||||
inline const char *CODE_COMPLETION = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Code Completion");
|
inline const char *CODE_COMPLETION = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Code Completion");
|
||||||
inline const char *CHAT_ASSISTANT = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Chat Assistant");
|
inline const char *CHAT_ASSISTANT = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Chat Assistant");
|
||||||
@@ -49,8 +46,6 @@ inline const char *RESET_SETTINGS = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Reset
|
|||||||
inline const char *CONFIRMATION
|
inline const char *CONFIRMATION
|
||||||
= QT_TRANSLATE_NOOP("QtC::QodeAssist",
|
= QT_TRANSLATE_NOOP("QtC::QodeAssist",
|
||||||
"Are you sure you want to reset all settings to default values?");
|
"Are you sure you want to reset all settings to default values?");
|
||||||
inline const char *CURRENT_TEMPLATE_DESCRIPTION
|
|
||||||
= QT_TRANSLATE_NOOP("QtC::QodeAssist", "Current template description:");
|
|
||||||
|
|
||||||
inline const char CONNECTION_ERROR[] = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Connection Error");
|
inline const char CONNECTION_ERROR[] = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Connection Error");
|
||||||
inline const char NO_MODELS_FOUND[]
|
inline const char NO_MODELS_FOUND[]
|
||||||
@@ -60,8 +55,7 @@ inline const char CHECK_CONNECTION[] = QT_TRANSLATE_NOOP(
|
|||||||
"Please verify the following:\n"
|
"Please verify the following:\n"
|
||||||
"- Server is running and accessible\n"
|
"- Server is running and accessible\n"
|
||||||
"- URL is correct\n"
|
"- URL is correct\n"
|
||||||
"- Provider is properly configured\n"
|
"- Provider is properly configured\n\n"
|
||||||
"- API key is correctly set (if required)\n\n"
|
|
||||||
"You can try selecting a different provider or changing the URL:");
|
"You can try selecting a different provider or changing the URL:");
|
||||||
inline const char SELECT_PROVIDER[] = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Select Provider");
|
inline const char SELECT_PROVIDER[] = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Select Provider");
|
||||||
inline const char SELECT_URL[] = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Select URL");
|
inline const char SELECT_URL[] = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Select URL");
|
||||||
@@ -79,7 +73,6 @@ inline const char OK[] = QT_TRANSLATE_NOOP("QtC::QodeAssist", "OK");
|
|||||||
inline const char CANCEL[] = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Cancel");
|
inline const char CANCEL[] = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Cancel");
|
||||||
inline const char ENTER_MODEL_MANUALLY[]
|
inline const char ENTER_MODEL_MANUALLY[]
|
||||||
= QT_TRANSLATE_NOOP("QtC::QodeAssist", "Enter Model Manually");
|
= QT_TRANSLATE_NOOP("QtC::QodeAssist", "Enter Model Manually");
|
||||||
inline const char CONFIGURE_API_KEY[] = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Configure API Key");
|
|
||||||
inline const char URL_SELECTION[] = QT_TRANSLATE_NOOP("QtC::QodeAssist", "URL Selection");
|
inline const char URL_SELECTION[] = QT_TRANSLATE_NOOP("QtC::QodeAssist", "URL Selection");
|
||||||
inline const char URL_SELECTION_INFO[] = QT_TRANSLATE_NOOP(
|
inline const char URL_SELECTION_INFO[] = QT_TRANSLATE_NOOP(
|
||||||
"QtC::QodeAssist",
|
"QtC::QodeAssist",
|
||||||
|
|||||||
@@ -29,60 +29,38 @@ class Alpaca : public LLMCore::PromptTemplate
|
|||||||
public:
|
public:
|
||||||
QString name() const override { return "Alpaca"; }
|
QString name() const override { return "Alpaca"; }
|
||||||
LLMCore::TemplateType type() const override { return LLMCore::TemplateType::Chat; }
|
LLMCore::TemplateType type() const override { return LLMCore::TemplateType::Chat; }
|
||||||
|
QString promptTemplate() const override { return {}; }
|
||||||
QStringList stopWords() const override
|
QStringList stopWords() const override
|
||||||
{
|
{
|
||||||
return QStringList() << "### Instruction:" << "### Response:";
|
return QStringList() << "### Instruction:" << "### Response:";
|
||||||
}
|
}
|
||||||
void prepareRequest(QJsonObject &request, const LLMCore::ContextData &context) const override
|
void prepareRequest(QJsonObject &request, const LLMCore::ContextData &context) const override
|
||||||
{
|
{
|
||||||
QJsonArray messages;
|
QJsonArray messages = request["messages"].toArray();
|
||||||
|
|
||||||
QString fullContent;
|
for (int i = 0; i < messages.size(); ++i) {
|
||||||
|
QJsonObject message = messages[i].toObject();
|
||||||
|
QString role = message["role"].toString();
|
||||||
|
QString content = message["content"].toString();
|
||||||
|
|
||||||
if (context.systemPrompt) {
|
QString formattedContent;
|
||||||
fullContent += context.systemPrompt.value() + "\n\n";
|
if (role == "system") {
|
||||||
|
formattedContent = content + "\n\n";
|
||||||
|
} else if (role == "user") {
|
||||||
|
formattedContent = "### Instruction:\n" + content + "\n\n";
|
||||||
|
} else if (role == "assistant") {
|
||||||
|
formattedContent = "### Response:\n" + content + "\n\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (context.history) {
|
message["content"] = formattedContent;
|
||||||
for (const auto &msg : context.history.value()) {
|
messages[i] = message;
|
||||||
if (msg.role == "user") {
|
|
||||||
fullContent += QString("### Instruction:\n%1\n\n").arg(msg.content);
|
|
||||||
} else if (msg.role == "assistant") {
|
|
||||||
fullContent += QString("### Response:\n%1\n\n").arg(msg.content);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
messages.append(QJsonObject{{"role", "user"}, {"content", fullContent}});
|
|
||||||
|
|
||||||
request["messages"] = messages;
|
request["messages"] = messages;
|
||||||
}
|
}
|
||||||
QString description() const override
|
QString description() const override
|
||||||
{
|
{
|
||||||
return "Template for models using Alpaca instruction format:\n\n"
|
return "The message will contain the following tokens: ### Instruction:\n### Response:\n";
|
||||||
"{\n"
|
|
||||||
" \"messages\": [\n"
|
|
||||||
" {\n"
|
|
||||||
" \"role\": \"user\",\n"
|
|
||||||
" \"content\": \"<system prompt>\\n\\n"
|
|
||||||
"### Instruction:\\n<user message>\\n\\n"
|
|
||||||
"### Response:\\n<assistant response>\\n\\n\"\n"
|
|
||||||
" }\n"
|
|
||||||
" ]\n"
|
|
||||||
"}\n\n"
|
|
||||||
"Combines all messages into a single formatted prompt.";
|
|
||||||
}
|
|
||||||
bool isSupportProvider(LLMCore::ProviderID id) const override
|
|
||||||
{
|
|
||||||
switch (id) {
|
|
||||||
case QodeAssist::LLMCore::ProviderID::Ollama:
|
|
||||||
case QodeAssist::LLMCore::ProviderID::LMStudio:
|
|
||||||
case QodeAssist::LLMCore::ProviderID::OpenRouter:
|
|
||||||
case QodeAssist::LLMCore::ProviderID::OpenAICompatible:
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
40
templates/BasicChat.hpp
Normal file
40
templates/BasicChat.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 <QJsonArray>
|
||||||
|
|
||||||
|
#include "llmcore/PromptTemplate.hpp"
|
||||||
|
|
||||||
|
namespace QodeAssist::Templates {
|
||||||
|
|
||||||
|
class BasicChat : public LLMCore::PromptTemplate
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
LLMCore::TemplateType type() const override { return LLMCore::TemplateType::Chat; }
|
||||||
|
QString name() const override { return "Basic Chat"; }
|
||||||
|
QString promptTemplate() const override { return {}; }
|
||||||
|
QStringList stopWords() const override { return QStringList(); }
|
||||||
|
void prepareRequest(QJsonObject &request, const LLMCore::ContextData &context) const override
|
||||||
|
{}
|
||||||
|
QString description() const override { return "chat without tokens"; }
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace QodeAssist::Templates
|
||||||
@@ -30,60 +30,30 @@ class ChatML : public LLMCore::PromptTemplate
|
|||||||
public:
|
public:
|
||||||
QString name() const override { return "ChatML"; }
|
QString name() const override { return "ChatML"; }
|
||||||
LLMCore::TemplateType type() const override { return LLMCore::TemplateType::Chat; }
|
LLMCore::TemplateType type() const override { return LLMCore::TemplateType::Chat; }
|
||||||
|
QString promptTemplate() const override { return {}; }
|
||||||
QStringList stopWords() const override
|
QStringList stopWords() const override
|
||||||
{
|
{
|
||||||
return QStringList() << "<|im_start|>" << "<|im_end|>";
|
return QStringList() << "<|im_start|>" << "<|im_end|>";
|
||||||
}
|
}
|
||||||
void prepareRequest(QJsonObject &request, const LLMCore::ContextData &context) const override
|
void prepareRequest(QJsonObject &request, const LLMCore::ContextData &context) const override
|
||||||
{
|
{
|
||||||
QJsonArray messages;
|
QJsonArray messages = request["messages"].toArray();
|
||||||
|
|
||||||
if (context.systemPrompt) {
|
for (int i = 0; i < messages.size(); ++i) {
|
||||||
messages.append(QJsonObject{
|
QJsonObject message = messages[i].toObject();
|
||||||
{"role", "system"},
|
QString role = message["role"].toString();
|
||||||
{"content",
|
QString content = message["content"].toString();
|
||||||
QString("<|im_start|>system\n%2\n<|im_end|>").arg(context.systemPrompt.value())}});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (context.history) {
|
message["content"] = QString("<|im_start|>%1\n%2\n<|im_end|>").arg(role, content);
|
||||||
for (const auto &msg : context.history.value()) {
|
|
||||||
messages.append(QJsonObject{
|
messages[i] = message;
|
||||||
{"role", msg.role},
|
|
||||||
{"content",
|
|
||||||
QString("<|im_start|>%1\n%2\n<|im_end|>").arg(msg.role, msg.content)}});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
request["messages"] = messages;
|
request["messages"] = messages;
|
||||||
}
|
}
|
||||||
QString description() const override
|
QString description() const override
|
||||||
{
|
{
|
||||||
return "Template for models supporting ChatML format:\n\n"
|
return "The message will contain the following tokens: <|im_start|>%1\n%2\n<|im_end|>";
|
||||||
"{\n"
|
|
||||||
" \"messages\": [\n"
|
|
||||||
" {\n"
|
|
||||||
" \"role\": \"system\",\n"
|
|
||||||
" \"content\": \"<|im_start|>system\\n<system prompt>\\n<|im_end|>\"\n"
|
|
||||||
" },\n"
|
|
||||||
" {\n"
|
|
||||||
" \"role\": \"user\",\n"
|
|
||||||
" \"content\": \"<|im_start|>user\\n<user message>\\n<|im_end|>\"\n"
|
|
||||||
" }\n"
|
|
||||||
" ]\n"
|
|
||||||
"}\n\n"
|
|
||||||
"Compatible with multiple providers supporting the ChatML token format.";
|
|
||||||
}
|
|
||||||
bool isSupportProvider(LLMCore::ProviderID id) const override
|
|
||||||
{
|
|
||||||
switch (id) {
|
|
||||||
case QodeAssist::LLMCore::ProviderID::Ollama:
|
|
||||||
case QodeAssist::LLMCore::ProviderID::LMStudio:
|
|
||||||
case QodeAssist::LLMCore::ProviderID::OpenRouter:
|
|
||||||
case QodeAssist::LLMCore::ProviderID::OpenAICompatible:
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -30,46 +30,10 @@ class Claude : public LLMCore::PromptTemplate
|
|||||||
public:
|
public:
|
||||||
LLMCore::TemplateType type() const override { return LLMCore::TemplateType::Chat; }
|
LLMCore::TemplateType type() const override { return LLMCore::TemplateType::Chat; }
|
||||||
QString name() const override { return "Claude"; }
|
QString name() const override { return "Claude"; }
|
||||||
|
QString promptTemplate() const override { return {}; }
|
||||||
QStringList stopWords() const override { return QStringList(); }
|
QStringList stopWords() const override { return QStringList(); }
|
||||||
void prepareRequest(QJsonObject &request, const LLMCore::ContextData &context) const override
|
void prepareRequest(QJsonObject &request, const LLMCore::ContextData &context) const override {}
|
||||||
{
|
QString description() const override { return "Claude"; }
|
||||||
QJsonArray messages;
|
|
||||||
|
|
||||||
if (context.systemPrompt) {
|
|
||||||
request["system"] = context.systemPrompt.value();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (context.history) {
|
|
||||||
for (const auto &msg : context.history.value()) {
|
|
||||||
if (msg.role != "system") {
|
|
||||||
messages.append(QJsonObject{{"role", msg.role}, {"content", msg.content}});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
request["messages"] = messages;
|
|
||||||
}
|
|
||||||
QString description() const override
|
|
||||||
{
|
|
||||||
return "Template for Anthropic's Claude models:\n\n"
|
|
||||||
"{\n"
|
|
||||||
" \"system\": \"<system prompt>\",\n"
|
|
||||||
" \"messages\": [\n"
|
|
||||||
" {\"role\": \"user\", \"content\": \"<user message>\"},\n"
|
|
||||||
" {\"role\": \"assistant\", \"content\": \"<assistant response>\"}\n"
|
|
||||||
" ]\n"
|
|
||||||
"}\n\n"
|
|
||||||
"Formats content according to Claude API specifications.";
|
|
||||||
}
|
|
||||||
bool isSupportProvider(LLMCore::ProviderID id) const override
|
|
||||||
{
|
|
||||||
switch (id) {
|
|
||||||
case QodeAssist::LLMCore::ProviderID::Claude:
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace QodeAssist::Templates
|
} // namespace QodeAssist::Templates
|
||||||
|
|||||||
@@ -26,35 +26,21 @@ namespace QodeAssist::Templates {
|
|||||||
class CodeLlamaFim : public LLMCore::PromptTemplate
|
class CodeLlamaFim : public LLMCore::PromptTemplate
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
LLMCore::TemplateType type() const override { return LLMCore::TemplateType::FIM; }
|
LLMCore::TemplateType type() const override { return LLMCore::TemplateType::Fim; }
|
||||||
QString name() const override { return "CodeLlama FIM"; }
|
QString name() const override { return "CodeLlama FIM"; }
|
||||||
|
QString promptTemplate() const override { return "<PRE> %1 <SUF>%2 <MID>"; }
|
||||||
QStringList stopWords() const override
|
QStringList stopWords() const override
|
||||||
{
|
{
|
||||||
return QStringList() << "<EOT>" << "<PRE>" << "<SUF" << "<MID>";
|
return QStringList() << "<EOT>" << "<PRE>" << "<SUF" << "<MID>";
|
||||||
}
|
}
|
||||||
void prepareRequest(QJsonObject &request, const LLMCore::ContextData &context) const override
|
void prepareRequest(QJsonObject &request, const LLMCore::ContextData &context) const override
|
||||||
{
|
{
|
||||||
request["prompt"] = QString("<PRE> %1 <SUF>%2 <MID>")
|
QString formattedPrompt = promptTemplate().arg(context.prefix, context.suffix);
|
||||||
.arg(context.prefix.value_or(""), context.suffix.value_or(""));
|
request["prompt"] = formattedPrompt;
|
||||||
request["system"] = context.systemPrompt.value_or("");
|
|
||||||
}
|
}
|
||||||
QString description() const override
|
QString description() const override
|
||||||
{
|
{
|
||||||
return "Specialized template for CodeLlama FIM:\n\n"
|
return "The message will contain the following tokens: <PRE> %1 <SUF>%2 <MID>";
|
||||||
"{\n"
|
|
||||||
" \"prompt\": \"<PRE> <code prefix> <SUF><code suffix> <MID>\",\n"
|
|
||||||
" \"system\": \"<system prompt>\"\n"
|
|
||||||
"}\n\n"
|
|
||||||
"Optimized for code completion with CodeLlama models.";
|
|
||||||
}
|
|
||||||
bool isSupportProvider(LLMCore::ProviderID id) const override
|
|
||||||
{
|
|
||||||
switch (id) {
|
|
||||||
case QodeAssist::LLMCore::ProviderID::Ollama:
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -26,8 +26,9 @@ namespace QodeAssist::Templates {
|
|||||||
class CodeLlamaQMLFim : public LLMCore::PromptTemplate
|
class CodeLlamaQMLFim : public LLMCore::PromptTemplate
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
LLMCore::TemplateType type() const override { return LLMCore::TemplateType::FIM; }
|
LLMCore::TemplateType type() const override { return LLMCore::TemplateType::Fim; }
|
||||||
QString name() const override { return "CodeLlama QML FIM"; }
|
QString name() const override { return "CodeLlama QML FIM"; }
|
||||||
|
QString promptTemplate() const override { return "<SUF>%1<PRE>%2<MID>"; }
|
||||||
QStringList stopWords() const override
|
QStringList stopWords() const override
|
||||||
{
|
{
|
||||||
return QStringList() << "<SUF>" << "<PRE>" << "</PRE>" << "</SUF>" << "< EOT >" << "\\end"
|
return QStringList() << "<SUF>" << "<PRE>" << "</PRE>" << "</SUF>" << "< EOT >" << "\\end"
|
||||||
@@ -35,27 +36,12 @@ public:
|
|||||||
}
|
}
|
||||||
void prepareRequest(QJsonObject &request, const LLMCore::ContextData &context) const override
|
void prepareRequest(QJsonObject &request, const LLMCore::ContextData &context) const override
|
||||||
{
|
{
|
||||||
request["prompt"] = QString("<SUF>%1<PRE>%2<MID>")
|
QString formattedPrompt = promptTemplate().arg(context.suffix, context.prefix);
|
||||||
.arg(context.suffix.value_or(""), context.prefix.value_or(""));
|
request["prompt"] = formattedPrompt;
|
||||||
request["system"] = context.systemPrompt.value_or("");
|
|
||||||
}
|
}
|
||||||
QString description() const override
|
QString description() const override
|
||||||
{
|
{
|
||||||
return "Specialized template for QML code completion with CodeLlama:\n\n"
|
return "The message will contain the following tokens: <SUF>%1<PRE>%2<MID>";
|
||||||
"{\n"
|
|
||||||
" \"prompt\": \"<SUF><code suffix><PRE><code prefix><MID>\",\n"
|
|
||||||
" \"system\": \"<system prompt>\"\n"
|
|
||||||
"}\n\n"
|
|
||||||
"Specifically optimized for QML/JavaScript code completion.";
|
|
||||||
}
|
|
||||||
bool isSupportProvider(LLMCore::ProviderID id) const override
|
|
||||||
{
|
|
||||||
switch (id) {
|
|
||||||
case QodeAssist::LLMCore::ProviderID::Ollama:
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,89 +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 <QJsonArray>
|
|
||||||
#include <QJsonObject>
|
|
||||||
|
|
||||||
#include "llmcore/PromptTemplate.hpp"
|
|
||||||
|
|
||||||
namespace QodeAssist::Templates {
|
|
||||||
|
|
||||||
class GoogleAI : public LLMCore::PromptTemplate
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
LLMCore::TemplateType type() const override { return LLMCore::TemplateType::Chat; }
|
|
||||||
QString name() const override { return "Google AI"; }
|
|
||||||
QStringList stopWords() const override { return QStringList(); }
|
|
||||||
|
|
||||||
void prepareRequest(QJsonObject &request, const LLMCore::ContextData &context) const override
|
|
||||||
{
|
|
||||||
QJsonArray contents;
|
|
||||||
|
|
||||||
if (context.systemPrompt && !context.systemPrompt->isEmpty()) {
|
|
||||||
request["system_instruction"] = QJsonObject{
|
|
||||||
{"parts", QJsonObject{{"text", context.systemPrompt.value()}}}};
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const auto &msg : context.history.value()) {
|
|
||||||
QJsonObject content;
|
|
||||||
QJsonArray parts;
|
|
||||||
|
|
||||||
parts.append(QJsonObject{{"text", msg.content}});
|
|
||||||
|
|
||||||
QString role = msg.role;
|
|
||||||
if (role == "assistant") {
|
|
||||||
role = "model";
|
|
||||||
}
|
|
||||||
|
|
||||||
content["role"] = role;
|
|
||||||
content["parts"] = parts;
|
|
||||||
contents.append(content);
|
|
||||||
}
|
|
||||||
|
|
||||||
request["contents"] = contents;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString description() const override
|
|
||||||
{
|
|
||||||
return "Template for Google AI models (Gemini):\n\n"
|
|
||||||
"{\n"
|
|
||||||
" \"system_instruction\": {\"parts\": {\"text\": \"<system prompt>\"}},\n"
|
|
||||||
" \"contents\": [\n"
|
|
||||||
" {\n"
|
|
||||||
" \"role\": \"user\",\n"
|
|
||||||
" \"parts\": [{\"text\": \"<user message>\"}]\n"
|
|
||||||
" },\n"
|
|
||||||
" {\n"
|
|
||||||
" \"role\": \"model\",\n"
|
|
||||||
" \"parts\": [{\"text\": \"<assistant response>\"}]\n"
|
|
||||||
" }\n"
|
|
||||||
" ]\n"
|
|
||||||
"}\n\n"
|
|
||||||
"Supports proper role mapping, including model/user roles.";
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isSupportProvider(LLMCore::ProviderID id) const override
|
|
||||||
{
|
|
||||||
return id == QodeAssist::LLMCore::ProviderID::GoogleAI;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace QodeAssist::Templates
|
|
||||||
@@ -29,58 +29,35 @@ class Llama2 : public LLMCore::PromptTemplate
|
|||||||
public:
|
public:
|
||||||
QString name() const override { return "Llama 2"; }
|
QString name() const override { return "Llama 2"; }
|
||||||
LLMCore::TemplateType type() const override { return LLMCore::TemplateType::Chat; }
|
LLMCore::TemplateType type() const override { return LLMCore::TemplateType::Chat; }
|
||||||
|
QString promptTemplate() const override { return {}; }
|
||||||
QStringList stopWords() const override { return QStringList() << "[INST]"; }
|
QStringList stopWords() const override { return QStringList() << "[INST]"; }
|
||||||
void prepareRequest(QJsonObject &request, const LLMCore::ContextData &context) const override
|
void prepareRequest(QJsonObject &request, const LLMCore::ContextData &context) const override
|
||||||
{
|
{
|
||||||
QJsonArray messages;
|
QJsonArray messages = request["messages"].toArray();
|
||||||
|
|
||||||
QString fullContent;
|
for (int i = 0; i < messages.size(); ++i) {
|
||||||
|
QJsonObject message = messages[i].toObject();
|
||||||
|
QString role = message["role"].toString();
|
||||||
|
QString content = message["content"].toString();
|
||||||
|
|
||||||
if (context.systemPrompt) {
|
QString formattedContent;
|
||||||
fullContent
|
if (role == "system") {
|
||||||
+= QString("[INST]<<SYS>>\n%1\n<</SYS>>[/INST]\n").arg(context.systemPrompt.value());
|
formattedContent = QString("[INST]<<SYS>>\n%1\n<</SYS>>[/INST]\n").arg(content);
|
||||||
|
} else if (role == "user") {
|
||||||
|
formattedContent = QString("[INST]%1[/INST]\n").arg(content);
|
||||||
|
} else if (role == "assistant") {
|
||||||
|
formattedContent = content + "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (context.history) {
|
message["content"] = formattedContent;
|
||||||
for (const auto &msg : context.history.value()) {
|
messages[i] = message;
|
||||||
if (msg.role == "user") {
|
|
||||||
fullContent += QString("[INST]%1[/INST]\n").arg(msg.content);
|
|
||||||
} else if (msg.role == "assistant") {
|
|
||||||
fullContent += msg.content + "\n";
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
messages.append(QJsonObject{{"role", "user"}, {"content", fullContent}});
|
|
||||||
|
|
||||||
request["messages"] = messages;
|
request["messages"] = messages;
|
||||||
}
|
}
|
||||||
QString description() const override
|
QString description() const override
|
||||||
{
|
{
|
||||||
return "Template for Llama 2 models:\n\n"
|
return "The message will contain the following tokens: [INST]%1[/INST]\n";
|
||||||
"{\n"
|
|
||||||
" \"messages\": [\n"
|
|
||||||
" {\n"
|
|
||||||
" \"role\": \"user\",\n"
|
|
||||||
" \"content\": \"[INST]<<SYS>>\\n<system prompt>\\n<</SYS>>[/INST]\\n"
|
|
||||||
"<assistant response>\\n"
|
|
||||||
"[INST]<user message>[/INST]\\n\"\n"
|
|
||||||
" }\n"
|
|
||||||
" ]\n"
|
|
||||||
"}\n\n"
|
|
||||||
"Compatible with Ollama, LM Studio, and other services for Llama 2.";
|
|
||||||
}
|
|
||||||
bool isSupportProvider(LLMCore::ProviderID id) const override
|
|
||||||
{
|
|
||||||
switch (id) {
|
|
||||||
case QodeAssist::LLMCore::ProviderID::Ollama:
|
|
||||||
case QodeAssist::LLMCore::ProviderID::LMStudio:
|
|
||||||
case QodeAssist::LLMCore::ProviderID::OpenRouter:
|
|
||||||
case QodeAssist::LLMCore::ProviderID::OpenAICompatible:
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -30,64 +30,32 @@ class Llama3 : public LLMCore::PromptTemplate
|
|||||||
public:
|
public:
|
||||||
QString name() const override { return "Llama 3"; }
|
QString name() const override { return "Llama 3"; }
|
||||||
LLMCore::TemplateType type() const override { return LLMCore::TemplateType::Chat; }
|
LLMCore::TemplateType type() const override { return LLMCore::TemplateType::Chat; }
|
||||||
|
QString promptTemplate() const override { return ""; }
|
||||||
QStringList stopWords() const override
|
QStringList stopWords() const override
|
||||||
{
|
{
|
||||||
return QStringList() << "<|start_header_id|>" << "<|end_header_id|>" << "<|eot_id|>";
|
return QStringList() << "<|start_header_id|>" << "<|end_header_id|>" << "<|eot_id|>";
|
||||||
}
|
}
|
||||||
void prepareRequest(QJsonObject &request, const LLMCore::ContextData &context) const override
|
void prepareRequest(QJsonObject &request, const LLMCore::ContextData &context) const override
|
||||||
{
|
{
|
||||||
QJsonArray messages;
|
QJsonArray messages = request["messages"].toArray();
|
||||||
|
|
||||||
if (context.systemPrompt) {
|
for (int i = 0; i < messages.size(); ++i) {
|
||||||
messages.append(QJsonObject{
|
QJsonObject message = messages[i].toObject();
|
||||||
{"role", "system"},
|
QString role = message["role"].toString();
|
||||||
{"content",
|
QString content = message["content"].toString();
|
||||||
QString("<|start_header_id|>system<|end_header_id|>%2<|eot_id|>")
|
|
||||||
.arg(context.systemPrompt.value())}});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (context.history) {
|
message["content"]
|
||||||
for (const auto &msg : context.history.value()) {
|
= QString("<|start_header_id|>%1<|end_header_id|>%2<|eot_id|>").arg(role, content);
|
||||||
messages.append(QJsonObject{
|
|
||||||
{"role", msg.role},
|
messages[i] = message;
|
||||||
{"content",
|
|
||||||
QString("<|start_header_id|>%1<|end_header_id|>%2<|eot_id|>")
|
|
||||||
.arg(msg.role, msg.content)}});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
request["messages"] = messages;
|
request["messages"] = messages;
|
||||||
}
|
}
|
||||||
QString description() const override
|
QString description() const override
|
||||||
{
|
{
|
||||||
return "Template for Llama 3 models:\n\n"
|
return "The message will contain the following tokens: "
|
||||||
"{\n"
|
"<|start_header_id|>%1<|end_header_id|>%2<|eot_id|>";
|
||||||
" \"messages\": [\n"
|
|
||||||
" {\n"
|
|
||||||
" \"role\": \"system\",\n"
|
|
||||||
" \"content\": \"<|start_header_id|>system<|end_header_id|><system "
|
|
||||||
"prompt><|eot_id|>\"\n"
|
|
||||||
" },\n"
|
|
||||||
" {\n"
|
|
||||||
" \"role\": \"user\",\n"
|
|
||||||
" \"content\": \"<|start_header_id|>user<|end_header_id|><user "
|
|
||||||
"message><|eot_id|>\"\n"
|
|
||||||
" }\n"
|
|
||||||
" ]\n"
|
|
||||||
"}\n\n"
|
|
||||||
"Compatible with Ollama, LM Studio, and OpenAI-compatible services for Llama 3.";
|
|
||||||
}
|
|
||||||
bool isSupportProvider(LLMCore::ProviderID id) const override
|
|
||||||
{
|
|
||||||
switch (id) {
|
|
||||||
case QodeAssist::LLMCore::ProviderID::Ollama:
|
|
||||||
case QodeAssist::LLMCore::ProviderID::LMStudio:
|
|
||||||
case QodeAssist::LLMCore::ProviderID::OpenRouter:
|
|
||||||
case QodeAssist::LLMCore::ProviderID::OpenAICompatible:
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,106 +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 <QJsonArray>
|
|
||||||
|
|
||||||
#include "llmcore/PromptTemplate.hpp"
|
|
||||||
|
|
||||||
namespace QodeAssist::Templates {
|
|
||||||
|
|
||||||
class MistralAIFim : public LLMCore::PromptTemplate
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
LLMCore::TemplateType type() const override { return LLMCore::TemplateType::FIM; }
|
|
||||||
QString name() const override { return "Mistral AI FIM"; }
|
|
||||||
QStringList stopWords() const override { return QStringList(); }
|
|
||||||
void prepareRequest(QJsonObject &request, const LLMCore::ContextData &context) const override
|
|
||||||
{
|
|
||||||
request["prompt"] = context.prefix.value_or("");
|
|
||||||
request["suffix"] = context.suffix.value_or("");
|
|
||||||
}
|
|
||||||
QString description() const override
|
|
||||||
{
|
|
||||||
return "Template for MistralAI models with FIM support:\n\n"
|
|
||||||
"{\n"
|
|
||||||
" \"prompt\": \"<code prefix>\",\n"
|
|
||||||
" \"suffix\": \"<code suffix>\"\n"
|
|
||||||
"}\n\n"
|
|
||||||
"Optimized for code completion with MistralAI models.";
|
|
||||||
}
|
|
||||||
bool isSupportProvider(LLMCore::ProviderID id) const override
|
|
||||||
{
|
|
||||||
switch (id) {
|
|
||||||
case QodeAssist::LLMCore::ProviderID::MistralAI:
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
class MistralAIChat : public LLMCore::PromptTemplate
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
LLMCore::TemplateType type() const override { return LLMCore::TemplateType::Chat; }
|
|
||||||
QString name() const override { return "Mistral AI Chat"; }
|
|
||||||
QStringList stopWords() const override { return QStringList(); }
|
|
||||||
|
|
||||||
void prepareRequest(QJsonObject &request, const LLMCore::ContextData &context) const override
|
|
||||||
{
|
|
||||||
QJsonArray messages;
|
|
||||||
|
|
||||||
if (context.systemPrompt) {
|
|
||||||
messages.append(
|
|
||||||
QJsonObject{{"role", "system"}, {"content", context.systemPrompt.value()}});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (context.history) {
|
|
||||||
for (const auto &msg : context.history.value()) {
|
|
||||||
messages.append(QJsonObject{{"role", msg.role}, {"content", msg.content}});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
request["messages"] = messages;
|
|
||||||
}
|
|
||||||
QString description() const override
|
|
||||||
{
|
|
||||||
return "Template for MistralAI chat-capable models:\n\n"
|
|
||||||
"{\n"
|
|
||||||
" \"messages\": [\n"
|
|
||||||
" {\"role\": \"system\", \"content\": \"<system prompt>\"},\n"
|
|
||||||
" {\"role\": \"user\", \"content\": \"<user message>\"},\n"
|
|
||||||
" {\"role\": \"assistant\", \"content\": \"<assistant response>\"}\n"
|
|
||||||
" ]\n"
|
|
||||||
"}\n\n"
|
|
||||||
"Supports system messages and conversation history.";
|
|
||||||
}
|
|
||||||
bool isSupportProvider(LLMCore::ProviderID id) const override
|
|
||||||
{
|
|
||||||
switch (id) {
|
|
||||||
case QodeAssist::LLMCore::ProviderID::MistralAI:
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace QodeAssist::Templates
|
|
||||||
@@ -25,84 +25,33 @@
|
|||||||
|
|
||||||
namespace QodeAssist::Templates {
|
namespace QodeAssist::Templates {
|
||||||
|
|
||||||
class OllamaFim : public LLMCore::PromptTemplate
|
class OllamaAutoFim : public LLMCore::PromptTemplate
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
LLMCore::TemplateType type() const override { return LLMCore::TemplateType::FIM; }
|
LLMCore::TemplateType type() const override { return LLMCore::TemplateType::Fim; }
|
||||||
QString name() const override { return "Ollama FIM"; }
|
QString name() const override { return "Ollama Auto FIM"; }
|
||||||
|
QString promptTemplate() const override { return {}; }
|
||||||
QStringList stopWords() const override { return QStringList(); }
|
QStringList stopWords() const override { return QStringList(); }
|
||||||
void prepareRequest(QJsonObject &request, const LLMCore::ContextData &context) const override
|
void prepareRequest(QJsonObject &request, const LLMCore::ContextData &context) const override
|
||||||
{
|
{
|
||||||
request["prompt"] = context.prefix.value_or("");
|
request["prompt"] = context.prefix;
|
||||||
request["suffix"] = context.suffix.value_or("");
|
request["suffix"] = context.suffix;
|
||||||
request["system"] = context.systemPrompt.value_or("");
|
|
||||||
}
|
|
||||||
QString description() const override
|
|
||||||
{
|
|
||||||
return "Default Ollama FIM (Fill-in-Middle) template with native format:\n\n"
|
|
||||||
"{\n"
|
|
||||||
" \"prompt\": \"<code prefix>\",\n"
|
|
||||||
" \"suffix\": \"<code suffix>\",\n"
|
|
||||||
" \"system\": \"<system prompt>\"\n"
|
|
||||||
"}\n\n"
|
|
||||||
"Recommended for Ollama models with FIM capability.";
|
|
||||||
}
|
|
||||||
bool isSupportProvider(LLMCore::ProviderID id) const override
|
|
||||||
{
|
|
||||||
switch (id) {
|
|
||||||
case QodeAssist::LLMCore::ProviderID::Ollama:
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
QString description() const override { return "template will take from ollama modelfile"; }
|
||||||
};
|
};
|
||||||
|
|
||||||
class OllamaChat : public LLMCore::PromptTemplate
|
class OllamaAutoChat : public LLMCore::PromptTemplate
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
LLMCore::TemplateType type() const override { return LLMCore::TemplateType::Chat; }
|
LLMCore::TemplateType type() const override { return LLMCore::TemplateType::Chat; }
|
||||||
QString name() const override { return "Ollama Chat"; }
|
QString name() const override { return "Ollama Auto Chat"; }
|
||||||
|
QString promptTemplate() const override { return {}; }
|
||||||
QStringList stopWords() const override { return QStringList(); }
|
QStringList stopWords() const override { return QStringList(); }
|
||||||
|
|
||||||
void prepareRequest(QJsonObject &request, const LLMCore::ContextData &context) const override
|
void prepareRequest(QJsonObject &request, const LLMCore::ContextData &context) const override
|
||||||
{
|
{
|
||||||
QJsonArray messages;
|
|
||||||
|
|
||||||
if (context.systemPrompt) {
|
|
||||||
messages.append(
|
|
||||||
QJsonObject{{"role", "system"}, {"content", context.systemPrompt.value()}});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (context.history) {
|
|
||||||
for (const auto &msg : context.history.value()) {
|
|
||||||
messages.append(QJsonObject{{"role", msg.role}, {"content", msg.content}});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
request["messages"] = messages;
|
|
||||||
}
|
|
||||||
QString description() const override
|
|
||||||
{
|
|
||||||
return "Template for Ollama Chat with message array format:\n\n"
|
|
||||||
"{\n"
|
|
||||||
" \"messages\": [\n"
|
|
||||||
" {\"role\": \"system\", \"content\": \"<system prompt>\"},\n"
|
|
||||||
" {\"role\": \"user\", \"content\": \"<user message>\"},\n"
|
|
||||||
" {\"role\": \"assistant\", \"content\": \"<assistant response>\"}\n"
|
|
||||||
" ]\n"
|
|
||||||
"}\n\n"
|
|
||||||
"Recommended for Ollama models with chat capability.";
|
|
||||||
}
|
|
||||||
bool isSupportProvider(LLMCore::ProviderID id) const override
|
|
||||||
{
|
|
||||||
switch (id) {
|
|
||||||
case QodeAssist::LLMCore::ProviderID::Ollama:
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
QString description() const override { return "template will take from ollama modelfile"; }
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace QodeAssist::Templates
|
} // namespace QodeAssist::Templates
|
||||||
|
|||||||
@@ -30,45 +30,10 @@ class OpenAI : public LLMCore::PromptTemplate
|
|||||||
public:
|
public:
|
||||||
LLMCore::TemplateType type() const override { return LLMCore::TemplateType::Chat; }
|
LLMCore::TemplateType type() const override { return LLMCore::TemplateType::Chat; }
|
||||||
QString name() const override { return "OpenAI"; }
|
QString name() const override { return "OpenAI"; }
|
||||||
|
QString promptTemplate() const override { return {}; }
|
||||||
QStringList stopWords() const override { return QStringList(); }
|
QStringList stopWords() const override { return QStringList(); }
|
||||||
void prepareRequest(QJsonObject &request, const LLMCore::ContextData &context) const override
|
void prepareRequest(QJsonObject &request, const LLMCore::ContextData &context) const override {}
|
||||||
{
|
QString description() const override { return "OpenAI"; }
|
||||||
QJsonArray messages;
|
|
||||||
|
|
||||||
if (context.systemPrompt) {
|
|
||||||
messages.append(
|
|
||||||
QJsonObject{{"role", "system"}, {"content", context.systemPrompt.value()}});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (context.history) {
|
|
||||||
for (const auto &msg : context.history.value()) {
|
|
||||||
messages.append(QJsonObject{{"role", msg.role}, {"content", msg.content}});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
request["messages"] = messages;
|
|
||||||
}
|
|
||||||
QString description() const override
|
|
||||||
{
|
|
||||||
return "Template for OpenAI models (GPT series):\n\n"
|
|
||||||
"{\n"
|
|
||||||
" \"messages\": [\n"
|
|
||||||
" {\"role\": \"system\", \"content\": \"<system prompt>\"},\n"
|
|
||||||
" {\"role\": \"user\", \"content\": \"<user message>\"},\n"
|
|
||||||
" {\"role\": \"assistant\", \"content\": \"<assistant response>\"}\n"
|
|
||||||
" ]\n"
|
|
||||||
"}\n\n"
|
|
||||||
"Standard Chat API format for OpenAI.";
|
|
||||||
}
|
|
||||||
bool isSupportProvider(LLMCore::ProviderID id) const override
|
|
||||||
{
|
|
||||||
switch (id) {
|
|
||||||
case QodeAssist::LLMCore::ProviderID::OpenAI:
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace QodeAssist::Templates
|
} // namespace QodeAssist::Templates
|
||||||
|
|||||||
@@ -1,76 +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 <QJsonArray>
|
|
||||||
|
|
||||||
#include "llmcore/PromptTemplate.hpp"
|
|
||||||
|
|
||||||
namespace QodeAssist::Templates {
|
|
||||||
|
|
||||||
class OpenAICompatible : public LLMCore::PromptTemplate
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
LLMCore::TemplateType type() const override { return LLMCore::TemplateType::Chat; }
|
|
||||||
QString name() const override { return "OpenAI Compatible"; }
|
|
||||||
QStringList stopWords() const override { return QStringList(); }
|
|
||||||
void prepareRequest(QJsonObject &request, const LLMCore::ContextData &context) const override
|
|
||||||
{
|
|
||||||
QJsonArray messages;
|
|
||||||
|
|
||||||
if (context.systemPrompt) {
|
|
||||||
messages.append(
|
|
||||||
QJsonObject{{"role", "system"}, {"content", context.systemPrompt.value()}});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (context.history) {
|
|
||||||
for (const auto &msg : context.history.value()) {
|
|
||||||
messages.append(QJsonObject{{"role", msg.role}, {"content", msg.content}});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
request["messages"] = messages;
|
|
||||||
}
|
|
||||||
QString description() const override
|
|
||||||
{
|
|
||||||
return "Generic template for OpenAI API-compatible services:\n\n"
|
|
||||||
"{\n"
|
|
||||||
" \"messages\": [\n"
|
|
||||||
" {\"role\": \"system\", \"content\": \"<system prompt>\"},\n"
|
|
||||||
" {\"role\": \"user\", \"content\": \"<user message>\"},\n"
|
|
||||||
" {\"role\": \"assistant\", \"content\": \"<assistant response>\"}\n"
|
|
||||||
" ]\n"
|
|
||||||
"}\n\n"
|
|
||||||
"Works with any service implementing the OpenAI Chat API specification.";
|
|
||||||
}
|
|
||||||
bool isSupportProvider(LLMCore::ProviderID id) const override
|
|
||||||
{
|
|
||||||
switch (id) {
|
|
||||||
case QodeAssist::LLMCore::ProviderID::OpenAICompatible:
|
|
||||||
case QodeAssist::LLMCore::ProviderID::OpenRouter:
|
|
||||||
case QodeAssist::LLMCore::ProviderID::LMStudio:
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace QodeAssist::Templates
|
|
||||||
@@ -28,32 +28,21 @@ class QwenFim : public LLMCore::PromptTemplate
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
QString name() const override { return "Qwen FIM"; }
|
QString name() const override { return "Qwen FIM"; }
|
||||||
LLMCore::TemplateType type() const override { return LLMCore::TemplateType::FIM; }
|
LLMCore::TemplateType type() const override { return LLMCore::TemplateType::Fim; }
|
||||||
|
QString promptTemplate() const override
|
||||||
|
{
|
||||||
|
return "<|fim_prefix|>%1<|fim_suffix|>%2<|fim_middle|>";
|
||||||
|
}
|
||||||
QStringList stopWords() const override { return QStringList() << "<|endoftext|>" << "<|EOT|>"; }
|
QStringList stopWords() const override { return QStringList() << "<|endoftext|>" << "<|EOT|>"; }
|
||||||
void prepareRequest(QJsonObject &request, const LLMCore::ContextData &context) const override
|
void prepareRequest(QJsonObject &request, const LLMCore::ContextData &context) const override
|
||||||
{
|
{
|
||||||
request["prompt"] = QString("<|fim_prefix|>%1<|fim_suffix|>%2<|fim_middle|>")
|
QString formattedPrompt = promptTemplate().arg(context.prefix, context.suffix);
|
||||||
.arg(context.prefix.value_or(""), context.suffix.value_or(""));
|
request["prompt"] = formattedPrompt;
|
||||||
request["system"] = context.systemPrompt.value_or("");
|
|
||||||
}
|
}
|
||||||
QString description() const override
|
QString description() const override
|
||||||
{
|
{
|
||||||
return "Template for Qwen models with FIM support:\n\n"
|
return "The message will contain the following tokens: "
|
||||||
"{\n"
|
"<|fim_prefix|>%1<|fim_suffix|>%2<|fim_middle|>";
|
||||||
" \"prompt\": \"<|fim_prefix|><code prefix><|fim_suffix|><code "
|
|
||||||
"suffix><|fim_middle|>\",\n"
|
|
||||||
" \"system\": \"<system prompt>\"\n"
|
|
||||||
"}\n\n"
|
|
||||||
"Ideal for code completion with Qwen models.";
|
|
||||||
}
|
|
||||||
bool isSupportProvider(LLMCore::ProviderID id) const override
|
|
||||||
{
|
|
||||||
switch (id) {
|
|
||||||
case QodeAssist::LLMCore::ProviderID::Ollama:
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -26,8 +26,9 @@ namespace QodeAssist::Templates {
|
|||||||
class StarCoder2Fim : public LLMCore::PromptTemplate
|
class StarCoder2Fim : public LLMCore::PromptTemplate
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
LLMCore::TemplateType type() const override { return LLMCore::TemplateType::FIM; }
|
LLMCore::TemplateType type() const override { return LLMCore::TemplateType::Fim; }
|
||||||
QString name() const override { return "StarCoder2 FIM"; }
|
QString name() const override { return "StarCoder2 FIM"; }
|
||||||
|
QString promptTemplate() const override { return "<fim_prefix>%1<fim_suffix>%2<fim_middle>"; }
|
||||||
QStringList stopWords() const override
|
QStringList stopWords() const override
|
||||||
{
|
{
|
||||||
return QStringList() << "<|endoftext|>" << "<file_sep>" << "<fim_prefix>" << "<fim_suffix>"
|
return QStringList() << "<|endoftext|>" << "<file_sep>" << "<fim_prefix>" << "<fim_suffix>"
|
||||||
@@ -35,27 +36,13 @@ public:
|
|||||||
}
|
}
|
||||||
void prepareRequest(QJsonObject &request, const LLMCore::ContextData &context) const override
|
void prepareRequest(QJsonObject &request, const LLMCore::ContextData &context) const override
|
||||||
{
|
{
|
||||||
request["prompt"] = QString("<fim_prefix>%1<fim_suffix>%2<fim_middle>")
|
QString formattedPrompt = promptTemplate().arg(context.prefix, context.suffix);
|
||||||
.arg(context.prefix.value_or(""), context.suffix.value_or(""));
|
request["prompt"] = formattedPrompt;
|
||||||
request["system"] = context.systemPrompt.value_or("");
|
|
||||||
}
|
}
|
||||||
QString description() const override
|
QString description() const override
|
||||||
{
|
{
|
||||||
return "Template for StarCoder2 with FIM format:\n\n"
|
return "The message will contain the following tokens: "
|
||||||
"{\n"
|
"<fim_prefix>%1<fim_suffix>%2<fim_middle>";
|
||||||
" \"prompt\": \"<fim_prefix><code prefix><fim_suffix><code suffix><fim_middle>\",\n"
|
|
||||||
" \"system\": \"<system prompt>\"\n"
|
|
||||||
"}\n\n"
|
|
||||||
"Includes stop words to prevent token duplication.";
|
|
||||||
}
|
|
||||||
bool isSupportProvider(LLMCore::ProviderID id) const override
|
|
||||||
{
|
|
||||||
switch (id) {
|
|
||||||
case QodeAssist::LLMCore::ProviderID::Ollama:
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -21,19 +21,17 @@
|
|||||||
|
|
||||||
#include "llmcore/PromptTemplateManager.hpp"
|
#include "llmcore/PromptTemplateManager.hpp"
|
||||||
#include "templates/Alpaca.hpp"
|
#include "templates/Alpaca.hpp"
|
||||||
|
#include "templates/BasicChat.hpp"
|
||||||
#include "templates/ChatML.hpp"
|
#include "templates/ChatML.hpp"
|
||||||
#include "templates/Claude.hpp"
|
#include "templates/Claude.hpp"
|
||||||
#include "templates/CodeLlamaFim.hpp"
|
#include "templates/CodeLlamaFim.hpp"
|
||||||
#include "templates/CodeLlamaQMLFim.hpp"
|
#include "templates/CodeLlamaQMLFim.hpp"
|
||||||
#include "templates/MistralAI.hpp"
|
#include "templates/CustomFimTemplate.hpp"
|
||||||
#include "templates/Ollama.hpp"
|
#include "templates/DeepSeekCoderFim.hpp"
|
||||||
#include "templates/OpenAI.hpp"
|
|
||||||
#include "templates/OpenAICompatible.hpp"
|
|
||||||
// #include "templates/CustomFimTemplate.hpp"
|
|
||||||
// #include "templates/DeepSeekCoderFim.hpp"
|
|
||||||
#include "templates/GoogleAI.hpp"
|
|
||||||
#include "templates/Llama2.hpp"
|
#include "templates/Llama2.hpp"
|
||||||
#include "templates/Llama3.hpp"
|
#include "templates/Llama3.hpp"
|
||||||
|
#include "templates/Ollama.hpp"
|
||||||
|
#include "templates/OpenAI.hpp"
|
||||||
#include "templates/Qwen.hpp"
|
#include "templates/Qwen.hpp"
|
||||||
#include "templates/StarCoder2Fim.hpp"
|
#include "templates/StarCoder2Fim.hpp"
|
||||||
|
|
||||||
@@ -42,24 +40,21 @@ namespace QodeAssist::Templates {
|
|||||||
inline void registerTemplates()
|
inline void registerTemplates()
|
||||||
{
|
{
|
||||||
auto &templateManager = LLMCore::PromptTemplateManager::instance();
|
auto &templateManager = LLMCore::PromptTemplateManager::instance();
|
||||||
templateManager.registerTemplate<OllamaChat>();
|
|
||||||
templateManager.registerTemplate<OllamaFim>();
|
|
||||||
templateManager.registerTemplate<CodeLlamaFim>();
|
templateManager.registerTemplate<CodeLlamaFim>();
|
||||||
|
templateManager.registerTemplate<StarCoder2Fim>();
|
||||||
|
templateManager.registerTemplate<DeepSeekCoderFim>();
|
||||||
|
templateManager.registerTemplate<CustomTemplate>();
|
||||||
|
templateManager.registerTemplate<QwenFim>();
|
||||||
|
templateManager.registerTemplate<OllamaAutoFim>();
|
||||||
|
templateManager.registerTemplate<OllamaAutoChat>();
|
||||||
|
templateManager.registerTemplate<BasicChat>();
|
||||||
|
templateManager.registerTemplate<Llama3>();
|
||||||
|
templateManager.registerTemplate<ChatML>();
|
||||||
|
templateManager.registerTemplate<Alpaca>();
|
||||||
|
templateManager.registerTemplate<Llama2>();
|
||||||
templateManager.registerTemplate<Claude>();
|
templateManager.registerTemplate<Claude>();
|
||||||
templateManager.registerTemplate<OpenAI>();
|
templateManager.registerTemplate<OpenAI>();
|
||||||
templateManager.registerTemplate<MistralAIFim>();
|
|
||||||
templateManager.registerTemplate<MistralAIChat>();
|
|
||||||
templateManager.registerTemplate<CodeLlamaQMLFim>();
|
templateManager.registerTemplate<CodeLlamaQMLFim>();
|
||||||
templateManager.registerTemplate<ChatML>();
|
|
||||||
templateManager.registerTemplate<Llama2>();
|
|
||||||
templateManager.registerTemplate<Llama3>();
|
|
||||||
templateManager.registerTemplate<StarCoder2Fim>();
|
|
||||||
// templateManager.registerTemplate<DeepSeekCoderFim>();
|
|
||||||
// templateManager.registerTemplate<CustomTemplate>();
|
|
||||||
templateManager.registerTemplate<QwenFim>();
|
|
||||||
templateManager.registerTemplate<OpenAICompatible>();
|
|
||||||
templateManager.registerTemplate<Alpaca>();
|
|
||||||
templateManager.registerTemplate<GoogleAI>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace QodeAssist::Templates
|
} // namespace QodeAssist::Templates
|
||||||
|
|||||||
Reference in New Issue
Block a user