mirror of
https://github.com/Palm1r/QodeAssist.git
synced 2026-02-12 10:10:44 -05:00
Compare commits
72 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b98f85a997 | |||
| 085659483f | |||
| 8a1fd5438e | |||
| 78f69e82a5 | |||
| 3d770f91c7 | |||
| c724bace06 | |||
| 719065ebfc | |||
| a218064a4f | |||
| 13cd12b00a | |||
| ed59be4199 | |||
| 7dd8b3d085 | |||
| 3839d6896c | |||
| 6b86637dcb | |||
| 58c3e26e7f | |||
| 98e1047bf1 | |||
| b6f36d61ae | |||
| f2f453ccc8 | |||
| 1bcfd749d5 | |||
| e66f467214 | |||
| c9a3cdaf25 | |||
| 7c483f89cd | |||
| 6c323642e4 | |||
| 3a494d5254 | |||
| 44b3b0cc0c | |||
| 3aae923d43 | |||
| f94c79a5ff | |||
| 9a5047618d | |||
| 90beebf2ee | |||
| 521261e0a3 | |||
| 5536de146c | |||
| 81ac3c71fb | |||
| 61ca5c9a1b | |||
| 8a167bf248 | |||
| ab97f39ea4 | |||
| 0d3493e7f6 | |||
| 1d062e1fe4 | |||
| 5dceb5cd19 | |||
| 69a8aa80d9 | |||
| e218699e64 | |||
| 3dc0d910bf | |||
| f9f2a86cea | |||
| 247256d4a4 | |||
| bcf7b6c226 | |||
| 29a3939c64 | |||
| cb3464170e | |||
| ca0fb5efbb | |||
| d8a01504a3 | |||
| 3b188740e8 | |||
| 0d22e1866e | |||
| 61196cae90 | |||
| 102bb114a1 | |||
| e507d7ee17 | |||
| ed55c829af | |||
| d651a246de | |||
| c8e0f3268e | |||
| 84025ec843 | |||
| f6fd411b2d | |||
| 903eb50e7a | |||
| e8f7f031b6 | |||
| 90ae6cd1c0 | |||
| 2ad0117498 | |||
| 9d58565de3 | |||
| 8dba9b4baa | |||
| 7ba615a72d | |||
| 1b06a651f0 | |||
| 912c3d8c04 | |||
| e924029ec2 | |||
| d96f44d42c | |||
| bd25736a55 | |||
| 60936f6d84 | |||
| 7d23d0323f | |||
| 1fa6a225a4 |
11
.github/scripts/plugin.json
vendored
11
.github/scripts/plugin.json
vendored
@ -6,22 +6,27 @@
|
||||
"llm",
|
||||
"ai"
|
||||
],
|
||||
"compatibility": "Qt 6.8.1",
|
||||
"compatibility": "Qt 6.8.2",
|
||||
"platforms": [
|
||||
"Windows",
|
||||
"macOS",
|
||||
"Linux"
|
||||
],
|
||||
"license": "GPLv3",
|
||||
"version": "0.4.0",
|
||||
"version": "0.5.2",
|
||||
"status": "draft",
|
||||
"is_pack": false,
|
||||
"released_at": null,
|
||||
"version_history": [
|
||||
{
|
||||
"version": "0.4.0",
|
||||
"is_latest": true,
|
||||
"is_latest": false,
|
||||
"released_at": "2024-01-24T15:00:00Z"
|
||||
},
|
||||
{
|
||||
"version": "0.5.2",
|
||||
"is_latest": true,
|
||||
"released_at": "2025-03-13T17:00:00Z"
|
||||
}
|
||||
],
|
||||
"icon": "https://github.com/user-attachments/assets/dc336712-83cb-440d-8761-8d0a31de898d",
|
||||
|
||||
31
.github/workflows/build_cmake.yml
vendored
31
.github/workflows/build_cmake.yml
vendored
@ -12,9 +12,9 @@ on:
|
||||
|
||||
env:
|
||||
PLUGIN_NAME: QodeAssist
|
||||
QT_VERSION: 6.8.1
|
||||
QT_CREATOR_VERSION: 15.0.1
|
||||
QT_CREATOR_VERSION_INTERNAL: 15.0.1
|
||||
QT_VERSION: 6.8.2
|
||||
QT_CREATOR_VERSION: 16.0.0
|
||||
QT_CREATOR_VERSION_INTERNAL: 16.0.0
|
||||
MACOS_DEPLOYMENT_TARGET: "11.0"
|
||||
CMAKE_VERSION: "3.29.6"
|
||||
NINJA_VERSION: "1.12.1"
|
||||
@ -36,8 +36,8 @@ jobs:
|
||||
environment_script: "C:/Program Files/Microsoft Visual Studio/2022/Enterprise/VC/Auxiliary/Build/vcvars64.bat",
|
||||
}
|
||||
- {
|
||||
name: "Ubuntu Latest GCC", artifact: "Linux-x64",
|
||||
os: ubuntu-latest,
|
||||
name: "Ubuntu 22.04 GCC", artifact: "Linux-x64",
|
||||
os: ubuntu-22.04,
|
||||
platform: linux_x64,
|
||||
cc: "gcc", cxx: "g++"
|
||||
}
|
||||
@ -70,7 +70,7 @@ jobs:
|
||||
cmakeVersion: ${{ env.CMAKE_VERSION }}
|
||||
ninjaVersion: ${{ env.NINJA_VERSION }}
|
||||
|
||||
- name: Install system libs
|
||||
- name: Install dependencies
|
||||
shell: cmake -P {0}
|
||||
run: |
|
||||
if ("${{ runner.os }}" STREQUAL "Linux")
|
||||
@ -78,7 +78,13 @@ jobs:
|
||||
COMMAND sudo apt update
|
||||
)
|
||||
execute_process(
|
||||
COMMAND sudo apt install libgl1-mesa-dev
|
||||
COMMAND sudo apt install
|
||||
# build dependencies
|
||||
libgl1-mesa-dev libgtest-dev libgmock-dev
|
||||
# runtime dependencies for tests (Qt is downloaded outside package manager,
|
||||
# thus minimal dependencies must be installed explicitly)
|
||||
libsecret-1-0 libxcb-cursor0 libxcb-icccm4 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-render0
|
||||
libxcb-shape0 libxcb-shm0 libxcb-sync1 libxcb-xfixes0 libxcb-xkb1 libxkbcommon-x11-0 xvfb
|
||||
RESULT_VARIABLE result
|
||||
)
|
||||
if (NOT result EQUAL 0)
|
||||
@ -240,19 +246,26 @@ jobs:
|
||||
|
||||
# The json is the same for all platforms, but we need to save one
|
||||
- name: Upload plugin json
|
||||
if: matrix.config.os == 'ubuntu-latest'
|
||||
if: startsWith(matrix.config.os, 'ubuntu')
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ env.PLUGIN_NAME }}-origin-json
|
||||
path: ./build/build/${{ env.PLUGIN_NAME }}.json
|
||||
|
||||
- name: Run unit tests
|
||||
if: startsWith(matrix.config.os, 'ubuntu')
|
||||
run: |
|
||||
xvfb-run ./build/build/test/QodeAssistTest
|
||||
|
||||
update_json:
|
||||
if: contains(github.ref, 'tags/v')
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-22.04
|
||||
needs: build
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
24
.github/workflows/check_formatting.yml
vendored
Normal file
24
.github/workflows/check_formatting.yml
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
name: Check formatting
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
format:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y clang-format-19
|
||||
- name: Check formatting
|
||||
run: |
|
||||
clang-format-19 --style=file -i $(git ls-files | fgrep .hpp)
|
||||
clang-format-19 --style=file -i $(git ls-files | fgrep .cpp)
|
||||
git diff --exit-code || exit 1
|
||||
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
[submodule "3rdparty/inja"]
|
||||
path = 3rdparty/inja
|
||||
url = https://github.com/pantor/inja
|
||||
1
3rdparty/inja
vendored
Submodule
1
3rdparty/inja
vendored
Submodule
Submodule 3rdparty/inja added at 384a6bef3f
@ -8,15 +8,38 @@ set(CMAKE_AUTOUIC ON)
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||
|
||||
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 Test REQUIRED)
|
||||
find_package(GTest)
|
||||
|
||||
# IDE_VERSION is defined by QtCreator package
|
||||
string(REGEX MATCH "([0-9]+)\\.([0-9]+)\\.([0-9]+)" version_match ${IDE_VERSION})
|
||||
set(QODEASSIST_QT_CREATOR_VERSION_MAJOR ${CMAKE_MATCH_1})
|
||||
set(QODEASSIST_QT_CREATOR_VERSION_MINOR ${CMAKE_MATCH_2})
|
||||
set(QODEASSIST_QT_CREATOR_VERSION_PATCH ${CMAKE_MATCH_3})
|
||||
|
||||
if(NOT version_match)
|
||||
message(FATAL_ERROR "Failed to parse Qt Creator version string: ${IDE_VERSION}")
|
||||
endif()
|
||||
|
||||
message(STATUS "Qt Creator Version: ${QODEASSIST_QT_CREATOR_VERSION_MAJOR}.${QODEASSIST_QT_CREATOR_VERSION_MINOR}.${QODEASSIST_QT_CREATOR_VERSION_PATCH}")
|
||||
|
||||
add_definitions(
|
||||
-DQODEASSIST_QT_CREATOR_VERSION_MAJOR=${QODEASSIST_QT_CREATOR_VERSION_MAJOR}
|
||||
-DQODEASSIST_QT_CREATOR_VERSION_MINOR=${QODEASSIST_QT_CREATOR_VERSION_MINOR}
|
||||
-DQODEASSIST_QT_CREATOR_VERSION_PATCH=${QODEASSIST_QT_CREATOR_VERSION_PATCH}
|
||||
)
|
||||
|
||||
add_subdirectory(llmcore)
|
||||
add_subdirectory(settings)
|
||||
add_subdirectory(logger)
|
||||
add_subdirectory(ChatView)
|
||||
add_subdirectory(context)
|
||||
if(GTest_FOUND)
|
||||
add_subdirectory(test)
|
||||
endif()
|
||||
|
||||
add_qtc_plugin(QodeAssist
|
||||
PLUGIN_DEPENDS
|
||||
@ -43,26 +66,32 @@ add_qtc_plugin(QodeAssist
|
||||
LLMClientInterface.hpp LLMClientInterface.cpp
|
||||
templates/Templates.hpp
|
||||
templates/CodeLlamaFim.hpp
|
||||
templates/StarCoder2Fim.hpp
|
||||
templates/DeepSeekCoderFim.hpp
|
||||
templates/CustomFimTemplate.hpp
|
||||
templates/Qwen.hpp
|
||||
templates/Ollama.hpp
|
||||
templates/BasicChat.hpp
|
||||
templates/Claude.hpp
|
||||
templates/OpenAI.hpp
|
||||
templates/MistralAI.hpp
|
||||
templates/StarCoder2Fim.hpp
|
||||
# templates/DeepSeekCoderFim.hpp
|
||||
# templates/CustomFimTemplate.hpp
|
||||
templates/Qwen.hpp
|
||||
templates/OpenAICompatible.hpp
|
||||
templates/Llama3.hpp
|
||||
templates/ChatML.hpp
|
||||
templates/Alpaca.hpp
|
||||
templates/Llama2.hpp
|
||||
templates/Claude.hpp
|
||||
templates/OpenAI.hpp
|
||||
templates/CodeLlamaQMLFim.hpp
|
||||
templates/GoogleAI.hpp
|
||||
templates/LlamaCppFim.hpp
|
||||
providers/Providers.hpp
|
||||
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/OpenAICompatProvider.hpp providers/OpenAICompatProvider.cpp
|
||||
providers/OpenRouterAIProvider.hpp providers/OpenRouterAIProvider.cpp
|
||||
providers/ClaudeProvider.hpp providers/ClaudeProvider.cpp
|
||||
providers/OpenAIProvider.hpp providers/OpenAIProvider.cpp
|
||||
providers/GoogleAIProvider.hpp providers/GoogleAIProvider.cpp
|
||||
providers/LlamaCppProvider.hpp providers/LlamaCppProvider.cpp
|
||||
QodeAssist.qrc
|
||||
LSPCompletion.hpp
|
||||
LLMSuggestion.hpp LLMSuggestion.cpp
|
||||
|
||||
@ -18,9 +18,9 @@
|
||||
*/
|
||||
|
||||
#include "ChatModel.hpp"
|
||||
#include <utils/aspects.h>
|
||||
#include <QtCore/qjsonobject.h>
|
||||
#include <QtQml>
|
||||
#include <utils/aspects.h>
|
||||
|
||||
#include "ChatAssistantSettings.hpp"
|
||||
|
||||
@ -31,10 +31,11 @@ ChatModel::ChatModel(QObject *parent)
|
||||
{
|
||||
auto &settings = Settings::chatAssistantSettings();
|
||||
|
||||
connect(&settings.chatTokensThreshold,
|
||||
&Utils::BaseAspect::changed,
|
||||
this,
|
||||
&ChatModel::tokensThresholdChanged);
|
||||
connect(
|
||||
&settings.chatTokensThreshold,
|
||||
&Utils::BaseAspect::changed,
|
||||
this,
|
||||
&ChatModel::tokensThresholdChanged);
|
||||
}
|
||||
|
||||
int ChatModel::rowCount(const QModelIndex &parent) const
|
||||
@ -127,7 +128,8 @@ QList<MessagePart> ChatModel::processMessageContent(const QString &content) cons
|
||||
while (blockMatches.hasNext()) {
|
||||
auto match = blockMatches.next();
|
||||
if (match.capturedStart() > lastIndex) {
|
||||
QString textBetween = content.mid(lastIndex, match.capturedStart() - lastIndex).trimmed();
|
||||
QString textBetween
|
||||
= content.mid(lastIndex, match.capturedStart() - lastIndex).trimmed();
|
||||
if (!textBetween.isEmpty()) {
|
||||
parts.append({MessagePart::Text, textBetween, ""});
|
||||
}
|
||||
|
||||
@ -24,42 +24,41 @@
|
||||
#include <QFileDialog>
|
||||
#include <QMessageBox>
|
||||
|
||||
#include <coreplugin/editormanager/editormanager.h>
|
||||
#include <coreplugin/icore.h>
|
||||
#include <projectexplorer/project.h>
|
||||
#include <projectexplorer/projectexplorer.h>
|
||||
#include <projectexplorer/projectmanager.h>
|
||||
#include <utils/theme/theme.h>
|
||||
#include <utils/utilsicons.h>
|
||||
#include <coreplugin/editormanager/editormanager.h>
|
||||
|
||||
#include "ChatAssistantSettings.hpp"
|
||||
#include "ChatSerializer.hpp"
|
||||
#include "GeneralSettings.hpp"
|
||||
#include "Logger.hpp"
|
||||
#include "ProjectSettings.hpp"
|
||||
#include "context/TokenUtils.hpp"
|
||||
#include "context/ContextManager.hpp"
|
||||
#include "context/TokenUtils.hpp"
|
||||
|
||||
namespace QodeAssist::Chat {
|
||||
|
||||
ChatRootView::ChatRootView(QQuickItem *parent)
|
||||
: QQuickItem(parent)
|
||||
, m_chatModel(new ChatModel(this))
|
||||
, m_clientInterface(new ClientInterface(m_chatModel, this))
|
||||
, m_promptProvider(LLMCore::PromptTemplateManager::instance())
|
||||
, m_clientInterface(new ClientInterface(m_chatModel, &m_promptProvider, this))
|
||||
{
|
||||
m_isSyncOpenFiles = Settings::chatAssistantSettings().linkOpenFiles();
|
||||
connect(&Settings::chatAssistantSettings().linkOpenFiles, &Utils::BaseAspect::changed,
|
||||
this,
|
||||
[this](){
|
||||
setIsSyncOpenFiles(Settings::chatAssistantSettings().linkOpenFiles());
|
||||
});
|
||||
connect(
|
||||
&Settings::chatAssistantSettings().linkOpenFiles,
|
||||
&Utils::BaseAspect::changed,
|
||||
this,
|
||||
[this]() { setIsSyncOpenFiles(Settings::chatAssistantSettings().linkOpenFiles()); });
|
||||
|
||||
auto &settings = Settings::generalSettings();
|
||||
|
||||
connect(&settings.caModel,
|
||||
&Utils::BaseAspect::changed,
|
||||
this,
|
||||
&ChatRootView::currentTemplateChanged);
|
||||
connect(
|
||||
&settings.caModel, &Utils::BaseAspect::changed, this, &ChatRootView::currentTemplateChanged);
|
||||
|
||||
connect(
|
||||
m_clientInterface,
|
||||
@ -76,10 +75,16 @@ ChatRootView::ChatRootView(QQuickItem *parent)
|
||||
connect(m_chatModel, &ChatModel::modelReseted, this, [this]() { setRecentFilePath(QString{}); });
|
||||
connect(this, &ChatRootView::attachmentFilesChanged, &ChatRootView::updateInputTokensCount);
|
||||
connect(this, &ChatRootView::linkedFilesChanged, &ChatRootView::updateInputTokensCount);
|
||||
connect(&Settings::chatAssistantSettings().useSystemPrompt, &Utils::BaseAspect::changed,
|
||||
this, &ChatRootView::updateInputTokensCount);
|
||||
connect(&Settings::chatAssistantSettings().systemPrompt, &Utils::BaseAspect::changed,
|
||||
this, &ChatRootView::updateInputTokensCount);
|
||||
connect(
|
||||
&Settings::chatAssistantSettings().useSystemPrompt,
|
||||
&Utils::BaseAspect::changed,
|
||||
this,
|
||||
&ChatRootView::updateInputTokensCount);
|
||||
connect(
|
||||
&Settings::chatAssistantSettings().systemPrompt,
|
||||
&Utils::BaseAspect::changed,
|
||||
this,
|
||||
&ChatRootView::updateInputTokensCount);
|
||||
|
||||
auto editors = Core::EditorManager::instance();
|
||||
|
||||
@ -160,9 +165,10 @@ QString ChatRootView::getChatsHistoryDir() const
|
||||
|
||||
if (auto project = ProjectExplorer::ProjectManager::startupProject()) {
|
||||
Settings::ProjectSettings projectSettings(project);
|
||||
path = projectSettings.chatHistoryPath().toString();
|
||||
path = projectSettings.chatHistoryPath().toFSPathString();
|
||||
} else {
|
||||
path = QString("%1/qodeassist/chat_history").arg(Core::ICore::userResourcePath().toString());
|
||||
path = QString("%1/qodeassist/chat_history")
|
||||
.arg(Core::ICore::userResourcePath().toFSPathString());
|
||||
}
|
||||
|
||||
QDir dir(path);
|
||||
@ -346,7 +352,7 @@ void ChatRootView::showAttachFilesDialog()
|
||||
dialog.setFileMode(QFileDialog::ExistingFiles);
|
||||
|
||||
if (auto project = ProjectExplorer::ProjectManager::startupProject()) {
|
||||
dialog.setDirectory(project->projectDirectory().toString());
|
||||
dialog.setDirectory(project->projectDirectory().toFSPathString());
|
||||
}
|
||||
|
||||
if (dialog.exec() == QDialog::Accepted) {
|
||||
@ -380,7 +386,7 @@ void ChatRootView::showLinkFilesDialog()
|
||||
dialog.setFileMode(QFileDialog::ExistingFiles);
|
||||
|
||||
if (auto project = ProjectExplorer::ProjectManager::startupProject()) {
|
||||
dialog.setDirectory(project->projectDirectory().toString());
|
||||
dialog.setDirectory(project->projectDirectory().toFSPathString());
|
||||
}
|
||||
|
||||
if (dialog.exec() == QDialog::Accepted) {
|
||||
@ -433,9 +439,10 @@ void ChatRootView::openChatHistoryFolder()
|
||||
QString path;
|
||||
if (auto project = ProjectExplorer::ProjectManager::startupProject()) {
|
||||
Settings::ProjectSettings projectSettings(project);
|
||||
path = projectSettings.chatHistoryPath().toString();
|
||||
path = projectSettings.chatHistoryPath().toFSPathString();
|
||||
} else {
|
||||
path = QString("%1/qodeassist/chat_history").arg(Core::ICore::userResourcePath().toString());
|
||||
path = QString("%1/qodeassist/chat_history")
|
||||
.arg(Core::ICore::userResourcePath().toFSPathString());
|
||||
}
|
||||
|
||||
QDir dir(path);
|
||||
@ -450,7 +457,7 @@ void ChatRootView::openChatHistoryFolder()
|
||||
void ChatRootView::updateInputTokensCount()
|
||||
{
|
||||
int inputTokens = m_messageTokensCount;
|
||||
auto& settings = Settings::chatAssistantSettings();
|
||||
auto &settings = Settings::chatAssistantSettings();
|
||||
|
||||
if (settings.useSystemPrompt()) {
|
||||
inputTokens += Context::TokenUtils::estimateTokens(settings.systemPrompt());
|
||||
@ -466,8 +473,8 @@ void ChatRootView::updateInputTokensCount()
|
||||
inputTokens += Context::TokenUtils::estimateFilesTokens(linkFiles);
|
||||
}
|
||||
|
||||
const auto& history = m_chatModel->getChatHistory();
|
||||
for (const auto& message : history) {
|
||||
const auto &history = m_chatModel->getChatHistory();
|
||||
for (const auto &message : history) {
|
||||
inputTokens += Context::TokenUtils::estimateTokens(message.content);
|
||||
inputTokens += 4; // + role
|
||||
}
|
||||
@ -489,7 +496,7 @@ bool ChatRootView::isSyncOpenFiles() const
|
||||
void ChatRootView::onEditorAboutToClose(Core::IEditor *editor)
|
||||
{
|
||||
if (auto document = editor->document(); document && isSyncOpenFiles()) {
|
||||
QString filePath = document->filePath().toString();
|
||||
QString filePath = document->filePath().toFSPathString();
|
||||
m_linkedFiles.removeOne(filePath);
|
||||
emit linkedFilesChanged();
|
||||
}
|
||||
@ -502,7 +509,7 @@ void ChatRootView::onEditorAboutToClose(Core::IEditor *editor)
|
||||
void ChatRootView::onAppendLinkFileFromEditor(Core::IEditor *editor)
|
||||
{
|
||||
if (auto document = editor->document(); document && isSyncOpenFiles()) {
|
||||
QString filePath = document->filePath().toString();
|
||||
QString filePath = document->filePath().toFSPathString();
|
||||
if (!m_linkedFiles.contains(filePath)) {
|
||||
m_linkedFiles.append(filePath);
|
||||
emit linkedFilesChanged();
|
||||
|
||||
@ -23,6 +23,7 @@
|
||||
|
||||
#include "ChatModel.hpp"
|
||||
#include "ClientInterface.hpp"
|
||||
#include "llmcore/PromptProviderChat.hpp"
|
||||
#include <coreplugin/editormanager/editormanager.h>
|
||||
|
||||
namespace QodeAssist::Chat {
|
||||
@ -99,6 +100,7 @@ private:
|
||||
QString getSuggestedFileName() const;
|
||||
|
||||
ChatModel *m_chatModel;
|
||||
LLMCore::PromptProviderChat m_promptProvider;
|
||||
ClientInterface *m_clientInterface;
|
||||
QString m_currentTemplate;
|
||||
QString m_recentFilePath;
|
||||
|
||||
@ -40,4 +40,4 @@ void ChatWidget::scrollToBottom()
|
||||
{
|
||||
QMetaObject::invokeMethod(rootObject(), "scrollToBottom");
|
||||
}
|
||||
}
|
||||
} // namespace QodeAssist::Chat
|
||||
|
||||
@ -38,4 +38,4 @@ signals:
|
||||
void clearPressed();
|
||||
};
|
||||
|
||||
}
|
||||
} // namespace QodeAssist::Chat
|
||||
|
||||
@ -19,11 +19,11 @@
|
||||
|
||||
#include "ClientInterface.hpp"
|
||||
|
||||
#include <texteditor/textdocument.h>
|
||||
#include <QFileInfo>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QUuid>
|
||||
#include <texteditor/textdocument.h>
|
||||
|
||||
#include <coreplugin/editormanager/editormanager.h>
|
||||
#include <coreplugin/editormanager/ieditor.h>
|
||||
@ -36,31 +36,34 @@
|
||||
#include "ContextManager.hpp"
|
||||
#include "GeneralSettings.hpp"
|
||||
#include "Logger.hpp"
|
||||
#include "PromptTemplateManager.hpp"
|
||||
#include "ProvidersManager.hpp"
|
||||
|
||||
namespace QodeAssist::Chat {
|
||||
|
||||
ClientInterface::ClientInterface(ChatModel *chatModel, QObject *parent)
|
||||
ClientInterface::ClientInterface(
|
||||
ChatModel *chatModel, LLMCore::IPromptProvider *promptProvider, QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_requestHandler(new LLMCore::RequestHandler(this))
|
||||
, m_chatModel(chatModel)
|
||||
, m_promptProvider(promptProvider)
|
||||
{
|
||||
connect(m_requestHandler,
|
||||
&LLMCore::RequestHandler::completionReceived,
|
||||
this,
|
||||
[this](const QString &completion, const QJsonObject &request, bool isComplete) {
|
||||
handleLLMResponse(completion, request, isComplete);
|
||||
});
|
||||
connect(
|
||||
m_requestHandler,
|
||||
&LLMCore::RequestHandler::completionReceived,
|
||||
this,
|
||||
[this](const QString &completion, const QJsonObject &request, bool isComplete) {
|
||||
handleLLMResponse(completion, request, isComplete);
|
||||
});
|
||||
|
||||
connect(m_requestHandler,
|
||||
&LLMCore::RequestHandler::requestFinished,
|
||||
this,
|
||||
[this](const QString &, bool success, const QString &errorString) {
|
||||
if (!success) {
|
||||
emit errorOccurred(errorString);
|
||||
}
|
||||
});
|
||||
connect(
|
||||
m_requestHandler,
|
||||
&LLMCore::RequestHandler::requestFinished,
|
||||
this,
|
||||
[this](const QString &, bool success, const QString &errorString) {
|
||||
if (!success) {
|
||||
emit errorOccurred(errorString);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ClientInterface::~ClientInterface() = default;
|
||||
@ -84,8 +87,7 @@ void ClientInterface::sendMessage(
|
||||
}
|
||||
|
||||
auto templateName = Settings::generalSettings().caTemplate();
|
||||
auto promptTemplate = LLMCore::PromptTemplateManager::instance().getChatTemplateByName(
|
||||
templateName);
|
||||
auto promptTemplate = m_promptProvider->getTemplateByName(templateName);
|
||||
|
||||
if (!promptTemplate) {
|
||||
LOG_MESSAGE(QString("No template found with name: %1").arg(templateName));
|
||||
@ -93,51 +95,47 @@ void ClientInterface::sendMessage(
|
||||
}
|
||||
|
||||
LLMCore::ContextData context;
|
||||
context.prefix = message;
|
||||
context.suffix = "";
|
||||
|
||||
QString systemPrompt;
|
||||
if (chatAssistantSettings.useSystemPrompt())
|
||||
systemPrompt = chatAssistantSettings.systemPrompt();
|
||||
|
||||
if (!linkedFiles.isEmpty()) {
|
||||
systemPrompt = getSystemPromptWithLinkedFiles(systemPrompt, linkedFiles);
|
||||
if (chatAssistantSettings.useSystemPrompt()) {
|
||||
QString systemPrompt = chatAssistantSettings.systemPrompt();
|
||||
if (!linkedFiles.isEmpty()) {
|
||||
systemPrompt = getSystemPromptWithLinkedFiles(systemPrompt, linkedFiles);
|
||||
}
|
||||
context.systemPrompt = systemPrompt;
|
||||
}
|
||||
|
||||
QJsonObject providerRequest;
|
||||
providerRequest["model"] = Settings::generalSettings().caModel();
|
||||
providerRequest["stream"] = chatAssistantSettings.stream();
|
||||
providerRequest["messages"] = m_chatModel->prepareMessagesForRequest(systemPrompt);
|
||||
|
||||
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");
|
||||
QVector<LLMCore::Message> messages;
|
||||
for (const auto &msg : m_chatModel->getChatHistory()) {
|
||||
messages.append({msg.role == ChatModel::ChatRole::User ? "user" : "assistant", msg.content});
|
||||
}
|
||||
context.history = messages;
|
||||
|
||||
LLMCore::LLMConfig config;
|
||||
config.requestType = LLMCore::RequestType::Chat;
|
||||
config.provider = provider;
|
||||
config.promptTemplate = promptTemplate;
|
||||
config.url = QString("%1%2").arg(Settings::generalSettings().caUrl(), provider->chatEndpoint());
|
||||
config.providerRequest = providerRequest;
|
||||
config.multiLineCompletion = false;
|
||||
config.apiKey = provider->apiKey();
|
||||
|
||||
QJsonObject request;
|
||||
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;
|
||||
if (provider->providerID() == LLMCore::ProviderID::GoogleAI) {
|
||||
QString stream = chatAssistantSettings.stream() ? QString{"streamGenerateContent?alt=sse"}
|
||||
: QString{"generateContent?"};
|
||||
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.provider
|
||||
->prepareRequest(config.providerRequest, promptTemplate, context, LLMCore::RequestType::Chat);
|
||||
|
||||
QJsonObject request{{"id", QUuid::createUuid().toString()}};
|
||||
m_requestHandler->sendLLMRequest(config, request);
|
||||
}
|
||||
|
||||
@ -153,9 +151,8 @@ void ClientInterface::cancelRequest()
|
||||
m_requestHandler->cancelRequest(id);
|
||||
}
|
||||
|
||||
void ClientInterface::handleLLMResponse(const QString &response,
|
||||
const QJsonObject &request,
|
||||
bool isComplete)
|
||||
void ClientInterface::handleLLMResponse(
|
||||
const QString &response, const QJsonObject &request, bool isComplete)
|
||||
{
|
||||
const auto message = response.trimmed();
|
||||
|
||||
@ -186,16 +183,17 @@ QString ClientInterface::getCurrentFileContext() const
|
||||
}
|
||||
|
||||
QString fileInfo = QString("Language: %1\nFile: %2\n\n")
|
||||
.arg(textDocument->mimeType(), textDocument->filePath().toString());
|
||||
.arg(textDocument->mimeType(), textDocument->filePath().toFSPathString());
|
||||
|
||||
QString content = textDocument->document()->toPlainText();
|
||||
|
||||
LOG_MESSAGE(QString("Got context from file: %1").arg(textDocument->filePath().toString()));
|
||||
LOG_MESSAGE(QString("Got context from file: %1").arg(textDocument->filePath().toFSPathString()));
|
||||
|
||||
return QString("Current file context:\n%1\nFile content:\n%2").arg(fileInfo, content);
|
||||
}
|
||||
|
||||
QString ClientInterface::getSystemPromptWithLinkedFiles(const QString &basePrompt, const QList<QString> &linkedFiles) const
|
||||
QString ClientInterface::getSystemPromptWithLinkedFiles(
|
||||
const QString &basePrompt, const QList<QString> &linkedFiles) const
|
||||
{
|
||||
QString updatedPrompt = basePrompt;
|
||||
|
||||
@ -204,8 +202,7 @@ QString ClientInterface::getSystemPromptWithLinkedFiles(const QString &basePromp
|
||||
|
||||
auto contentFiles = Context::ContextManager::instance().getContentFiles(linkedFiles);
|
||||
for (const auto &file : contentFiles) {
|
||||
updatedPrompt += QString("\nFile: %1\nContent:\n%2\n")
|
||||
.arg(file.filename, file.content);
|
||||
updatedPrompt += QString("\nFile: %1\nContent:\n%2\n").arg(file.filename, file.content);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -25,6 +25,7 @@
|
||||
|
||||
#include "ChatModel.hpp"
|
||||
#include "RequestHandler.hpp"
|
||||
#include "llmcore/IPromptProvider.hpp"
|
||||
|
||||
namespace QodeAssist::Chat {
|
||||
|
||||
@ -33,7 +34,8 @@ class ClientInterface : public QObject
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ClientInterface(ChatModel *chatModel, QObject *parent = nullptr);
|
||||
explicit ClientInterface(
|
||||
ChatModel *chatModel, LLMCore::IPromptProvider *promptProvider, QObject *parent = nullptr);
|
||||
~ClientInterface();
|
||||
|
||||
void sendMessage(
|
||||
@ -51,11 +53,11 @@ private:
|
||||
void handleLLMResponse(const QString &response, const QJsonObject &request, bool isComplete);
|
||||
QString getCurrentFileContext() const;
|
||||
QString getSystemPromptWithLinkedFiles(
|
||||
const QString &basePrompt,
|
||||
const QList<QString> &linkedFiles) const;
|
||||
const QString &basePrompt, const QList<QString> &linkedFiles) const;
|
||||
|
||||
LLMCore::RequestHandler *m_requestHandler;
|
||||
LLMCore::IPromptProvider *m_promptProvider = nullptr;
|
||||
ChatModel *m_chatModel;
|
||||
LLMCore::RequestHandler *m_requestHandler;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Chat
|
||||
|
||||
@ -27,14 +27,18 @@ Rectangle {
|
||||
|
||||
property alias msgModel: msgCreator.model
|
||||
property alias messageAttachments: attachmentsModel.model
|
||||
property bool isUserMessage: false
|
||||
|
||||
height: msgColumn.implicitHeight + 10
|
||||
radius: 8
|
||||
color: isUserMessage ? palette.alternateBase
|
||||
: palette.base
|
||||
|
||||
ColumnLayout {
|
||||
id: msgColumn
|
||||
|
||||
width: parent.width
|
||||
x: 5
|
||||
width: parent.width - x
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 5
|
||||
|
||||
@ -113,6 +117,17 @@ 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 {
|
||||
required property var itemData
|
||||
height: implicitHeight + 10
|
||||
|
||||
@ -96,8 +96,7 @@ ChatRootView {
|
||||
width: ListView.view.width - scroll.width
|
||||
msgModel: root.chatModel.processMessageContent(model.content)
|
||||
messageAttachments: model.attachments
|
||||
color: model.roleType === ChatModel.User ? palette.alternateBase
|
||||
: palette.base
|
||||
isUserMessage: model.roleType === ChatModel.User
|
||||
}
|
||||
|
||||
header: Item {
|
||||
|
||||
156
CodeHandler.cpp
156
CodeHandler.cpp
@ -1,5 +1,6 @@
|
||||
/*
|
||||
/*
|
||||
* Copyright (C) 2024 Petr Mironychev
|
||||
* Copyright (C) 2025 Povilas Kanapickas <povilas@radix.lt>
|
||||
*
|
||||
* This file is part of QodeAssist.
|
||||
*
|
||||
@ -18,41 +19,122 @@
|
||||
*/
|
||||
|
||||
#include "CodeHandler.hpp"
|
||||
#include <QFileInfo>
|
||||
#include <QHash>
|
||||
|
||||
namespace QodeAssist {
|
||||
|
||||
QString CodeHandler::processText(QString text)
|
||||
struct LanguageProperties
|
||||
{
|
||||
QString name;
|
||||
QString commentStyle;
|
||||
QVector<QString> namesFromModel;
|
||||
QVector<QString> fileExtensions;
|
||||
};
|
||||
|
||||
const QVector<LanguageProperties> &getKnownLanguages()
|
||||
{
|
||||
static QVector<LanguageProperties> knownLanguages = {
|
||||
{"python", "#", {"python", "py"}, {"py"}},
|
||||
{"lua", "--", {"lua"}, {"lua"}},
|
||||
{"js", "//", {"js", "javascript"}, {"js", "jsx"}},
|
||||
{"ts", "//", {"ts", "typescript"}, {"ts", "tsx"}},
|
||||
{"c-like", "//", {"c", "c++", "cpp"}, {"c", "h", "cpp", "hpp"}},
|
||||
{"java", "//", {"java"}, {"java"}},
|
||||
{"c#", "//", {"cs", "csharp"}, {"cs"}},
|
||||
{"php", "//", {"php"}, {"php"}},
|
||||
{"ruby", "#", {"rb", "ruby"}, {"rb"}},
|
||||
{"go", "//", {"go"}, {"go"}},
|
||||
{"swift", "//", {"swift"}, {"swift"}},
|
||||
{"kotlin", "//", {"kt", "kotlin"}, {"kt", "kotlin"}},
|
||||
{"scala", "//", {"scala"}, {"scala"}},
|
||||
{"r", "#", {"r"}, {"r"}},
|
||||
{"shell", "#", {"shell", "bash", "sh"}, {"sh", "bash"}},
|
||||
{"perl", "#", {"pl", "perl"}, {"pl"}},
|
||||
{"hs", "--", {"hs", "haskell"}, {"hs"}},
|
||||
};
|
||||
|
||||
return knownLanguages;
|
||||
}
|
||||
|
||||
static QHash<QString, QString> buildLanguageToCommentPrefixMap()
|
||||
{
|
||||
QHash<QString, QString> result;
|
||||
for (const auto &languageProps : getKnownLanguages()) {
|
||||
result[languageProps.name] = languageProps.commentStyle;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static QHash<QString, QString> buildExtensionToLanguageMap()
|
||||
{
|
||||
QHash<QString, QString> result;
|
||||
for (const auto &languageProps : getKnownLanguages()) {
|
||||
for (const auto &extension : languageProps.fileExtensions) {
|
||||
result[extension] = languageProps.name;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static QHash<QString, QString> buildModelLanguageNameToLanguageMap()
|
||||
{
|
||||
QHash<QString, QString> result;
|
||||
for (const auto &languageProps : getKnownLanguages()) {
|
||||
for (const auto &nameFromModel : languageProps.namesFromModel) {
|
||||
result[nameFromModel] = languageProps.name;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
QString CodeHandler::processText(QString text, QString currentFilePath)
|
||||
{
|
||||
QString result;
|
||||
QStringList lines = text.split('\n');
|
||||
bool inCodeBlock = false;
|
||||
QString pendingComments;
|
||||
QString currentLanguage;
|
||||
|
||||
auto currentFileExtension = QFileInfo(currentFilePath).suffix();
|
||||
auto currentLanguage = detectLanguageFromExtension(currentFileExtension);
|
||||
|
||||
auto addPendingCommentsIfAny = [&]() {
|
||||
if (pendingComments.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
QStringList commentLines = pendingComments.split('\n');
|
||||
QString commentPrefix = getCommentPrefix(currentLanguage);
|
||||
|
||||
for (const QString &commentLine : commentLines) {
|
||||
if (!commentLine.trimmed().isEmpty()) {
|
||||
result += commentPrefix + " " + commentLine.trimmed() + "\n";
|
||||
} else {
|
||||
result += "\n";
|
||||
}
|
||||
}
|
||||
pendingComments.clear();
|
||||
};
|
||||
|
||||
for (const QString &line : lines) {
|
||||
if (line.trimmed().startsWith("```")) {
|
||||
if (!inCodeBlock) {
|
||||
currentLanguage = detectLanguage(line);
|
||||
auto lineLanguage = detectLanguageFromLine(line);
|
||||
if (!lineLanguage.isEmpty()) {
|
||||
currentLanguage = lineLanguage;
|
||||
}
|
||||
|
||||
addPendingCommentsIfAny();
|
||||
|
||||
if (lineLanguage.isEmpty()) {
|
||||
// language not detected, so add direct output from model, if any
|
||||
result += line.trimmed().mid(3) + "\n"; // add the remainder of line after ```
|
||||
}
|
||||
}
|
||||
inCodeBlock = !inCodeBlock;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (inCodeBlock) {
|
||||
if (!pendingComments.isEmpty()) {
|
||||
QStringList commentLines = pendingComments.split('\n');
|
||||
QString commentPrefix = getCommentPrefix(currentLanguage);
|
||||
|
||||
for (const QString &commentLine : commentLines) {
|
||||
if (!commentLine.trimmed().isEmpty()) {
|
||||
result += commentPrefix + " " + commentLine.trimmed() + "\n";
|
||||
} else {
|
||||
result += "\n";
|
||||
}
|
||||
}
|
||||
pendingComments.clear();
|
||||
}
|
||||
result += line + "\n";
|
||||
} else {
|
||||
QString trimmed = line.trimmed();
|
||||
@ -64,45 +146,27 @@ QString CodeHandler::processText(QString text)
|
||||
}
|
||||
}
|
||||
|
||||
if (!pendingComments.isEmpty()) {
|
||||
QStringList commentLines = pendingComments.split('\n');
|
||||
QString commentPrefix = getCommentPrefix(currentLanguage);
|
||||
|
||||
for (const QString &commentLine : commentLines) {
|
||||
if (!commentLine.trimmed().isEmpty()) {
|
||||
result += commentPrefix + " " + commentLine.trimmed() + "\n";
|
||||
} else {
|
||||
result += "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
addPendingCommentsIfAny();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
QString CodeHandler::getCommentPrefix(const QString &language)
|
||||
{
|
||||
static const QHash<QString, QString> commentPrefixes
|
||||
= {{"python", "#"}, {"py", "#"}, {"lua", "--"}, {"javascript", "//"},
|
||||
{"js", "//"}, {"typescript", "//"}, {"ts", "//"}, {"cpp", "//"},
|
||||
{"c++", "//"}, {"c", "//"}, {"java", "//"}, {"csharp", "//"},
|
||||
{"cs", "//"}, {"php", "//"}, {"ruby", "#"}, {"rb", "#"},
|
||||
{"rust", "//"}, {"rs", "//"}, {"go", "//"}, {"swift", "//"},
|
||||
{"kotlin", "//"}, {"kt", "//"}, {"scala", "//"}, {"r", "#"},
|
||||
{"shell", "#"}, {"bash", "#"}, {"sh", "#"}, {"perl", "#"},
|
||||
{"pl", "#"}, {"haskell", "--"}, {"hs", "--"}};
|
||||
|
||||
return commentPrefixes.value(language.toLower(), "//");
|
||||
static const auto commentPrefixes = buildLanguageToCommentPrefixMap();
|
||||
return commentPrefixes.value(language, "//");
|
||||
}
|
||||
|
||||
QString CodeHandler::detectLanguage(const QString &line)
|
||||
QString CodeHandler::detectLanguageFromLine(const QString &line)
|
||||
{
|
||||
QString trimmed = line.trimmed();
|
||||
if (trimmed.length() <= 3) { // Если только ```
|
||||
return QString();
|
||||
}
|
||||
static const auto modelNameToLanguage = buildModelLanguageNameToLanguageMap();
|
||||
return modelNameToLanguage.value(line.trimmed().mid(3).trimmed(), "");
|
||||
}
|
||||
|
||||
return trimmed.mid(3).trimmed();
|
||||
QString CodeHandler::detectLanguageFromExtension(const QString &extension)
|
||||
{
|
||||
static const auto extensionToLanguage = buildExtensionToLanguageMap();
|
||||
return extensionToLanguage.value(extension.toLower(), "");
|
||||
}
|
||||
|
||||
const QRegularExpression &CodeHandler::getFullCodeBlockRegex()
|
||||
|
||||
@ -28,11 +28,20 @@ namespace QodeAssist {
|
||||
class CodeHandler
|
||||
{
|
||||
public:
|
||||
static QString processText(QString text);
|
||||
static QString processText(QString text, QString currentFileName);
|
||||
|
||||
/**
|
||||
* Detects language from line, or returns empty string if this was not possible
|
||||
*/
|
||||
static QString detectLanguageFromLine(const QString &line);
|
||||
|
||||
/**
|
||||
* Detects language file name, or returns empty string if this was not possible
|
||||
*/
|
||||
static QString detectLanguageFromExtension(const QString &extension);
|
||||
|
||||
private:
|
||||
static QString getCommentPrefix(const QString &language);
|
||||
static QString detectLanguage(const QString &line);
|
||||
|
||||
static const QRegularExpression &getFullCodeBlockRegex();
|
||||
static const QRegularExpression &getPartialStartBlockRegex();
|
||||
|
||||
@ -19,8 +19,8 @@
|
||||
|
||||
#include "ConfigurationManager.hpp"
|
||||
|
||||
#include <QTimer>
|
||||
#include <settings/ButtonAspect.hpp>
|
||||
#include <QTimer>
|
||||
|
||||
#include "QodeAssisttr.h"
|
||||
|
||||
@ -35,6 +35,28 @@ ConfigurationManager &ConfigurationManager::instance()
|
||||
void ConfigurationManager::init()
|
||||
{
|
||||
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)
|
||||
@ -64,6 +86,14 @@ void ConfigurationManager::setupConnections()
|
||||
connect(&m_generalSettings.ccPreset1SelectModel, &Button::clicked, this, &Config::selectModel);
|
||||
connect(
|
||||
&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()
|
||||
@ -81,10 +111,8 @@ void ConfigurationManager::selectProvider()
|
||||
: m_generalSettings.caProvider;
|
||||
|
||||
QTimer::singleShot(0, this, [this, providersList, &targetSettings] {
|
||||
m_generalSettings.showSelectionDialog(providersList,
|
||||
targetSettings,
|
||||
Tr::tr("Select LLM Provider"),
|
||||
Tr::tr("Providers:"));
|
||||
m_generalSettings.showSelectionDialog(
|
||||
providersList, targetSettings, Tr::tr("Select LLM Provider"), Tr::tr("Providers:"));
|
||||
});
|
||||
}
|
||||
|
||||
@ -137,19 +165,22 @@ void ConfigurationManager::selectTemplate()
|
||||
|
||||
const bool isCodeCompletion = (settingsButton == &m_generalSettings.ccSelectTemplate);
|
||||
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 ? m_templateManger.fimTemplatesNames()
|
||||
: m_templateManger.chatTemplatesNames();
|
||||
const auto templateList = isCodeCompletion || isPreset1
|
||||
? m_templateManger.getFimTemplatesForProvider(providerID)
|
||||
: m_templateManger.getChatTemplatesForProvider(providerID);
|
||||
|
||||
auto &targetSettings = isCodeCompletion ? m_generalSettings.ccTemplate
|
||||
: isPreset1 ? m_generalSettings.ccPreset1Template
|
||||
: m_generalSettings.caTemplate;
|
||||
|
||||
QTimer::singleShot(0, &m_generalSettings, [this, templateList, &targetSettings]() {
|
||||
m_generalSettings.showSelectionDialog(templateList,
|
||||
targetSettings,
|
||||
Tr::tr("Select Template"),
|
||||
Tr::tr("Templates:"));
|
||||
m_generalSettings.showSelectionDialog(
|
||||
templateList, targetSettings, Tr::tr("Select Template"), Tr::tr("Templates:"));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -36,6 +36,9 @@ public:
|
||||
|
||||
void init();
|
||||
|
||||
void updateTemplateDescription(const Utils::StringAspect &templateAspect);
|
||||
void updateAllTemplateDescriptions();
|
||||
|
||||
public slots:
|
||||
void selectProvider();
|
||||
void selectModel();
|
||||
|
||||
@ -23,27 +23,40 @@
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
|
||||
#include <llmcore/RequestConfig.hpp>
|
||||
#include <texteditor/textdocument.h>
|
||||
|
||||
#include "CodeHandler.hpp"
|
||||
#include "context/ContextManager.hpp"
|
||||
#include "context/DocumentContextReader.hpp"
|
||||
#include "llmcore/MessageBuilder.hpp"
|
||||
#include "context/Utils.hpp"
|
||||
#include "llmcore/PromptTemplateManager.hpp"
|
||||
#include "llmcore/ProvidersManager.hpp"
|
||||
#include "logger/Logger.hpp"
|
||||
#include "settings/CodeCompletionSettings.hpp"
|
||||
#include "settings/GeneralSettings.hpp"
|
||||
#include <llmcore/RequestConfig.hpp>
|
||||
|
||||
namespace QodeAssist {
|
||||
|
||||
LLMClientInterface::LLMClientInterface()
|
||||
: m_requestHandler(this)
|
||||
LLMClientInterface::LLMClientInterface(
|
||||
const Settings::GeneralSettings &generalSettings,
|
||||
const Settings::CodeCompletionSettings &completeSettings,
|
||||
LLMCore::IProviderRegistry &providerRegistry,
|
||||
LLMCore::IPromptProvider *promptProvider,
|
||||
LLMCore::RequestHandlerBase &requestHandler,
|
||||
Context::IDocumentReader &documentReader,
|
||||
IRequestPerformanceLogger &performanceLogger)
|
||||
: m_generalSettings(generalSettings)
|
||||
, m_completeSettings(completeSettings)
|
||||
, m_providerRegistry(providerRegistry)
|
||||
, m_promptProvider(promptProvider)
|
||||
, m_requestHandler(requestHandler)
|
||||
, m_documentReader(documentReader)
|
||||
, m_performanceLogger(performanceLogger)
|
||||
{
|
||||
connect(&m_requestHandler,
|
||||
&LLMCore::RequestHandler::completionReceived,
|
||||
this,
|
||||
&LLMClientInterface::sendCompletionToClient);
|
||||
connect(
|
||||
&m_requestHandler,
|
||||
&LLMCore::RequestHandler::completionReceived,
|
||||
this,
|
||||
&LLMClientInterface::sendCompletionToClient);
|
||||
}
|
||||
|
||||
Utils::FilePath LLMClientInterface::serverDeviceTemplate() const
|
||||
@ -75,7 +88,7 @@ void LLMClientInterface::sendData(const QByteArray &data)
|
||||
handleTextDocumentDidOpen(request);
|
||||
} else if (method == "getCompletionsCycling") {
|
||||
QString requestId = request["id"].toString();
|
||||
startTimeMeasurement(requestId);
|
||||
m_performanceLogger.startTimeMeasurement(requestId);
|
||||
handleCompletion(request);
|
||||
} else if (method == "$/cancelRequest") {
|
||||
handleCancelRequest(request);
|
||||
@ -146,43 +159,38 @@ void LLMClientInterface::handleExit(const QJsonObject &request)
|
||||
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)
|
||||
{
|
||||
const auto updatedContext = prepareContext(request);
|
||||
auto &completeSettings = Settings::codeCompletionSettings();
|
||||
auto &generalSettings = Settings::generalSettings();
|
||||
auto filePath = Context::extractFilePathFromRequest(request);
|
||||
auto documentInfo = m_documentReader.readDocument(filePath);
|
||||
if (!documentInfo.document) {
|
||||
LOG_MESSAGE("Error: Document is not available for" + filePath);
|
||||
return;
|
||||
}
|
||||
|
||||
bool isPreset1Active = isSpecifyCompletion(request);
|
||||
auto updatedContext = prepareContext(request, documentInfo);
|
||||
|
||||
const auto providerName = !isPreset1Active ? generalSettings.ccProvider()
|
||||
: generalSettings.ccPreset1Provider();
|
||||
const auto modelName = !isPreset1Active ? generalSettings.ccModel()
|
||||
: generalSettings.ccPreset1Model();
|
||||
const auto url = !isPreset1Active ? generalSettings.ccUrl() : generalSettings.ccPreset1Url();
|
||||
bool isPreset1Active
|
||||
= Context::ContextManager::isSpecifyCompletion(documentInfo, m_generalSettings);
|
||||
|
||||
const auto provider = LLMCore::ProvidersManager::instance().getProviderByName(providerName);
|
||||
const auto providerName = !isPreset1Active ? m_generalSettings.ccProvider()
|
||||
: m_generalSettings.ccPreset1Provider();
|
||||
const auto modelName = !isPreset1Active ? m_generalSettings.ccModel()
|
||||
: m_generalSettings.ccPreset1Model();
|
||||
const auto url = !isPreset1Active ? m_generalSettings.ccUrl()
|
||||
: m_generalSettings.ccPreset1Url();
|
||||
|
||||
const auto provider = m_providerRegistry.getProviderByName(providerName);
|
||||
|
||||
if (!provider) {
|
||||
LOG_MESSAGE(QString("No provider found with name: %1").arg(providerName));
|
||||
return;
|
||||
}
|
||||
|
||||
auto templateName = !isPreset1Active ? generalSettings.ccTemplate()
|
||||
: generalSettings.ccPreset1Template();
|
||||
auto templateName = !isPreset1Active ? m_generalSettings.ccTemplate()
|
||||
: m_generalSettings.ccPreset1Template();
|
||||
|
||||
auto promptTemplate = LLMCore::PromptTemplateManager::instance().getFimTemplateByName(
|
||||
templateName);
|
||||
auto promptTemplate = m_promptProvider->getTemplateByName(templateName);
|
||||
|
||||
if (!promptTemplate) {
|
||||
LOG_MESSAGE(QString("No template found with name: %1").arg(templateName));
|
||||
@ -194,44 +202,57 @@ void LLMClientInterface::handleCompletion(const QJsonObject &request)
|
||||
config.requestType = LLMCore::RequestType::CodeCompletion;
|
||||
config.provider = provider;
|
||||
config.promptTemplate = promptTemplate;
|
||||
config.url = QUrl(QString("%1%2").arg(
|
||||
url,
|
||||
promptTemplate->type() == LLMCore::TemplateType::Fim ? provider->completionEndpoint()
|
||||
: provider->chatEndpoint()));
|
||||
// TODO refactor networking
|
||||
if (provider->providerID() == LLMCore::ProviderID::GoogleAI) {
|
||||
QString stream = m_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(
|
||||
url,
|
||||
promptTemplate->type() == LLMCore::TemplateType::FIM ? provider->completionEndpoint()
|
||||
: provider->chatEndpoint()));
|
||||
config.providerRequest = {{"model", modelName}, {"stream", m_completeSettings.stream()}};
|
||||
}
|
||||
config.apiKey = provider->apiKey();
|
||||
|
||||
config.providerRequest = {{"model", modelName}, {"stream", completeSettings.stream()}};
|
||||
|
||||
config.multiLineCompletion = completeSettings.multiLineCompletion();
|
||||
config.multiLineCompletion = m_completeSettings.multiLineCompletion();
|
||||
|
||||
const auto stopWords = QJsonArray::fromStringList(config.promptTemplate->stopWords());
|
||||
if (!stopWords.isEmpty())
|
||||
config.providerRequest["stop"] = stopWords;
|
||||
|
||||
QString systemPrompt;
|
||||
if (completeSettings.useSystemPrompt())
|
||||
systemPrompt.append(completeSettings.systemPrompt());
|
||||
if (!updatedContext.fileContext.isEmpty())
|
||||
systemPrompt.append(updatedContext.fileContext);
|
||||
if (m_completeSettings.useSystemPrompt())
|
||||
systemPrompt.append(
|
||||
m_completeSettings.useUserMessageTemplateForCC()
|
||||
&& promptTemplate->type() == LLMCore::TemplateType::Chat
|
||||
? m_completeSettings.systemPromptForNonFimModels()
|
||||
: m_completeSettings.systemPrompt());
|
||||
if (updatedContext.fileContext.has_value())
|
||||
systemPrompt.append(updatedContext.fileContext.value());
|
||||
|
||||
QString userMessage;
|
||||
if (completeSettings.useUserMessageTemplateForCC() && promptTemplate->type() == LLMCore::TemplateType::Chat) {
|
||||
userMessage = completeSettings.userMessageTemplateForCC().arg(updatedContext.prefix, updatedContext.suffix);
|
||||
} else {
|
||||
userMessage = updatedContext.prefix;
|
||||
updatedContext.systemPrompt = systemPrompt;
|
||||
|
||||
if (promptTemplate->type() == LLMCore::TemplateType::Chat) {
|
||||
QString userMessage;
|
||||
if (m_completeSettings.useUserMessageTemplateForCC()) {
|
||||
userMessage = m_completeSettings.processMessageToFIM(
|
||||
updatedContext.prefix.value_or(""), updatedContext.suffix.value_or(""));
|
||||
} else {
|
||||
userMessage = updatedContext.prefix.value_or("") + updatedContext.suffix.value_or("");
|
||||
}
|
||||
|
||||
// TODO refactor add message
|
||||
QVector<LLMCore::Message> messages;
|
||||
messages.append({"user", userMessage});
|
||||
updatedContext.history = messages;
|
||||
}
|
||||
|
||||
auto message = LLMCore::MessageBuilder()
|
||||
.addSystemMessage(systemPrompt)
|
||||
.addUserMessage(userMessage)
|
||||
.addSuffix(updatedContext.suffix)
|
||||
.addTokenizer(promptTemplate);
|
||||
|
||||
message.saveTo(
|
||||
config.provider->prepareRequest(
|
||||
config.providerRequest,
|
||||
providerName == "Ollama" ? LLMCore::ProvidersApi::Ollama : LLMCore::ProvidersApi::OpenAI);
|
||||
|
||||
config.provider->prepareRequest(config.providerRequest, LLMCore::RequestType::CodeCompletion);
|
||||
promptTemplate,
|
||||
updatedContext,
|
||||
LLMCore::RequestType::CodeCompletion);
|
||||
|
||||
auto errors = config.provider->validateRequest(config.providerRequest, promptTemplate->type());
|
||||
if (!errors.isEmpty()) {
|
||||
@ -242,59 +263,32 @@ void LLMClientInterface::handleCompletion(const QJsonObject &request)
|
||||
m_requestHandler.sendLLMRequest(config, request);
|
||||
}
|
||||
|
||||
LLMCore::ContextData LLMClientInterface::prepareContext(const QJsonObject &request,
|
||||
const QStringView &accumulatedCompletion)
|
||||
LLMCore::ContextData LLMClientInterface::prepareContext(
|
||||
const QJsonObject &request, const Context::DocumentInfo &documentInfo)
|
||||
{
|
||||
QJsonObject params = request["params"].toObject();
|
||||
QJsonObject doc = params["doc"].toObject();
|
||||
QJsonObject position = doc["position"].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 LLMCore::ContextData{};
|
||||
}
|
||||
|
||||
int cursorPosition = position["character"].toInt();
|
||||
int lineNumber = position["line"].toInt();
|
||||
|
||||
Context::DocumentContextReader reader(textDocument);
|
||||
return reader.prepareContext(lineNumber, cursorPosition);
|
||||
Context::DocumentContextReader
|
||||
reader(documentInfo.document, documentInfo.mimeType, documentInfo.filePath);
|
||||
return reader.prepareContext(lineNumber, cursorPosition, m_completeSettings);
|
||||
}
|
||||
|
||||
Context::ProgrammingLanguage LLMClientInterface::getDocumentLanguage(const QJsonObject &request) const
|
||||
void LLMClientInterface::sendCompletionToClient(
|
||||
const QString &completion, const QJsonObject &request, bool isComplete)
|
||||
{
|
||||
QJsonObject params = request["params"].toObject();
|
||||
QJsonObject doc = params["doc"].toObject();
|
||||
QString uri = doc["uri"].toString();
|
||||
auto filePath = Context::extractFilePathFromRequest(request);
|
||||
auto documentInfo = m_documentReader.readDocument(filePath);
|
||||
bool isPreset1Active
|
||||
= Context::ContextManager::isSpecifyCompletion(documentInfo, m_generalSettings);
|
||||
|
||||
Utils::FilePath filePath = Utils::FilePath::fromString(QUrl(uri).toLocalFile());
|
||||
TextEditor::TextDocument *textDocument = TextEditor::TextDocument::textDocumentForFilePath(
|
||||
filePath);
|
||||
auto templateName = !isPreset1Active ? m_generalSettings.ccTemplate()
|
||||
: m_generalSettings.ccPreset1Template();
|
||||
|
||||
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,
|
||||
const QJsonObject &request,
|
||||
bool isComplete)
|
||||
{
|
||||
bool isPreset1Active = isSpecifyCompletion(request);
|
||||
|
||||
auto templateName = !isPreset1Active ? Settings::generalSettings().ccTemplate()
|
||||
: Settings::generalSettings().ccPreset1Template();
|
||||
|
||||
auto promptTemplate = LLMCore::PromptTemplateManager::instance().getFimTemplateByName(
|
||||
templateName);
|
||||
auto promptTemplate = m_promptProvider->getTemplateByName(templateName);
|
||||
|
||||
QJsonObject position = request["params"].toObject()["doc"].toObject()["position"].toObject();
|
||||
|
||||
@ -306,18 +300,18 @@ void LLMClientInterface::sendCompletionToClient(const QString &completion,
|
||||
QJsonArray completions;
|
||||
QJsonObject completionItem;
|
||||
|
||||
LOG_MESSAGE(QString("Completions before filter: \n%1").arg(completion));
|
||||
|
||||
QString processedCompletion
|
||||
= promptTemplate->type() == LLMCore::TemplateType::Chat
|
||||
&& Settings::codeCompletionSettings().smartProcessInstuctText()
|
||||
? CodeHandler::processText(completion)
|
||||
&& m_completeSettings.smartProcessInstuctText()
|
||||
? CodeHandler::processText(completion, Context::extractFilePathFromRequest(request))
|
||||
: completion;
|
||||
|
||||
completionItem[LanguageServerProtocol::textKey] = processedCompletion;
|
||||
QJsonObject range;
|
||||
range["start"] = position;
|
||||
QJsonObject end = position;
|
||||
end["character"] = position["character"].toInt() + processedCompletion.length();
|
||||
range["end"] = end;
|
||||
range["end"] = position;
|
||||
completionItem[LanguageServerProtocol::rangeKey] = range;
|
||||
completionItem[LanguageServerProtocol::positionKey] = position;
|
||||
completions.append(completionItem);
|
||||
@ -329,37 +323,15 @@ void LLMClientInterface::sendCompletionToClient(const QString &completion,
|
||||
QString("Completions: \n%1")
|
||||
.arg(QString::fromUtf8(QJsonDocument(completions).toJson(QJsonDocument::Indented))));
|
||||
|
||||
LOG_MESSAGE(QString("Full response: \n%1")
|
||||
.arg(QString::fromUtf8(QJsonDocument(response).toJson(QJsonDocument::Indented))));
|
||||
LOG_MESSAGE(
|
||||
QString("Full response: \n%1")
|
||||
.arg(QString::fromUtf8(QJsonDocument(response).toJson(QJsonDocument::Indented))));
|
||||
|
||||
QString requestId = request["id"].toString();
|
||||
endTimeMeasurement(requestId);
|
||||
m_performanceLogger.endTimeMeasurement(requestId);
|
||||
emit messageReceived(LanguageServerProtocol::JsonRpcMessage(response));
|
||||
}
|
||||
|
||||
void LLMClientInterface::startTimeMeasurement(const QString &requestId)
|
||||
{
|
||||
m_requestStartTimes[requestId] = QDateTime::currentMSecsSinceEpoch();
|
||||
}
|
||||
|
||||
void LLMClientInterface::endTimeMeasurement(const QString &requestId)
|
||||
{
|
||||
if (m_requestStartTimes.contains(requestId)) {
|
||||
qint64 startTime = m_requestStartTimes[requestId];
|
||||
qint64 endTime = QDateTime::currentMSecsSinceEpoch();
|
||||
qint64 totalTime = endTime - startTime;
|
||||
logPerformance(requestId, "TotalCompletionTime", totalTime);
|
||||
m_requestStartTimes.remove(requestId);
|
||||
}
|
||||
}
|
||||
|
||||
void LLMClientInterface::logPerformance(const QString &requestId,
|
||||
const QString &operation,
|
||||
qint64 elapsedMs)
|
||||
{
|
||||
LOG_MESSAGE(QString("Performance: %1 %2 took %3 ms").arg(requestId, operation).arg(elapsedMs));
|
||||
}
|
||||
|
||||
void LLMClientInterface::parseCurrentMessage() {}
|
||||
|
||||
} // namespace QodeAssist
|
||||
|
||||
@ -22,9 +22,15 @@
|
||||
#include <languageclient/languageclientinterface.h>
|
||||
#include <texteditor/texteditor.h>
|
||||
|
||||
#include <context/IDocumentReader.hpp>
|
||||
#include <context/ProgrammingLanguage.hpp>
|
||||
#include <llmcore/ContextData.hpp>
|
||||
#include <llmcore/IPromptProvider.hpp>
|
||||
#include <llmcore/IProviderRegistry.hpp>
|
||||
#include <llmcore/RequestHandler.hpp>
|
||||
#include <logger/IRequestPerformanceLogger.hpp>
|
||||
#include <settings/CodeCompletionSettings.hpp>
|
||||
#include <settings/GeneralSettings.hpp>
|
||||
|
||||
class QNetworkReply;
|
||||
class QNetworkAccessManager;
|
||||
@ -36,19 +42,27 @@ class LLMClientInterface : public LanguageClient::BaseClientInterface
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
LLMClientInterface();
|
||||
LLMClientInterface(
|
||||
const Settings::GeneralSettings &generalSettings,
|
||||
const Settings::CodeCompletionSettings &completeSettings,
|
||||
LLMCore::IProviderRegistry &providerRegistry,
|
||||
LLMCore::IPromptProvider *promptProvider,
|
||||
LLMCore::RequestHandlerBase &requestHandler,
|
||||
Context::IDocumentReader &documentReader,
|
||||
IRequestPerformanceLogger &performanceLogger);
|
||||
|
||||
Utils::FilePath serverDeviceTemplate() const override;
|
||||
|
||||
void sendCompletionToClient(const QString &completion,
|
||||
const QJsonObject &request,
|
||||
bool isComplete);
|
||||
void sendCompletionToClient(
|
||||
const QString &completion, const QJsonObject &request, bool isComplete);
|
||||
|
||||
void handleCompletion(const QJsonObject &request);
|
||||
|
||||
// exposed for tests
|
||||
void sendData(const QByteArray &data) override;
|
||||
|
||||
protected:
|
||||
void startImpl() override;
|
||||
void sendData(const QByteArray &data) override;
|
||||
void parseCurrentMessage() override;
|
||||
|
||||
private:
|
||||
@ -60,17 +74,16 @@ private:
|
||||
void handleCancelRequest(const QJsonObject &request);
|
||||
|
||||
LLMCore::ContextData prepareContext(
|
||||
const QJsonObject &request, const QStringView &accumulatedCompletion = QString{});
|
||||
Context::ProgrammingLanguage getDocumentLanguage(const QJsonObject &request) const;
|
||||
bool isSpecifyCompletion(const QJsonObject &request);
|
||||
const QJsonObject &request, const Context::DocumentInfo &documentInfo);
|
||||
|
||||
LLMCore::RequestHandler m_requestHandler;
|
||||
const Settings::CodeCompletionSettings &m_completeSettings;
|
||||
const Settings::GeneralSettings &m_generalSettings;
|
||||
LLMCore::IPromptProvider *m_promptProvider = nullptr;
|
||||
LLMCore::IProviderRegistry &m_providerRegistry;
|
||||
LLMCore::RequestHandlerBase &m_requestHandler;
|
||||
Context::IDocumentReader &m_documentReader;
|
||||
IRequestPerformanceLogger &m_performanceLogger;
|
||||
QElapsedTimer m_completionTimer;
|
||||
QMap<QString, qint64> m_requestStartTimes;
|
||||
|
||||
void startTimeMeasurement(const QString &requestId);
|
||||
void endTimeMeasurement(const QString &requestId);
|
||||
void logPerformance(const QString &requestId, const QString &operation, qint64 elapsedMs);
|
||||
};
|
||||
|
||||
} // namespace QodeAssist
|
||||
|
||||
@ -68,9 +68,10 @@ class GetCompletionParams : public LanguageServerProtocol::JsonObject
|
||||
public:
|
||||
static constexpr LanguageServerProtocol::Key docKey{"doc"};
|
||||
|
||||
GetCompletionParams(const LanguageServerProtocol::TextDocumentIdentifier &document,
|
||||
int version,
|
||||
const LanguageServerProtocol::Position &position)
|
||||
GetCompletionParams(
|
||||
const LanguageServerProtocol::TextDocumentIdentifier &document,
|
||||
int version,
|
||||
const LanguageServerProtocol::Position &position)
|
||||
{
|
||||
setTextDocument(document);
|
||||
setVersion(version);
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
"Id" : "qodeassist",
|
||||
"Name" : "QodeAssist",
|
||||
"Version" : "0.4.13",
|
||||
"Version" : "0.5.2",
|
||||
"Vendor" : "Petr Mironychev",
|
||||
"VendorId" : "petrmironychev",
|
||||
"Copyright" : "(C) ${IDE_COPYRIGHT_YEAR} Petr Mironychev, (C) ${IDE_COPYRIGHT_YEAR} The Qt Company Ltd",
|
||||
|
||||
@ -44,8 +44,8 @@ using namespace Core;
|
||||
|
||||
namespace QodeAssist {
|
||||
|
||||
QodeAssistClient::QodeAssistClient()
|
||||
: LanguageClient::Client(new LLMClientInterface())
|
||||
QodeAssistClient::QodeAssistClient(LLMClientInterface *clientInterface)
|
||||
: LanguageClient::Client(clientInterface)
|
||||
, m_recentCharCount(0)
|
||||
{
|
||||
setName("Qode Assist");
|
||||
@ -147,9 +147,10 @@ void QodeAssistClient::requestCompletions(TextEditor::TextEditorWidget *editor)
|
||||
return;
|
||||
|
||||
const FilePath filePath = editor->textDocument()->filePath();
|
||||
GetCompletionRequest request{{TextDocumentIdentifier(hostPathToServerUri(filePath)),
|
||||
documentVersion(filePath),
|
||||
Position(cursor.mainCursor())}};
|
||||
GetCompletionRequest request{
|
||||
{TextDocumentIdentifier(hostPathToServerUri(filePath)),
|
||||
documentVersion(filePath),
|
||||
Position(cursor.mainCursor())}};
|
||||
request.setResponseCallback([this, editor = QPointer<TextEditorWidget>(editor)](
|
||||
const GetCompletionRequest::Response &response) {
|
||||
QTC_ASSERT(editor, return);
|
||||
@ -188,8 +189,8 @@ void QodeAssistClient::scheduleRequest(TextEditor::TextEditorWidget *editor)
|
||||
it.value()->setProperty("cursorPosition", editor->textCursor().position());
|
||||
it.value()->start(Settings::codeCompletionSettings().startSuggestionTimer());
|
||||
}
|
||||
void QodeAssistClient::handleCompletions(const GetCompletionRequest::Response &response,
|
||||
TextEditor::TextEditorWidget *editor)
|
||||
void QodeAssistClient::handleCompletions(
|
||||
const GetCompletionRequest::Response &response, TextEditor::TextEditorWidget *editor)
|
||||
{
|
||||
if (response.error())
|
||||
log(*response.error());
|
||||
@ -267,18 +268,13 @@ void QodeAssistClient::setupConnections()
|
||||
openDocument(textDocument);
|
||||
};
|
||||
|
||||
m_documentOpenedConnection = connect(EditorManager::instance(),
|
||||
&EditorManager::documentOpened,
|
||||
this,
|
||||
openDoc);
|
||||
m_documentClosedConnection = connect(EditorManager::instance(),
|
||||
&EditorManager::documentClosed,
|
||||
this,
|
||||
[this](IDocument *document) {
|
||||
if (auto textDocument = qobject_cast<TextDocument *>(
|
||||
document))
|
||||
closeDocument(textDocument);
|
||||
});
|
||||
m_documentOpenedConnection
|
||||
= connect(EditorManager::instance(), &EditorManager::documentOpened, this, openDoc);
|
||||
m_documentClosedConnection = connect(
|
||||
EditorManager::instance(), &EditorManager::documentClosed, this, [this](IDocument *document) {
|
||||
if (auto textDocument = qobject_cast<TextDocument *>(document))
|
||||
closeDocument(textDocument);
|
||||
});
|
||||
|
||||
for (IDocument *doc : DocumentModel::openedDocuments())
|
||||
openDoc(doc);
|
||||
|
||||
@ -24,16 +24,18 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <languageclient/client.h>
|
||||
|
||||
#include "LLMClientInterface.hpp"
|
||||
#include "LSPCompletion.hpp"
|
||||
#include <languageclient/client.h>
|
||||
#include <llmcore/IPromptProvider.hpp>
|
||||
#include <llmcore/IProviderRegistry.hpp>
|
||||
|
||||
namespace QodeAssist {
|
||||
|
||||
class QodeAssistClient : public LanguageClient::Client
|
||||
{
|
||||
public:
|
||||
explicit QodeAssistClient();
|
||||
explicit QodeAssistClient(LLMClientInterface *clientInterface);
|
||||
~QodeAssistClient() override;
|
||||
|
||||
void openDocument(TextEditor::TextDocument *document) override;
|
||||
@ -43,8 +45,8 @@ public:
|
||||
|
||||
private:
|
||||
void scheduleRequest(TextEditor::TextEditorWidget *editor);
|
||||
void handleCompletions(const GetCompletionRequest::Response &response,
|
||||
TextEditor::TextEditorWidget *editor);
|
||||
void handleCompletions(
|
||||
const GetCompletionRequest::Response &response, TextEditor::TextEditorWidget *editor);
|
||||
void cancelRunningRequest(TextEditor::TextEditorWidget *editor);
|
||||
bool isEnabled(ProjectExplorer::Project *project) const;
|
||||
|
||||
|
||||
102
README.md
102
README.md
@ -3,7 +3,7 @@
|
||||

|
||||

|
||||

|
||||

|
||||
[](https://discord.gg/BGMkUsXUgf)
|
||||
|
||||
 QodeAssist is an AI-powered coding assistant plugin for Qt Creator. It provides intelligent code completion and suggestions for C++ and QML, leveraging large language models through local providers like Ollama. Enhance your coding productivity with context-aware AI assistance directly in your Qt development environment.
|
||||
|
||||
@ -27,16 +27,18 @@
|
||||
2. [Install plugin to QtCreator](#install-plugin-to-qtcreator)
|
||||
3. [Configure for Anthropic Claude](#configure-for-anthropic-claude)
|
||||
4. [Configure for OpenAI](#configure-for-openai)
|
||||
5. [Configure for using Ollama](#configure-for-using-ollama)
|
||||
6. [System Prompt Configuration](#system-prompt-configuration)
|
||||
7. [File Context Features](#file-context-features)
|
||||
8. [Template-Model Compatibility](#template-model-compatibility)
|
||||
9. [QtCreator Version Compatibility](#qtcreator-version-compatibility)
|
||||
10. [Development Progress](#development-progress)
|
||||
11. [Hotkeys](#hotkeys)
|
||||
12. [Troubleshooting](#troubleshooting)
|
||||
13. [Support the Development](#support-the-development-of-qodeassist)
|
||||
14. [How to Build](#how-to-build)
|
||||
5. [Configure for Mistral AI](#configure-for-mistral-ai)
|
||||
6. [Configure for Google AI](#configure-for-google-ai)
|
||||
7. [Configure for Ollama](#configure-for-ollama)
|
||||
8. [Configure for llama.cpp](#configure-for-llamacpp)
|
||||
9. [System Prompt Configuration](#system-prompt-configuration)
|
||||
10. [File Context Features](#file-context-features)
|
||||
11. [QtCreator Version Compatibility](#qtcreator-version-compatibility)
|
||||
12. [Development Progress](#development-progress)
|
||||
13. [Hotkeys](#hotkeys)
|
||||
14. [Troubleshooting](#troubleshooting)
|
||||
15. [Support the Development](#support-the-development-of-qodeassist)
|
||||
16. [How to Build](#how-to-build)
|
||||
|
||||
## Overview
|
||||
|
||||
@ -50,14 +52,19 @@
|
||||
- Automatic syncing with open editor files (optional)
|
||||
- Support for multiple LLM providers:
|
||||
- Ollama
|
||||
- llama.cpp
|
||||
- OpenAI
|
||||
- Anthropic Claude
|
||||
- LM Studio
|
||||
- OpenAI-compatible providers(eg. https://openrouter.ai)
|
||||
- Mistral AI
|
||||
- Google AI
|
||||
- OpenAI-compatible providers(eg. llama.cpp, https://openrouter.ai)
|
||||
- Extensive library of model-specific templates
|
||||
- Custom template support
|
||||
- Easy configuration and model selection
|
||||
|
||||
Join our Discord Community: Have questions or want to discuss QodeAssist? Join our [Discord server](https://discord.gg/BGMkUsXUgf) to connect with other users and get support!
|
||||
|
||||
<details>
|
||||
<summary>Code completion: (click to expand)</summary>
|
||||
<img src="https://github.com/user-attachments/assets/255a52f1-5cc0-4ca3-b05c-c4cf9cdbe25a" width="600" alt="QodeAssistPreview">
|
||||
@ -120,7 +127,33 @@
|
||||
<img width="829" alt="OpenAI Settings" src="https://github.com/user-attachments/assets/4716f790-6159-44d0-a8f4-565ccb6eb713" />
|
||||
</details>
|
||||
|
||||
## Configure for using Ollama
|
||||
## Configure for Mistral AI
|
||||
1. Open Qt Creator settings and navigate to the QodeAssist section
|
||||
2. Go to Provider Settings tab and configure Mistral AI api key
|
||||
3. Return to General tab and configure:
|
||||
- Set "Mistral AI" as the provider for code completion or/and chat assistant
|
||||
- Set the OpenAI URL (https://api.mistral.ai)
|
||||
- Select your preferred model (e.g., mistral-large-latest)
|
||||
- Choose the Mistral AI template for code completion or/and chat
|
||||
<details>
|
||||
<summary>Example of Mistral AI settings: (click to expand)</summary>
|
||||
<img width="829" alt="Mistral AI Settings" src="https://github.com/user-attachments/assets/1c5ed13b-a29b-43f7-b33f-2e05fdea540c" />
|
||||
</details>
|
||||
|
||||
## Configure for Google AI
|
||||
1. Open Qt Creator settings and navigate to the QodeAssist section
|
||||
2. Go to Provider Settings tab and configure Google AI api key
|
||||
3. Return to General tab and configure:
|
||||
- Set "Google AI" as the provider for code completion or/and chat assistant
|
||||
- Set the OpenAI URL (https://generativelanguage.googleapis.com/v1beta)
|
||||
- Select your preferred model (e.g., gemini-2.0-flash)
|
||||
- Choose the Google AI template
|
||||
<details>
|
||||
<summary>Example of Google AI settings: (click to expand)</summary>
|
||||
<img width="829" alt="Google AI Settings" src="https://github.com/user-attachments/assets/046ede65-a94d-496c-bc6c-41f3750be12a" />
|
||||
</details>
|
||||
|
||||
## Configure for Ollama
|
||||
|
||||
1. Install [Ollama](https://ollama.com). Make sure to review the system requirements before installation.
|
||||
2. Install a language models in Ollama via terminal. For example, you can run:
|
||||
@ -153,6 +186,18 @@ You're all set! QodeAssist is now ready to use in Qt Creator.
|
||||
<img width="824" alt="Ollama Settings" src="https://github.com/user-attachments/assets/ed64e03a-a923-467a-aa44-4f790e315b53" />
|
||||
</details>
|
||||
|
||||
## Configure for llama.cpp
|
||||
1. Open Qt Creator settings and navigate to the QodeAssist section
|
||||
2. Go to General tab and configure:
|
||||
- Set "llama.cpp" as the provider for code completion or/and chat assistant
|
||||
- Set the llama.cpp URL (e.g. http://localhost:8080)
|
||||
- Fill in model name
|
||||
- Choose template for model(e.g. llama.cpp FIM for any model with FIM support)
|
||||
<details>
|
||||
<summary>Example of llama.cpp settings: (click to expand)</summary>
|
||||
<img width="829" alt="llama.cpp Settings" src="https://github.com/user-attachments/assets/8c75602c-60f3-49ed-a7a9-d3c972061ea2" />
|
||||
</details>
|
||||
|
||||
## System Prompt Configuration
|
||||
|
||||
The plugin comes with default system prompts optimized for chat and instruct models, as these currently provide better results for code assistance. If you prefer using FIM (Fill-in-Middle) models, you can easily customize the system prompt in the settings.
|
||||
@ -190,25 +235,9 @@ Linked files provide persistent context throughout the conversation:
|
||||
- Supports automatic syncing with open editor files (can be enabled in settings)
|
||||
- Files can be added/removed at any time during the conversation
|
||||
|
||||
## Template-Model Compatibility
|
||||
|
||||
| Template | Compatible Models | Purpose |
|
||||
|----------|------------------|----------|
|
||||
| CodeLlama FIM | `codellama:code` | Code completion |
|
||||
| DeepSeekCoder FIM | `deepseek-coder-v2`, `deepseek-v2.5` | Code completion |
|
||||
| Ollama Auto FIM | `Any Ollama base/fim models` | Code completion |
|
||||
| Qwen FIM | `Qwen 2.5 models(exclude instruct)` | Code completion |
|
||||
| StarCoder2 FIM | `starcoder2 base model` | Code completion |
|
||||
| Alpaca | `starcoder2:instruct` | Chat assistance |
|
||||
| Basic Chat| `Messages without tokens` | Chat assistance |
|
||||
| ChatML | `Qwen 2.5 models(exclude base models)` | Chat assistance |
|
||||
| Llama2 | `llama2 model family`, `codellama:instruct` | Chat assistance |
|
||||
| Llama3 | `llama3 model family` | Chat assistance |
|
||||
| Ollama Auto Chat | `Any Ollama chat/instruct models` | Chat assistance |
|
||||
|
||||
## QtCreator Version Compatibility
|
||||
|
||||
- QtCreator 15.0.1 - 0.4.8 - 0.4.x
|
||||
- QtCreator 15.0.1 - 0.4.8 - 0.5.x
|
||||
- QtCreator 15.0.0 - 0.4.0 - 0.4.7
|
||||
- QtCreator 14.0.2 - 0.2.3 - 0.3.x
|
||||
- QtCreator 14.0.1 - 0.2.2 plugin version and below
|
||||
@ -239,16 +268,13 @@ If QodeAssist is having problems connecting to the LLM provider, please check th
|
||||
- For Ollama, the default is usually http://localhost:11434
|
||||
- For LM Studio, the default is usually http://localhost:1234
|
||||
|
||||
2. Check the endpoint:
|
||||
2. Confirm that the selected model and template are compatible:
|
||||
|
||||
Make sure the endpoint in the settings matches the one required by your provider
|
||||
- For Ollama, it should be /api/generate
|
||||
- For LM Studio and OpenAI compatible providers, it's usually /v1/chat/completions
|
||||
Ensure you've chosen the correct model in the "Select Models" option
|
||||
Verify that the selected prompt template matches the model you're using
|
||||
|
||||
3. Confirm that the selected model and template are compatible:
|
||||
|
||||
Ensure you've chosen the correct model in the "Select Models" option
|
||||
Verify that the selected prompt template matches the model you're using
|
||||
3. On Linux the prebuilt binaries support only ubuntu 22.04+ or simililliar os.
|
||||
If you need compatiblity with another os, you have to build manualy. our experiments and resolution you can check here: https://github.com/Palm1r/QodeAssist/issues/48
|
||||
|
||||
If you're still experiencing issues with QodeAssist, you can try resetting the settings to their default values:
|
||||
1. Open Qt Creator settings
|
||||
|
||||
28
Version.hpp
Normal file
28
Version.hpp
Normal file
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (C) 2025 Povilas Kanapickas <povilas@radix.lt>
|
||||
*
|
||||
* 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 <QtGlobal>
|
||||
|
||||
#define QODEASSIST_QT_CREATOR_VERSION \
|
||||
QT_VERSION_CHECK( \
|
||||
QODEASSIST_QT_CREATOR_VERSION_MAJOR, \
|
||||
QODEASSIST_QT_CREATOR_VERSION_MINOR, \
|
||||
QODEASSIST_QT_CREATOR_VERSION_PATCH)
|
||||
@ -23,16 +23,15 @@
|
||||
|
||||
namespace QodeAssist::Chat {
|
||||
|
||||
NavigationPanel::NavigationPanel() {
|
||||
NavigationPanel::NavigationPanel()
|
||||
{
|
||||
setDisplayName(tr("QodeAssist Chat"));
|
||||
setPriority(500);
|
||||
setId("QodeAssistChat");
|
||||
setActivationSequence(QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key_C));
|
||||
}
|
||||
|
||||
NavigationPanel::~NavigationPanel()
|
||||
{
|
||||
}
|
||||
NavigationPanel::~NavigationPanel() {}
|
||||
|
||||
Core::NavigationView NavigationPanel::createWidget()
|
||||
{
|
||||
@ -42,4 +41,4 @@ Core::NavigationView NavigationPanel::createWidget()
|
||||
return view;
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace QodeAssist::Chat
|
||||
|
||||
@ -19,8 +19,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <coreplugin/inavigationwidgetfactory.h>
|
||||
#include <QObject>
|
||||
|
||||
namespace QodeAssist::Chat {
|
||||
|
||||
@ -34,4 +34,4 @@ public:
|
||||
Core::NavigationView createWidget() override;
|
||||
};
|
||||
|
||||
}
|
||||
} // namespace QodeAssist::Chat
|
||||
|
||||
@ -3,6 +3,8 @@ add_library(Context STATIC
|
||||
ChangesManager.h ChangesManager.cpp
|
||||
ContextManager.hpp ContextManager.cpp
|
||||
ContentFile.hpp
|
||||
DocumentReaderQtCreator.hpp
|
||||
IDocumentReader.hpp
|
||||
TokenUtils.hpp TokenUtils.cpp
|
||||
ProgrammingLanguage.hpp ProgrammingLanguage.cpp
|
||||
)
|
||||
|
||||
@ -30,17 +30,12 @@ ChangesManager &ChangesManager::instance()
|
||||
|
||||
ChangesManager::ChangesManager()
|
||||
: QObject(nullptr)
|
||||
{
|
||||
}
|
||||
{}
|
||||
|
||||
ChangesManager::~ChangesManager()
|
||||
{
|
||||
}
|
||||
ChangesManager::~ChangesManager() {}
|
||||
|
||||
void ChangesManager::addChange(TextEditor::TextDocument *document,
|
||||
int position,
|
||||
int charsRemoved,
|
||||
int charsAdded)
|
||||
void ChangesManager::addChange(
|
||||
TextEditor::TextDocument *document, int position, int charsRemoved, int charsAdded)
|
||||
{
|
||||
auto &documentQueue = m_documentChanges[document];
|
||||
|
||||
@ -51,9 +46,10 @@ void ChangesManager::addChange(TextEditor::TextDocument *document,
|
||||
|
||||
ChangeInfo change{fileName, lineNumber, lineContent};
|
||||
|
||||
auto it = std::find_if(documentQueue.begin(),
|
||||
documentQueue.end(),
|
||||
[lineNumber](const ChangeInfo &c) { return c.lineNumber == lineNumber; });
|
||||
auto it
|
||||
= std::find_if(documentQueue.begin(), documentQueue.end(), [lineNumber](const ChangeInfo &c) {
|
||||
return c.lineNumber == lineNumber;
|
||||
});
|
||||
|
||||
if (it != documentQueue.end()) {
|
||||
it->lineContent = lineContent;
|
||||
|
||||
@ -19,11 +19,11 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <texteditor/textdocument.h>
|
||||
#include <QDateTime>
|
||||
#include <QHash>
|
||||
#include <QQueue>
|
||||
#include <QTimer>
|
||||
#include <texteditor/textdocument.h>
|
||||
|
||||
namespace QodeAssist::Context {
|
||||
|
||||
@ -41,10 +41,8 @@ public:
|
||||
|
||||
static ChangesManager &instance();
|
||||
|
||||
void addChange(TextEditor::TextDocument *document,
|
||||
int position,
|
||||
int charsRemoved,
|
||||
int charsAdded);
|
||||
void addChange(
|
||||
TextEditor::TextDocument *document, int position, int charsRemoved, int charsAdded);
|
||||
QString getRecentChangesContext(const TextEditor::TextDocument *currentDocument) const;
|
||||
|
||||
private:
|
||||
|
||||
@ -21,8 +21,12 @@
|
||||
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QJsonObject>
|
||||
#include <QTextStream>
|
||||
|
||||
#include "GeneralSettings.hpp"
|
||||
#include "Logger.hpp"
|
||||
|
||||
namespace QodeAssist::Context {
|
||||
|
||||
ContextManager &ContextManager::instance()
|
||||
@ -64,4 +68,24 @@ ContentFile ContextManager::createContentFile(const QString &filePath) const
|
||||
return contentFile;
|
||||
}
|
||||
|
||||
ProgrammingLanguage ContextManager::getDocumentLanguage(const DocumentInfo &documentInfo)
|
||||
{
|
||||
if (!documentInfo.document) {
|
||||
LOG_MESSAGE("Error: Document is not available for" + documentInfo.filePath);
|
||||
return Context::ProgrammingLanguage::Unknown;
|
||||
}
|
||||
|
||||
return Context::ProgrammingLanguageUtils::fromMimeType(documentInfo.mimeType);
|
||||
}
|
||||
|
||||
bool ContextManager::isSpecifyCompletion(
|
||||
const DocumentInfo &documentInfo, const Settings::GeneralSettings &generalSettings)
|
||||
{
|
||||
Context::ProgrammingLanguage documentLanguage = getDocumentLanguage(documentInfo);
|
||||
Context::ProgrammingLanguage preset1Language = Context::ProgrammingLanguageUtils::fromString(
|
||||
generalSettings.preset1Language.displayForIndex(generalSettings.preset1Language()));
|
||||
|
||||
return generalSettings.specifyPreset1() && documentLanguage == preset1Language;
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Context
|
||||
|
||||
@ -23,6 +23,9 @@
|
||||
#include <QString>
|
||||
|
||||
#include "ContentFile.hpp"
|
||||
#include "IDocumentReader.hpp"
|
||||
#include "ProgrammingLanguage.hpp"
|
||||
#include "settings/GeneralSettings.hpp"
|
||||
|
||||
namespace QodeAssist::Context {
|
||||
|
||||
@ -35,6 +38,10 @@ public:
|
||||
QString readFile(const QString &filePath) const;
|
||||
QList<ContentFile> getContentFiles(const QStringList &filePaths) const;
|
||||
|
||||
static ProgrammingLanguage getDocumentLanguage(const DocumentInfo &documentInfo);
|
||||
static bool isSpecifyCompletion(
|
||||
const DocumentInfo &documentInfo, const Settings::GeneralSettings &generalSettings);
|
||||
|
||||
private:
|
||||
explicit ContextManager(QObject *parent = nullptr);
|
||||
~ContextManager() = default;
|
||||
|
||||
@ -19,9 +19,9 @@
|
||||
|
||||
#include "DocumentContextReader.hpp"
|
||||
|
||||
#include <languageserverprotocol/lsptypes.h>
|
||||
#include <QFileInfo>
|
||||
#include <QTextBlock>
|
||||
#include <languageserverprotocol/lsptypes.h>
|
||||
|
||||
#include "CodeCompletionSettings.hpp"
|
||||
|
||||
@ -41,17 +41,18 @@ const QRegularExpression &getNameRegex()
|
||||
|
||||
const QRegularExpression &getCommentRegex()
|
||||
{
|
||||
static const QRegularExpression
|
||||
commentRegex(R"((/\*[\s\S]*?\*/|//.*$|#.*$|//{2,}[\s\S]*?//{2,}))",
|
||||
QRegularExpression::MultilineOption);
|
||||
static const QRegularExpression commentRegex(
|
||||
R"((/\*[\s\S]*?\*/|//.*$|#.*$|//{2,}[\s\S]*?//{2,}))", QRegularExpression::MultilineOption);
|
||||
return commentRegex;
|
||||
}
|
||||
|
||||
namespace QodeAssist::Context {
|
||||
|
||||
DocumentContextReader::DocumentContextReader(TextEditor::TextDocument *textDocument)
|
||||
: m_textDocument(textDocument)
|
||||
, m_document(textDocument->document())
|
||||
DocumentContextReader::DocumentContextReader(
|
||||
QTextDocument *document, const QString &mimeType, const QString &filePath)
|
||||
: m_document(document)
|
||||
, m_mimeType(mimeType)
|
||||
, m_filePath(filePath)
|
||||
{
|
||||
m_copyrightInfo = findCopyright();
|
||||
}
|
||||
@ -80,26 +81,26 @@ QString DocumentContextReader::getLineText(int lineNumber, int cursorPosition) c
|
||||
return QString();
|
||||
}
|
||||
|
||||
QString DocumentContextReader::getContextBefore(int lineNumber,
|
||||
int cursorPosition,
|
||||
int linesCount) const
|
||||
QString DocumentContextReader::getContextBefore(
|
||||
int lineNumber, int cursorPosition, int linesCount) const
|
||||
{
|
||||
int effectiveStartLine;
|
||||
int startLine = lineNumber - linesCount + 1;
|
||||
if (m_copyrightInfo.found) {
|
||||
effectiveStartLine = qMax(m_copyrightInfo.endLine + 1, lineNumber - linesCount);
|
||||
} else {
|
||||
effectiveStartLine = qMax(0, lineNumber - linesCount);
|
||||
startLine = qMax(m_copyrightInfo.endLine + 1, startLine);
|
||||
}
|
||||
|
||||
return getContextBetween(effectiveStartLine, lineNumber, cursorPosition);
|
||||
return getContextBetween(startLine, -1, lineNumber, cursorPosition);
|
||||
}
|
||||
|
||||
QString DocumentContextReader::getContextAfter(int lineNumber,
|
||||
int cursorPosition,
|
||||
int linesCount) const
|
||||
QString DocumentContextReader::getContextAfter(
|
||||
int lineNumber, int cursorPosition, int linesCount) const
|
||||
{
|
||||
int endLine = qMin(m_document->blockCount() - 1, lineNumber + linesCount);
|
||||
return getContextBetween(lineNumber + 1, endLine, cursorPosition);
|
||||
int endLine = lineNumber + linesCount - 1;
|
||||
if (m_copyrightInfo.found && m_copyrightInfo.endLine >= lineNumber) {
|
||||
lineNumber = m_copyrightInfo.endLine + 1;
|
||||
cursorPosition = -1;
|
||||
}
|
||||
return getContextBetween(lineNumber, cursorPosition, endLine, -1);
|
||||
}
|
||||
|
||||
QString DocumentContextReader::readWholeFileBefore(int lineNumber, int cursorPosition) const
|
||||
@ -109,31 +110,26 @@ QString DocumentContextReader::readWholeFileBefore(int lineNumber, int cursorPos
|
||||
startLine = m_copyrightInfo.endLine + 1;
|
||||
}
|
||||
|
||||
startLine = qMin(startLine, lineNumber);
|
||||
|
||||
QString result = getContextBetween(startLine, lineNumber, cursorPosition);
|
||||
|
||||
return result;
|
||||
return getContextBetween(startLine, -1, lineNumber, cursorPosition);
|
||||
}
|
||||
|
||||
QString DocumentContextReader::readWholeFileAfter(int lineNumber, int cursorPosition) const
|
||||
{
|
||||
return getContextBetween(lineNumber, m_document->blockCount() - 1, cursorPosition);
|
||||
int endLine = m_document->blockCount() - 1;
|
||||
if (m_copyrightInfo.found && m_copyrightInfo.endLine >= lineNumber) {
|
||||
lineNumber = m_copyrightInfo.endLine + 1;
|
||||
cursorPosition = -1;
|
||||
}
|
||||
return getContextBetween(lineNumber, cursorPosition, endLine, -1);
|
||||
}
|
||||
|
||||
QString DocumentContextReader::getLanguageAndFileInfo() const
|
||||
{
|
||||
if (!m_textDocument)
|
||||
return QString();
|
||||
|
||||
QString language = LanguageServerProtocol::TextDocumentItem::mimeTypeToLanguageId(
|
||||
m_textDocument->mimeType());
|
||||
QString mimeType = m_textDocument->mimeType();
|
||||
QString filePath = m_textDocument->filePath().toString();
|
||||
QString fileExtension = QFileInfo(filePath).suffix();
|
||||
QString language = LanguageServerProtocol::TextDocumentItem::mimeTypeToLanguageId(m_mimeType);
|
||||
QString fileExtension = QFileInfo(m_filePath).suffix();
|
||||
|
||||
return QString("Language: %1 (MIME: %2) filepath: %3(%4)\n\n")
|
||||
.arg(language, mimeType, filePath, fileExtension);
|
||||
.arg(language, m_mimeType, m_filePath, fileExtension);
|
||||
}
|
||||
|
||||
CopyrightInfo DocumentContextReader::findCopyright()
|
||||
@ -179,20 +175,74 @@ CopyrightInfo DocumentContextReader::findCopyright()
|
||||
return result;
|
||||
}
|
||||
|
||||
QString DocumentContextReader::getContextBetween(int startLine,
|
||||
int endLine,
|
||||
int cursorPosition) const
|
||||
QString DocumentContextReader::getContextBetween(
|
||||
int startLine, int startCursorPosition, int endLine, int endCursorPosition) const
|
||||
{
|
||||
QString context;
|
||||
for (int i = startLine; i <= endLine; ++i) {
|
||||
QTextBlock block = m_document->findBlockByNumber(i);
|
||||
|
||||
startLine = qMax(startLine, 0);
|
||||
endLine = qMin(endLine, m_document->blockCount() - 1);
|
||||
|
||||
if (startLine > endLine) {
|
||||
return context;
|
||||
}
|
||||
|
||||
if (startLine == endLine) {
|
||||
auto block = m_document->findBlockByNumber(startLine);
|
||||
if (!block.isValid()) {
|
||||
break;
|
||||
return context;
|
||||
}
|
||||
if (i == endLine) {
|
||||
context += block.text().left(cursorPosition);
|
||||
|
||||
auto text = block.text();
|
||||
|
||||
if (startCursorPosition < 0) {
|
||||
startCursorPosition = 0;
|
||||
}
|
||||
if (endCursorPosition < 0) {
|
||||
endCursorPosition = text.size();
|
||||
}
|
||||
|
||||
if (startCursorPosition >= endCursorPosition) {
|
||||
return context;
|
||||
}
|
||||
|
||||
return text.mid(startCursorPosition, endCursorPosition - startCursorPosition);
|
||||
}
|
||||
|
||||
// first line
|
||||
{
|
||||
auto block = m_document->findBlockByNumber(startLine);
|
||||
if (!block.isValid()) {
|
||||
return context;
|
||||
}
|
||||
auto text = block.text();
|
||||
if (startCursorPosition < 0) {
|
||||
context += text + "\n";
|
||||
} else {
|
||||
context += block.text() + "\n";
|
||||
context += text.right(text.size() - startCursorPosition) + "\n";
|
||||
}
|
||||
}
|
||||
|
||||
// intermediate lines, if any
|
||||
for (int i = startLine + 1; i <= endLine - 1; ++i) {
|
||||
auto block = m_document->findBlockByNumber(i);
|
||||
if (!block.isValid()) {
|
||||
return context;
|
||||
}
|
||||
context += block.text() + "\n";
|
||||
}
|
||||
|
||||
// last line
|
||||
{
|
||||
auto block = m_document->findBlockByNumber(endLine);
|
||||
if (!block.isValid()) {
|
||||
return context;
|
||||
}
|
||||
auto text = block.text();
|
||||
if (endCursorPosition < 0) {
|
||||
context += text;
|
||||
} else {
|
||||
context += text.left(endCursorPosition);
|
||||
}
|
||||
}
|
||||
|
||||
@ -204,47 +254,31 @@ CopyrightInfo DocumentContextReader::copyrightInfo() const
|
||||
return m_copyrightInfo;
|
||||
}
|
||||
|
||||
LLMCore::ContextData DocumentContextReader::prepareContext(int lineNumber, int cursorPosition) const
|
||||
LLMCore::ContextData DocumentContextReader::prepareContext(
|
||||
int lineNumber, int cursorPosition, const Settings::CodeCompletionSettings &settings) const
|
||||
{
|
||||
QString contextBefore = getContextBefore(lineNumber, cursorPosition);
|
||||
QString contextAfter = getContextAfter(lineNumber, cursorPosition);
|
||||
QString contextBefore;
|
||||
QString contextAfter;
|
||||
if (settings.readFullFile()) {
|
||||
contextBefore = readWholeFileBefore(lineNumber, cursorPosition);
|
||||
contextAfter = readWholeFileAfter(lineNumber, cursorPosition);
|
||||
} else {
|
||||
// Note that readStrings{After,Before}Cursor include current line, but linesCount argument of
|
||||
// getContext{After,Before} do not
|
||||
contextBefore
|
||||
= getContextBefore(lineNumber, cursorPosition, settings.readStringsBeforeCursor() + 1);
|
||||
contextAfter
|
||||
= getContextAfter(lineNumber, cursorPosition, settings.readStringsAfterCursor() + 1);
|
||||
}
|
||||
|
||||
QString fileContext;
|
||||
if (Settings::codeCompletionSettings().useFilePathInContext())
|
||||
fileContext.append("\n ").append(getLanguageAndFileInfo());
|
||||
fileContext.append("\n ").append(getLanguageAndFileInfo());
|
||||
|
||||
if (Settings::codeCompletionSettings().useProjectChangesCache())
|
||||
fileContext.append("\n ").append(
|
||||
ChangesManager::instance().getRecentChangesContext(m_textDocument));
|
||||
if (settings.useProjectChangesCache())
|
||||
fileContext.append("Recent Project Changes Context:\n ")
|
||||
.append(ChangesManager::instance().getRecentChangesContext(m_textDocument));
|
||||
|
||||
return {contextBefore, contextAfter, fileContext};
|
||||
}
|
||||
|
||||
QString DocumentContextReader::getContextBefore(int lineNumber, int cursorPosition) const
|
||||
{
|
||||
if (Settings::codeCompletionSettings().readFullFile()) {
|
||||
return readWholeFileBefore(lineNumber, cursorPosition);
|
||||
} else {
|
||||
int effectiveStartLine;
|
||||
int beforeCursor = Settings::codeCompletionSettings().readStringsBeforeCursor();
|
||||
if (m_copyrightInfo.found) {
|
||||
effectiveStartLine = qMax(m_copyrightInfo.endLine + 1, lineNumber - beforeCursor);
|
||||
} else {
|
||||
effectiveStartLine = qMax(0, lineNumber - beforeCursor);
|
||||
}
|
||||
return getContextBetween(effectiveStartLine, lineNumber, cursorPosition);
|
||||
}
|
||||
}
|
||||
|
||||
QString DocumentContextReader::getContextAfter(int lineNumber, int cursorPosition) const
|
||||
{
|
||||
if (Settings::codeCompletionSettings().readFullFile()) {
|
||||
return readWholeFileAfter(lineNumber, cursorPosition);
|
||||
} else {
|
||||
int endLine = qMin(m_document->blockCount() - 1,
|
||||
lineNumber + Settings::codeCompletionSettings().readStringsAfterCursor());
|
||||
return getContextBetween(lineNumber + 1, endLine, -1);
|
||||
}
|
||||
return {.prefix = contextBefore, .suffix = contextAfter, .fileContext = fileContext};
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Context
|
||||
|
||||
@ -23,6 +23,7 @@
|
||||
#include <QTextDocument>
|
||||
|
||||
#include <llmcore/ContextData.hpp>
|
||||
#include <settings/CodeCompletionSettings.hpp>
|
||||
|
||||
namespace QodeAssist::Context {
|
||||
|
||||
@ -36,28 +37,55 @@ struct CopyrightInfo
|
||||
class DocumentContextReader
|
||||
{
|
||||
public:
|
||||
DocumentContextReader(TextEditor::TextDocument *textDocument);
|
||||
DocumentContextReader(
|
||||
QTextDocument *m_document, const QString &mimeType, const QString &filePath);
|
||||
|
||||
QString getLineText(int lineNumber, int cursorPosition = -1) const;
|
||||
|
||||
/**
|
||||
* @brief Retrieves @c linesCount lines of context ending at @c lineNumber at
|
||||
* @c cursorPosition in that line. The line at @c lineNumber is inclusive regardless of
|
||||
* @c cursorPosition.
|
||||
*/
|
||||
QString getContextBefore(int lineNumber, int cursorPosition, int linesCount) const;
|
||||
|
||||
/**
|
||||
* @brief Retrieves @c linesCount lines of context starting at @c lineNumber at
|
||||
* @c cursorPosition in that line. The line at @c lineNumber is inclusive regardless of
|
||||
* @c cursorPosition.
|
||||
*/
|
||||
QString getContextAfter(int lineNumber, int cursorPosition, int linesCount) const;
|
||||
|
||||
/**
|
||||
* @brief Retrieves whole file ending at @c lineNumber at @c cursorPosition in that line.
|
||||
*/
|
||||
QString readWholeFileBefore(int lineNumber, int cursorPosition) const;
|
||||
|
||||
/**
|
||||
* @brief Retrieves whole file starting at @c lineNumber at @c cursorPosition in that line.
|
||||
*/
|
||||
QString readWholeFileAfter(int lineNumber, int cursorPosition) const;
|
||||
|
||||
QString getLanguageAndFileInfo() const;
|
||||
CopyrightInfo findCopyright();
|
||||
QString getContextBetween(int startLine, int endLine, int cursorPosition) const;
|
||||
QString getContextBetween(
|
||||
int startLine, int startCursorPosition, int endLine, int endCursorPosition) const;
|
||||
|
||||
CopyrightInfo copyrightInfo() const;
|
||||
|
||||
LLMCore::ContextData prepareContext(int lineNumber, int cursorPosition) const;
|
||||
|
||||
private:
|
||||
QString getContextBefore(int lineNumber, int cursorPosition) const;
|
||||
QString getContextAfter(int lineNumber, int cursorPosition) const;
|
||||
LLMCore::ContextData prepareContext(
|
||||
int lineNumber, int cursorPosition, const Settings::CodeCompletionSettings &settings) const;
|
||||
|
||||
private:
|
||||
TextEditor::TextDocument *m_textDocument;
|
||||
QTextDocument *m_document;
|
||||
QString m_mimeType;
|
||||
QString m_filePath;
|
||||
|
||||
// Used to omit copyright headers from context. If context would otherwise include copyright
|
||||
// header it is excluded by deleting it from the returned context. This means, that the
|
||||
// returned context may contain less information than requested. If the cursor is within copyright
|
||||
// header, then the context may be empty if the context window is small.
|
||||
CopyrightInfo m_copyrightInfo;
|
||||
};
|
||||
|
||||
|
||||
45
context/DocumentReaderQtCreator.hpp
Normal file
45
context/DocumentReaderQtCreator.hpp
Normal file
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright (C) 2025 Povilas Kanapickas
|
||||
*
|
||||
* 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 "IDocumentReader.hpp"
|
||||
|
||||
#include <texteditor/textdocument.h>
|
||||
|
||||
namespace QodeAssist::Context {
|
||||
|
||||
class DocumentReaderQtCreator : public IDocumentReader
|
||||
{
|
||||
public:
|
||||
DocumentInfo readDocument(const QString &path) const override
|
||||
{
|
||||
auto *textDocument = TextEditor::TextDocument::textDocumentForFilePath(
|
||||
Utils::FilePath::fromString(path));
|
||||
if (!textDocument) {
|
||||
return {};
|
||||
}
|
||||
return {
|
||||
.document = textDocument->document(),
|
||||
.mimeType = textDocument->mimeType(),
|
||||
.filePath = path};
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Context
|
||||
41
context/IDocumentReader.hpp
Normal file
41
context/IDocumentReader.hpp
Normal file
@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (C) 2025 Povilas Kanapickas
|
||||
*
|
||||
* 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 <QTextDocument>
|
||||
|
||||
namespace QodeAssist::Context {
|
||||
|
||||
struct DocumentInfo
|
||||
{
|
||||
QTextDocument *document = nullptr; // not owned
|
||||
QString mimeType;
|
||||
QString filePath;
|
||||
};
|
||||
|
||||
class IDocumentReader
|
||||
{
|
||||
public:
|
||||
virtual ~IDocumentReader() = default;
|
||||
|
||||
virtual DocumentInfo readDocument(const QString &path) const = 0;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Context
|
||||
@ -21,7 +21,7 @@
|
||||
|
||||
namespace QodeAssist::Context {
|
||||
|
||||
int TokenUtils::estimateTokens(const QString& text)
|
||||
int TokenUtils::estimateTokens(const QString &text)
|
||||
{
|
||||
if (text.isEmpty()) {
|
||||
return 0;
|
||||
@ -31,7 +31,7 @@ int TokenUtils::estimateTokens(const QString& text)
|
||||
return text.length() / 4;
|
||||
}
|
||||
|
||||
int TokenUtils::estimateFileTokens(const Context::ContentFile& file)
|
||||
int TokenUtils::estimateFileTokens(const Context::ContentFile &file)
|
||||
{
|
||||
int total = 0;
|
||||
|
||||
@ -42,13 +42,13 @@ int TokenUtils::estimateFileTokens(const Context::ContentFile& file)
|
||||
return total;
|
||||
}
|
||||
|
||||
int TokenUtils::estimateFilesTokens(const QList<Context::ContentFile>& files)
|
||||
int TokenUtils::estimateFilesTokens(const QList<Context::ContentFile> &files)
|
||||
{
|
||||
int total = 0;
|
||||
for (const auto& file : files) {
|
||||
for (const auto &file : files) {
|
||||
total += estimateFileTokens(file);
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace QodeAssist::Context
|
||||
|
||||
@ -19,18 +19,18 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
#include "ContentFile.hpp"
|
||||
#include <QList>
|
||||
#include <QString>
|
||||
|
||||
namespace QodeAssist::Context {
|
||||
|
||||
class TokenUtils
|
||||
{
|
||||
public:
|
||||
static int estimateTokens(const QString& text);
|
||||
static int estimateFileTokens(const Context::ContentFile& file);
|
||||
static int estimateFilesTokens(const QList<Context::ContentFile>& files);
|
||||
static int estimateTokens(const QString &text);
|
||||
static int estimateFileTokens(const Context::ContentFile &file);
|
||||
static int estimateFilesTokens(const QList<Context::ContentFile> &files);
|
||||
};
|
||||
|
||||
}
|
||||
} // namespace QodeAssist::Context
|
||||
|
||||
34
context/Utils.hpp
Normal file
34
context/Utils.hpp
Normal file
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (C) 2025 Povilas Kanapickas <povilas@radix.lt>
|
||||
*
|
||||
* This file is part of QodeAssist.
|
||||
*
|
||||
* QodeAssist is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* QodeAssist is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QJsonObject>
|
||||
|
||||
namespace QodeAssist::Context {
|
||||
|
||||
inline QString extractFilePathFromRequest(const QJsonObject &request)
|
||||
{
|
||||
QJsonObject params = request["params"].toObject();
|
||||
QJsonObject doc = params["doc"].toObject();
|
||||
QString uri = doc["uri"].toString();
|
||||
return QUrl(uri).toLocalFile();
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Context
|
||||
@ -3,14 +3,19 @@ add_library(LLMCore STATIC
|
||||
Provider.hpp
|
||||
ProvidersManager.hpp ProvidersManager.cpp
|
||||
ContextData.hpp
|
||||
IPromptProvider.hpp
|
||||
IProviderRegistry.hpp
|
||||
PromptProviderChat.hpp
|
||||
PromptProviderFim.hpp
|
||||
PromptTemplate.hpp
|
||||
PromptTemplateManager.hpp PromptTemplateManager.cpp
|
||||
RequestConfig.hpp
|
||||
RequestHandlerBase.hpp RequestHandlerBase.cpp
|
||||
RequestHandler.hpp RequestHandler.cpp
|
||||
OllamaMessage.hpp OllamaMessage.cpp
|
||||
OpenAIMessage.hpp OpenAIMessage.cpp
|
||||
ValidationUtils.hpp ValidationUtils.cpp
|
||||
MessageBuilder.hpp MessageBuilder.cpp
|
||||
ProviderID.hpp
|
||||
)
|
||||
|
||||
target_link_libraries(LLMCore
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
/*
|
||||
/*
|
||||
* Copyright (C) 2024 Petr Mironychev
|
||||
*
|
||||
* This file is part of QodeAssist.
|
||||
@ -20,14 +20,29 @@
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
#include <QVector>
|
||||
|
||||
namespace QodeAssist::LLMCore {
|
||||
|
||||
struct Message
|
||||
{
|
||||
QString role;
|
||||
QString content;
|
||||
|
||||
// clang-format off
|
||||
bool operator==(const Message&) const = default;
|
||||
// clang-format on
|
||||
};
|
||||
|
||||
struct ContextData
|
||||
{
|
||||
QString prefix;
|
||||
QString suffix;
|
||||
QString fileContext;
|
||||
std::optional<QString> systemPrompt;
|
||||
std::optional<QString> prefix;
|
||||
std::optional<QString> suffix;
|
||||
std::optional<QString> fileContext;
|
||||
std::optional<QVector<Message>> history;
|
||||
|
||||
bool operator==(const ContextData &) const = default;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::LLMCore
|
||||
|
||||
39
llmcore/IPromptProvider.hpp
Normal file
39
llmcore/IPromptProvider.hpp
Normal file
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright (C) 2025 Povilas Kanapickas <povilas@radix.lt>
|
||||
*
|
||||
* This file is part of QodeAssist.
|
||||
*
|
||||
* QodeAssist is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* QodeAssist is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "PromptTemplate.hpp"
|
||||
#include <QString>
|
||||
|
||||
namespace QodeAssist::LLMCore {
|
||||
|
||||
class IPromptProvider
|
||||
{
|
||||
public:
|
||||
virtual ~IPromptProvider() = default;
|
||||
|
||||
virtual PromptTemplate *getTemplateByName(const QString &templateName) const = 0;
|
||||
|
||||
virtual QStringList templatesNames() const = 0;
|
||||
|
||||
virtual QStringList getTemplatesForProvider(ProviderID id) const = 0;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::LLMCore
|
||||
36
llmcore/IProviderRegistry.hpp
Normal file
36
llmcore/IProviderRegistry.hpp
Normal file
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (C) 2025 Povilas Kanapickas <povilas@radix.lt>
|
||||
*
|
||||
* 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 "Provider.hpp"
|
||||
|
||||
namespace QodeAssist::LLMCore {
|
||||
|
||||
class IProviderRegistry
|
||||
{
|
||||
public:
|
||||
virtual ~IProviderRegistry() = default;
|
||||
|
||||
virtual Provider *getProviderByName(const QString &providerName) = 0;
|
||||
|
||||
virtual QStringList providersNames() const = 0;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::LLMCore
|
||||
@ -1,93 +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 "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);
|
||||
}
|
||||
}
|
||||
@ -1,68 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Petr Mironychev
|
||||
*
|
||||
* This file is part of QodeAssist.
|
||||
*
|
||||
* QodeAssist is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* QodeAssist is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <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
|
||||
53
llmcore/PromptProviderChat.hpp
Normal file
53
llmcore/PromptProviderChat.hpp
Normal file
@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (C) 2025 Povilas Kanapickas <povilas@radix.lt>
|
||||
*
|
||||
* 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 "IPromptProvider.hpp"
|
||||
#include "PromptTemplate.hpp"
|
||||
#include "PromptTemplateManager.hpp"
|
||||
|
||||
namespace QodeAssist::LLMCore {
|
||||
|
||||
class PromptProviderChat : public IPromptProvider
|
||||
{
|
||||
public:
|
||||
explicit PromptProviderChat(PromptTemplateManager &templateManager)
|
||||
: m_templateManager(templateManager)
|
||||
{}
|
||||
|
||||
~PromptProviderChat() = default;
|
||||
|
||||
PromptTemplate *getTemplateByName(const QString &templateName) const override
|
||||
{
|
||||
return m_templateManager.getChatTemplateByName(templateName);
|
||||
}
|
||||
|
||||
QStringList templatesNames() const override { return m_templateManager.chatTemplatesNames(); }
|
||||
|
||||
QStringList getTemplatesForProvider(ProviderID id) const override
|
||||
{
|
||||
return m_templateManager.getChatTemplatesForProvider(id);
|
||||
}
|
||||
|
||||
private:
|
||||
PromptTemplateManager &m_templateManager;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::LLMCore
|
||||
52
llmcore/PromptProviderFim.hpp
Normal file
52
llmcore/PromptProviderFim.hpp
Normal file
@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (C) 2025 Povilas Kanapickas <povilas@radix.lt>
|
||||
*
|
||||
* 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 "IPromptProvider.hpp"
|
||||
#include "PromptTemplateManager.hpp"
|
||||
|
||||
namespace QodeAssist::LLMCore {
|
||||
|
||||
class PromptProviderFim : public IPromptProvider
|
||||
{
|
||||
public:
|
||||
explicit PromptProviderFim(PromptTemplateManager &templateManager)
|
||||
: m_templateManager(templateManager)
|
||||
{}
|
||||
|
||||
~PromptProviderFim() = default;
|
||||
|
||||
PromptTemplate *getTemplateByName(const QString &templateName) const override
|
||||
{
|
||||
return m_templateManager.getFimTemplateByName(templateName);
|
||||
}
|
||||
|
||||
QStringList templatesNames() const override { return m_templateManager.fimTemplatesNames(); }
|
||||
|
||||
QStringList getTemplatesForProvider(ProviderID id) const override
|
||||
{
|
||||
return m_templateManager.getFimTemplatesForProvider(id);
|
||||
}
|
||||
|
||||
private:
|
||||
PromptTemplateManager &m_templateManager;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::LLMCore
|
||||
@ -24,10 +24,11 @@
|
||||
#include <QString>
|
||||
|
||||
#include "ContextData.hpp"
|
||||
#include "ProviderID.hpp"
|
||||
|
||||
namespace QodeAssist::LLMCore {
|
||||
|
||||
enum class TemplateType { Chat, Fim };
|
||||
enum class TemplateType { Chat, FIM };
|
||||
|
||||
class PromptTemplate
|
||||
{
|
||||
@ -35,9 +36,9 @@ public:
|
||||
virtual ~PromptTemplate() = default;
|
||||
virtual TemplateType type() const = 0;
|
||||
virtual QString name() const = 0;
|
||||
virtual QString promptTemplate() const = 0;
|
||||
virtual QStringList stopWords() const = 0;
|
||||
virtual void prepareRequest(QJsonObject &request, const ContextData &context) const = 0;
|
||||
virtual QString description() const = 0;
|
||||
virtual bool isSupportProvider(ProviderID id) const = 0;
|
||||
};
|
||||
} // namespace QodeAssist::LLMCore
|
||||
|
||||
@ -37,6 +37,32 @@ QStringList PromptTemplateManager::chatTemplatesNames() const
|
||||
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()
|
||||
{
|
||||
qDeleteAll(m_fimTemplates);
|
||||
@ -44,11 +70,15 @@ PromptTemplateManager::~PromptTemplateManager()
|
||||
|
||||
PromptTemplate *PromptTemplateManager::getFimTemplateByName(const QString &templateName)
|
||||
{
|
||||
if (!m_fimTemplates.contains(templateName))
|
||||
return m_fimTemplates.first();
|
||||
return m_fimTemplates[templateName];
|
||||
}
|
||||
|
||||
PromptTemplate *PromptTemplateManager::getChatTemplateByName(const QString &templateName)
|
||||
{
|
||||
if (!m_chatTemplates.contains(templateName))
|
||||
return m_chatTemplates.first();
|
||||
return m_chatTemplates[templateName];
|
||||
}
|
||||
|
||||
|
||||
@ -35,8 +35,7 @@ public:
|
||||
template<typename T>
|
||||
void registerTemplate()
|
||||
{
|
||||
static_assert(std::is_base_of<PromptTemplate, T>::value,
|
||||
"T must inherit from PromptTemplate");
|
||||
static_assert(std::is_base_of<PromptTemplate, T>::value, "T must inherit from PromptTemplate");
|
||||
T *template_ptr = new T();
|
||||
QString name = template_ptr->name();
|
||||
m_fimTemplates[name] = template_ptr;
|
||||
@ -51,6 +50,9 @@ public:
|
||||
QStringList fimTemplatesNames() const;
|
||||
QStringList chatTemplatesNames() const;
|
||||
|
||||
QStringList getFimTemplatesForProvider(ProviderID id);
|
||||
QStringList getChatTemplatesForProvider(ProviderID id);
|
||||
|
||||
private:
|
||||
PromptTemplateManager() = default;
|
||||
PromptTemplateManager(const PromptTemplateManager &) = delete;
|
||||
|
||||
@ -23,6 +23,7 @@
|
||||
#include <QNetworkRequest>
|
||||
#include <QString>
|
||||
|
||||
#include "ContextData.hpp"
|
||||
#include "PromptTemplate.hpp"
|
||||
#include "RequestType.hpp"
|
||||
|
||||
@ -41,13 +42,18 @@ public:
|
||||
virtual QString completionEndpoint() const = 0;
|
||||
virtual QString chatEndpoint() const = 0;
|
||||
virtual bool supportsModelListing() const = 0;
|
||||
|
||||
virtual void prepareRequest(QJsonObject &request, RequestType type) = 0;
|
||||
virtual void prepareRequest(
|
||||
QJsonObject &request,
|
||||
LLMCore::PromptTemplate *prompt,
|
||||
LLMCore::ContextData context,
|
||||
LLMCore::RequestType type)
|
||||
= 0;
|
||||
virtual bool handleResponse(QNetworkReply *reply, QString &accumulatedResponse) = 0;
|
||||
virtual QList<QString> getInstalledModels(const QString &url) = 0;
|
||||
virtual QList<QString> validateRequest(const QJsonObject &request, TemplateType type) = 0;
|
||||
virtual QString apiKey() const = 0;
|
||||
virtual void prepareNetworkRequest(QNetworkRequest &networkRequest) const = 0;
|
||||
virtual ProviderID providerID() const = 0;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::LLMCore
|
||||
|
||||
34
llmcore/ProviderID.hpp
Normal file
34
llmcore/ProviderID.hpp
Normal file
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
namespace QodeAssist::LLMCore {
|
||||
|
||||
enum class ProviderID {
|
||||
Any,
|
||||
Ollama,
|
||||
LMStudio,
|
||||
Claude,
|
||||
OpenAI,
|
||||
OpenAICompatible,
|
||||
MistralAI,
|
||||
OpenRouter,
|
||||
GoogleAI,
|
||||
LlamaCpp
|
||||
};
|
||||
}
|
||||
@ -39,6 +39,8 @@ ProvidersManager::~ProvidersManager()
|
||||
|
||||
Provider *ProvidersManager::getProviderByName(const QString &providerName)
|
||||
{
|
||||
if (!m_providers.contains(providerName))
|
||||
return m_providers.first();
|
||||
return m_providers[providerName];
|
||||
}
|
||||
|
||||
|
||||
@ -21,12 +21,12 @@
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include "IProviderRegistry.hpp"
|
||||
#include <QMap>
|
||||
#include "Provider.hpp"
|
||||
|
||||
namespace QodeAssist::LLMCore {
|
||||
|
||||
class ProvidersManager
|
||||
class ProvidersManager : public IProviderRegistry
|
||||
{
|
||||
public:
|
||||
static ProvidersManager &instance();
|
||||
@ -41,9 +41,9 @@ public:
|
||||
m_providers[name] = provider;
|
||||
}
|
||||
|
||||
Provider *getProviderByName(const QString &providerName);
|
||||
Provider *getProviderByName(const QString &providerName) override;
|
||||
|
||||
QStringList providersNames() const;
|
||||
QStringList providersNames() const override;
|
||||
|
||||
private:
|
||||
ProvidersManager() = default;
|
||||
|
||||
@ -19,11 +19,11 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QJsonObject>
|
||||
#include <QUrl>
|
||||
#include "PromptTemplate.hpp"
|
||||
#include "Provider.hpp"
|
||||
#include "RequestType.hpp"
|
||||
#include <QJsonObject>
|
||||
#include <QUrl>
|
||||
|
||||
namespace QodeAssist::LLMCore {
|
||||
|
||||
|
||||
@ -26,22 +26,23 @@
|
||||
namespace QodeAssist::LLMCore {
|
||||
|
||||
RequestHandler::RequestHandler(QObject *parent)
|
||||
: QObject(parent)
|
||||
: RequestHandlerBase(parent)
|
||||
, m_manager(new QNetworkAccessManager(this))
|
||||
{}
|
||||
|
||||
void RequestHandler::sendLLMRequest(const LLMConfig &config, const QJsonObject &request)
|
||||
{
|
||||
LOG_MESSAGE(QString("Sending request to llm: \nurl: %1\nRequest body:\n%2")
|
||||
.arg(config.url.toString(),
|
||||
.arg(
|
||||
config.url.toString(),
|
||||
QString::fromUtf8(
|
||||
QJsonDocument(config.providerRequest).toJson(QJsonDocument::Indented))));
|
||||
|
||||
QNetworkRequest networkRequest(config.url);
|
||||
config.provider->prepareNetworkRequest(networkRequest);
|
||||
|
||||
QNetworkReply *reply = m_manager->post(networkRequest,
|
||||
QJsonDocument(config.providerRequest).toJson());
|
||||
QNetworkReply *reply
|
||||
= m_manager->post(networkRequest, QJsonDocument(config.providerRequest).toJson());
|
||||
if (!reply) {
|
||||
LOG_MESSAGE("Error: Failed to create network reply");
|
||||
return;
|
||||
@ -58,7 +59,10 @@ void RequestHandler::sendLLMRequest(const LLMConfig &config, const QJsonObject &
|
||||
reply->deleteLater();
|
||||
m_activeRequests.remove(requestId);
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
LOG_MESSAGE(QString("Error in QodeAssist request: %1").arg(reply->errorString()));
|
||||
LOG_MESSAGE(QString("Error details: %1\nStatus code: %2\nResponse: %3")
|
||||
.arg(reply->errorString())
|
||||
.arg(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt())
|
||||
.arg(QString(reply->readAll())));
|
||||
emit requestFinished(requestId, false, reply->errorString());
|
||||
} else {
|
||||
LOG_MESSAGE("Request finished successfully");
|
||||
@ -67,9 +71,8 @@ void RequestHandler::sendLLMRequest(const LLMConfig &config, const QJsonObject &
|
||||
});
|
||||
}
|
||||
|
||||
void RequestHandler::handleLLMResponse(QNetworkReply *reply,
|
||||
const QJsonObject &request,
|
||||
const LLMConfig &config)
|
||||
void RequestHandler::handleLLMResponse(
|
||||
QNetworkReply *reply, const QJsonObject &request, const LLMConfig &config)
|
||||
{
|
||||
QString &accumulatedResponse = m_accumulatedResponses[reply];
|
||||
|
||||
@ -82,8 +85,8 @@ void RequestHandler::handleLLMResponse(QNetworkReply *reply,
|
||||
}
|
||||
|
||||
if (isComplete) {
|
||||
auto cleanedCompletion = removeStopWords(accumulatedResponse,
|
||||
config.promptTemplate->stopWords());
|
||||
auto cleanedCompletion
|
||||
= removeStopWords(accumulatedResponse, config.promptTemplate->stopWords());
|
||||
|
||||
emit completionReceived(cleanedCompletion, request, true);
|
||||
}
|
||||
@ -140,36 +143,4 @@ QString RequestHandler::removeStopWords(const QStringView &completion, const QSt
|
||||
return filteredCompletion;
|
||||
}
|
||||
|
||||
void RequestHandler::removeCodeBlockWrappers(QString &response)
|
||||
{
|
||||
static const QRegularExpression
|
||||
fullCodeBlockRegex(R"(```[\w\s]*\n([\s\S]*?)```)", QRegularExpression::MultilineOption);
|
||||
static const QRegularExpression
|
||||
partialStartBlockRegex(R"(```[\w\s]*\n([\s\S]*?)$)", QRegularExpression::MultilineOption);
|
||||
static const QRegularExpression
|
||||
partialEndBlockRegex(R"(^([\s\S]*?)```)", QRegularExpression::MultilineOption);
|
||||
|
||||
QRegularExpressionMatchIterator matchIterator = fullCodeBlockRegex.globalMatch(response);
|
||||
while (matchIterator.hasNext()) {
|
||||
QRegularExpressionMatch match = matchIterator.next();
|
||||
QString codeBlock = match.captured(0);
|
||||
QString codeContent = match.captured(1).trimmed();
|
||||
response.replace(codeBlock, codeContent);
|
||||
}
|
||||
|
||||
QRegularExpressionMatch startMatch = partialStartBlockRegex.match(response);
|
||||
if (startMatch.hasMatch()) {
|
||||
QString partialBlock = startMatch.captured(0);
|
||||
QString codeContent = startMatch.captured(1).trimmed();
|
||||
response.replace(partialBlock, codeContent);
|
||||
}
|
||||
|
||||
QRegularExpressionMatch endMatch = partialEndBlockRegex.match(response);
|
||||
if (endMatch.hasMatch()) {
|
||||
QString partialBlock = endMatch.captured(0);
|
||||
QString codeContent = endMatch.captured(1).trimmed();
|
||||
response.replace(partialBlock, codeContent);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::LLMCore
|
||||
|
||||
@ -24,40 +24,32 @@
|
||||
#include <QObject>
|
||||
|
||||
#include "RequestConfig.hpp"
|
||||
#include "RequestHandlerBase.hpp"
|
||||
|
||||
class QNetworkReply;
|
||||
|
||||
namespace QodeAssist::LLMCore {
|
||||
|
||||
class RequestHandler : public QObject
|
||||
class RequestHandler : public RequestHandlerBase
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit RequestHandler(QObject *parent = nullptr);
|
||||
|
||||
void sendLLMRequest(const LLMConfig &config, const QJsonObject &request);
|
||||
void handleLLMResponse(QNetworkReply *reply,
|
||||
const QJsonObject &request,
|
||||
const LLMConfig &config);
|
||||
bool cancelRequest(const QString &id);
|
||||
|
||||
signals:
|
||||
void completionReceived(const QString &completion, const QJsonObject &request, bool isComplete);
|
||||
void requestFinished(const QString &requestId, bool success, const QString &errorString);
|
||||
void requestCancelled(const QString &id);
|
||||
void sendLLMRequest(const LLMConfig &config, const QJsonObject &request) override;
|
||||
bool cancelRequest(const QString &id) override;
|
||||
void handleLLMResponse(QNetworkReply *reply, const QJsonObject &request, const LLMConfig &config);
|
||||
|
||||
private:
|
||||
QNetworkAccessManager *m_manager;
|
||||
QMap<QString, QNetworkReply *> m_activeRequests;
|
||||
QMap<QNetworkReply *, QString> m_accumulatedResponses;
|
||||
|
||||
bool processSingleLineCompletion(QNetworkReply *reply,
|
||||
const QJsonObject &request,
|
||||
const QString &accumulatedResponse,
|
||||
const LLMConfig &config);
|
||||
bool processSingleLineCompletion(
|
||||
QNetworkReply *reply,
|
||||
const QJsonObject &request,
|
||||
const QString &accumulatedResponse,
|
||||
const LLMConfig &config);
|
||||
QString removeStopWords(const QStringView &completion, const QStringList &stopWords);
|
||||
void removeCodeBlockWrappers(QString &response);
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::LLMCore
|
||||
|
||||
30
llmcore/RequestHandlerBase.cpp
Normal file
30
llmcore/RequestHandlerBase.cpp
Normal file
@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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 "RequestHandlerBase.hpp"
|
||||
|
||||
namespace QodeAssist::LLMCore {
|
||||
|
||||
RequestHandlerBase::RequestHandlerBase(QObject *parent)
|
||||
: QObject(parent)
|
||||
{}
|
||||
|
||||
RequestHandlerBase::~RequestHandlerBase() = default;
|
||||
|
||||
} // namespace QodeAssist::LLMCore
|
||||
45
llmcore/RequestHandlerBase.hpp
Normal file
45
llmcore/RequestHandlerBase.hpp
Normal file
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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 "RequestConfig.hpp"
|
||||
#include <QJsonObject>
|
||||
#include <QObject>
|
||||
|
||||
namespace QodeAssist::LLMCore {
|
||||
|
||||
class RequestHandlerBase : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit RequestHandlerBase(QObject *parent = nullptr);
|
||||
~RequestHandlerBase() override;
|
||||
|
||||
virtual void sendLLMRequest(const LLMConfig &config, const QJsonObject &request) = 0;
|
||||
virtual bool cancelRequest(const QString &id) = 0;
|
||||
|
||||
signals:
|
||||
void completionReceived(const QString &completion, const QJsonObject &request, bool isComplete);
|
||||
void requestFinished(const QString &requestId, bool success, const QString &errorString);
|
||||
void requestCancelled(const QString &id);
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::LLMCore
|
||||
@ -21,5 +21,5 @@
|
||||
|
||||
namespace QodeAssist::LLMCore {
|
||||
|
||||
enum RequestType { CodeCompletion, Chat };
|
||||
enum RequestType { CodeCompletion, Chat, Embedding };
|
||||
}
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
add_library(QodeAssistLogger STATIC
|
||||
EmptyRequestPerformanceLogger.hpp
|
||||
IRequestPerformanceLogger.hpp
|
||||
Logger.cpp
|
||||
Logger.hpp
|
||||
RequestPerformanceLogger.hpp RequestPerformanceLogger.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(QodeAssistLogger
|
||||
|
||||
32
logger/EmptyRequestPerformanceLogger.hpp
Normal file
32
logger/EmptyRequestPerformanceLogger.hpp
Normal file
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (C) 2025 Povilas Kanapickas <povilas@radix.lt>
|
||||
*
|
||||
* 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 "IRequestPerformanceLogger.hpp"
|
||||
|
||||
namespace QodeAssist {
|
||||
|
||||
class EmptyRequestPerformanceLogger : public IRequestPerformanceLogger
|
||||
{
|
||||
public:
|
||||
void startTimeMeasurement(const QString &requestId) override {}
|
||||
void endTimeMeasurement(const QString &requestId) override {}
|
||||
void logPerformance(const QString &requestId, qint64 elapsedMs) override {}
|
||||
};
|
||||
|
||||
} // namespace QodeAssist
|
||||
36
logger/IRequestPerformanceLogger.hpp
Normal file
36
logger/IRequestPerformanceLogger.hpp
Normal file
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (C) 2025 Povilas Kanapickas <povilas@radix.lt>
|
||||
*
|
||||
* This file is part of QodeAssist.
|
||||
*
|
||||
* QodeAssist is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* QodeAssist is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
|
||||
namespace QodeAssist {
|
||||
|
||||
class IRequestPerformanceLogger
|
||||
{
|
||||
public:
|
||||
virtual ~IRequestPerformanceLogger() = default;
|
||||
|
||||
virtual void startTimeMeasurement(const QString &requestId) = 0;
|
||||
virtual void endTimeMeasurement(const QString &requestId) = 0;
|
||||
virtual void logPerformance(const QString &requestId, qint64 elapsedMs) = 0;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist
|
||||
55
logger/RequestPerformanceLogger.cpp
Normal file
55
logger/RequestPerformanceLogger.cpp
Normal file
@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright (C) 2025 Povilas Kanapickas <povilas@radix.lt>
|
||||
*
|
||||
* 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 "RequestPerformanceLogger.hpp"
|
||||
#include "Logger.hpp"
|
||||
|
||||
namespace QodeAssist {
|
||||
|
||||
void RequestPerformanceLogger::startTimeMeasurement(const QString &requestId)
|
||||
{
|
||||
m_requestStartTimes[requestId] = QDateTime::currentMSecsSinceEpoch();
|
||||
}
|
||||
|
||||
void RequestPerformanceLogger::endTimeMeasurement(const QString &requestId)
|
||||
{
|
||||
if (!m_requestStartTimes.contains(requestId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
qint64 startTime = m_requestStartTimes[requestId];
|
||||
qint64 endTime = QDateTime::currentMSecsSinceEpoch();
|
||||
qint64 totalTime = endTime - startTime;
|
||||
logPerformance(requestId, totalTime);
|
||||
m_requestStartTimes.remove(requestId);
|
||||
}
|
||||
|
||||
void RequestPerformanceLogger::logPerformance(const QString &requestId, qint64 elapsedMs)
|
||||
{
|
||||
LOG_MESSAGE(
|
||||
QString("Performance: %1 total completion time took %2 ms").arg(requestId).arg(elapsedMs));
|
||||
}
|
||||
|
||||
void RequestPerformanceLogger::logPerformance(
|
||||
const QString &requestId, const QString &operation, qint64 elapsedMs)
|
||||
{
|
||||
LOG_MESSAGE(QString("Performance: %1 %2 took %3 ms").arg(requestId, operation).arg(elapsedMs));
|
||||
}
|
||||
|
||||
} // namespace QodeAssist
|
||||
41
logger/RequestPerformanceLogger.hpp
Normal file
41
logger/RequestPerformanceLogger.hpp
Normal file
@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (C) 2025 Povilas Kanapickas <povilas@radix.lt>
|
||||
*
|
||||
* 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 "IRequestPerformanceLogger.hpp"
|
||||
#include <QDateTime>
|
||||
#include <QMap>
|
||||
|
||||
namespace QodeAssist {
|
||||
|
||||
class RequestPerformanceLogger : public IRequestPerformanceLogger
|
||||
{
|
||||
public:
|
||||
RequestPerformanceLogger() = default;
|
||||
~RequestPerformanceLogger() override = default;
|
||||
|
||||
void startTimeMeasurement(const QString &requestId) override;
|
||||
void endTimeMeasurement(const QString &requestId) override;
|
||||
void logPerformance(const QString &requestId, qint64 elapsedMs) override;
|
||||
void logPerformance(const QString &requestId, const QString &operation, qint64 elapsedMs);
|
||||
|
||||
private:
|
||||
QMap<QString, qint64> m_requestStartTimes;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist
|
||||
@ -30,13 +30,10 @@
|
||||
#include "logger/Logger.hpp"
|
||||
#include "settings/ChatAssistantSettings.hpp"
|
||||
#include "settings/CodeCompletionSettings.hpp"
|
||||
#include "settings/GeneralSettings.hpp"
|
||||
#include "settings/ProviderSettings.hpp"
|
||||
|
||||
namespace QodeAssist::Providers {
|
||||
|
||||
ClaudeProvider::ClaudeProvider() {}
|
||||
|
||||
QString ClaudeProvider::name() const
|
||||
{
|
||||
return "Claude";
|
||||
@ -62,31 +59,17 @@ bool ClaudeProvider::supportsModelListing() const
|
||||
return true;
|
||||
}
|
||||
|
||||
void ClaudeProvider::prepareRequest(QJsonObject &request, LLMCore::RequestType type)
|
||||
void ClaudeProvider::prepareRequest(
|
||||
QJsonObject &request,
|
||||
LLMCore::PromptTemplate *prompt,
|
||||
LLMCore::ContextData context,
|
||||
LLMCore::RequestType type)
|
||||
{
|
||||
auto prepareMessages = [](QJsonObject &req) -> QJsonArray {
|
||||
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);
|
||||
}
|
||||
}
|
||||
} 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;
|
||||
};
|
||||
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();
|
||||
@ -98,11 +81,6 @@ void ClaudeProvider::prepareRequest(QJsonObject &request, LLMCore::RequestType t
|
||||
request["stream"] = true;
|
||||
};
|
||||
|
||||
QJsonArray messages = prepareMessages(request);
|
||||
if (!messages.isEmpty()) {
|
||||
request["messages"] = std::move(messages);
|
||||
}
|
||||
|
||||
if (type == LLMCore::RequestType::CodeCompletion) {
|
||||
applyModelParams(Settings::codeCompletionSettings());
|
||||
} else {
|
||||
@ -235,4 +213,9 @@ void ClaudeProvider::prepareNetworkRequest(QNetworkRequest &networkRequest) cons
|
||||
}
|
||||
}
|
||||
|
||||
LLMCore::ProviderID ClaudeProvider::providerID() const
|
||||
{
|
||||
return LLMCore::ProviderID::Claude;
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Providers
|
||||
|
||||
@ -26,19 +26,22 @@ namespace QodeAssist::Providers {
|
||||
class ClaudeProvider : public LLMCore::Provider
|
||||
{
|
||||
public:
|
||||
ClaudeProvider();
|
||||
|
||||
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::RequestType type) 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
|
||||
|
||||
303
providers/GoogleAIProvider.cpp
Normal file
303
providers/GoogleAIProvider.cpp
Normal file
@ -0,0 +1,303 @@
|
||||
/*
|
||||
* 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
|
||||
51
providers/GoogleAIProvider.hpp
Normal file
51
providers/GoogleAIProvider.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 "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,8 +33,6 @@
|
||||
|
||||
namespace QodeAssist::Providers {
|
||||
|
||||
LMStudioProvider::LMStudioProvider() {}
|
||||
|
||||
QString LMStudioProvider::name() const
|
||||
{
|
||||
return "LM Studio";
|
||||
@ -60,47 +58,6 @@ bool LMStudioProvider::supportsModelListing() const
|
||||
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)
|
||||
{
|
||||
QByteArray data = reply->readAll();
|
||||
@ -211,4 +168,42 @@ void LMStudioProvider::prepareNetworkRequest(QNetworkRequest &networkRequest) co
|
||||
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
|
||||
|
||||
@ -26,19 +26,22 @@ namespace QodeAssist::Providers {
|
||||
class LMStudioProvider : public LLMCore::Provider
|
||||
{
|
||||
public:
|
||||
LMStudioProvider();
|
||||
|
||||
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::RequestType type) 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
|
||||
|
||||
213
providers/LlamaCppProvider.cpp
Normal file
213
providers/LlamaCppProvider.cpp
Normal file
@ -0,0 +1,213 @@
|
||||
/*
|
||||
* 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 "LlamaCppProvider.hpp"
|
||||
|
||||
#include <QEventLoop>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QNetworkReply>
|
||||
|
||||
#include "llmcore/OpenAIMessage.hpp"
|
||||
#include "llmcore/ValidationUtils.hpp"
|
||||
#include "logger/Logger.hpp"
|
||||
#include "settings/ChatAssistantSettings.hpp"
|
||||
#include "settings/CodeCompletionSettings.hpp"
|
||||
|
||||
namespace QodeAssist::Providers {
|
||||
|
||||
QString LlamaCppProvider::name() const
|
||||
{
|
||||
return "llama.cpp";
|
||||
}
|
||||
|
||||
QString LlamaCppProvider::url() const
|
||||
{
|
||||
return "http://localhost:8080";
|
||||
}
|
||||
|
||||
QString LlamaCppProvider::completionEndpoint() const
|
||||
{
|
||||
return "/infill";
|
||||
}
|
||||
|
||||
QString LlamaCppProvider::chatEndpoint() const
|
||||
{
|
||||
return "/v1/chat/completions";
|
||||
}
|
||||
|
||||
bool LlamaCppProvider::supportsModelListing() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void LlamaCppProvider::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());
|
||||
}
|
||||
}
|
||||
|
||||
bool LlamaCppProvider::handleResponse(QNetworkReply *reply, QString &accumulatedResponse)
|
||||
{
|
||||
QByteArray data = reply->readAll();
|
||||
if (data.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isDone = data.contains("\"stop\":true") || data.contains("data: [DONE]");
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
QJsonObject obj = doc.object();
|
||||
|
||||
if (obj.contains("content")) {
|
||||
QString content = obj["content"].toString();
|
||||
if (!content.isEmpty()) {
|
||||
accumulatedResponse += content;
|
||||
}
|
||||
} else if (obj.contains("choices")) {
|
||||
auto message = LLMCore::OpenAIMessage::fromJson(obj);
|
||||
if (message.hasError()) {
|
||||
LOG_MESSAGE("Error in llama.cpp response: " + message.error);
|
||||
continue;
|
||||
}
|
||||
|
||||
QString content = message.getContent();
|
||||
if (!content.isEmpty()) {
|
||||
accumulatedResponse += content;
|
||||
}
|
||||
|
||||
if (message.isDone()) {
|
||||
isDone = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (obj["stop"].toBool()) {
|
||||
isDone = true;
|
||||
}
|
||||
}
|
||||
|
||||
return isDone;
|
||||
}
|
||||
|
||||
QList<QString> LlamaCppProvider::getInstalledModels(const QString &url)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
QList<QString> LlamaCppProvider::validateRequest(
|
||||
const QJsonObject &request, LLMCore::TemplateType type)
|
||||
{
|
||||
if (type == LLMCore::TemplateType::FIM) {
|
||||
const auto infillReq = QJsonObject{
|
||||
{"model", {}},
|
||||
{"input_prefix", {}},
|
||||
{"input_suffix", {}},
|
||||
{"prompt", {}},
|
||||
{"temperature", {}},
|
||||
{"top_p", {}},
|
||||
{"top_k", {}},
|
||||
{"max_tokens", {}},
|
||||
{"frequency_penalty", {}},
|
||||
{"presence_penalty", {}},
|
||||
{"stop", QJsonArray{}},
|
||||
{"stream", {}}};
|
||||
|
||||
return LLMCore::ValidationUtils::validateRequestFields(request, infillReq);
|
||||
} else {
|
||||
const auto chatReq = QJsonObject{
|
||||
{"model", {}},
|
||||
{"messages", QJsonArray{{QJsonObject{{"role", {}}, {"content", {}}}}}},
|
||||
{"temperature", {}},
|
||||
{"max_tokens", {}},
|
||||
{"top_p", {}},
|
||||
{"top_k", {}},
|
||||
{"frequency_penalty", {}},
|
||||
{"presence_penalty", {}},
|
||||
{"stop", QJsonArray{}},
|
||||
{"stream", {}}};
|
||||
|
||||
return LLMCore::ValidationUtils::validateRequestFields(request, chatReq);
|
||||
}
|
||||
}
|
||||
|
||||
QString LlamaCppProvider::apiKey() const
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
void LlamaCppProvider::prepareNetworkRequest(QNetworkRequest &networkRequest) const
|
||||
{
|
||||
networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
}
|
||||
|
||||
LLMCore::ProviderID LlamaCppProvider::providerID() const
|
||||
{
|
||||
return LLMCore::ProviderID::LlamaCpp;
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Providers
|
||||
47
providers/LlamaCppProvider.hpp
Normal file
47
providers/LlamaCppProvider.hpp
Normal file
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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 LlamaCppProvider : 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
|
||||
220
providers/MistralAIProvider.cpp
Normal file
220
providers/MistralAIProvider.cpp
Normal file
@ -0,0 +1,220 @@
|
||||
#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
|
||||
47
providers/MistralAIProvider.hpp
Normal file
47
providers/MistralAIProvider.hpp
Normal file
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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,8 +33,6 @@
|
||||
|
||||
namespace QodeAssist::Providers {
|
||||
|
||||
OllamaProvider::OllamaProvider() {}
|
||||
|
||||
QString OllamaProvider::name() const
|
||||
{
|
||||
return "Ollama";
|
||||
@ -60,8 +58,18 @@ bool OllamaProvider::supportsModelListing() const
|
||||
return true;
|
||||
}
|
||||
|
||||
void OllamaProvider::prepareRequest(QJsonObject &request, LLMCore::RequestType type)
|
||||
void OllamaProvider::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 applySettings = [&request](const auto &settings) {
|
||||
QJsonObject options;
|
||||
options["num_predict"] = settings.maxTokens();
|
||||
@ -104,9 +112,8 @@ bool OllamaProvider::handleResponse(QNetworkReply *reply, QString &accumulatedRe
|
||||
}
|
||||
|
||||
const QString endpoint = reply->url().path();
|
||||
auto messageType = endpoint == completionEndpoint()
|
||||
? LLMCore::OllamaMessage::Type::Generate
|
||||
: LLMCore::OllamaMessage::Type::Chat;
|
||||
auto messageType = endpoint == completionEndpoint() ? LLMCore::OllamaMessage::Type::Generate
|
||||
: LLMCore::OllamaMessage::Type::Chat;
|
||||
|
||||
auto message = LLMCore::OllamaMessage::fromJson(line, messageType);
|
||||
if (message.hasError()) {
|
||||
@ -192,7 +199,7 @@ QList<QString> OllamaProvider::validateRequest(const QJsonObject &request, LLMCo
|
||||
{"presence_penalty", {}}}}};
|
||||
|
||||
return LLMCore::ValidationUtils::validateRequestFields(
|
||||
request, type == LLMCore::TemplateType::Fim ? fimReq : messageReq);
|
||||
request, type == LLMCore::TemplateType::FIM ? fimReq : messageReq);
|
||||
}
|
||||
|
||||
QString OllamaProvider::apiKey() const
|
||||
@ -203,6 +210,11 @@ QString OllamaProvider::apiKey() const
|
||||
void OllamaProvider::prepareNetworkRequest(QNetworkRequest &networkRequest) const
|
||||
{
|
||||
networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
};
|
||||
}
|
||||
|
||||
LLMCore::ProviderID OllamaProvider::providerID() const
|
||||
{
|
||||
return LLMCore::ProviderID::Ollama;
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Providers
|
||||
|
||||
@ -26,19 +26,22 @@ namespace QodeAssist::Providers {
|
||||
class OllamaProvider : public LLMCore::Provider
|
||||
{
|
||||
public:
|
||||
OllamaProvider();
|
||||
|
||||
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::RequestType type) 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
|
||||
|
||||
@ -34,8 +34,6 @@
|
||||
|
||||
namespace QodeAssist::Providers {
|
||||
|
||||
OpenAICompatProvider::OpenAICompatProvider() {}
|
||||
|
||||
QString OpenAICompatProvider::name() const
|
||||
{
|
||||
return "OpenAI Compatible";
|
||||
@ -61,20 +59,17 @@ bool OpenAICompatProvider::supportsModelListing() const
|
||||
return false;
|
||||
}
|
||||
|
||||
void OpenAICompatProvider::prepareRequest(QJsonObject &request, LLMCore::RequestType type)
|
||||
void OpenAICompatProvider::prepareRequest(
|
||||
QJsonObject &request,
|
||||
LLMCore::PromptTemplate *prompt,
|
||||
LLMCore::ContextData context,
|
||||
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;
|
||||
};
|
||||
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();
|
||||
@ -90,11 +85,6 @@ void OpenAICompatProvider::prepareRequest(QJsonObject &request, LLMCore::Request
|
||||
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 {
|
||||
@ -190,4 +180,9 @@ void OpenAICompatProvider::prepareNetworkRequest(QNetworkRequest &networkRequest
|
||||
}
|
||||
}
|
||||
|
||||
LLMCore::ProviderID OpenAICompatProvider::providerID() const
|
||||
{
|
||||
return LLMCore::ProviderID::OpenAICompatible;
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Providers
|
||||
|
||||
@ -26,19 +26,22 @@ namespace QodeAssist::Providers {
|
||||
class OpenAICompatProvider : public LLMCore::Provider
|
||||
{
|
||||
public:
|
||||
OpenAICompatProvider();
|
||||
|
||||
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::RequestType type) 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
|
||||
|
||||
@ -35,8 +35,6 @@
|
||||
|
||||
namespace QodeAssist::Providers {
|
||||
|
||||
OpenAIProvider::OpenAIProvider() {}
|
||||
|
||||
QString OpenAIProvider::name() const
|
||||
{
|
||||
return "OpenAI";
|
||||
@ -62,20 +60,17 @@ bool OpenAIProvider::supportsModelListing() const
|
||||
return true;
|
||||
}
|
||||
|
||||
void OpenAIProvider::prepareRequest(QJsonObject &request, LLMCore::RequestType type)
|
||||
void OpenAIProvider::prepareRequest(
|
||||
QJsonObject &request,
|
||||
LLMCore::PromptTemplate *prompt,
|
||||
LLMCore::ContextData context,
|
||||
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;
|
||||
};
|
||||
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();
|
||||
@ -91,11 +86,6 @@ void OpenAIProvider::prepareRequest(QJsonObject &request, LLMCore::RequestType t
|
||||
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 {
|
||||
@ -181,7 +171,9 @@ QList<QString> OpenAIProvider::getInstalledModels(const QString &url)
|
||||
QJsonObject modelObject = value.toObject();
|
||||
if (modelObject.contains("id")) {
|
||||
QString modelId = modelObject["id"].toString();
|
||||
if (modelId.startsWith("gpt")) {
|
||||
if (!modelId.contains("dall-e") && !modelId.contains("whisper")
|
||||
&& !modelId.contains("tts") && !modelId.contains("davinci")
|
||||
&& !modelId.contains("babbage") && !modelId.contains("omni")) {
|
||||
models.append(modelId);
|
||||
}
|
||||
}
|
||||
@ -226,4 +218,9 @@ void OpenAIProvider::prepareNetworkRequest(QNetworkRequest &networkRequest) cons
|
||||
}
|
||||
}
|
||||
|
||||
LLMCore::ProviderID OpenAIProvider::providerID() const
|
||||
{
|
||||
return LLMCore::ProviderID::OpenAI;
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Providers
|
||||
|
||||
@ -26,19 +26,22 @@ namespace QodeAssist::Providers {
|
||||
class OpenAIProvider : public LLMCore::Provider
|
||||
{
|
||||
public:
|
||||
OpenAIProvider();
|
||||
|
||||
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::RequestType type) 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
|
||||
|
||||
@ -19,8 +19,6 @@
|
||||
|
||||
#include "OpenRouterAIProvider.hpp"
|
||||
|
||||
#include "settings/ChatAssistantSettings.hpp"
|
||||
#include "settings/CodeCompletionSettings.hpp"
|
||||
#include "settings/ProviderSettings.hpp"
|
||||
|
||||
#include <QJsonArray>
|
||||
@ -33,8 +31,6 @@
|
||||
|
||||
namespace QodeAssist::Providers {
|
||||
|
||||
OpenRouterProvider::OpenRouterProvider() {}
|
||||
|
||||
QString OpenRouterProvider::name() const
|
||||
{
|
||||
return "OpenRouter";
|
||||
@ -45,47 +41,6 @@ QString OpenRouterProvider::url() const
|
||||
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)
|
||||
{
|
||||
QByteArray data = reply->readAll();
|
||||
@ -142,4 +97,9 @@ QString OpenRouterProvider::apiKey() const
|
||||
return Settings::providerSettings().openRouterApiKey();
|
||||
}
|
||||
|
||||
LLMCore::ProviderID OpenRouterProvider::providerID() const
|
||||
{
|
||||
return LLMCore::ProviderID::OpenRouter;
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Providers
|
||||
|
||||
@ -27,13 +27,11 @@ namespace QodeAssist::Providers {
|
||||
class OpenRouterProvider : public OpenAICompatProvider
|
||||
{
|
||||
public:
|
||||
OpenRouterProvider();
|
||||
|
||||
QString name() const override;
|
||||
QString url() const override;
|
||||
void prepareRequest(QJsonObject &request, LLMCore::RequestType type) override;
|
||||
bool handleResponse(QNetworkReply *reply, QString &accumulatedResponse) override;
|
||||
QString apiKey() const override;
|
||||
LLMCore::ProviderID providerID() const override;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Providers
|
||||
|
||||
@ -21,7 +21,10 @@
|
||||
|
||||
#include "llmcore/ProvidersManager.hpp"
|
||||
#include "providers/ClaudeProvider.hpp"
|
||||
#include "providers/GoogleAIProvider.hpp"
|
||||
#include "providers/LMStudioProvider.hpp"
|
||||
#include "providers/LlamaCppProvider.hpp"
|
||||
#include "providers/MistralAIProvider.hpp"
|
||||
#include "providers/OllamaProvider.hpp"
|
||||
#include "providers/OpenAICompatProvider.hpp"
|
||||
#include "providers/OpenAIProvider.hpp"
|
||||
@ -33,11 +36,14 @@ inline void registerProviders()
|
||||
{
|
||||
auto &providerManager = LLMCore::ProvidersManager::instance();
|
||||
providerManager.registerProvider<OllamaProvider>();
|
||||
providerManager.registerProvider<LMStudioProvider>();
|
||||
providerManager.registerProvider<OpenAICompatProvider>();
|
||||
providerManager.registerProvider<OpenRouterProvider>();
|
||||
providerManager.registerProvider<ClaudeProvider>();
|
||||
providerManager.registerProvider<OpenAIProvider>();
|
||||
providerManager.registerProvider<OpenAICompatProvider>();
|
||||
providerManager.registerProvider<LMStudioProvider>();
|
||||
providerManager.registerProvider<OpenRouterProvider>();
|
||||
providerManager.registerProvider<MistralAIProvider>();
|
||||
providerManager.registerProvider<GoogleAIProvider>();
|
||||
providerManager.registerProvider<LlamaCppProvider>();
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Providers
|
||||
|
||||
@ -43,10 +43,16 @@
|
||||
|
||||
#include "ConfigurationManager.hpp"
|
||||
#include "QodeAssistClient.hpp"
|
||||
#include "Version.hpp"
|
||||
#include "chat/ChatOutputPane.h"
|
||||
#include "chat/NavigationPanel.hpp"
|
||||
#include "context/DocumentReaderQtCreator.hpp"
|
||||
#include "llmcore/PromptProviderFim.hpp"
|
||||
#include "llmcore/ProvidersManager.hpp"
|
||||
#include "logger/RequestPerformanceLogger.hpp"
|
||||
#include "settings/GeneralSettings.hpp"
|
||||
#include "settings/ProjectSettingsPanel.hpp"
|
||||
#include "settings/SettingsConstants.hpp"
|
||||
|
||||
#include "UpdateStatusWidget.hpp"
|
||||
#include "providers/Providers.hpp"
|
||||
@ -66,6 +72,8 @@ class QodeAssistPlugin final : public ExtensionSystem::IPlugin
|
||||
public:
|
||||
QodeAssistPlugin()
|
||||
: m_updater(new PluginUpdater(this))
|
||||
, m_promptProvider(LLMCore::PromptTemplateManager::instance())
|
||||
, m_requestHandler(this)
|
||||
{}
|
||||
|
||||
~QodeAssistPlugin() final
|
||||
@ -77,6 +85,13 @@ public:
|
||||
|
||||
void initialize() final
|
||||
{
|
||||
#if QODEASSIST_QT_CREATOR_VERSION >= QT_VERSION_CHECK(15, 0, 83)
|
||||
Core::IOptionsPage::registerCategory(
|
||||
Constants::QODE_ASSIST_GENERAL_OPTIONS_CATEGORY,
|
||||
Constants::QODE_ASSIST_GENERAL_OPTIONS_DISPLAY_CATEGORY,
|
||||
":/resources/images/qoderassist-icon.png");
|
||||
#endif
|
||||
|
||||
Providers::registerProviders();
|
||||
Templates::registerTemplates();
|
||||
|
||||
@ -108,8 +123,10 @@ public:
|
||||
UpdateDialog::checkForUpdatesAndShow(Core::ICore::mainWindow());
|
||||
});
|
||||
|
||||
m_chatOutputPane = new Chat::ChatOutputPane(this);
|
||||
m_navigationPanel = new Chat::NavigationPanel();
|
||||
if (Settings::generalSettings().enableChat()) {
|
||||
m_chatOutputPane = new Chat::ChatOutputPane(this);
|
||||
m_navigationPanel = new Chat::NavigationPanel();
|
||||
}
|
||||
|
||||
Settings::setupProjectPanel();
|
||||
ConfigurationManager::instance().init();
|
||||
@ -124,7 +141,14 @@ public:
|
||||
void restartClient()
|
||||
{
|
||||
LanguageClient::LanguageClientManager::shutdownClient(m_qodeAssistClient);
|
||||
m_qodeAssistClient = new QodeAssistClient();
|
||||
m_qodeAssistClient = new QodeAssistClient(new LLMClientInterface(
|
||||
Settings::generalSettings(),
|
||||
Settings::codeCompletionSettings(),
|
||||
LLMCore::ProvidersManager::instance(),
|
||||
&m_promptProvider,
|
||||
m_requestHandler,
|
||||
m_documentReader,
|
||||
m_performanceLogger));
|
||||
}
|
||||
|
||||
bool delayedInitialize() final
|
||||
@ -165,6 +189,10 @@ private:
|
||||
}
|
||||
|
||||
QPointer<QodeAssistClient> m_qodeAssistClient;
|
||||
LLMCore::PromptProviderFim m_promptProvider;
|
||||
LLMCore::RequestHandler m_requestHandler{this};
|
||||
Context::DocumentReaderQtCreator m_documentReader;
|
||||
RequestPerformanceLogger m_performanceLogger;
|
||||
QPointer<Chat::ChatOutputPane> m_chatOutputPane;
|
||||
QPointer<Chat::NavigationPanel> m_navigationPanel;
|
||||
QPointer<PluginUpdater> m_updater;
|
||||
|
||||
@ -19,9 +19,9 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QPushButton>
|
||||
#include <utils/aspects.h>
|
||||
#include <utils/layoutbuilder.h>
|
||||
#include <QPushButton>
|
||||
|
||||
class ButtonAspect : public Utils::BaseAspect
|
||||
{
|
||||
|
||||
@ -19,10 +19,10 @@
|
||||
|
||||
#include "ChatAssistantSettings.hpp"
|
||||
|
||||
#include <QMessageBox>
|
||||
#include <coreplugin/dialogs/ioptionspage.h>
|
||||
#include <coreplugin/icore.h>
|
||||
#include <utils/layoutbuilder.h>
|
||||
#include <QMessageBox>
|
||||
|
||||
#include "SettingsConstants.hpp"
|
||||
#include "SettingsTr.hpp"
|
||||
@ -195,10 +195,11 @@ ChatAssistantSettings::ChatAssistantSettings()
|
||||
|
||||
void ChatAssistantSettings::setupConnections()
|
||||
{
|
||||
connect(&resetToDefaults,
|
||||
&ButtonAspect::clicked,
|
||||
this,
|
||||
&ChatAssistantSettings::resetSettingsToDefaults);
|
||||
connect(
|
||||
&resetToDefaults,
|
||||
&ButtonAspect::clicked,
|
||||
this,
|
||||
&ChatAssistantSettings::resetSettingsToDefaults);
|
||||
}
|
||||
|
||||
void ChatAssistantSettings::resetSettingsToDefaults()
|
||||
|
||||
@ -19,10 +19,10 @@
|
||||
|
||||
#include "CodeCompletionSettings.hpp"
|
||||
|
||||
#include <QMessageBox>
|
||||
#include <coreplugin/dialogs/ioptionspage.h>
|
||||
#include <coreplugin/icore.h>
|
||||
#include <utils/layoutbuilder.h>
|
||||
#include <QMessageBox>
|
||||
|
||||
#include "SettingsConstants.hpp"
|
||||
#include "SettingsTr.hpp"
|
||||
@ -90,7 +90,7 @@ CodeCompletionSettings::CodeCompletionSettings()
|
||||
maxTokens.setSettingsKey(Constants::CC_MAX_TOKENS);
|
||||
maxTokens.setLabelText(Tr::tr("Max Tokens:"));
|
||||
maxTokens.setRange(-1, 900000);
|
||||
maxTokens.setDefaultValue(50);
|
||||
maxTokens.setDefaultValue(100);
|
||||
|
||||
// Advanced Parameters
|
||||
useTopP.setSettingsKey(Constants::CC_USE_TOP_P);
|
||||
@ -152,30 +152,45 @@ CodeCompletionSettings::CodeCompletionSettings()
|
||||
systemPrompt.setSettingsKey(Constants::CC_SYSTEM_PROMPT);
|
||||
systemPrompt.setDisplayStyle(Utils::StringAspect::TextEditDisplay);
|
||||
systemPrompt.setDefaultValue(
|
||||
"You are an expert in C++, Qt, and QML programming. Your task is to provide code "
|
||||
"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.");
|
||||
"You are an expert C++, Qt, and QML code completion assistant. Your task is to provide "
|
||||
"precise and contextually appropriate code completions.\n\n");
|
||||
|
||||
useUserMessageTemplateForCC.setSettingsKey(Constants::CC_USE_USER_TEMPLATE);
|
||||
useUserMessageTemplateForCC.setDefaultValue(true);
|
||||
useUserMessageTemplateForCC.setLabelText(Tr::tr("Use User Template for code completion message for non-FIM models"));
|
||||
useUserMessageTemplateForCC.setLabelText(
|
||||
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.setDisplayStyle(Utils::StringAspect::TextEditDisplay);
|
||||
userMessageTemplateForCC.setDefaultValue("Here is the code context with insertion points: <code_context>"
|
||||
"\nBefore: %1After: %2\n </code_context>");
|
||||
|
||||
useFilePathInContext.setSettingsKey(Constants::CC_USE_FILE_PATH_IN_CONTEXT);
|
||||
useFilePathInContext.setDefaultValue(true);
|
||||
useFilePathInContext.setLabelText(Tr::tr("Use File Path in Context"));
|
||||
userMessageTemplateForCC.setLabelText(Tr::tr("User message for non FIM models:"));
|
||||
userMessageTemplateForCC.setDefaultValue(
|
||||
"Here is the code context with insertion points:\n"
|
||||
"<code_context>\n${prefix}<cursor>${suffix}\n</code_context>\n\n");
|
||||
|
||||
useProjectChangesCache.setSettingsKey(Constants::CC_USE_PROJECT_CHANGES_CACHE);
|
||||
useProjectChangesCache.setDefaultValue(true);
|
||||
@ -234,13 +249,18 @@ CodeCompletionSettings::CodeCompletionSettings()
|
||||
contextGrid.addRow({Row{readFullFile}});
|
||||
contextGrid.addRow({Row{readFileParts, readStringsBeforeCursor, readStringsAfterCursor}});
|
||||
|
||||
auto contextItem = Column{Row{contextGrid, Stretch{1}},
|
||||
Row{useSystemPrompt, Stretch{1}},
|
||||
systemPrompt,
|
||||
Row{useUserMessageTemplateForCC, Stretch{1}},
|
||||
userMessageTemplateForCC,
|
||||
Row{useFilePathInContext, Stretch{1}},
|
||||
Row{useProjectChangesCache, maxChangesCacheSize, Stretch{1}}};
|
||||
auto contextItem = Column{
|
||||
Row{contextGrid, Stretch{1}},
|
||||
Row{useSystemPrompt, Stretch{1}},
|
||||
Group{title(Tr::tr("Prompts for FIM models")), Column{systemPrompt}},
|
||||
Group{
|
||||
title(Tr::tr("Prompts for Non FIM models")),
|
||||
Column{
|
||||
Row{useUserMessageTemplateForCC, Stretch{1}},
|
||||
systemPromptForNonFimModels,
|
||||
userMessageTemplateForCC,
|
||||
}},
|
||||
Row{useProjectChangesCache, maxChangesCacheSize, Stretch{1}}};
|
||||
|
||||
return Column{
|
||||
Row{Stretch{1}, resetToDefaults},
|
||||
@ -277,10 +297,11 @@ CodeCompletionSettings::CodeCompletionSettings()
|
||||
|
||||
void CodeCompletionSettings::setupConnections()
|
||||
{
|
||||
connect(&resetToDefaults,
|
||||
&ButtonAspect::clicked,
|
||||
this,
|
||||
&CodeCompletionSettings::resetSettingsToDefaults);
|
||||
connect(
|
||||
&resetToDefaults,
|
||||
&ButtonAspect::clicked,
|
||||
this,
|
||||
&CodeCompletionSettings::resetSettingsToDefaults);
|
||||
|
||||
connect(&readFullFile, &Utils::BoolAspect::volatileValueChanged, this, [this]() {
|
||||
if (readFullFile.volatileValue()) {
|
||||
@ -327,14 +348,24 @@ void CodeCompletionSettings::resetSettingsToDefaults()
|
||||
resetAspect(readStringsAfterCursor);
|
||||
resetAspect(useSystemPrompt);
|
||||
resetAspect(systemPrompt);
|
||||
resetAspect(useFilePathInContext);
|
||||
resetAspect(useProjectChangesCache);
|
||||
resetAspect(maxChangesCacheSize);
|
||||
resetAspect(ollamaLivetime);
|
||||
resetAspect(contextWindow);
|
||||
resetAspect(useUserMessageTemplateForCC);
|
||||
resetAspect(userMessageTemplateForCC);
|
||||
resetAspect(systemPromptForNonFimModels);
|
||||
}
|
||||
}
|
||||
|
||||
QString CodeCompletionSettings::processMessageToFIM(const QString &prefix, const QString &suffix) const
|
||||
{
|
||||
QString result = userMessageTemplateForCC();
|
||||
result.replace("${prefix}", prefix);
|
||||
result.replace("${suffix}", suffix);
|
||||
return result;
|
||||
}
|
||||
|
||||
class CodeCompletionSettingsPage : public Core::IOptionsPage
|
||||
{
|
||||
public:
|
||||
|
||||
@ -67,8 +67,8 @@ public:
|
||||
Utils::BoolAspect useSystemPrompt{this};
|
||||
Utils::StringAspect systemPrompt{this};
|
||||
Utils::BoolAspect useUserMessageTemplateForCC{this};
|
||||
Utils::StringAspect systemPromptForNonFimModels{this};
|
||||
Utils::StringAspect userMessageTemplateForCC{this};
|
||||
Utils::BoolAspect useFilePathInContext{this};
|
||||
Utils::BoolAspect useProjectChangesCache{this};
|
||||
Utils::IntegerAspect maxChangesCacheSize{this};
|
||||
|
||||
@ -79,6 +79,8 @@ public:
|
||||
// API Configuration Settings
|
||||
Utils::StringAspect apiKey{this};
|
||||
|
||||
QString processMessageToFIM(const QString &prefix, const QString &suffix) const;
|
||||
|
||||
private:
|
||||
void setupConnections();
|
||||
void resetSettingsToDefaults();
|
||||
|
||||
@ -19,12 +19,12 @@
|
||||
|
||||
#include "CustomPromptSettings.hpp"
|
||||
|
||||
#include <QFileDialog>
|
||||
#include <QJsonParseError>
|
||||
#include <QMessageBox>
|
||||
#include <coreplugin/dialogs/ioptionspage.h>
|
||||
#include <coreplugin/icore.h>
|
||||
#include <utils/layoutbuilder.h>
|
||||
#include <QFileDialog>
|
||||
#include <QJsonParseError>
|
||||
#include <QMessageBox>
|
||||
|
||||
#include "SettingsConstants.hpp"
|
||||
#include "SettingsTr.hpp"
|
||||
@ -86,30 +86,36 @@ CustomPromptSettings::CustomPromptSettings()
|
||||
|
||||
setLayouter([this]() {
|
||||
using namespace Layouting;
|
||||
return Column{Group{title(Tr::tr("Custom prompt for FIM model")),
|
||||
Column{Row{customJsonLabel, Stretch{1}, resetToDefaults},
|
||||
Row{customJsonTemplate,
|
||||
Column{saveCustomTemplateButton,
|
||||
loadCustomTemplateButton,
|
||||
customJsonLegend,
|
||||
Stretch{1}}}}}};
|
||||
return Column{Group{
|
||||
title(Tr::tr("Custom prompt for FIM model")),
|
||||
Column{
|
||||
Row{customJsonLabel, Stretch{1}, resetToDefaults},
|
||||
Row{customJsonTemplate,
|
||||
Column{
|
||||
saveCustomTemplateButton,
|
||||
loadCustomTemplateButton,
|
||||
customJsonLegend,
|
||||
Stretch{1}}}}}};
|
||||
});
|
||||
}
|
||||
|
||||
void CustomPromptSettings::setupConnection()
|
||||
{
|
||||
connect(&resetToDefaults,
|
||||
&ButtonAspect::clicked,
|
||||
this,
|
||||
&CustomPromptSettings::resetSettingsToDefaults);
|
||||
connect(&saveCustomTemplateButton,
|
||||
&ButtonAspect::clicked,
|
||||
this,
|
||||
&CustomPromptSettings::saveCustomTemplate);
|
||||
connect(&loadCustomTemplateButton,
|
||||
&ButtonAspect::clicked,
|
||||
this,
|
||||
&CustomPromptSettings::loadCustomTemplate);
|
||||
connect(
|
||||
&resetToDefaults,
|
||||
&ButtonAspect::clicked,
|
||||
this,
|
||||
&CustomPromptSettings::resetSettingsToDefaults);
|
||||
connect(
|
||||
&saveCustomTemplateButton,
|
||||
&ButtonAspect::clicked,
|
||||
this,
|
||||
&CustomPromptSettings::saveCustomTemplate);
|
||||
connect(
|
||||
&loadCustomTemplateButton,
|
||||
&ButtonAspect::clicked,
|
||||
this,
|
||||
&CustomPromptSettings::loadCustomTemplate);
|
||||
}
|
||||
|
||||
void CustomPromptSettings::resetSettingsToDefaults()
|
||||
@ -128,10 +134,8 @@ void CustomPromptSettings::resetSettingsToDefaults()
|
||||
|
||||
void CustomPromptSettings::saveCustomTemplate()
|
||||
{
|
||||
QString fileName = QFileDialog::getSaveFileName(nullptr,
|
||||
Tr::tr("Save JSON Template"),
|
||||
QString(),
|
||||
Tr::tr("JSON Files (*.json)"));
|
||||
QString fileName = QFileDialog::getSaveFileName(
|
||||
nullptr, Tr::tr("Save JSON Template"), QString(), Tr::tr("JSON Files (*.json)"));
|
||||
if (fileName.isEmpty())
|
||||
return;
|
||||
|
||||
@ -140,22 +144,19 @@ void CustomPromptSettings::saveCustomTemplate()
|
||||
QTextStream out(&file);
|
||||
out << customJsonTemplate.value();
|
||||
file.close();
|
||||
QMessageBox::information(nullptr,
|
||||
Tr::tr("Save Successful"),
|
||||
Tr::tr("JSON template has been saved successfully."));
|
||||
QMessageBox::information(
|
||||
nullptr,
|
||||
Tr::tr("Save Successful"),
|
||||
Tr::tr("JSON template has been saved successfully."));
|
||||
} else {
|
||||
QMessageBox::critical(nullptr,
|
||||
Tr::tr("Save Failed"),
|
||||
Tr::tr("Failed to save JSON template."));
|
||||
QMessageBox::critical(nullptr, Tr::tr("Save Failed"), Tr::tr("Failed to save JSON template."));
|
||||
}
|
||||
}
|
||||
|
||||
void CustomPromptSettings::loadCustomTemplate()
|
||||
{
|
||||
QString fileName = QFileDialog::getOpenFileName(nullptr,
|
||||
Tr::tr("Load JSON Template"),
|
||||
QString(),
|
||||
Tr::tr("JSON Files (*.json)"));
|
||||
QString fileName = QFileDialog::getOpenFileName(
|
||||
nullptr, Tr::tr("Load JSON Template"), QString(), Tr::tr("JSON Files (*.json)"));
|
||||
if (fileName.isEmpty())
|
||||
return;
|
||||
|
||||
@ -169,18 +170,16 @@ void CustomPromptSettings::loadCustomTemplate()
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonContent.toUtf8(), &parseError);
|
||||
if (parseError.error == QJsonParseError::NoError) {
|
||||
customJsonTemplate.setVolatileValue(jsonContent);
|
||||
QMessageBox::information(nullptr,
|
||||
Tr::tr("Load Successful"),
|
||||
Tr::tr("JSON template has been loaded successfully."));
|
||||
QMessageBox::information(
|
||||
nullptr,
|
||||
Tr::tr("Load Successful"),
|
||||
Tr::tr("JSON template has been loaded successfully."));
|
||||
} else {
|
||||
QMessageBox::critical(nullptr,
|
||||
Tr::tr("Invalid JSON"),
|
||||
Tr::tr("The selected file contains invalid JSON."));
|
||||
QMessageBox::critical(
|
||||
nullptr, Tr::tr("Invalid JSON"), Tr::tr("The selected file contains invalid JSON."));
|
||||
}
|
||||
} else {
|
||||
QMessageBox::critical(nullptr,
|
||||
Tr::tr("Load Failed"),
|
||||
Tr::tr("Failed to load JSON template."));
|
||||
QMessageBox::critical(nullptr, Tr::tr("Load Failed"), Tr::tr("Failed to load JSON template."));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -21,6 +21,7 @@
|
||||
|
||||
#include <coreplugin/dialogs/ioptionspage.h>
|
||||
#include <coreplugin/icore.h>
|
||||
#include <utils/detailswidget.h>
|
||||
#include <utils/layoutbuilder.h>
|
||||
#include <utils/utilsicons.h>
|
||||
#include <QInputDialog>
|
||||
@ -32,6 +33,7 @@
|
||||
#include <QtWidgets/qradiobutton.h>
|
||||
#include <QtWidgets/qstackedwidget.h>
|
||||
|
||||
#include "../Version.hpp"
|
||||
#include "Logger.hpp"
|
||||
#include "SettingsConstants.hpp"
|
||||
#include "SettingsDialog.hpp"
|
||||
@ -41,6 +43,17 @@
|
||||
|
||||
namespace QodeAssist::Settings {
|
||||
|
||||
void addDialogButtons(QBoxLayout *layout, QAbstractButton *okButton, QAbstractButton *cancelButton)
|
||||
{
|
||||
#if defined(Q_OS_MACOS)
|
||||
layout->addWidget(cancelButton);
|
||||
layout->addWidget(okButton);
|
||||
#else
|
||||
layout->addWidget(okButton);
|
||||
layout->addWidget(cancelButton);
|
||||
#endif
|
||||
}
|
||||
|
||||
GeneralSettings &generalSettings()
|
||||
{
|
||||
static GeneralSettings settings;
|
||||
@ -59,12 +72,17 @@ GeneralSettings::GeneralSettings()
|
||||
|
||||
enableLogging.setSettingsKey(Constants::ENABLE_LOGGING);
|
||||
enableLogging.setLabelText(TrConstants::ENABLE_LOG);
|
||||
enableLogging.setToolTip(TrConstants::ENABLE_LOG_TOOLTIP);
|
||||
enableLogging.setDefaultValue(false);
|
||||
|
||||
enableCheckUpdate.setSettingsKey(Constants::ENABLE_CHECK_UPDATE);
|
||||
enableCheckUpdate.setLabelText(TrConstants::ENABLE_CHECK_UPDATE_ON_START);
|
||||
enableCheckUpdate.setDefaultValue(true);
|
||||
|
||||
enableChat.setSettingsKey(Constants::ENABLE_CHAT);
|
||||
enableChat.setLabelText(TrConstants::ENABLE_CHAT);
|
||||
enableChat.setDefaultValue(true);
|
||||
|
||||
resetToDefaults.m_buttonText = TrConstants::RESET_TO_DEFAULTS;
|
||||
checkUpdate.m_buttonText = TrConstants::CHECK_UPDATE;
|
||||
|
||||
@ -89,6 +107,11 @@ GeneralSettings::GeneralSettings()
|
||||
ccStatus.setDefaultValue("");
|
||||
ccTest.m_buttonText = TrConstants::TEST;
|
||||
|
||||
ccTemplateDescription.setDisplayStyle(Utils::StringAspect::TextEditDisplay);
|
||||
ccTemplateDescription.setReadOnly(true);
|
||||
ccTemplateDescription.setDefaultValue("");
|
||||
ccTemplateDescription.setLabelText(TrConstants::CURRENT_TEMPLATE_DESCRIPTION);
|
||||
|
||||
// preset1
|
||||
specifyPreset1.setSettingsKey(Constants::CC_SPECIFY_PRESET1);
|
||||
specifyPreset1.setLabelText(TrConstants::ADD_NEW_PRESET);
|
||||
@ -144,6 +167,11 @@ GeneralSettings::GeneralSettings()
|
||||
caStatus.setDefaultValue("");
|
||||
caTest.m_buttonText = TrConstants::TEST;
|
||||
|
||||
caTemplateDescription.setDisplayStyle(Utils::StringAspect::TextEditDisplay);
|
||||
caTemplateDescription.setReadOnly(true);
|
||||
caTemplateDescription.setDefaultValue("");
|
||||
caTemplateDescription.setLabelText(TrConstants::CURRENT_TEMPLATE_DESCRIPTION);
|
||||
|
||||
readSettings();
|
||||
|
||||
Logger::instance().setLoggingEnabled(enableLogging());
|
||||
@ -175,13 +203,19 @@ GeneralSettings::GeneralSettings()
|
||||
|
||||
auto ccGroup = Group{
|
||||
title(TrConstants::CODE_COMPLETION),
|
||||
Column{ccGrid, Row{specifyPreset1, preset1Language, Stretch{1}}, ccPreset1Grid}};
|
||||
auto caGroup = Group{title(TrConstants::CHAT_ASSISTANT), caGrid};
|
||||
Column{
|
||||
ccGrid,
|
||||
ccTemplateDescription,
|
||||
Row{specifyPreset1, preset1Language, Stretch{1}},
|
||||
ccPreset1Grid}};
|
||||
auto caGroup
|
||||
= Group{title(TrConstants::CHAT_ASSISTANT), Column{caGrid, caTemplateDescription}};
|
||||
|
||||
auto rootLayout = Column{
|
||||
Row{enableQodeAssist, Stretch{1}, Row{checkUpdate, resetToDefaults}},
|
||||
Row{enableLogging, Stretch{1}},
|
||||
Row{enableCheckUpdate, Stretch{1}},
|
||||
Row{enableChat, Stretch{1}},
|
||||
Space{8},
|
||||
ccGroup,
|
||||
Space{8},
|
||||
@ -192,10 +226,8 @@ GeneralSettings::GeneralSettings()
|
||||
});
|
||||
}
|
||||
|
||||
void GeneralSettings::showSelectionDialog(const QStringList &data,
|
||||
Utils::StringAspect &aspect,
|
||||
const QString &title,
|
||||
const QString &text)
|
||||
void GeneralSettings::showSelectionDialog(
|
||||
const QStringList &data, Utils::StringAspect &aspect, const QString &title, const QString &text)
|
||||
{
|
||||
if (data.isEmpty())
|
||||
return;
|
||||
@ -239,6 +271,7 @@ void GeneralSettings::showModelsNotFoundDialog(Utils::StringAspect &aspect)
|
||||
auto selectProviderBtn = new QPushButton(TrConstants::SELECT_PROVIDER);
|
||||
auto selectUrlBtn = new QPushButton(TrConstants::SELECT_URL);
|
||||
auto enterManuallyBtn = new QPushButton(TrConstants::ENTER_MODEL_MANUALLY);
|
||||
auto configureApiKeyBtn = new QPushButton(TrConstants::CONFIGURE_API_KEY);
|
||||
|
||||
connect(selectProviderBtn, &QPushButton::clicked, &dialog, [this, providerButton, &dialog]() {
|
||||
dialog.close();
|
||||
@ -255,9 +288,15 @@ void GeneralSettings::showModelsNotFoundDialog(Utils::StringAspect &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(selectUrlBtn);
|
||||
dialog.buttonLayout()->addWidget(enterManuallyBtn);
|
||||
dialog.buttonLayout()->addWidget(configureApiKeyBtn);
|
||||
}
|
||||
|
||||
auto closeBtn = new QPushButton(TrConstants::CLOSE);
|
||||
@ -295,8 +334,7 @@ void GeneralSettings::showModelsNotSupportedDialog(Utils::StringAspect &aspect)
|
||||
auto cancelButton = new QPushButton(TrConstants::CANCEL);
|
||||
connect(cancelButton, &QPushButton::clicked, &dialog, &QDialog::reject);
|
||||
|
||||
dialog.buttonLayout()->addWidget(cancelButton);
|
||||
dialog.buttonLayout()->addWidget(okButton);
|
||||
addDialogButtons(dialog.buttonLayout(), okButton, cancelButton);
|
||||
|
||||
modelList->setFocus();
|
||||
dialog.exec();
|
||||
@ -335,8 +373,7 @@ void GeneralSettings::showUrlSelectionDialog(
|
||||
auto cancelButton = new QPushButton(TrConstants::CANCEL);
|
||||
connect(cancelButton, &QPushButton::clicked, &dialog, &QDialog::reject);
|
||||
|
||||
dialog.buttonLayout()->addWidget(cancelButton);
|
||||
dialog.buttonLayout()->addWidget(okButton);
|
||||
addDialogButtons(dialog.buttonLayout(), okButton, cancelButton);
|
||||
|
||||
urlList->setFocus();
|
||||
dialog.exec();
|
||||
@ -372,10 +409,11 @@ void GeneralSettings::setupConnections()
|
||||
void GeneralSettings::resetPageToDefaults()
|
||||
{
|
||||
QMessageBox::StandardButton reply;
|
||||
reply = QMessageBox::question(Core::ICore::dialogParent(),
|
||||
TrConstants::RESET_SETTINGS,
|
||||
TrConstants::CONFIRMATION,
|
||||
QMessageBox::Yes | QMessageBox::No);
|
||||
reply = QMessageBox::question(
|
||||
Core::ICore::dialogParent(),
|
||||
TrConstants::RESET_SETTINGS,
|
||||
TrConstants::CONFIRMATION,
|
||||
QMessageBox::Yes | QMessageBox::No);
|
||||
|
||||
if (reply == QMessageBox::Yes) {
|
||||
resetAspect(enableQodeAssist);
|
||||
@ -407,8 +445,10 @@ public:
|
||||
setId(Constants::QODE_ASSIST_GENERAL_SETTINGS_PAGE_ID);
|
||||
setDisplayName(TrConstants::GENERAL);
|
||||
setCategory(Constants::QODE_ASSIST_GENERAL_OPTIONS_CATEGORY);
|
||||
#if QODEASSIST_QT_CREATOR_VERSION < QT_VERSION_CHECK(15, 0, 83)
|
||||
setDisplayCategory(Constants::QODE_ASSIST_GENERAL_OPTIONS_DISPLAY_CATEGORY);
|
||||
setCategoryIconPath(":/resources/images/qoderassist-icon.png");
|
||||
#endif
|
||||
setSettingsProvider([] { return &generalSettings(); });
|
||||
}
|
||||
};
|
||||
|
||||
@ -36,6 +36,8 @@ public:
|
||||
Utils::BoolAspect enableQodeAssist{this};
|
||||
Utils::BoolAspect enableLogging{this};
|
||||
Utils::BoolAspect enableCheckUpdate{this};
|
||||
Utils::BoolAspect enableChat{this};
|
||||
|
||||
ButtonAspect checkUpdate{this};
|
||||
ButtonAspect resetToDefaults{this};
|
||||
|
||||
@ -55,6 +57,8 @@ public:
|
||||
Utils::StringAspect ccStatus{this};
|
||||
ButtonAspect ccTest{this};
|
||||
|
||||
Utils::StringAspect ccTemplateDescription{this};
|
||||
|
||||
// TODO create dynamic presets system
|
||||
// preset1 for code completion settings
|
||||
Utils::BoolAspect specifyPreset1{this};
|
||||
@ -88,10 +92,13 @@ public:
|
||||
Utils::StringAspect caStatus{this};
|
||||
ButtonAspect caTest{this};
|
||||
|
||||
void showSelectionDialog(const QStringList &data,
|
||||
Utils::StringAspect &aspect,
|
||||
const QString &title = {},
|
||||
const QString &text = {});
|
||||
Utils::StringAspect caTemplateDescription{this};
|
||||
|
||||
void showSelectionDialog(
|
||||
const QStringList &data,
|
||||
Utils::StringAspect &aspect,
|
||||
const QString &title = {},
|
||||
const QString &text = {});
|
||||
|
||||
void showModelsNotFoundDialog(Utils::StringAspect &aspect);
|
||||
|
||||
|
||||
@ -43,8 +43,8 @@ ProjectSettings::ProjectSettings(ProjectExplorer::Project *project)
|
||||
chatHistoryPath.setExpectedKind(Utils::PathChooser::ExistingDirectory);
|
||||
chatHistoryPath.setLabelText(Tr::tr("Chat History Path:"));
|
||||
|
||||
QString projectChatHistoryPath
|
||||
= QString("%1/qodeassist/chat_history").arg(Core::ICore::userResourcePath().toString());
|
||||
QString projectChatHistoryPath = QString("%1/qodeassist/chat_history")
|
||||
.arg(Core::ICore::userResourcePath().toFSPathString());
|
||||
|
||||
chatHistoryPath.setDefaultValue(projectChatHistoryPath);
|
||||
|
||||
|
||||
@ -69,6 +69,7 @@ ProviderSettings::ProviderSettings()
|
||||
claudeApiKey.setDefaultValue("");
|
||||
claudeApiKey.setAutoApply(true);
|
||||
|
||||
// OpenAI Settings
|
||||
openAiApiKey.setSettingsKey(Constants::OPEN_AI_API_KEY);
|
||||
openAiApiKey.setLabelText(Tr::tr("OpenAI API Key:"));
|
||||
openAiApiKey.setDisplayStyle(Utils::StringAspect::LineEditDisplay);
|
||||
@ -77,6 +78,24 @@ ProviderSettings::ProviderSettings()
|
||||
openAiApiKey.setDefaultValue("");
|
||||
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");
|
||||
|
||||
readSettings();
|
||||
@ -96,6 +115,10 @@ ProviderSettings::ProviderSettings()
|
||||
Group{title(Tr::tr("OpenAI Compatible Settings")), Column{openAiCompatApiKey}},
|
||||
Space{8},
|
||||
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}};
|
||||
});
|
||||
}
|
||||
@ -112,6 +135,12 @@ void ProviderSettings::setupConnections()
|
||||
});
|
||||
connect(&claudeApiKey, &ButtonAspect::changed, this, [this]() { claudeApiKey.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()
|
||||
@ -128,6 +157,8 @@ void ProviderSettings::resetSettingsToDefaults()
|
||||
resetAspect(openAiCompatApiKey);
|
||||
resetAspect(claudeApiKey);
|
||||
resetAspect(openAiApiKey);
|
||||
resetAspect(mistralAiApiKey);
|
||||
resetAspect(googleAiApiKey);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -37,6 +37,8 @@ public:
|
||||
Utils::StringAspect openAiCompatApiKey{this};
|
||||
Utils::StringAspect claudeApiKey{this};
|
||||
Utils::StringAspect openAiApiKey{this};
|
||||
Utils::StringAspect mistralAiApiKey{this};
|
||||
Utils::StringAspect googleAiApiKey{this};
|
||||
|
||||
private:
|
||||
void setupConnections();
|
||||
|
||||
@ -21,9 +21,6 @@
|
||||
|
||||
namespace QodeAssist::Constants {
|
||||
|
||||
const char ACTION_ID[] = "QodeAssist.Action";
|
||||
const char MENU_ID[] = "QodeAssist.Menu";
|
||||
|
||||
// project settings
|
||||
|
||||
const char QODE_ASSIST_PROJECT_SETTINGS_ID[] = "QodeAssist.ProjectSettings";
|
||||
@ -60,6 +57,7 @@ const char ENABLE_QODE_ASSIST[] = "QodeAssist.enableQodeAssist";
|
||||
const char CC_AUTO_COMPLETION[] = "QodeAssist.ccAutoCompletion";
|
||||
const char ENABLE_LOGGING[] = "QodeAssist.enableLogging";
|
||||
const char ENABLE_CHECK_UPDATE[] = "QodeAssist.enableCheckUpdate";
|
||||
const char ENABLE_CHAT[] = "QodeAssist.enableChat";
|
||||
|
||||
const char PROVIDER_PATHS[] = "QodeAssist.providerPaths";
|
||||
const char СС_START_SUGGESTION_TIMER[] = "QodeAssist.startSuggestionTimer";
|
||||
@ -86,8 +84,6 @@ const char QODE_ASSIST_CUSTOM_PROMPT_SETTINGS_PAGE_ID[] = "QodeAssist.4CustomPro
|
||||
const char QODE_ASSIST_GENERAL_OPTIONS_CATEGORY[] = "QodeAssist.Category";
|
||||
const char QODE_ASSIST_GENERAL_OPTIONS_DISPLAY_CATEGORY[] = "Qode Assist";
|
||||
|
||||
const char QODE_ASSIST_REQUEST_SUGGESTION[] = "QodeAssist.RequestSuggestion";
|
||||
|
||||
// Provider Settings Page ID
|
||||
const char QODE_ASSIST_PROVIDER_SETTINGS_PAGE_ID[] = "QodeAssist.5ProviderSettingsPageId";
|
||||
|
||||
@ -100,14 +96,18 @@ const char CLAUDE_API_KEY[] = "QodeAssist.claudeApiKey";
|
||||
const char CLAUDE_API_KEY_HISTORY[] = "QodeAssist.claudeApiKeyHistory";
|
||||
const char OPEN_AI_API_KEY[] = "QodeAssist.openAiApiKey";
|
||||
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
|
||||
const char CC_READ_FULL_FILE[] = "QodeAssist.ccReadFullFile";
|
||||
const char CC_READ_STRINGS_BEFORE_CURSOR[] = "QodeAssist.ccReadStringsBeforeCursor";
|
||||
const char CC_READ_STRINGS_AFTER_CURSOR[] = "QodeAssist.ccReadStringsAfterCursor";
|
||||
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_FOR_NON_FIM[] = "QodeAssist.ccSystemPromptForNonFim";
|
||||
const char CC_USE_USER_TEMPLATE[] = "QodeAssist.ccUseUserTemplate";
|
||||
const char CC_USER_TEMPLATE[] = "QodeAssist.ccUserTemplate";
|
||||
const char CC_USE_PROJECT_CHANGES_CACHE[] = "QodeAssist.ccUseProjectChangesCache";
|
||||
|
||||
@ -26,8 +26,8 @@ namespace QodeAssist::Settings {
|
||||
namespace TrConstants {
|
||||
inline const char *ENABLE_QODE_ASSIST = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Enable Qode Assist");
|
||||
inline const char *GENERAL = QT_TRANSLATE_NOOP("QtC::QodeAssist", "General");
|
||||
inline const char *RESET_TO_DEFAULTS = QT_TRANSLATE_NOOP("QtC::QodeAssist",
|
||||
"Reset Page to Defaults");
|
||||
inline const char *RESET_TO_DEFAULTS
|
||||
= QT_TRANSLATE_NOOP("QtC::QodeAssist", "Reset Page to Defaults");
|
||||
inline const char *CHECK_UPDATE = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Check Update");
|
||||
inline const char *SELECT = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Select...");
|
||||
inline const char *PROVIDER = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Provider:");
|
||||
@ -37,15 +37,21 @@ inline const char *URL = QT_TRANSLATE_NOOP("QtC::QodeAssist", "URL:");
|
||||
inline const char *STATUS = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Status:");
|
||||
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_TOOLTIP
|
||||
= QT_TRANSLATE_NOOP("QtC::QodeAssist", "Log messages are visible in General Messages pane");
|
||||
inline const char *ENABLE_CHECK_UPDATE_ON_START
|
||||
= 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 *CHAT_ASSISTANT = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Chat Assistant");
|
||||
inline const char *RESET_SETTINGS = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Reset Settings");
|
||||
inline const char *CONFIRMATION
|
||||
= QT_TRANSLATE_NOOP("QtC::QodeAssist",
|
||||
"Are you sure you want to reset all settings to default values?");
|
||||
inline const char *CONFIRMATION = QT_TRANSLATE_NOOP(
|
||||
"QtC::QodeAssist", "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 NO_MODELS_FOUND[]
|
||||
@ -55,7 +61,8 @@ inline const char CHECK_CONNECTION[] = QT_TRANSLATE_NOOP(
|
||||
"Please verify the following:\n"
|
||||
"- Server is running and accessible\n"
|
||||
"- URL is correct\n"
|
||||
"- Provider is properly configured\n\n"
|
||||
"- Provider is properly configured\n"
|
||||
"- API key is correctly set (if required)\n\n"
|
||||
"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_URL[] = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Select URL");
|
||||
@ -73,6 +80,7 @@ inline const char OK[] = QT_TRANSLATE_NOOP("QtC::QodeAssist", "OK");
|
||||
inline const char CANCEL[] = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Cancel");
|
||||
inline const char 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_INFO[] = QT_TRANSLATE_NOOP(
|
||||
"QtC::QodeAssist",
|
||||
|
||||
@ -19,13 +19,13 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <utils/aspects.h>
|
||||
#include <utils/layoutbuilder.h>
|
||||
#include <QCoreApplication>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QPushButton>
|
||||
#include <QtCore/qtimer.h>
|
||||
#include <utils/aspects.h>
|
||||
#include <utils/layoutbuilder.h>
|
||||
|
||||
namespace QodeAssist::Settings {
|
||||
|
||||
@ -67,10 +67,11 @@ void resetAspect(AspectType &aspect)
|
||||
aspect.setVolatileValue(aspect.defaultValue());
|
||||
}
|
||||
|
||||
inline void initStringAspect(Utils::StringAspect &aspect,
|
||||
const Utils::Key &settingsKey,
|
||||
const QString &labelText,
|
||||
const QString &defaultValue)
|
||||
inline void initStringAspect(
|
||||
Utils::StringAspect &aspect,
|
||||
const Utils::Key &settingsKey,
|
||||
const QString &labelText,
|
||||
const QString &defaultValue)
|
||||
{
|
||||
aspect.setSettingsKey(settingsKey);
|
||||
aspect.setLabelText(labelText);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user