mirror of
https://github.com/Palm1r/QodeAssist.git
synced 2026-06-14 02:09:22 -04:00
Compare commits
8 Commits
v0.5.10
...
dev-rag-ba
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
142afa725f | ||
|
|
f36db033e6 | ||
|
|
5dfcf74128 | ||
|
|
02101665ca | ||
|
|
77a03d42ed | ||
|
|
09c38c8b0e | ||
|
|
7b73d7af7b | ||
|
|
5a426b4d9f |
51
.github/scripts/plugin.json
vendored
51
.github/scripts/plugin.json
vendored
@@ -6,67 +6,22 @@
|
|||||||
"llm",
|
"llm",
|
||||||
"ai"
|
"ai"
|
||||||
],
|
],
|
||||||
"compatibility": "Qt 6.8.3",
|
"compatibility": "Qt 6.8.1",
|
||||||
"platforms": [
|
"platforms": [
|
||||||
"Windows",
|
"Windows",
|
||||||
"macOS",
|
"macOS",
|
||||||
"Linux"
|
"Linux"
|
||||||
],
|
],
|
||||||
"license": "GPLv3",
|
"license": "GPLv3",
|
||||||
"version": "0.5.10",
|
"version": "0.4.0",
|
||||||
"status": "draft",
|
"status": "draft",
|
||||||
"is_pack": false,
|
"is_pack": false,
|
||||||
"released_at": null,
|
"released_at": null,
|
||||||
"version_history": [
|
"version_history": [
|
||||||
{
|
{
|
||||||
"version": "0.4.0",
|
"version": "0.4.0",
|
||||||
"is_latest": false,
|
"is_latest": true,
|
||||||
"released_at": "2024-01-24T15:00:00Z"
|
"released_at": "2024-01-24T15:00:00Z"
|
||||||
},
|
|
||||||
{
|
|
||||||
"version": "0.5.2",
|
|
||||||
"is_latest": false,
|
|
||||||
"released_at": "2025-03-13T17:00:00Z"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"version": "0.5.3",
|
|
||||||
"is_latest": false,
|
|
||||||
"released_at": "2025-03-14T11:00:00Z"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"version": "0.5.4",
|
|
||||||
"is_latest": false,
|
|
||||||
"released_at": "2025-03-17T03:00:00Z"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"version": "0.5.5",
|
|
||||||
"is_latest": false,
|
|
||||||
"released_at": "2025-03-20T19:00:00Z"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"version": "0.5.6",
|
|
||||||
"is_latest": false,
|
|
||||||
"released_at": "2025-04-04T19:00:00Z"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"version": "0.5.7",
|
|
||||||
"is_latest": false,
|
|
||||||
"released_at": "2025-04-14T01:00:00Z"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"version": "0.5.8",
|
|
||||||
"is_latest": true,
|
|
||||||
"released_at": "2025-04-17T10:00:00Z"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"version": "0.5.9",
|
|
||||||
"is_latest": true,
|
|
||||||
"released_at": "2025-04-21T10:00:00Z"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"version": "0.5.10",
|
|
||||||
"is_latest": true,
|
|
||||||
"released_at": "2025-04-24T10:00:00Z"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "https://github.com/user-attachments/assets/dc336712-83cb-440d-8761-8d0a31de898d",
|
"icon": "https://github.com/user-attachments/assets/dc336712-83cb-440d-8761-8d0a31de898d",
|
||||||
|
|||||||
37
.github/workflows/build_cmake.yml
vendored
37
.github/workflows/build_cmake.yml
vendored
@@ -12,9 +12,9 @@ on:
|
|||||||
|
|
||||||
env:
|
env:
|
||||||
PLUGIN_NAME: QodeAssist
|
PLUGIN_NAME: QodeAssist
|
||||||
QT_VERSION: 6.8.3
|
QT_VERSION: 6.8.1
|
||||||
QT_CREATOR_VERSION: 16.0.1
|
QT_CREATOR_VERSION: 15.0.1
|
||||||
QT_CREATOR_VERSION_INTERNAL: 16.0.1
|
QT_CREATOR_VERSION_INTERNAL: 15.0.1
|
||||||
MACOS_DEPLOYMENT_TARGET: "11.0"
|
MACOS_DEPLOYMENT_TARGET: "11.0"
|
||||||
CMAKE_VERSION: "3.29.6"
|
CMAKE_VERSION: "3.29.6"
|
||||||
NINJA_VERSION: "1.12.1"
|
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",
|
environment_script: "C:/Program Files/Microsoft Visual Studio/2022/Enterprise/VC/Auxiliary/Build/vcvars64.bat",
|
||||||
}
|
}
|
||||||
- {
|
- {
|
||||||
name: "Ubuntu 22.04 GCC", artifact: "Linux-x64",
|
name: "Ubuntu Latest GCC", artifact: "Linux-x64",
|
||||||
os: ubuntu-22.04,
|
os: ubuntu-latest,
|
||||||
platform: linux_x64,
|
platform: linux_x64,
|
||||||
cc: "gcc", cxx: "g++"
|
cc: "gcc", cxx: "g++"
|
||||||
}
|
}
|
||||||
@@ -70,7 +70,7 @@ jobs:
|
|||||||
cmakeVersion: ${{ env.CMAKE_VERSION }}
|
cmakeVersion: ${{ env.CMAKE_VERSION }}
|
||||||
ninjaVersion: ${{ env.NINJA_VERSION }}
|
ninjaVersion: ${{ env.NINJA_VERSION }}
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install system libs
|
||||||
shell: cmake -P {0}
|
shell: cmake -P {0}
|
||||||
run: |
|
run: |
|
||||||
if ("${{ runner.os }}" STREQUAL "Linux")
|
if ("${{ runner.os }}" STREQUAL "Linux")
|
||||||
@@ -78,13 +78,7 @@ jobs:
|
|||||||
COMMAND sudo apt update
|
COMMAND sudo apt update
|
||||||
)
|
)
|
||||||
execute_process(
|
execute_process(
|
||||||
COMMAND sudo apt install
|
COMMAND sudo apt install libgl1-mesa-dev
|
||||||
# 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
|
RESULT_VARIABLE result
|
||||||
)
|
)
|
||||||
if (NOT result EQUAL 0)
|
if (NOT result EQUAL 0)
|
||||||
@@ -223,7 +217,7 @@ jobs:
|
|||||||
COMMAND python
|
COMMAND python
|
||||||
-u
|
-u
|
||||||
"${{ steps.qt_creator.outputs.qtc_dir }}/${build_plugin_py}"
|
"${{ steps.qt_creator.outputs.qtc_dir }}/${build_plugin_py}"
|
||||||
--name "$ENV{PLUGIN_NAME}-v${{ steps.git.outputs.tag }}-QtC$ENV{QT_CREATOR_VERSION}-${{ matrix.config.artifact }}"
|
--name "$ENV{PLUGIN_NAME}-$ENV{QT_CREATOR_VERSION}-${{ matrix.config.artifact }}"
|
||||||
--src .
|
--src .
|
||||||
--build build
|
--build build
|
||||||
--qt-path "${{ steps.qt.outputs.qt_dir }}"
|
--qt-path "${{ steps.qt.outputs.qt_dir }}"
|
||||||
@@ -241,31 +235,24 @@ jobs:
|
|||||||
- name: Upload
|
- name: Upload
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
path: ./${{ env.PLUGIN_NAME }}-v${{ steps.git.outputs.tag }}-QtC${{ env.QT_CREATOR_VERSION }}-${{ matrix.config.artifact }}.7z
|
path: ./${{ env.PLUGIN_NAME }}-${{ env.QT_CREATOR_VERSION }}-${{ matrix.config.artifact }}.7z
|
||||||
name: ${{ env.PLUGIN_NAME}}-v${{ steps.git.outputs.tag }}-QtC${{ env.QT_CREATOR_VERSION }}-${{ matrix.config.artifact }}.7z
|
name: ${{ env.PLUGIN_NAME}}-${{ env.QT_CREATOR_VERSION }}-${{ matrix.config.artifact }}.7z
|
||||||
|
|
||||||
# The json is the same for all platforms, but we need to save one
|
# The json is the same for all platforms, but we need to save one
|
||||||
- name: Upload plugin json
|
- name: Upload plugin json
|
||||||
if: startsWith(matrix.config.os, 'ubuntu')
|
if: matrix.config.os == 'ubuntu-latest'
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: ${{ env.PLUGIN_NAME }}-origin-json
|
name: ${{ env.PLUGIN_NAME }}-origin-json
|
||||||
path: ./build/build/${{ env.PLUGIN_NAME }}.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:
|
update_json:
|
||||||
if: contains(github.ref, 'tags/v')
|
if: contains(github.ref, 'tags/v')
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-latest
|
||||||
needs: build
|
needs: build
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
|
||||||
submodules: recursive
|
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: '20'
|
node-version: '20'
|
||||||
|
|||||||
24
.github/workflows/check_formatting.yml
vendored
24
.github/workflows/check_formatting.yml
vendored
@@ -1,24 +0,0 @@
|
|||||||
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
3
.gitmodules
vendored
@@ -1,3 +0,0 @@
|
|||||||
[submodule "3rdparty/inja"]
|
|
||||||
path = 3rdparty/inja
|
|
||||||
url = https://github.com/pantor/inja
|
|
||||||
1
3rdparty/inja
vendored
1
3rdparty/inja
vendored
Submodule 3rdparty/inja deleted from 384a6bef3f
@@ -8,38 +8,15 @@ set(CMAKE_AUTOUIC ON)
|
|||||||
set(CMAKE_CXX_STANDARD 20)
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||||
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
|
||||||
|
|
||||||
find_package(QtCreator REQUIRED COMPONENTS Core)
|
find_package(QtCreator REQUIRED COMPONENTS Core)
|
||||||
find_package(Qt6 COMPONENTS Core Gui Quick Widgets Network Test REQUIRED)
|
find_package(Qt6 COMPONENTS Core Gui Quick Widgets Network 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(llmcore)
|
||||||
add_subdirectory(settings)
|
add_subdirectory(settings)
|
||||||
add_subdirectory(logger)
|
add_subdirectory(logger)
|
||||||
add_subdirectory(ChatView)
|
add_subdirectory(ChatView)
|
||||||
add_subdirectory(context)
|
add_subdirectory(context)
|
||||||
if(GTest_FOUND)
|
|
||||||
add_subdirectory(test)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
add_qtc_plugin(QodeAssist
|
add_qtc_plugin(QodeAssist
|
||||||
PLUGIN_DEPENDS
|
PLUGIN_DEPENDS
|
||||||
@@ -66,33 +43,26 @@ add_qtc_plugin(QodeAssist
|
|||||||
LLMClientInterface.hpp LLMClientInterface.cpp
|
LLMClientInterface.hpp LLMClientInterface.cpp
|
||||||
templates/Templates.hpp
|
templates/Templates.hpp
|
||||||
templates/CodeLlamaFim.hpp
|
templates/CodeLlamaFim.hpp
|
||||||
templates/Ollama.hpp
|
|
||||||
templates/Claude.hpp
|
|
||||||
templates/OpenAI.hpp
|
|
||||||
templates/MistralAI.hpp
|
|
||||||
templates/StarCoder2Fim.hpp
|
templates/StarCoder2Fim.hpp
|
||||||
# templates/DeepSeekCoderFim.hpp
|
templates/DeepSeekCoderFim.hpp
|
||||||
# templates/CustomFimTemplate.hpp
|
templates/CustomFimTemplate.hpp
|
||||||
templates/Qwen.hpp
|
templates/Qwen.hpp
|
||||||
templates/OpenAICompatible.hpp
|
templates/Ollama.hpp
|
||||||
|
templates/BasicChat.hpp
|
||||||
templates/Llama3.hpp
|
templates/Llama3.hpp
|
||||||
templates/ChatML.hpp
|
templates/ChatML.hpp
|
||||||
templates/Alpaca.hpp
|
templates/Alpaca.hpp
|
||||||
templates/Llama2.hpp
|
templates/Llama2.hpp
|
||||||
|
templates/Claude.hpp
|
||||||
|
templates/OpenAI.hpp
|
||||||
templates/CodeLlamaQMLFim.hpp
|
templates/CodeLlamaQMLFim.hpp
|
||||||
templates/GoogleAI.hpp
|
|
||||||
templates/LlamaCppFim.hpp
|
|
||||||
providers/Providers.hpp
|
providers/Providers.hpp
|
||||||
providers/OllamaProvider.hpp providers/OllamaProvider.cpp
|
providers/OllamaProvider.hpp providers/OllamaProvider.cpp
|
||||||
providers/ClaudeProvider.hpp providers/ClaudeProvider.cpp
|
|
||||||
providers/OpenAIProvider.hpp providers/OpenAIProvider.cpp
|
|
||||||
providers/MistralAIProvider.hpp providers/MistralAIProvider.cpp
|
|
||||||
providers/LMStudioProvider.hpp providers/LMStudioProvider.cpp
|
providers/LMStudioProvider.hpp providers/LMStudioProvider.cpp
|
||||||
providers/OpenAICompatProvider.hpp providers/OpenAICompatProvider.cpp
|
providers/OpenAICompatProvider.hpp providers/OpenAICompatProvider.cpp
|
||||||
providers/OpenRouterAIProvider.hpp providers/OpenRouterAIProvider.cpp
|
providers/OpenRouterAIProvider.hpp providers/OpenRouterAIProvider.cpp
|
||||||
providers/GoogleAIProvider.hpp providers/GoogleAIProvider.cpp
|
providers/ClaudeProvider.hpp providers/ClaudeProvider.cpp
|
||||||
providers/LlamaCppProvider.hpp providers/LlamaCppProvider.cpp
|
providers/OpenAIProvider.hpp providers/OpenAIProvider.cpp
|
||||||
providers/CodestralProvider.hpp providers/CodestralProvider.cpp
|
|
||||||
QodeAssist.qrc
|
QodeAssist.qrc
|
||||||
LSPCompletion.hpp
|
LSPCompletion.hpp
|
||||||
LLMSuggestion.hpp LLMSuggestion.cpp
|
LLMSuggestion.hpp LLMSuggestion.cpp
|
||||||
@@ -102,27 +72,4 @@ add_qtc_plugin(QodeAssist
|
|||||||
ConfigurationManager.hpp ConfigurationManager.cpp
|
ConfigurationManager.hpp ConfigurationManager.cpp
|
||||||
CodeHandler.hpp CodeHandler.cpp
|
CodeHandler.hpp CodeHandler.cpp
|
||||||
UpdateStatusWidget.hpp UpdateStatusWidget.cpp
|
UpdateStatusWidget.hpp UpdateStatusWidget.cpp
|
||||||
widgets/CompletionProgressHandler.hpp widgets/CompletionProgressHandler.cpp
|
|
||||||
widgets/ProgressWidget.hpp widgets/ProgressWidget.cpp
|
|
||||||
widgets/EditorChatButton.hpp widgets/EditorChatButton.cpp
|
|
||||||
widgets/EditorChatButtonHandler.hpp widgets/EditorChatButtonHandler.cpp
|
|
||||||
widgets/QuickRefactorDialog.hpp widgets/QuickRefactorDialog.cpp
|
|
||||||
QuickRefactorHandler.hpp QuickRefactorHandler.cpp
|
|
||||||
)
|
)
|
||||||
|
|
||||||
get_target_property(QtCreatorCorePath QtCreator::Core LOCATION)
|
|
||||||
find_program(QtCreatorExecutable
|
|
||||||
NAMES
|
|
||||||
qtcreator "Qt Creator"
|
|
||||||
PATHS
|
|
||||||
"${QtCreatorCorePath}/../../../bin"
|
|
||||||
"${QtCreatorCorePath}/../../../MacOS"
|
|
||||||
NO_DEFAULT_PATH
|
|
||||||
)
|
|
||||||
if (QtCreatorExecutable)
|
|
||||||
add_custom_target(RunQtCreator
|
|
||||||
COMMAND ${QtCreatorExecutable} -pluginpath $<TARGET_FILE_DIR:QodeAssist>
|
|
||||||
DEPENDS QodeAssist
|
|
||||||
)
|
|
||||||
set_target_properties(RunQtCreator PROPERTIES FOLDER "qtc_runnable")
|
|
||||||
endif()
|
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ qt_add_qml_module(QodeAssistChatView
|
|||||||
qml/parts/TopBar.qml
|
qml/parts/TopBar.qml
|
||||||
qml/parts/BottomBar.qml
|
qml/parts/BottomBar.qml
|
||||||
qml/parts/AttachedFilesPlace.qml
|
qml/parts/AttachedFilesPlace.qml
|
||||||
qml/parts/ChatPreviewBar.qml
|
|
||||||
RESOURCES
|
RESOURCES
|
||||||
icons/attach-file-light.svg
|
icons/attach-file-light.svg
|
||||||
icons/attach-file-dark.svg
|
icons/attach-file-dark.svg
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2024-2025 Petr Mironychev
|
* Copyright (C) 2024 Petr Mironychev
|
||||||
*
|
*
|
||||||
* This file is part of QodeAssist.
|
* This file is part of QodeAssist.
|
||||||
*
|
*
|
||||||
@@ -18,9 +18,9 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "ChatModel.hpp"
|
#include "ChatModel.hpp"
|
||||||
#include <utils/aspects.h>
|
|
||||||
#include <QtCore/qjsonobject.h>
|
#include <QtCore/qjsonobject.h>
|
||||||
#include <QtQml>
|
#include <QtQml>
|
||||||
|
#include <utils/aspects.h>
|
||||||
|
|
||||||
#include "ChatAssistantSettings.hpp"
|
#include "ChatAssistantSettings.hpp"
|
||||||
|
|
||||||
@@ -31,8 +31,7 @@ ChatModel::ChatModel(QObject *parent)
|
|||||||
{
|
{
|
||||||
auto &settings = Settings::chatAssistantSettings();
|
auto &settings = Settings::chatAssistantSettings();
|
||||||
|
|
||||||
connect(
|
connect(&settings.chatTokensThreshold,
|
||||||
&settings.chatTokensThreshold,
|
|
||||||
&Utils::BaseAspect::changed,
|
&Utils::BaseAspect::changed,
|
||||||
this,
|
this,
|
||||||
&ChatModel::tokensThresholdChanged);
|
&ChatModel::tokensThresholdChanged);
|
||||||
@@ -124,13 +123,11 @@ QList<MessagePart> ChatModel::processMessageContent(const QString &content) cons
|
|||||||
QRegularExpression codeBlockRegex("```(\\w*)\\n?([\\s\\S]*?)```");
|
QRegularExpression codeBlockRegex("```(\\w*)\\n?([\\s\\S]*?)```");
|
||||||
int lastIndex = 0;
|
int lastIndex = 0;
|
||||||
auto blockMatches = codeBlockRegex.globalMatch(content);
|
auto blockMatches = codeBlockRegex.globalMatch(content);
|
||||||
bool foundCodeBlock = blockMatches.hasNext();
|
|
||||||
|
|
||||||
while (blockMatches.hasNext()) {
|
while (blockMatches.hasNext()) {
|
||||||
auto match = blockMatches.next();
|
auto match = blockMatches.next();
|
||||||
if (match.capturedStart() > lastIndex) {
|
if (match.capturedStart() > lastIndex) {
|
||||||
QString textBetween
|
QString textBetween = content.mid(lastIndex, match.capturedStart() - lastIndex).trimmed();
|
||||||
= content.mid(lastIndex, match.capturedStart() - lastIndex).trimmed();
|
|
||||||
if (!textBetween.isEmpty()) {
|
if (!textBetween.isEmpty()) {
|
||||||
parts.append({MessagePart::Text, textBetween, ""});
|
parts.append({MessagePart::Text, textBetween, ""});
|
||||||
}
|
}
|
||||||
@@ -141,19 +138,7 @@ QList<MessagePart> ChatModel::processMessageContent(const QString &content) cons
|
|||||||
|
|
||||||
if (lastIndex < content.length()) {
|
if (lastIndex < content.length()) {
|
||||||
QString remainingText = content.mid(lastIndex).trimmed();
|
QString remainingText = content.mid(lastIndex).trimmed();
|
||||||
|
if (!remainingText.isEmpty()) {
|
||||||
QRegularExpression unclosedBlockRegex("```(\\w*)\\n?([\\s\\S]*)$");
|
|
||||||
auto unclosedMatch = unclosedBlockRegex.match(remainingText);
|
|
||||||
|
|
||||||
if (unclosedMatch.hasMatch()) {
|
|
||||||
QString beforeCodeBlock = remainingText.left(unclosedMatch.capturedStart()).trimmed();
|
|
||||||
if (!beforeCodeBlock.isEmpty()) {
|
|
||||||
parts.append({MessagePart::Text, beforeCodeBlock, ""});
|
|
||||||
}
|
|
||||||
|
|
||||||
parts.append(
|
|
||||||
{MessagePart::Code, unclosedMatch.captured(2).trimmed(), unclosedMatch.captured(1)});
|
|
||||||
} else if (!remainingText.isEmpty()) {
|
|
||||||
parts.append({MessagePart::Text, remainingText, ""});
|
parts.append({MessagePart::Text, remainingText, ""});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -210,16 +195,4 @@ QString ChatModel::lastMessageId() const
|
|||||||
return !m_messages.isEmpty() ? m_messages.last().id : "";
|
return !m_messages.isEmpty() ? m_messages.last().id : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChatModel::resetModelTo(int index)
|
|
||||||
{
|
|
||||||
if (index < 0 || index >= m_messages.size())
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (index < m_messages.size()) {
|
|
||||||
beginRemoveRows(QModelIndex(), index, m_messages.size() - 1);
|
|
||||||
m_messages.remove(index, m_messages.size() - index);
|
|
||||||
endRemoveRows();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace QodeAssist::Chat
|
} // namespace QodeAssist::Chat
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2024-2025 Petr Mironychev
|
* Copyright (C) 2024 Petr Mironychev
|
||||||
*
|
*
|
||||||
* This file is part of QodeAssist.
|
* This file is part of QodeAssist.
|
||||||
*
|
*
|
||||||
@@ -73,8 +73,6 @@ public:
|
|||||||
QString currentModel() const;
|
QString currentModel() const;
|
||||||
QString lastMessageId() const;
|
QString lastMessageId() const;
|
||||||
|
|
||||||
Q_INVOKABLE void resetModelTo(int index);
|
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void tokensThresholdChanged();
|
void tokensThresholdChanged();
|
||||||
void modelReseted();
|
void modelReseted();
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2024-2025 Petr Mironychev
|
* Copyright (C) 2024 Petr Mironychev
|
||||||
*
|
*
|
||||||
* This file is part of QodeAssist.
|
* This file is part of QodeAssist.
|
||||||
*
|
*
|
||||||
@@ -29,6 +29,7 @@
|
|||||||
#include <projectexplorer/project.h>
|
#include <projectexplorer/project.h>
|
||||||
#include <projectexplorer/projectexplorer.h>
|
#include <projectexplorer/projectexplorer.h>
|
||||||
#include <projectexplorer/projectmanager.h>
|
#include <projectexplorer/projectmanager.h>
|
||||||
|
#include <projectexplorer/projecttree.h>
|
||||||
#include <utils/theme/theme.h>
|
#include <utils/theme/theme.h>
|
||||||
#include <utils/utilsicons.h>
|
#include <utils/utilsicons.h>
|
||||||
|
|
||||||
@@ -38,6 +39,8 @@
|
|||||||
#include "Logger.hpp"
|
#include "Logger.hpp"
|
||||||
#include "ProjectSettings.hpp"
|
#include "ProjectSettings.hpp"
|
||||||
#include "context/ContextManager.hpp"
|
#include "context/ContextManager.hpp"
|
||||||
|
#include "context/FileChunker.hpp"
|
||||||
|
#include "context/RAGManager.hpp"
|
||||||
#include "context/TokenUtils.hpp"
|
#include "context/TokenUtils.hpp"
|
||||||
|
|
||||||
namespace QodeAssist::Chat {
|
namespace QodeAssist::Chat {
|
||||||
@@ -45,20 +48,21 @@ namespace QodeAssist::Chat {
|
|||||||
ChatRootView::ChatRootView(QQuickItem *parent)
|
ChatRootView::ChatRootView(QQuickItem *parent)
|
||||||
: QQuickItem(parent)
|
: QQuickItem(parent)
|
||||||
, m_chatModel(new ChatModel(this))
|
, m_chatModel(new ChatModel(this))
|
||||||
, m_promptProvider(LLMCore::PromptTemplateManager::instance())
|
, m_clientInterface(new ClientInterface(m_chatModel, this))
|
||||||
, m_clientInterface(new ClientInterface(m_chatModel, &m_promptProvider, this))
|
|
||||||
{
|
{
|
||||||
m_isSyncOpenFiles = Settings::chatAssistantSettings().linkOpenFiles();
|
m_isSyncOpenFiles = Settings::chatAssistantSettings().linkOpenFiles();
|
||||||
connect(
|
connect(&Settings::chatAssistantSettings().linkOpenFiles, &Utils::BaseAspect::changed,
|
||||||
&Settings::chatAssistantSettings().linkOpenFiles,
|
|
||||||
&Utils::BaseAspect::changed,
|
|
||||||
this,
|
this,
|
||||||
[this]() { setIsSyncOpenFiles(Settings::chatAssistantSettings().linkOpenFiles()); });
|
[this](){
|
||||||
|
setIsSyncOpenFiles(Settings::chatAssistantSettings().linkOpenFiles());
|
||||||
|
});
|
||||||
|
|
||||||
auto &settings = Settings::generalSettings();
|
auto &settings = Settings::generalSettings();
|
||||||
|
|
||||||
connect(
|
connect(&settings.caModel,
|
||||||
&settings.caModel, &Utils::BaseAspect::changed, this, &ChatRootView::currentTemplateChanged);
|
&Utils::BaseAspect::changed,
|
||||||
|
this,
|
||||||
|
&ChatRootView::currentTemplateChanged);
|
||||||
|
|
||||||
connect(
|
connect(
|
||||||
m_clientInterface,
|
m_clientInterface,
|
||||||
@@ -75,16 +79,10 @@ ChatRootView::ChatRootView(QQuickItem *parent)
|
|||||||
connect(m_chatModel, &ChatModel::modelReseted, this, [this]() { setRecentFilePath(QString{}); });
|
connect(m_chatModel, &ChatModel::modelReseted, this, [this]() { setRecentFilePath(QString{}); });
|
||||||
connect(this, &ChatRootView::attachmentFilesChanged, &ChatRootView::updateInputTokensCount);
|
connect(this, &ChatRootView::attachmentFilesChanged, &ChatRootView::updateInputTokensCount);
|
||||||
connect(this, &ChatRootView::linkedFilesChanged, &ChatRootView::updateInputTokensCount);
|
connect(this, &ChatRootView::linkedFilesChanged, &ChatRootView::updateInputTokensCount);
|
||||||
connect(
|
connect(&Settings::chatAssistantSettings().useSystemPrompt, &Utils::BaseAspect::changed,
|
||||||
&Settings::chatAssistantSettings().useSystemPrompt,
|
this, &ChatRootView::updateInputTokensCount);
|
||||||
&Utils::BaseAspect::changed,
|
connect(&Settings::chatAssistantSettings().systemPrompt, &Utils::BaseAspect::changed,
|
||||||
this,
|
this, &ChatRootView::updateInputTokensCount);
|
||||||
&ChatRootView::updateInputTokensCount);
|
|
||||||
connect(
|
|
||||||
&Settings::chatAssistantSettings().systemPrompt,
|
|
||||||
&Utils::BaseAspect::changed,
|
|
||||||
this,
|
|
||||||
&ChatRootView::updateInputTokensCount);
|
|
||||||
|
|
||||||
auto editors = Core::EditorManager::instance();
|
auto editors = Core::EditorManager::instance();
|
||||||
|
|
||||||
@@ -165,10 +163,9 @@ QString ChatRootView::getChatsHistoryDir() const
|
|||||||
|
|
||||||
if (auto project = ProjectExplorer::ProjectManager::startupProject()) {
|
if (auto project = ProjectExplorer::ProjectManager::startupProject()) {
|
||||||
Settings::ProjectSettings projectSettings(project);
|
Settings::ProjectSettings projectSettings(project);
|
||||||
path = projectSettings.chatHistoryPath().toFSPathString();
|
path = projectSettings.chatHistoryPath().toString();
|
||||||
} else {
|
} else {
|
||||||
path = QString("%1/qodeassist/chat_history")
|
path = QString("%1/qodeassist/chat_history").arg(Core::ICore::userResourcePath().toString());
|
||||||
.arg(Core::ICore::userResourcePath().toFSPathString());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QDir dir(path);
|
QDir dir(path);
|
||||||
@@ -352,7 +349,7 @@ void ChatRootView::showAttachFilesDialog()
|
|||||||
dialog.setFileMode(QFileDialog::ExistingFiles);
|
dialog.setFileMode(QFileDialog::ExistingFiles);
|
||||||
|
|
||||||
if (auto project = ProjectExplorer::ProjectManager::startupProject()) {
|
if (auto project = ProjectExplorer::ProjectManager::startupProject()) {
|
||||||
dialog.setDirectory(project->projectDirectory().toFSPathString());
|
dialog.setDirectory(project->projectDirectory().toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dialog.exec() == QDialog::Accepted) {
|
if (dialog.exec() == QDialog::Accepted) {
|
||||||
@@ -386,7 +383,7 @@ void ChatRootView::showLinkFilesDialog()
|
|||||||
dialog.setFileMode(QFileDialog::ExistingFiles);
|
dialog.setFileMode(QFileDialog::ExistingFiles);
|
||||||
|
|
||||||
if (auto project = ProjectExplorer::ProjectManager::startupProject()) {
|
if (auto project = ProjectExplorer::ProjectManager::startupProject()) {
|
||||||
dialog.setDirectory(project->projectDirectory().toFSPathString());
|
dialog.setDirectory(project->projectDirectory().toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dialog.exec() == QDialog::Accepted) {
|
if (dialog.exec() == QDialog::Accepted) {
|
||||||
@@ -439,10 +436,9 @@ void ChatRootView::openChatHistoryFolder()
|
|||||||
QString path;
|
QString path;
|
||||||
if (auto project = ProjectExplorer::ProjectManager::startupProject()) {
|
if (auto project = ProjectExplorer::ProjectManager::startupProject()) {
|
||||||
Settings::ProjectSettings projectSettings(project);
|
Settings::ProjectSettings projectSettings(project);
|
||||||
path = projectSettings.chatHistoryPath().toFSPathString();
|
path = projectSettings.chatHistoryPath().toString();
|
||||||
} else {
|
} else {
|
||||||
path = QString("%1/qodeassist/chat_history")
|
path = QString("%1/qodeassist/chat_history").arg(Core::ICore::userResourcePath().toString());
|
||||||
.arg(Core::ICore::userResourcePath().toFSPathString());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QDir dir(path);
|
QDir dir(path);
|
||||||
@@ -454,27 +450,90 @@ void ChatRootView::openChatHistoryFolder()
|
|||||||
QDesktopServices::openUrl(url);
|
QDesktopServices::openUrl(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ChatRootView.cpp
|
||||||
|
|
||||||
|
void ChatRootView::testRAG(const QString &message)
|
||||||
|
{
|
||||||
|
auto project = ProjectExplorer::ProjectTree::currentProject();
|
||||||
|
if (!project) {
|
||||||
|
qDebug() << "No active project found";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString TEST_QUERY = message;
|
||||||
|
|
||||||
|
qDebug() << "Starting RAG test with query:";
|
||||||
|
qDebug() << TEST_QUERY;
|
||||||
|
qDebug() << "\nFirst, processing project files...";
|
||||||
|
|
||||||
|
auto files = Context::ContextManager::instance().getProjectSourceFiles(project);
|
||||||
|
// Было: auto future = Context::RAGManager::instance().processFiles(project, files);
|
||||||
|
// Стало:
|
||||||
|
auto future = Context::RAGManager::instance().processProjectFiles(project, files);
|
||||||
|
|
||||||
|
connect(
|
||||||
|
&Context::RAGManager::instance(),
|
||||||
|
&Context::RAGManager::vectorizationProgress,
|
||||||
|
this,
|
||||||
|
[](int processed, int total) {
|
||||||
|
qDebug() << QString("Vectorization progress: %1 of %2 files").arg(processed).arg(total);
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(
|
||||||
|
&Context::RAGManager::instance(),
|
||||||
|
&Context::RAGManager::vectorizationFinished,
|
||||||
|
this,
|
||||||
|
[this, project, TEST_QUERY]() {
|
||||||
|
qDebug() << "\nVectorization completed. Starting similarity search...\n";
|
||||||
|
// Было: Context::RAGManager::instance().searchSimilarDocuments(TEST_QUERY, project, 5);
|
||||||
|
// Стало:
|
||||||
|
auto future = Context::RAGManager::instance().findRelevantChunks(TEST_QUERY, project, 5);
|
||||||
|
future.then([](const QList<Context::RAGManager::ChunkSearchResult> &results) {
|
||||||
|
qDebug() << "Found" << results.size() << "relevant chunks:";
|
||||||
|
for (const auto &result : results) {
|
||||||
|
qDebug() << "File:" << result.filePath;
|
||||||
|
qDebug() << "Lines:" << result.startLine << "-" << result.endLine;
|
||||||
|
qDebug() << "Score:" << result.combinedScore;
|
||||||
|
qDebug() << "Content:" << result.content;
|
||||||
|
qDebug() << "---";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatRootView::testChunking()
|
||||||
|
{
|
||||||
|
auto project = ProjectExplorer::ProjectTree::currentProject();
|
||||||
|
if (!project) {
|
||||||
|
qDebug() << "No active project found";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Context::FileChunker::ChunkingConfig config;
|
||||||
|
Context::ContextManager::instance().testProjectChunks(project, config);
|
||||||
|
}
|
||||||
|
|
||||||
void ChatRootView::updateInputTokensCount()
|
void ChatRootView::updateInputTokensCount()
|
||||||
{
|
{
|
||||||
int inputTokens = m_messageTokensCount;
|
int inputTokens = m_messageTokensCount;
|
||||||
auto &settings = Settings::chatAssistantSettings();
|
auto& settings = Settings::chatAssistantSettings();
|
||||||
|
|
||||||
if (settings.useSystemPrompt()) {
|
if (settings.useSystemPrompt()) {
|
||||||
inputTokens += Context::TokenUtils::estimateTokens(settings.systemPrompt());
|
inputTokens += Context::TokenUtils::estimateTokens(settings.systemPrompt());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!m_attachmentFiles.isEmpty()) {
|
if (!m_attachmentFiles.isEmpty()) {
|
||||||
auto attachFiles = m_clientInterface->contextManager()->getContentFiles(m_attachmentFiles);
|
auto attachFiles = Context::ContextManager::instance().getContentFiles(m_attachmentFiles);
|
||||||
inputTokens += Context::TokenUtils::estimateFilesTokens(attachFiles);
|
inputTokens += Context::TokenUtils::estimateFilesTokens(attachFiles);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!m_linkedFiles.isEmpty()) {
|
if (!m_linkedFiles.isEmpty()) {
|
||||||
auto linkFiles = m_clientInterface->contextManager()->getContentFiles(m_linkedFiles);
|
auto linkFiles = Context::ContextManager::instance().getContentFiles(m_linkedFiles);
|
||||||
inputTokens += Context::TokenUtils::estimateFilesTokens(linkFiles);
|
inputTokens += Context::TokenUtils::estimateFilesTokens(linkFiles);
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto &history = m_chatModel->getChatHistory();
|
const auto& history = m_chatModel->getChatHistory();
|
||||||
for (const auto &message : history) {
|
for (const auto& message : history) {
|
||||||
inputTokens += Context::TokenUtils::estimateTokens(message.content);
|
inputTokens += Context::TokenUtils::estimateTokens(message.content);
|
||||||
inputTokens += 4; // + role
|
inputTokens += 4; // + role
|
||||||
}
|
}
|
||||||
@@ -496,7 +555,7 @@ bool ChatRootView::isSyncOpenFiles() const
|
|||||||
void ChatRootView::onEditorAboutToClose(Core::IEditor *editor)
|
void ChatRootView::onEditorAboutToClose(Core::IEditor *editor)
|
||||||
{
|
{
|
||||||
if (auto document = editor->document(); document && isSyncOpenFiles()) {
|
if (auto document = editor->document(); document && isSyncOpenFiles()) {
|
||||||
QString filePath = document->filePath().toFSPathString();
|
QString filePath = document->filePath().toString();
|
||||||
m_linkedFiles.removeOne(filePath);
|
m_linkedFiles.removeOne(filePath);
|
||||||
emit linkedFilesChanged();
|
emit linkedFilesChanged();
|
||||||
}
|
}
|
||||||
@@ -509,8 +568,8 @@ void ChatRootView::onEditorAboutToClose(Core::IEditor *editor)
|
|||||||
void ChatRootView::onAppendLinkFileFromEditor(Core::IEditor *editor)
|
void ChatRootView::onAppendLinkFileFromEditor(Core::IEditor *editor)
|
||||||
{
|
{
|
||||||
if (auto document = editor->document(); document && isSyncOpenFiles()) {
|
if (auto document = editor->document(); document && isSyncOpenFiles()) {
|
||||||
QString filePath = document->filePath().toFSPathString();
|
QString filePath = document->filePath().toString();
|
||||||
if (!m_linkedFiles.contains(filePath) && !shouldIgnoreFileForAttach(document->filePath())) {
|
if (!m_linkedFiles.contains(filePath)) {
|
||||||
m_linkedFiles.append(filePath);
|
m_linkedFiles.append(filePath);
|
||||||
emit linkedFilesChanged();
|
emit linkedFilesChanged();
|
||||||
}
|
}
|
||||||
@@ -537,19 +596,4 @@ void ChatRootView::setRecentFilePath(const QString &filePath)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ChatRootView::shouldIgnoreFileForAttach(const Utils::FilePath &filePath)
|
|
||||||
{
|
|
||||||
auto project = ProjectExplorer::ProjectManager::projectForFile(filePath);
|
|
||||||
if (project
|
|
||||||
&& m_clientInterface->contextManager()
|
|
||||||
->ignoreManager()
|
|
||||||
->shouldIgnore(filePath.toFSPathString(), project)) {
|
|
||||||
LOG_MESSAGE(QString("Ignoring file for attachment due to .qodeassistignore: %1")
|
|
||||||
.arg(filePath.toFSPathString()));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace QodeAssist::Chat
|
} // namespace QodeAssist::Chat
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2024-2025 Petr Mironychev
|
* Copyright (C) 2024 Petr Mironychev
|
||||||
*
|
*
|
||||||
* This file is part of QodeAssist.
|
* This file is part of QodeAssist.
|
||||||
*
|
*
|
||||||
@@ -23,7 +23,6 @@
|
|||||||
|
|
||||||
#include "ChatModel.hpp"
|
#include "ChatModel.hpp"
|
||||||
#include "ClientInterface.hpp"
|
#include "ClientInterface.hpp"
|
||||||
#include "llmcore/PromptProviderChat.hpp"
|
|
||||||
#include <coreplugin/editormanager/editormanager.h>
|
#include <coreplugin/editormanager/editormanager.h>
|
||||||
|
|
||||||
namespace QodeAssist::Chat {
|
namespace QodeAssist::Chat {
|
||||||
@@ -66,6 +65,8 @@ public:
|
|||||||
Q_INVOKABLE void calculateMessageTokensCount(const QString &message);
|
Q_INVOKABLE void calculateMessageTokensCount(const QString &message);
|
||||||
Q_INVOKABLE void setIsSyncOpenFiles(bool state);
|
Q_INVOKABLE void setIsSyncOpenFiles(bool state);
|
||||||
Q_INVOKABLE void openChatHistoryFolder();
|
Q_INVOKABLE void openChatHistoryFolder();
|
||||||
|
Q_INVOKABLE void testRAG(const QString &message);
|
||||||
|
Q_INVOKABLE void testChunking();
|
||||||
|
|
||||||
Q_INVOKABLE void updateInputTokensCount();
|
Q_INVOKABLE void updateInputTokensCount();
|
||||||
int inputTokensCount() const;
|
int inputTokensCount() const;
|
||||||
@@ -78,7 +79,6 @@ public:
|
|||||||
|
|
||||||
QString chatFileName() const;
|
QString chatFileName() const;
|
||||||
void setRecentFilePath(const QString &filePath);
|
void setRecentFilePath(const QString &filePath);
|
||||||
bool shouldIgnoreFileForAttach(const Utils::FilePath &filePath);
|
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void sendMessage(const QString &message);
|
void sendMessage(const QString &message);
|
||||||
@@ -101,7 +101,6 @@ private:
|
|||||||
QString getSuggestedFileName() const;
|
QString getSuggestedFileName() const;
|
||||||
|
|
||||||
ChatModel *m_chatModel;
|
ChatModel *m_chatModel;
|
||||||
LLMCore::PromptProviderChat m_promptProvider;
|
|
||||||
ClientInterface *m_clientInterface;
|
ClientInterface *m_clientInterface;
|
||||||
QString m_currentTemplate;
|
QString m_currentTemplate;
|
||||||
QString m_recentFilePath;
|
QString m_recentFilePath;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2024-2025 Petr Mironychev
|
* Copyright (C) 2024 Petr Mironychev
|
||||||
*
|
*
|
||||||
* This file is part of QodeAssist.
|
* This file is part of QodeAssist.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2024-2025 Petr Mironychev
|
* Copyright (C) 2024 Petr Mironychev
|
||||||
*
|
*
|
||||||
* This file is part of QodeAssist.
|
* This file is part of QodeAssist.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2024-2025 Petr Mironychev
|
* Copyright (C) 2024 Petr Mironychev
|
||||||
*
|
*
|
||||||
* This file is part of QodeAssist.
|
* This file is part of QodeAssist.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2024-2025 Petr Mironychev
|
* Copyright (C) 2024 Petr Mironychev
|
||||||
*
|
*
|
||||||
* This file is part of QodeAssist.
|
* This file is part of QodeAssist.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2024-2025 Petr Mironychev
|
* Copyright (C) 2024 Petr Mironychev
|
||||||
*
|
*
|
||||||
* This file is part of QodeAssist.
|
* This file is part of QodeAssist.
|
||||||
*
|
*
|
||||||
@@ -40,4 +40,4 @@ void ChatWidget::scrollToBottom()
|
|||||||
{
|
{
|
||||||
QMetaObject::invokeMethod(rootObject(), "scrollToBottom");
|
QMetaObject::invokeMethod(rootObject(), "scrollToBottom");
|
||||||
}
|
}
|
||||||
} // namespace QodeAssist::Chat
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2024-2025 Petr Mironychev
|
* Copyright (C) 2024 Petr Mironychev
|
||||||
*
|
*
|
||||||
* This file is part of QodeAssist.
|
* This file is part of QodeAssist.
|
||||||
*
|
*
|
||||||
@@ -38,4 +38,4 @@ signals:
|
|||||||
void clearPressed();
|
void clearPressed();
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace QodeAssist::Chat
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2024-2025 Petr Mironychev
|
* Copyright (C) 2024 Petr Mironychev
|
||||||
*
|
*
|
||||||
* This file is part of QodeAssist.
|
* This file is part of QodeAssist.
|
||||||
*
|
*
|
||||||
@@ -19,11 +19,11 @@
|
|||||||
|
|
||||||
#include "ClientInterface.hpp"
|
#include "ClientInterface.hpp"
|
||||||
|
|
||||||
#include <texteditor/textdocument.h>
|
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QUuid>
|
#include <QUuid>
|
||||||
|
#include <texteditor/textdocument.h>
|
||||||
|
|
||||||
#include <coreplugin/editormanager/editormanager.h>
|
#include <coreplugin/editormanager/editormanager.h>
|
||||||
#include <coreplugin/editormanager/ieditor.h>
|
#include <coreplugin/editormanager/ieditor.h>
|
||||||
@@ -33,30 +33,27 @@
|
|||||||
#include <texteditor/texteditor.h>
|
#include <texteditor/texteditor.h>
|
||||||
|
|
||||||
#include "ChatAssistantSettings.hpp"
|
#include "ChatAssistantSettings.hpp"
|
||||||
|
#include "ContextManager.hpp"
|
||||||
#include "GeneralSettings.hpp"
|
#include "GeneralSettings.hpp"
|
||||||
#include "Logger.hpp"
|
#include "Logger.hpp"
|
||||||
|
#include "PromptTemplateManager.hpp"
|
||||||
#include "ProvidersManager.hpp"
|
#include "ProvidersManager.hpp"
|
||||||
|
|
||||||
namespace QodeAssist::Chat {
|
namespace QodeAssist::Chat {
|
||||||
|
|
||||||
ClientInterface::ClientInterface(
|
ClientInterface::ClientInterface(ChatModel *chatModel, QObject *parent)
|
||||||
ChatModel *chatModel, LLMCore::IPromptProvider *promptProvider, QObject *parent)
|
|
||||||
: QObject(parent)
|
: QObject(parent)
|
||||||
, m_requestHandler(new LLMCore::RequestHandler(this))
|
, m_requestHandler(new LLMCore::RequestHandler(this))
|
||||||
, m_chatModel(chatModel)
|
, m_chatModel(chatModel)
|
||||||
, m_promptProvider(promptProvider)
|
|
||||||
, m_contextManager(new Context::ContextManager(this))
|
|
||||||
{
|
{
|
||||||
connect(
|
connect(m_requestHandler,
|
||||||
m_requestHandler,
|
|
||||||
&LLMCore::RequestHandler::completionReceived,
|
&LLMCore::RequestHandler::completionReceived,
|
||||||
this,
|
this,
|
||||||
[this](const QString &completion, const QJsonObject &request, bool isComplete) {
|
[this](const QString &completion, const QJsonObject &request, bool isComplete) {
|
||||||
handleLLMResponse(completion, request, isComplete);
|
handleLLMResponse(completion, request, isComplete);
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(
|
connect(m_requestHandler,
|
||||||
m_requestHandler,
|
|
||||||
&LLMCore::RequestHandler::requestFinished,
|
&LLMCore::RequestHandler::requestFinished,
|
||||||
this,
|
this,
|
||||||
[this](const QString &, bool success, const QString &errorString) {
|
[this](const QString &, bool success, const QString &errorString) {
|
||||||
@@ -73,7 +70,7 @@ void ClientInterface::sendMessage(
|
|||||||
{
|
{
|
||||||
cancelRequest();
|
cancelRequest();
|
||||||
|
|
||||||
auto attachFiles = m_contextManager->getContentFiles(attachments);
|
auto attachFiles = Context::ContextManager::instance().getContentFiles(attachments);
|
||||||
m_chatModel->addMessage(message, ChatModel::ChatRole::User, "", attachFiles);
|
m_chatModel->addMessage(message, ChatModel::ChatRole::User, "", attachFiles);
|
||||||
|
|
||||||
auto &chatAssistantSettings = Settings::chatAssistantSettings();
|
auto &chatAssistantSettings = Settings::chatAssistantSettings();
|
||||||
@@ -87,7 +84,8 @@ void ClientInterface::sendMessage(
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto templateName = Settings::generalSettings().caTemplate();
|
auto templateName = Settings::generalSettings().caTemplate();
|
||||||
auto promptTemplate = m_promptProvider->getTemplateByName(templateName);
|
auto promptTemplate = LLMCore::PromptTemplateManager::instance().getChatTemplateByName(
|
||||||
|
templateName);
|
||||||
|
|
||||||
if (!promptTemplate) {
|
if (!promptTemplate) {
|
||||||
LOG_MESSAGE(QString("No template found with name: %1").arg(templateName));
|
LOG_MESSAGE(QString("No template found with name: %1").arg(templateName));
|
||||||
@@ -95,47 +93,51 @@ void ClientInterface::sendMessage(
|
|||||||
}
|
}
|
||||||
|
|
||||||
LLMCore::ContextData context;
|
LLMCore::ContextData context;
|
||||||
|
context.prefix = message;
|
||||||
|
context.suffix = "";
|
||||||
|
|
||||||
|
QString systemPrompt;
|
||||||
|
if (chatAssistantSettings.useSystemPrompt())
|
||||||
|
systemPrompt = chatAssistantSettings.systemPrompt();
|
||||||
|
|
||||||
if (chatAssistantSettings.useSystemPrompt()) {
|
|
||||||
QString systemPrompt = chatAssistantSettings.systemPrompt();
|
|
||||||
if (!linkedFiles.isEmpty()) {
|
if (!linkedFiles.isEmpty()) {
|
||||||
systemPrompt = getSystemPromptWithLinkedFiles(systemPrompt, linkedFiles);
|
systemPrompt = getSystemPromptWithLinkedFiles(systemPrompt, linkedFiles);
|
||||||
}
|
}
|
||||||
context.systemPrompt = systemPrompt;
|
|
||||||
}
|
|
||||||
|
|
||||||
QVector<LLMCore::Message> messages;
|
QJsonObject providerRequest;
|
||||||
for (const auto &msg : m_chatModel->getChatHistory()) {
|
providerRequest["model"] = Settings::generalSettings().caModel();
|
||||||
messages.append({msg.role == ChatModel::ChatRole::User ? "user" : "assistant", msg.content});
|
providerRequest["stream"] = chatAssistantSettings.stream();
|
||||||
}
|
providerRequest["messages"] = m_chatModel->prepareMessagesForRequest(systemPrompt);
|
||||||
context.history = messages;
|
|
||||||
|
if (promptTemplate)
|
||||||
|
promptTemplate->prepareRequest(providerRequest, context);
|
||||||
|
else
|
||||||
|
qWarning("No prompt template found");
|
||||||
|
|
||||||
|
if (provider)
|
||||||
|
provider->prepareRequest(providerRequest, LLMCore::RequestType::Chat);
|
||||||
|
else
|
||||||
|
qWarning("No provider found");
|
||||||
|
|
||||||
LLMCore::LLMConfig config;
|
LLMCore::LLMConfig config;
|
||||||
config.requestType = LLMCore::RequestType::Chat;
|
config.requestType = LLMCore::RequestType::Chat;
|
||||||
config.provider = provider;
|
config.provider = provider;
|
||||||
config.promptTemplate = promptTemplate;
|
config.promptTemplate = promptTemplate;
|
||||||
if (provider->providerID() == LLMCore::ProviderID::GoogleAI) {
|
config.url = QString("%1%2").arg(Settings::generalSettings().caUrl(), provider->chatEndpoint());
|
||||||
QString stream = chatAssistantSettings.stream() ? QString{"streamGenerateContent?alt=sse"}
|
config.providerRequest = providerRequest;
|
||||||
: QString{"generateContent?"};
|
config.multiLineCompletion = false;
|
||||||
config.url = QUrl(QString("%1/models/%2:%3")
|
|
||||||
.arg(
|
|
||||||
Settings::generalSettings().caUrl(),
|
|
||||||
Settings::generalSettings().caModel(),
|
|
||||||
stream));
|
|
||||||
} else {
|
|
||||||
config.url
|
|
||||||
= QString("%1%2").arg(Settings::generalSettings().caUrl(), provider->chatEndpoint());
|
|
||||||
config.providerRequest
|
|
||||||
= {{"model", Settings::generalSettings().caModel()},
|
|
||||||
{"stream", chatAssistantSettings.stream()}};
|
|
||||||
}
|
|
||||||
|
|
||||||
config.apiKey = provider->apiKey();
|
config.apiKey = provider->apiKey();
|
||||||
|
|
||||||
config.provider
|
QJsonObject request;
|
||||||
->prepareRequest(config.providerRequest, promptTemplate, context, LLMCore::RequestType::Chat);
|
request["id"] = QUuid::createUuid().toString();
|
||||||
|
|
||||||
|
auto errors = config.provider->validateRequest(config.providerRequest, promptTemplate->type());
|
||||||
|
if (!errors.isEmpty()) {
|
||||||
|
LOG_MESSAGE("Validate errors for chat request:");
|
||||||
|
LOG_MESSAGES(errors);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
QJsonObject request{{"id", QUuid::createUuid().toString()}};
|
|
||||||
m_requestHandler->sendLLMRequest(config, request);
|
m_requestHandler->sendLLMRequest(config, request);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,8 +153,9 @@ void ClientInterface::cancelRequest()
|
|||||||
m_requestHandler->cancelRequest(id);
|
m_requestHandler->cancelRequest(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClientInterface::handleLLMResponse(
|
void ClientInterface::handleLLMResponse(const QString &response,
|
||||||
const QString &response, const QJsonObject &request, bool isComplete)
|
const QJsonObject &request,
|
||||||
|
bool isComplete)
|
||||||
{
|
{
|
||||||
const auto message = response.trimmed();
|
const auto message = response.trimmed();
|
||||||
|
|
||||||
@@ -183,35 +186,30 @@ QString ClientInterface::getCurrentFileContext() const
|
|||||||
}
|
}
|
||||||
|
|
||||||
QString fileInfo = QString("Language: %1\nFile: %2\n\n")
|
QString fileInfo = QString("Language: %1\nFile: %2\n\n")
|
||||||
.arg(textDocument->mimeType(), textDocument->filePath().toFSPathString());
|
.arg(textDocument->mimeType(), textDocument->filePath().toString());
|
||||||
|
|
||||||
QString content = textDocument->document()->toPlainText();
|
QString content = textDocument->document()->toPlainText();
|
||||||
|
|
||||||
LOG_MESSAGE(QString("Got context from file: %1").arg(textDocument->filePath().toFSPathString()));
|
LOG_MESSAGE(QString("Got context from file: %1").arg(textDocument->filePath().toString()));
|
||||||
|
|
||||||
return QString("Current file context:\n%1\nFile content:\n%2").arg(fileInfo, content);
|
return QString("Current file context:\n%1\nFile content:\n%2").arg(fileInfo, content);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString ClientInterface::getSystemPromptWithLinkedFiles(
|
QString ClientInterface::getSystemPromptWithLinkedFiles(const QString &basePrompt, const QList<QString> &linkedFiles) const
|
||||||
const QString &basePrompt, const QList<QString> &linkedFiles) const
|
|
||||||
{
|
{
|
||||||
QString updatedPrompt = basePrompt;
|
QString updatedPrompt = basePrompt;
|
||||||
|
|
||||||
if (!linkedFiles.isEmpty()) {
|
if (!linkedFiles.isEmpty()) {
|
||||||
updatedPrompt += "\n\nLinked files for reference:\n";
|
updatedPrompt += "\n\nLinked files for reference:\n";
|
||||||
|
|
||||||
auto contentFiles = m_contextManager->getContentFiles(linkedFiles);
|
auto contentFiles = Context::ContextManager::instance().getContentFiles(linkedFiles);
|
||||||
for (const auto &file : contentFiles) {
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return updatedPrompt;
|
return updatedPrompt;
|
||||||
}
|
}
|
||||||
|
|
||||||
Context::ContextManager *ClientInterface::contextManager() const
|
|
||||||
{
|
|
||||||
return m_contextManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace QodeAssist::Chat
|
} // namespace QodeAssist::Chat
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2024-2025 Petr Mironychev
|
* Copyright (C) 2024 Petr Mironychev
|
||||||
*
|
*
|
||||||
* This file is part of QodeAssist.
|
* This file is part of QodeAssist.
|
||||||
*
|
*
|
||||||
@@ -25,8 +25,6 @@
|
|||||||
|
|
||||||
#include "ChatModel.hpp"
|
#include "ChatModel.hpp"
|
||||||
#include "RequestHandler.hpp"
|
#include "RequestHandler.hpp"
|
||||||
#include "llmcore/IPromptProvider.hpp"
|
|
||||||
#include <context/ContextManager.hpp>
|
|
||||||
|
|
||||||
namespace QodeAssist::Chat {
|
namespace QodeAssist::Chat {
|
||||||
|
|
||||||
@@ -35,8 +33,7 @@ class ClientInterface : public QObject
|
|||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit ClientInterface(
|
explicit ClientInterface(ChatModel *chatModel, QObject *parent = nullptr);
|
||||||
ChatModel *chatModel, LLMCore::IPromptProvider *promptProvider, QObject *parent = nullptr);
|
|
||||||
~ClientInterface();
|
~ClientInterface();
|
||||||
|
|
||||||
void sendMessage(
|
void sendMessage(
|
||||||
@@ -46,8 +43,6 @@ public:
|
|||||||
void clearMessages();
|
void clearMessages();
|
||||||
void cancelRequest();
|
void cancelRequest();
|
||||||
|
|
||||||
Context::ContextManager *contextManager() const;
|
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void errorOccurred(const QString &error);
|
void errorOccurred(const QString &error);
|
||||||
void messageReceivedCompletely();
|
void messageReceivedCompletely();
|
||||||
@@ -56,12 +51,11 @@ private:
|
|||||||
void handleLLMResponse(const QString &response, const QJsonObject &request, bool isComplete);
|
void handleLLMResponse(const QString &response, const QJsonObject &request, bool isComplete);
|
||||||
QString getCurrentFileContext() const;
|
QString getCurrentFileContext() const;
|
||||||
QString getSystemPromptWithLinkedFiles(
|
QString getSystemPromptWithLinkedFiles(
|
||||||
const QString &basePrompt, const QList<QString> &linkedFiles) const;
|
const QString &basePrompt,
|
||||||
|
const QList<QString> &linkedFiles) const;
|
||||||
|
|
||||||
LLMCore::IPromptProvider *m_promptProvider = nullptr;
|
|
||||||
ChatModel *m_chatModel;
|
|
||||||
LLMCore::RequestHandler *m_requestHandler;
|
LLMCore::RequestHandler *m_requestHandler;
|
||||||
Context::ContextManager *m_contextManager;
|
ChatModel *m_chatModel;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace QodeAssist::Chat
|
} // namespace QodeAssist::Chat
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2024-2025 Petr Mironychev
|
* Copyright (C) 2024 Petr Mironychev
|
||||||
*
|
*
|
||||||
* This file is part of QodeAssist.
|
* This file is part of QodeAssist.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2024-2025 Petr Mironychev
|
* Copyright (C) 2024 Petr Mironychev
|
||||||
*
|
*
|
||||||
* This file is part of QodeAssist.
|
* This file is part of QodeAssist.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2024-2025 Petr Mironychev
|
* Copyright (C) 2024 Petr Mironychev
|
||||||
*
|
*
|
||||||
* This file is part of QodeAssist.
|
* This file is part of QodeAssist.
|
||||||
*
|
*
|
||||||
@@ -27,25 +27,14 @@ Rectangle {
|
|||||||
|
|
||||||
property alias msgModel: msgCreator.model
|
property alias msgModel: msgCreator.model
|
||||||
property alias messageAttachments: attachmentsModel.model
|
property alias messageAttachments: attachmentsModel.model
|
||||||
property bool isUserMessage: false
|
|
||||||
property int messageIndex: -1
|
|
||||||
|
|
||||||
signal resetChatToMessage(int index)
|
|
||||||
|
|
||||||
height: msgColumn.implicitHeight + 10
|
height: msgColumn.implicitHeight + 10
|
||||||
radius: 8
|
radius: 8
|
||||||
color: isUserMessage ? palette.alternateBase
|
|
||||||
: palette.base
|
|
||||||
|
|
||||||
HoverHandler {
|
|
||||||
id: mouse
|
|
||||||
}
|
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
id: msgColumn
|
id: msgColumn
|
||||||
|
|
||||||
x: 5
|
width: parent.width
|
||||||
width: parent.width - x
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
spacing: 5
|
spacing: 5
|
||||||
|
|
||||||
@@ -124,32 +113,6 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: userMessageMarker
|
|
||||||
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
width: 3
|
|
||||||
height: root.height - root.radius
|
|
||||||
color: "#92BD6C"
|
|
||||||
radius: root.radius
|
|
||||||
visible: root.isUserMessage
|
|
||||||
}
|
|
||||||
|
|
||||||
QoAButton {
|
|
||||||
id: stopButtonId
|
|
||||||
|
|
||||||
anchors {
|
|
||||||
right: parent.right
|
|
||||||
top: parent.top
|
|
||||||
}
|
|
||||||
|
|
||||||
text: qsTr("ResetTo")
|
|
||||||
visible: root.isUserMessage && mouse.hovered
|
|
||||||
onClicked: function() {
|
|
||||||
root.resetChatToMessage(root.messageIndex)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
component TextComponent : TextBlock {
|
component TextComponent : TextBlock {
|
||||||
required property var itemData
|
required property var itemData
|
||||||
height: implicitHeight + 10
|
height: implicitHeight + 10
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2024-2025 Petr Mironychev
|
* Copyright (C) 2024 Petr Mironychev
|
||||||
*
|
*
|
||||||
* This file is part of QodeAssist.
|
* This file is part of QodeAssist.
|
||||||
*
|
*
|
||||||
@@ -76,10 +76,6 @@ ChatRootView {
|
|||||||
text: qsTr("Latest chat file name: %1").arg(root.chatFileName.length > 0 ? root.chatFileName : "Unsaved")
|
text: qsTr("Latest chat file name: %1").arg(root.chatFileName.length > 0 ? root.chatFileName : "Unsaved")
|
||||||
}
|
}
|
||||||
openChatHistory.onClicked: root.openChatHistoryFolder()
|
openChatHistory.onClicked: root.openChatHistoryFolder()
|
||||||
expandScrollbar {
|
|
||||||
text: scroll.isPreviewMode ? "»" : "«"
|
|
||||||
onClicked: scroll.isPreviewMode = !scroll.isPreviewMode
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ListView {
|
ListView {
|
||||||
@@ -96,19 +92,12 @@ ChatRootView {
|
|||||||
|
|
||||||
delegate: ChatItem {
|
delegate: ChatItem {
|
||||||
required property var model
|
required property var model
|
||||||
required property int index
|
|
||||||
|
|
||||||
width: ListView.view.width - scroll.width
|
width: ListView.view.width - scroll.width
|
||||||
msgModel: root.chatModel.processMessageContent(model.content)
|
msgModel: root.chatModel.processMessageContent(model.content)
|
||||||
messageAttachments: model.attachments
|
messageAttachments: model.attachments
|
||||||
isUserMessage: model.roleType === ChatModel.User
|
color: model.roleType === ChatModel.User ? palette.alternateBase
|
||||||
messageIndex: index
|
: palette.base
|
||||||
|
|
||||||
onResetChatToMessage: function(index) {
|
|
||||||
messageInput.text = model.content
|
|
||||||
messageInput.cursorPosition = model.content.length
|
|
||||||
root.chatModel.resetModelTo(index)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
header: Item {
|
header: Item {
|
||||||
@@ -118,50 +107,6 @@ ChatRootView {
|
|||||||
|
|
||||||
ScrollBar.vertical: QQC.ScrollBar {
|
ScrollBar.vertical: QQC.ScrollBar {
|
||||||
id: scroll
|
id: scroll
|
||||||
|
|
||||||
property bool isPreviewMode: false
|
|
||||||
readonly property int previewWidth: 30
|
|
||||||
|
|
||||||
implicitWidth: isPreviewMode ? scroll.previewWidth : 16
|
|
||||||
|
|
||||||
contentItem: Rectangle {
|
|
||||||
implicitWidth: scroll.isPreviewMode ? scroll.previewWidth : 6
|
|
||||||
implicitHeight: 100
|
|
||||||
radius: 3
|
|
||||||
color: scroll.pressed ? palette.dark :
|
|
||||||
scroll.hovered ? palette.mid :
|
|
||||||
palette.button
|
|
||||||
|
|
||||||
Behavior on implicitWidth {
|
|
||||||
NumberAnimation { duration: 150 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
background: Rectangle {
|
|
||||||
color: scroll.isPreviewMode ? "transparent" :
|
|
||||||
palette.window.hslLightness > 0.5 ?
|
|
||||||
Qt.darker(palette.window, 1.1) :
|
|
||||||
Qt.lighter(palette.window, 1.1)
|
|
||||||
radius: 3
|
|
||||||
}
|
|
||||||
|
|
||||||
ChatPreviewBar {
|
|
||||||
anchors.fill: parent
|
|
||||||
targetView: chatListView
|
|
||||||
visible: parent.isPreviewMode
|
|
||||||
opacity: parent.isPreviewMode ? 1 : 0
|
|
||||||
|
|
||||||
Behavior on opacity {
|
|
||||||
NumberAnimation { duration: 150 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on implicitWidth {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: 150
|
|
||||||
easing.type: Easing.InOutQuad
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onCountChanged: {
|
onCountChanged: {
|
||||||
@@ -253,6 +198,8 @@ ChatRootView {
|
|||||||
}
|
}
|
||||||
attachFiles.onClicked: root.showAttachFilesDialog()
|
attachFiles.onClicked: root.showAttachFilesDialog()
|
||||||
linkFiles.onClicked: root.showLinkFilesDialog()
|
linkFiles.onClicked: root.showLinkFilesDialog()
|
||||||
|
testRag.onClicked: root.testRAG(messageInput.text)
|
||||||
|
testChunks.onClicked: root.testChunking()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2024-2025 Petr Mironychev
|
* Copyright (C) 2024 Petr Mironychev
|
||||||
*
|
*
|
||||||
* This file is part of QodeAssist.
|
* This file is part of QodeAssist.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2024-2025 Petr Mironychev
|
* Copyright (C) 2024 Petr Mironychev
|
||||||
*
|
*
|
||||||
* This file is part of QodeAssist.
|
* This file is part of QodeAssist.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2024-2025 Petr Mironychev
|
* Copyright (C) 2024 Petr Mironychev
|
||||||
*
|
*
|
||||||
* This file is part of QodeAssist.
|
* This file is part of QodeAssist.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2024-2025 Petr Mironychev
|
* Copyright (C) 2024 Petr Mironychev
|
||||||
*
|
*
|
||||||
* This file is part of QodeAssist.
|
* This file is part of QodeAssist.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2024-2025 Petr Mironychev
|
* Copyright (C) 2024 Petr Mironychev
|
||||||
*
|
*
|
||||||
* This file is part of QodeAssist.
|
* This file is part of QodeAssist.
|
||||||
*
|
*
|
||||||
@@ -30,6 +30,8 @@ Rectangle {
|
|||||||
property alias syncOpenFiles: syncOpenFilesId
|
property alias syncOpenFiles: syncOpenFilesId
|
||||||
property alias attachFiles: attachFilesId
|
property alias attachFiles: attachFilesId
|
||||||
property alias linkFiles: linkFilesId
|
property alias linkFiles: linkFilesId
|
||||||
|
property alias testRag: testRagId
|
||||||
|
property alias testChunks: testChunksId
|
||||||
|
|
||||||
color: palette.window.hslLightness > 0.5 ?
|
color: palette.window.hslLightness > 0.5 ?
|
||||||
Qt.darker(palette.window, 1.1) :
|
Qt.darker(palette.window, 1.1) :
|
||||||
@@ -91,6 +93,18 @@ Rectangle {
|
|||||||
ToolTip.text: qsTr("Automatically synchronize currently opened files with the model context")
|
ToolTip.text: qsTr("Automatically synchronize currently opened files with the model context")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QoAButton {
|
||||||
|
id: testRagId
|
||||||
|
|
||||||
|
text: qsTr("Test RAG")
|
||||||
|
}
|
||||||
|
|
||||||
|
QoAButton {
|
||||||
|
id: testChunksId
|
||||||
|
|
||||||
|
text: qsTr("Test Chunks")
|
||||||
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,135 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2025 Petr Mironychev
|
|
||||||
*
|
|
||||||
* This file is part of QodeAssist.
|
|
||||||
*
|
|
||||||
* QodeAssist is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* QodeAssist is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import QtQuick
|
|
||||||
import QtQuick.Controls
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
property ListView targetView: null
|
|
||||||
property int previewWidth: 50
|
|
||||||
property color userMessageColor: "#92BD6C"
|
|
||||||
property color assistantMessageColor: palette.button
|
|
||||||
|
|
||||||
width: previewWidth
|
|
||||||
color: palette.window.hslLightness > 0.5 ?
|
|
||||||
Qt.darker(palette.window, 1.1) :
|
|
||||||
Qt.lighter(palette.window, 1.1)
|
|
||||||
|
|
||||||
Behavior on opacity {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: 150
|
|
||||||
easing.type: Easing.InOutQuad
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
id: previewContainer
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: 2
|
|
||||||
spacing: 2
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
model: targetView ? targetView.model : null
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
required property int index
|
|
||||||
required property var model
|
|
||||||
|
|
||||||
width: parent.width
|
|
||||||
height: {
|
|
||||||
if (!targetView || !targetView.count) return 0
|
|
||||||
const availableHeight = root.height - ((targetView.count - 1) * previewContainer.spacing)
|
|
||||||
return availableHeight / targetView.count
|
|
||||||
}
|
|
||||||
|
|
||||||
radius: 4
|
|
||||||
color: model.roleType === ChatModel.User ?
|
|
||||||
userMessageColor :
|
|
||||||
assistantMessageColor
|
|
||||||
|
|
||||||
opacity: root.opacity
|
|
||||||
transform: Translate {
|
|
||||||
x: root.opacity * 50 - 50
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on transform {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: 150
|
|
||||||
easing.type: Easing.InOutQuad
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
onClicked: {
|
|
||||||
if (targetView) {
|
|
||||||
targetView.positionViewAtIndex(index, ListView.Center)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
HoverHandler {
|
|
||||||
id: hover
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
color: palette.highlight
|
|
||||||
opacity: hover.hovered ? 0.2 : 0
|
|
||||||
radius: parent.radius
|
|
||||||
|
|
||||||
Behavior on opacity {
|
|
||||||
NumberAnimation { duration: 150 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
color: palette.highlight
|
|
||||||
opacity: {
|
|
||||||
if (!targetView) return 0
|
|
||||||
const viewY = targetView.contentY
|
|
||||||
const viewHeight = targetView.height
|
|
||||||
const totalHeight = targetView.contentHeight
|
|
||||||
const itemPosition = index / targetView.count * totalHeight
|
|
||||||
const itemHeight = totalHeight / targetView.count
|
|
||||||
|
|
||||||
return (itemPosition + itemHeight > viewY &&
|
|
||||||
itemPosition < viewY + viewHeight) ? 0.2 : 0
|
|
||||||
}
|
|
||||||
radius: parent.radius
|
|
||||||
|
|
||||||
Behavior on opacity {
|
|
||||||
NumberAnimation { duration: 150 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ToolTip.visible: hover.hovered
|
|
||||||
ToolTip.text: {
|
|
||||||
const maxPreviewLength = 100
|
|
||||||
return model.content.length > maxPreviewLength ?
|
|
||||||
model.content.substring(0, maxPreviewLength) + "..." :
|
|
||||||
model.content
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2024-2025 Petr Mironychev
|
* Copyright (C) 2024 Petr Mironychev
|
||||||
*
|
*
|
||||||
* This file is part of QodeAssist.
|
* This file is part of QodeAssist.
|
||||||
*
|
*
|
||||||
@@ -30,7 +30,6 @@ Rectangle {
|
|||||||
property alias tokensBadge: tokensBadgeId
|
property alias tokensBadge: tokensBadgeId
|
||||||
property alias recentPath: recentPathId
|
property alias recentPath: recentPathId
|
||||||
property alias openChatHistory: openChatHistoryId
|
property alias openChatHistory: openChatHistoryId
|
||||||
property alias expandScrollbar: expandScrollbarId
|
|
||||||
|
|
||||||
color: palette.window.hslLightness > 0.5 ?
|
color: palette.window.hslLightness > 0.5 ?
|
||||||
Qt.darker(palette.window, 1.1) :
|
Qt.darker(palette.window, 1.1) :
|
||||||
@@ -85,12 +84,5 @@ Rectangle {
|
|||||||
Badge {
|
Badge {
|
||||||
id: tokensBadgeId
|
id: tokensBadgeId
|
||||||
}
|
}
|
||||||
|
|
||||||
QoAButton {
|
|
||||||
id: expandScrollbarId
|
|
||||||
|
|
||||||
width: 16
|
|
||||||
height: 16
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
190
CodeHandler.cpp
190
CodeHandler.cpp
@@ -1,6 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2024-2025 Petr Mironychev
|
* Copyright (C) 2024 Petr Mironychev
|
||||||
* Copyright (C) 2025 Povilas Kanapickas <povilas@radix.lt>
|
|
||||||
*
|
*
|
||||||
* This file is part of QodeAssist.
|
* This file is part of QodeAssist.
|
||||||
*
|
*
|
||||||
@@ -19,131 +18,29 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "CodeHandler.hpp"
|
#include "CodeHandler.hpp"
|
||||||
#include <settings/CodeCompletionSettings.hpp>
|
|
||||||
#include <QFileInfo>
|
|
||||||
#include <QHash>
|
#include <QHash>
|
||||||
|
|
||||||
namespace QodeAssist {
|
namespace QodeAssist {
|
||||||
|
|
||||||
struct LanguageProperties
|
QString CodeHandler::processText(QString text)
|
||||||
{
|
|
||||||
QString name;
|
|
||||||
QString commentStyle;
|
|
||||||
QVector<QString> namesFromModel;
|
|
||||||
QVector<QString> fileExtensions;
|
|
||||||
};
|
|
||||||
|
|
||||||
const QVector<LanguageProperties> customLanguagesFromSettings()
|
|
||||||
{
|
|
||||||
QVector<LanguageProperties> customLanguages;
|
|
||||||
|
|
||||||
const QStringList customLanguagesList = Settings::codeCompletionSettings().customLanguages();
|
|
||||||
for (const QString &entry : customLanguagesList) {
|
|
||||||
if (entry.trimmed().isEmpty()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
QStringList parts = entry.split(',');
|
|
||||||
if (parts.size() < 4) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString name = parts[0].trimmed();
|
|
||||||
QString commentStyle = parts[1].trimmed();
|
|
||||||
QStringList modelNamesList = parts[2].trimmed().split(' ', Qt::SkipEmptyParts);
|
|
||||||
QStringList extensionsList = parts[3].trimmed().split(' ', Qt::SkipEmptyParts);
|
|
||||||
|
|
||||||
if (!name.isEmpty() && !commentStyle.isEmpty() && !modelNamesList.isEmpty()
|
|
||||||
&& !extensionsList.isEmpty()) {
|
|
||||||
QVector<QString> modelNames;
|
|
||||||
for (const auto &modelName : modelNamesList) {
|
|
||||||
modelNames.append(modelName);
|
|
||||||
}
|
|
||||||
|
|
||||||
QVector<QString> extensions;
|
|
||||||
for (const auto &ext : extensionsList) {
|
|
||||||
extensions.append(ext);
|
|
||||||
}
|
|
||||||
|
|
||||||
customLanguages.append({name, commentStyle, modelNames, extensions});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return customLanguages;
|
|
||||||
}
|
|
||||||
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"}},
|
|
||||||
{"qml", "//", {"qml"}, {"qml"}},
|
|
||||||
};
|
|
||||||
|
|
||||||
knownLanguages.append(customLanguagesFromSettings());
|
|
||||||
|
|
||||||
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;
|
QString result;
|
||||||
QStringList lines = text.split('\n');
|
QStringList lines = text.split('\n');
|
||||||
bool inCodeBlock = false;
|
bool inCodeBlock = false;
|
||||||
QString pendingComments;
|
QString pendingComments;
|
||||||
|
QString currentLanguage;
|
||||||
|
|
||||||
auto currentFileExtension = QFileInfo(currentFilePath).suffix();
|
for (const QString &line : lines) {
|
||||||
auto currentLanguage = detectLanguageFromExtension(currentFileExtension);
|
if (line.trimmed().startsWith("```")) {
|
||||||
|
if (!inCodeBlock) {
|
||||||
auto addPendingCommentsIfAny = [&]() {
|
currentLanguage = detectLanguage(line);
|
||||||
if (pendingComments.isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
inCodeBlock = !inCodeBlock;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inCodeBlock) {
|
||||||
|
if (!pendingComments.isEmpty()) {
|
||||||
QStringList commentLines = pendingComments.split('\n');
|
QStringList commentLines = pendingComments.split('\n');
|
||||||
QString commentPrefix = getCommentPrefix(currentLanguage);
|
QString commentPrefix = getCommentPrefix(currentLanguage);
|
||||||
|
|
||||||
@@ -155,28 +52,7 @@ QString CodeHandler::processText(QString text, QString currentFilePath)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
pendingComments.clear();
|
pendingComments.clear();
|
||||||
};
|
|
||||||
|
|
||||||
for (const QString &line : lines) {
|
|
||||||
if (line.trimmed().startsWith("```")) {
|
|
||||||
if (!inCodeBlock) {
|
|
||||||
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) {
|
|
||||||
result += line + "\n";
|
result += line + "\n";
|
||||||
} else {
|
} else {
|
||||||
QString trimmed = line.trimmed();
|
QString trimmed = line.trimmed();
|
||||||
@@ -188,27 +64,45 @@ QString CodeHandler::processText(QString text, QString currentFilePath)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
addPendingCommentsIfAny();
|
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";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString CodeHandler::getCommentPrefix(const QString &language)
|
QString CodeHandler::getCommentPrefix(const QString &language)
|
||||||
{
|
{
|
||||||
static const auto commentPrefixes = buildLanguageToCommentPrefixMap();
|
static const QHash<QString, QString> commentPrefixes
|
||||||
return commentPrefixes.value(language, "//");
|
= {{"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(), "//");
|
||||||
}
|
}
|
||||||
|
|
||||||
QString CodeHandler::detectLanguageFromLine(const QString &line)
|
QString CodeHandler::detectLanguage(const QString &line)
|
||||||
{
|
{
|
||||||
static const auto modelNameToLanguage = buildModelLanguageNameToLanguageMap();
|
QString trimmed = line.trimmed();
|
||||||
return modelNameToLanguage.value(line.trimmed().mid(3).trimmed(), "");
|
if (trimmed.length() <= 3) { // Если только ```
|
||||||
}
|
return QString();
|
||||||
|
}
|
||||||
|
|
||||||
QString CodeHandler::detectLanguageFromExtension(const QString &extension)
|
return trimmed.mid(3).trimmed();
|
||||||
{
|
|
||||||
static const auto extensionToLanguage = buildExtensionToLanguageMap();
|
|
||||||
return extensionToLanguage.value(extension.toLower(), "");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const QRegularExpression &CodeHandler::getFullCodeBlockRegex()
|
const QRegularExpression &CodeHandler::getFullCodeBlockRegex()
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2024-2025 Petr Mironychev
|
* Copyright (C) 2024 Petr Mironychev
|
||||||
*
|
*
|
||||||
* This file is part of QodeAssist.
|
* This file is part of QodeAssist.
|
||||||
*
|
*
|
||||||
@@ -28,20 +28,11 @@ namespace QodeAssist {
|
|||||||
class CodeHandler
|
class CodeHandler
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
static QString processText(QString text, QString currentFileName);
|
static QString processText(QString text);
|
||||||
|
|
||||||
/**
|
|
||||||
* 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:
|
private:
|
||||||
static QString getCommentPrefix(const QString &language);
|
static QString getCommentPrefix(const QString &language);
|
||||||
|
static QString detectLanguage(const QString &line);
|
||||||
|
|
||||||
static const QRegularExpression &getFullCodeBlockRegex();
|
static const QRegularExpression &getFullCodeBlockRegex();
|
||||||
static const QRegularExpression &getPartialStartBlockRegex();
|
static const QRegularExpression &getPartialStartBlockRegex();
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2024-2025 Petr Mironychev
|
* Copyright (C) 2024 Petr Mironychev
|
||||||
*
|
*
|
||||||
* This file is part of QodeAssist.
|
* This file is part of QodeAssist.
|
||||||
*
|
*
|
||||||
@@ -19,8 +19,8 @@
|
|||||||
|
|
||||||
#include "ConfigurationManager.hpp"
|
#include "ConfigurationManager.hpp"
|
||||||
|
|
||||||
#include <settings/ButtonAspect.hpp>
|
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
|
#include <settings/ButtonAspect.hpp>
|
||||||
|
|
||||||
#include "QodeAssisttr.h"
|
#include "QodeAssisttr.h"
|
||||||
|
|
||||||
@@ -35,49 +35,6 @@ ConfigurationManager &ConfigurationManager::instance()
|
|||||||
void ConfigurationManager::init()
|
void ConfigurationManager::init()
|
||||||
{
|
{
|
||||||
setupConnections();
|
setupConnections();
|
||||||
updateAllTemplateDescriptions();
|
|
||||||
checkAllTemplate();
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ConfigurationManager::checkTemplate(const Utils::StringAspect &templateAspect)
|
|
||||||
{
|
|
||||||
LLMCore::PromptTemplate *templ = m_templateManger.getFimTemplateByName(templateAspect.value());
|
|
||||||
|
|
||||||
if (templ->name() == templateAspect.value())
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (&templateAspect == &m_generalSettings.ccTemplate) {
|
|
||||||
m_generalSettings.ccTemplate.setValue(templ->name());
|
|
||||||
} else if (&templateAspect == &m_generalSettings.caTemplate) {
|
|
||||||
m_generalSettings.caTemplate.setValue(templ->name());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ConfigurationManager::checkAllTemplate()
|
|
||||||
{
|
|
||||||
checkTemplate(m_generalSettings.ccTemplate);
|
|
||||||
checkTemplate(m_generalSettings.caTemplate);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ConfigurationManager::ConfigurationManager(QObject *parent)
|
ConfigurationManager::ConfigurationManager(QObject *parent)
|
||||||
@@ -107,14 +64,6 @@ void ConfigurationManager::setupConnections()
|
|||||||
connect(&m_generalSettings.ccPreset1SelectModel, &Button::clicked, this, &Config::selectModel);
|
connect(&m_generalSettings.ccPreset1SelectModel, &Button::clicked, this, &Config::selectModel);
|
||||||
connect(
|
connect(
|
||||||
&m_generalSettings.ccPreset1SelectTemplate, &Button::clicked, this, &Config::selectTemplate);
|
&m_generalSettings.ccPreset1SelectTemplate, &Button::clicked, this, &Config::selectTemplate);
|
||||||
|
|
||||||
connect(&m_generalSettings.ccTemplate, &Utils::StringAspect::changed, this, [this]() {
|
|
||||||
updateTemplateDescription(m_generalSettings.ccTemplate);
|
|
||||||
});
|
|
||||||
|
|
||||||
connect(&m_generalSettings.caTemplate, &Utils::StringAspect::changed, this, [this]() {
|
|
||||||
updateTemplateDescription(m_generalSettings.caTemplate);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConfigurationManager::selectProvider()
|
void ConfigurationManager::selectProvider()
|
||||||
@@ -132,8 +81,10 @@ void ConfigurationManager::selectProvider()
|
|||||||
: m_generalSettings.caProvider;
|
: m_generalSettings.caProvider;
|
||||||
|
|
||||||
QTimer::singleShot(0, this, [this, providersList, &targetSettings] {
|
QTimer::singleShot(0, this, [this, providersList, &targetSettings] {
|
||||||
m_generalSettings.showSelectionDialog(
|
m_generalSettings.showSelectionDialog(providersList,
|
||||||
providersList, targetSettings, Tr::tr("Select LLM Provider"), Tr::tr("Providers:"));
|
targetSettings,
|
||||||
|
Tr::tr("Select LLM Provider"),
|
||||||
|
Tr::tr("Providers:"));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,22 +137,19 @@ void ConfigurationManager::selectTemplate()
|
|||||||
|
|
||||||
const bool isCodeCompletion = (settingsButton == &m_generalSettings.ccSelectTemplate);
|
const bool isCodeCompletion = (settingsButton == &m_generalSettings.ccSelectTemplate);
|
||||||
const bool isPreset1 = (settingsButton == &m_generalSettings.ccPreset1SelectTemplate);
|
const bool isPreset1 = (settingsButton == &m_generalSettings.ccPreset1SelectTemplate);
|
||||||
const QString providerName = isCodeCompletion ? m_generalSettings.ccProvider.volatileValue()
|
|
||||||
: isPreset1 ? m_generalSettings.ccPreset1Provider.volatileValue()
|
|
||||||
: m_generalSettings.caProvider.volatileValue();
|
|
||||||
auto providerID = m_providersManager.getProviderByName(providerName)->providerID();
|
|
||||||
|
|
||||||
const auto templateList = isCodeCompletion || isPreset1
|
const auto templateList = isCodeCompletion || isPreset1 ? m_templateManger.fimTemplatesNames()
|
||||||
? m_templateManger.getFimTemplatesForProvider(providerID)
|
: m_templateManger.chatTemplatesNames();
|
||||||
: m_templateManger.getChatTemplatesForProvider(providerID);
|
|
||||||
|
|
||||||
auto &targetSettings = isCodeCompletion ? m_generalSettings.ccTemplate
|
auto &targetSettings = isCodeCompletion ? m_generalSettings.ccTemplate
|
||||||
: isPreset1 ? m_generalSettings.ccPreset1Template
|
: isPreset1 ? m_generalSettings.ccPreset1Template
|
||||||
: m_generalSettings.caTemplate;
|
: m_generalSettings.caTemplate;
|
||||||
|
|
||||||
QTimer::singleShot(0, &m_generalSettings, [this, templateList, &targetSettings]() {
|
QTimer::singleShot(0, &m_generalSettings, [this, templateList, &targetSettings]() {
|
||||||
m_generalSettings.showSelectionDialog(
|
m_generalSettings.showSelectionDialog(templateList,
|
||||||
templateList, targetSettings, Tr::tr("Select Template"), Tr::tr("Templates:"));
|
targetSettings,
|
||||||
|
Tr::tr("Select Template"),
|
||||||
|
Tr::tr("Templates:"));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2024-2025 Petr Mironychev
|
* Copyright (C) 2024 Petr Mironychev
|
||||||
*
|
*
|
||||||
* This file is part of QodeAssist.
|
* This file is part of QodeAssist.
|
||||||
*
|
*
|
||||||
@@ -36,11 +36,6 @@ public:
|
|||||||
|
|
||||||
void init();
|
void init();
|
||||||
|
|
||||||
void updateTemplateDescription(const Utils::StringAspect &templateAspect);
|
|
||||||
void updateAllTemplateDescriptions();
|
|
||||||
void checkTemplate(const Utils::StringAspect &templateAspect);
|
|
||||||
void checkAllTemplate();
|
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void selectProvider();
|
void selectProvider();
|
||||||
void selectModel();
|
void selectModel();
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2024-2025 Petr Mironychev
|
* Copyright (C) 2024 Petr Mironychev
|
||||||
*
|
*
|
||||||
* This file is part of QodeAssist.
|
* This file is part of QodeAssist.
|
||||||
*
|
*
|
||||||
@@ -23,37 +23,24 @@
|
|||||||
#include <QNetworkAccessManager>
|
#include <QNetworkAccessManager>
|
||||||
#include <QNetworkReply>
|
#include <QNetworkReply>
|
||||||
|
|
||||||
|
#include <llmcore/RequestConfig.hpp>
|
||||||
|
#include <texteditor/textdocument.h>
|
||||||
|
|
||||||
#include "CodeHandler.hpp"
|
#include "CodeHandler.hpp"
|
||||||
#include "context/DocumentContextReader.hpp"
|
#include "context/DocumentContextReader.hpp"
|
||||||
#include "context/Utils.hpp"
|
#include "llmcore/MessageBuilder.hpp"
|
||||||
#include "llmcore/PromptTemplateManager.hpp"
|
#include "llmcore/PromptTemplateManager.hpp"
|
||||||
#include "llmcore/ProvidersManager.hpp"
|
#include "llmcore/ProvidersManager.hpp"
|
||||||
#include "logger/Logger.hpp"
|
#include "logger/Logger.hpp"
|
||||||
#include "settings/CodeCompletionSettings.hpp"
|
#include "settings/CodeCompletionSettings.hpp"
|
||||||
#include "settings/GeneralSettings.hpp"
|
#include "settings/GeneralSettings.hpp"
|
||||||
#include <llmcore/RequestConfig.hpp>
|
|
||||||
|
|
||||||
namespace QodeAssist {
|
namespace QodeAssist {
|
||||||
|
|
||||||
LLMClientInterface::LLMClientInterface(
|
LLMClientInterface::LLMClientInterface()
|
||||||
const Settings::GeneralSettings &generalSettings,
|
: m_requestHandler(this)
|
||||||
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)
|
|
||||||
, m_contextManager(new Context::ContextManager(this))
|
|
||||||
{
|
{
|
||||||
connect(
|
connect(&m_requestHandler,
|
||||||
&m_requestHandler,
|
|
||||||
&LLMCore::RequestHandler::completionReceived,
|
&LLMCore::RequestHandler::completionReceived,
|
||||||
this,
|
this,
|
||||||
&LLMClientInterface::sendCompletionToClient);
|
&LLMClientInterface::sendCompletionToClient);
|
||||||
@@ -61,7 +48,7 @@ LLMClientInterface::LLMClientInterface(
|
|||||||
|
|
||||||
Utils::FilePath LLMClientInterface::serverDeviceTemplate() const
|
Utils::FilePath LLMClientInterface::serverDeviceTemplate() const
|
||||||
{
|
{
|
||||||
return "QodeAssist";
|
return "Qode Assist";
|
||||||
}
|
}
|
||||||
|
|
||||||
void LLMClientInterface::startImpl()
|
void LLMClientInterface::startImpl()
|
||||||
@@ -88,7 +75,7 @@ void LLMClientInterface::sendData(const QByteArray &data)
|
|||||||
handleTextDocumentDidOpen(request);
|
handleTextDocumentDidOpen(request);
|
||||||
} else if (method == "getCompletionsCycling") {
|
} else if (method == "getCompletionsCycling") {
|
||||||
QString requestId = request["id"].toString();
|
QString requestId = request["id"].toString();
|
||||||
m_performanceLogger.startTimeMeasurement(requestId);
|
startTimeMeasurement(requestId);
|
||||||
handleCompletion(request);
|
handleCompletion(request);
|
||||||
} else if (method == "$/cancelRequest") {
|
} else if (method == "$/cancelRequest") {
|
||||||
handleCancelRequest(request);
|
handleCancelRequest(request);
|
||||||
@@ -159,37 +146,43 @@ void LLMClientInterface::handleExit(const QJsonObject &request)
|
|||||||
emit finished();
|
emit finished();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool QodeAssist::LLMClientInterface::isSpecifyCompletion(const QJsonObject &request)
|
||||||
|
{
|
||||||
|
auto &generalSettings = Settings::generalSettings();
|
||||||
|
|
||||||
|
Context::ProgrammingLanguage documentLanguage = getDocumentLanguage(request);
|
||||||
|
Context::ProgrammingLanguage preset1Language = Context::ProgrammingLanguageUtils::fromString(
|
||||||
|
generalSettings.preset1Language.displayForIndex(generalSettings.preset1Language()));
|
||||||
|
|
||||||
|
return generalSettings.specifyPreset1() && documentLanguage == preset1Language;
|
||||||
|
}
|
||||||
|
|
||||||
void LLMClientInterface::handleCompletion(const QJsonObject &request)
|
void LLMClientInterface::handleCompletion(const QJsonObject &request)
|
||||||
{
|
{
|
||||||
auto filePath = Context::extractFilePathFromRequest(request);
|
const auto updatedContext = prepareContext(request);
|
||||||
auto documentInfo = m_documentReader.readDocument(filePath);
|
auto &completeSettings = Settings::codeCompletionSettings();
|
||||||
if (!documentInfo.document) {
|
auto &generalSettings = Settings::generalSettings();
|
||||||
LOG_MESSAGE("Error: Document is not available for" + filePath);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto updatedContext = prepareContext(request, documentInfo);
|
bool isPreset1Active = isSpecifyCompletion(request);
|
||||||
|
|
||||||
bool isPreset1Active = m_contextManager->isSpecifyCompletion(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();
|
||||||
|
|
||||||
const auto providerName = !isPreset1Active ? m_generalSettings.ccProvider()
|
const auto provider = LLMCore::ProvidersManager::instance().getProviderByName(providerName);
|
||||||
: 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) {
|
if (!provider) {
|
||||||
LOG_MESSAGE(QString("No provider found with name: %1").arg(providerName));
|
LOG_MESSAGE(QString("No provider found with name: %1").arg(providerName));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto templateName = !isPreset1Active ? m_generalSettings.ccTemplate()
|
auto templateName = !isPreset1Active ? generalSettings.ccTemplate()
|
||||||
: m_generalSettings.ccPreset1Template();
|
: generalSettings.ccPreset1Template();
|
||||||
|
|
||||||
auto promptTemplate = m_promptProvider->getTemplateByName(templateName);
|
auto promptTemplate = LLMCore::PromptTemplateManager::instance().getFimTemplateByName(
|
||||||
|
templateName);
|
||||||
|
|
||||||
if (!promptTemplate) {
|
if (!promptTemplate) {
|
||||||
LOG_MESSAGE(QString("No template found with name: %1").arg(templateName));
|
LOG_MESSAGE(QString("No template found with name: %1").arg(templateName));
|
||||||
@@ -201,70 +194,44 @@ void LLMClientInterface::handleCompletion(const QJsonObject &request)
|
|||||||
config.requestType = LLMCore::RequestType::CodeCompletion;
|
config.requestType = LLMCore::RequestType::CodeCompletion;
|
||||||
config.provider = provider;
|
config.provider = provider;
|
||||||
config.promptTemplate = promptTemplate;
|
config.promptTemplate = promptTemplate;
|
||||||
// TODO refactor networking
|
|
||||||
if (provider->providerID() == LLMCore::ProviderID::GoogleAI) {
|
|
||||||
QString stream = 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(
|
config.url = QUrl(QString("%1%2").arg(
|
||||||
url,
|
url,
|
||||||
promptTemplate->type() == LLMCore::TemplateType::FIM ? provider->completionEndpoint()
|
promptTemplate->type() == LLMCore::TemplateType::Fim ? provider->completionEndpoint()
|
||||||
: provider->chatEndpoint()));
|
: provider->chatEndpoint()));
|
||||||
config.providerRequest = {{"model", modelName}, {"stream", m_completeSettings.stream()}};
|
|
||||||
}
|
|
||||||
config.apiKey = provider->apiKey();
|
config.apiKey = provider->apiKey();
|
||||||
config.multiLineCompletion = m_completeSettings.multiLineCompletion();
|
|
||||||
|
config.providerRequest = {{"model", modelName}, {"stream", completeSettings.stream()}};
|
||||||
|
|
||||||
|
config.multiLineCompletion = completeSettings.multiLineCompletion();
|
||||||
|
|
||||||
const auto stopWords = QJsonArray::fromStringList(config.promptTemplate->stopWords());
|
const auto stopWords = QJsonArray::fromStringList(config.promptTemplate->stopWords());
|
||||||
if (!stopWords.isEmpty())
|
if (!stopWords.isEmpty())
|
||||||
config.providerRequest["stop"] = stopWords;
|
config.providerRequest["stop"] = stopWords;
|
||||||
|
|
||||||
QString systemPrompt;
|
QString systemPrompt;
|
||||||
if (m_completeSettings.useSystemPrompt())
|
if (completeSettings.useSystemPrompt())
|
||||||
systemPrompt.append(
|
systemPrompt.append(completeSettings.systemPrompt());
|
||||||
m_completeSettings.useUserMessageTemplateForCC()
|
if (!updatedContext.fileContext.isEmpty())
|
||||||
&& promptTemplate->type() == LLMCore::TemplateType::Chat
|
systemPrompt.append(updatedContext.fileContext);
|
||||||
? m_completeSettings.systemPromptForNonFimModels()
|
|
||||||
: m_completeSettings.systemPrompt());
|
|
||||||
if (updatedContext.fileContext.has_value())
|
|
||||||
systemPrompt.append(updatedContext.fileContext.value());
|
|
||||||
|
|
||||||
if (m_completeSettings.useOpenFilesContext()) {
|
|
||||||
if (provider->providerID() == LLMCore::ProviderID::LlamaCpp) {
|
|
||||||
for (const auto openedFilePath : m_contextManager->openedFiles({filePath})) {
|
|
||||||
if (!updatedContext.filesMetadata) {
|
|
||||||
updatedContext.filesMetadata = QList<LLMCore::FileMetadata>();
|
|
||||||
}
|
|
||||||
updatedContext.filesMetadata->append({openedFilePath.first, openedFilePath.second});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
systemPrompt.append(m_contextManager->openedFilesContext({filePath}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
updatedContext.systemPrompt = systemPrompt;
|
|
||||||
|
|
||||||
if (promptTemplate->type() == LLMCore::TemplateType::Chat) {
|
|
||||||
QString userMessage;
|
QString userMessage;
|
||||||
if (m_completeSettings.useUserMessageTemplateForCC()) {
|
if (completeSettings.useUserMessageTemplateForCC() && promptTemplate->type() == LLMCore::TemplateType::Chat) {
|
||||||
userMessage = m_completeSettings.processMessageToFIM(
|
userMessage = completeSettings.userMessageTemplateForCC().arg(updatedContext.prefix, updatedContext.suffix);
|
||||||
updatedContext.prefix.value_or(""), updatedContext.suffix.value_or(""));
|
|
||||||
} else {
|
} else {
|
||||||
userMessage = updatedContext.prefix.value_or("") + updatedContext.suffix.value_or("");
|
userMessage = updatedContext.prefix;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO refactor add message
|
auto message = LLMCore::MessageBuilder()
|
||||||
QVector<LLMCore::Message> messages;
|
.addSystemMessage(systemPrompt)
|
||||||
messages.append({"user", userMessage});
|
.addUserMessage(userMessage)
|
||||||
updatedContext.history = messages;
|
.addSuffix(updatedContext.suffix)
|
||||||
}
|
.addTokenizer(promptTemplate);
|
||||||
|
|
||||||
config.provider->prepareRequest(
|
message.saveTo(
|
||||||
config.providerRequest,
|
config.providerRequest,
|
||||||
promptTemplate,
|
providerName == "Ollama" ? LLMCore::ProvidersApi::Ollama : LLMCore::ProvidersApi::OpenAI);
|
||||||
updatedContext,
|
|
||||||
LLMCore::RequestType::CodeCompletion);
|
config.provider->prepareRequest(config.providerRequest, LLMCore::RequestType::CodeCompletion);
|
||||||
|
|
||||||
auto errors = config.provider->validateRequest(config.providerRequest, promptTemplate->type());
|
auto errors = config.provider->validateRequest(config.providerRequest, promptTemplate->type());
|
||||||
if (!errors.isEmpty()) {
|
if (!errors.isEmpty()) {
|
||||||
@@ -275,36 +242,59 @@ void LLMClientInterface::handleCompletion(const QJsonObject &request)
|
|||||||
m_requestHandler.sendLLMRequest(config, request);
|
m_requestHandler.sendLLMRequest(config, request);
|
||||||
}
|
}
|
||||||
|
|
||||||
LLMCore::ContextData LLMClientInterface::prepareContext(
|
LLMCore::ContextData LLMClientInterface::prepareContext(const QJsonObject &request,
|
||||||
const QJsonObject &request, const Context::DocumentInfo &documentInfo)
|
const QStringView &accumulatedCompletion)
|
||||||
{
|
{
|
||||||
QJsonObject params = request["params"].toObject();
|
QJsonObject params = request["params"].toObject();
|
||||||
QJsonObject doc = params["doc"].toObject();
|
QJsonObject doc = params["doc"].toObject();
|
||||||
QJsonObject position = doc["position"].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 cursorPosition = position["character"].toInt();
|
||||||
int lineNumber = position["line"].toInt();
|
int lineNumber = position["line"].toInt();
|
||||||
|
|
||||||
Context::DocumentContextReader
|
Context::DocumentContextReader reader(textDocument);
|
||||||
reader(documentInfo.document, documentInfo.mimeType, documentInfo.filePath);
|
return reader.prepareContext(lineNumber, cursorPosition);
|
||||||
return reader.prepareContext(lineNumber, cursorPosition, m_completeSettings);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Context::ContextManager *LLMClientInterface::contextManager() const
|
Context::ProgrammingLanguage LLMClientInterface::getDocumentLanguage(const QJsonObject &request) const
|
||||||
{
|
{
|
||||||
return m_contextManager;
|
QJsonObject params = request["params"].toObject();
|
||||||
|
QJsonObject doc = params["doc"].toObject();
|
||||||
|
QString uri = doc["uri"].toString();
|
||||||
|
|
||||||
|
Utils::FilePath filePath = Utils::FilePath::fromString(QUrl(uri).toLocalFile());
|
||||||
|
TextEditor::TextDocument *textDocument = TextEditor::TextDocument::textDocumentForFilePath(
|
||||||
|
filePath);
|
||||||
|
|
||||||
|
if (!textDocument) {
|
||||||
|
LOG_MESSAGE("Error: Document is not available for" + filePath.toString());
|
||||||
|
return Context::ProgrammingLanguage::Unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Context::ProgrammingLanguageUtils::fromMimeType(textDocument->mimeType());
|
||||||
}
|
}
|
||||||
|
|
||||||
void LLMClientInterface::sendCompletionToClient(
|
void LLMClientInterface::sendCompletionToClient(const QString &completion,
|
||||||
const QString &completion, const QJsonObject &request, bool isComplete)
|
const QJsonObject &request,
|
||||||
|
bool isComplete)
|
||||||
{
|
{
|
||||||
auto filePath = Context::extractFilePathFromRequest(request);
|
bool isPreset1Active = isSpecifyCompletion(request);
|
||||||
auto documentInfo = m_documentReader.readDocument(filePath);
|
|
||||||
bool isPreset1Active = m_contextManager->isSpecifyCompletion(documentInfo);
|
|
||||||
|
|
||||||
auto templateName = !isPreset1Active ? m_generalSettings.ccTemplate()
|
auto templateName = !isPreset1Active ? Settings::generalSettings().ccTemplate()
|
||||||
: m_generalSettings.ccPreset1Template();
|
: Settings::generalSettings().ccPreset1Template();
|
||||||
|
|
||||||
auto promptTemplate = m_promptProvider->getTemplateByName(templateName);
|
auto promptTemplate = LLMCore::PromptTemplateManager::instance().getFimTemplateByName(
|
||||||
|
templateName);
|
||||||
|
|
||||||
QJsonObject position = request["params"].toObject()["doc"].toObject()["position"].toObject();
|
QJsonObject position = request["params"].toObject()["doc"].toObject()["position"].toObject();
|
||||||
|
|
||||||
@@ -316,12 +306,10 @@ void LLMClientInterface::sendCompletionToClient(
|
|||||||
QJsonArray completions;
|
QJsonArray completions;
|
||||||
QJsonObject completionItem;
|
QJsonObject completionItem;
|
||||||
|
|
||||||
LOG_MESSAGE(QString("Completions before filter: \n%1").arg(completion));
|
|
||||||
|
|
||||||
QString processedCompletion
|
QString processedCompletion
|
||||||
= promptTemplate->type() == LLMCore::TemplateType::Chat
|
= promptTemplate->type() == LLMCore::TemplateType::Chat
|
||||||
&& m_completeSettings.smartProcessInstuctText()
|
&& Settings::codeCompletionSettings().smartProcessInstuctText()
|
||||||
? CodeHandler::processText(completion, Context::extractFilePathFromRequest(request))
|
? CodeHandler::processText(completion)
|
||||||
: completion;
|
: completion;
|
||||||
|
|
||||||
completionItem[LanguageServerProtocol::textKey] = processedCompletion;
|
completionItem[LanguageServerProtocol::textKey] = processedCompletion;
|
||||||
@@ -341,13 +329,37 @@ void LLMClientInterface::sendCompletionToClient(
|
|||||||
QString("Completions: \n%1")
|
QString("Completions: \n%1")
|
||||||
.arg(QString::fromUtf8(QJsonDocument(completions).toJson(QJsonDocument::Indented))));
|
.arg(QString::fromUtf8(QJsonDocument(completions).toJson(QJsonDocument::Indented))));
|
||||||
|
|
||||||
LOG_MESSAGE(
|
LOG_MESSAGE(QString("Full response: \n%1")
|
||||||
QString("Full response: \n%1")
|
|
||||||
.arg(QString::fromUtf8(QJsonDocument(response).toJson(QJsonDocument::Indented))));
|
.arg(QString::fromUtf8(QJsonDocument(response).toJson(QJsonDocument::Indented))));
|
||||||
|
|
||||||
QString requestId = request["id"].toString();
|
QString requestId = request["id"].toString();
|
||||||
m_performanceLogger.endTimeMeasurement(requestId);
|
endTimeMeasurement(requestId);
|
||||||
emit messageReceived(LanguageServerProtocol::JsonRpcMessage(response));
|
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
|
} // namespace QodeAssist
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2024-2025 Petr Mironychev
|
* Copyright (C) 2024 Petr Mironychev
|
||||||
*
|
*
|
||||||
* This file is part of QodeAssist.
|
* This file is part of QodeAssist.
|
||||||
*
|
*
|
||||||
@@ -22,16 +22,9 @@
|
|||||||
#include <languageclient/languageclientinterface.h>
|
#include <languageclient/languageclientinterface.h>
|
||||||
#include <texteditor/texteditor.h>
|
#include <texteditor/texteditor.h>
|
||||||
|
|
||||||
#include <context/ContextManager.hpp>
|
|
||||||
#include <context/IDocumentReader.hpp>
|
|
||||||
#include <context/ProgrammingLanguage.hpp>
|
#include <context/ProgrammingLanguage.hpp>
|
||||||
#include <llmcore/ContextData.hpp>
|
#include <llmcore/ContextData.hpp>
|
||||||
#include <llmcore/IPromptProvider.hpp>
|
|
||||||
#include <llmcore/IProviderRegistry.hpp>
|
|
||||||
#include <llmcore/RequestHandler.hpp>
|
#include <llmcore/RequestHandler.hpp>
|
||||||
#include <logger/IRequestPerformanceLogger.hpp>
|
|
||||||
#include <settings/CodeCompletionSettings.hpp>
|
|
||||||
#include <settings/GeneralSettings.hpp>
|
|
||||||
|
|
||||||
class QNetworkReply;
|
class QNetworkReply;
|
||||||
class QNetworkAccessManager;
|
class QNetworkAccessManager;
|
||||||
@@ -43,29 +36,20 @@ class LLMClientInterface : public LanguageClient::BaseClientInterface
|
|||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
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;
|
Utils::FilePath serverDeviceTemplate() const override;
|
||||||
|
|
||||||
void sendCompletionToClient(
|
void sendCompletionToClient(const QString &completion,
|
||||||
const QString &completion, const QJsonObject &request, bool isComplete);
|
const QJsonObject &request,
|
||||||
|
bool isComplete);
|
||||||
|
|
||||||
void handleCompletion(const QJsonObject &request);
|
void handleCompletion(const QJsonObject &request);
|
||||||
|
|
||||||
// exposed for tests
|
|
||||||
void sendData(const QByteArray &data) override;
|
|
||||||
|
|
||||||
Context::ContextManager *contextManager() const;
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void startImpl() override;
|
void startImpl() override;
|
||||||
|
void sendData(const QByteArray &data) override;
|
||||||
|
void parseCurrentMessage() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void handleInitialize(const QJsonObject &request);
|
void handleInitialize(const QJsonObject &request);
|
||||||
@@ -76,17 +60,17 @@ private:
|
|||||||
void handleCancelRequest(const QJsonObject &request);
|
void handleCancelRequest(const QJsonObject &request);
|
||||||
|
|
||||||
LLMCore::ContextData prepareContext(
|
LLMCore::ContextData prepareContext(
|
||||||
const QJsonObject &request, const Context::DocumentInfo &documentInfo);
|
const QJsonObject &request, const QStringView &accumulatedCompletion = QString{});
|
||||||
|
Context::ProgrammingLanguage getDocumentLanguage(const QJsonObject &request) const;
|
||||||
|
bool isSpecifyCompletion(const QJsonObject &request);
|
||||||
|
|
||||||
const Settings::CodeCompletionSettings &m_completeSettings;
|
LLMCore::RequestHandler m_requestHandler;
|
||||||
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;
|
QElapsedTimer m_completionTimer;
|
||||||
Context::ContextManager *m_contextManager;
|
QMap<QString, qint64> m_requestStartTimes;
|
||||||
|
|
||||||
|
void startTimeMeasurement(const QString &requestId);
|
||||||
|
void endTimeMeasurement(const QString &requestId);
|
||||||
|
void logPerformance(const QString &requestId, const QString &operation, qint64 elapsedMs);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace QodeAssist
|
} // namespace QodeAssist
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2023 The Qt Company Ltd.
|
* Copyright (C) 2023 The Qt Company Ltd.
|
||||||
* Copyright (C) 2024-2025 Petr Mironychev
|
* Copyright (C) 2024 Petr Mironychev
|
||||||
*
|
*
|
||||||
* This file is part of QodeAssist.
|
* This file is part of QodeAssist.
|
||||||
*
|
*
|
||||||
@@ -29,36 +29,6 @@
|
|||||||
|
|
||||||
namespace QodeAssist {
|
namespace QodeAssist {
|
||||||
|
|
||||||
QString mergeWithRightText(const QString &suggestion, const QString &rightText)
|
|
||||||
{
|
|
||||||
if (suggestion.isEmpty() || rightText.isEmpty()) {
|
|
||||||
return suggestion;
|
|
||||||
}
|
|
||||||
|
|
||||||
int j = 0;
|
|
||||||
QString processed = rightText;
|
|
||||||
QSet<int> matchedPositions;
|
|
||||||
|
|
||||||
for (int i = 0; i < suggestion.length() && j < processed.length(); ++i) {
|
|
||||||
if (suggestion[i] == processed[j]) {
|
|
||||||
matchedPositions.insert(j);
|
|
||||||
++j;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (matchedPositions.isEmpty()) {
|
|
||||||
return suggestion + rightText;
|
|
||||||
}
|
|
||||||
|
|
||||||
QList<int> positions = matchedPositions.values();
|
|
||||||
std::sort(positions.begin(), positions.end(), std::greater<int>());
|
|
||||||
for (int pos : positions) {
|
|
||||||
processed.remove(pos, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return suggestion;
|
|
||||||
}
|
|
||||||
|
|
||||||
LLMSuggestion::LLMSuggestion(
|
LLMSuggestion::LLMSuggestion(
|
||||||
const QList<Data> &suggestions, QTextDocument *sourceDocument, int currentCompletion)
|
const QList<Data> &suggestions, QTextDocument *sourceDocument, int currentCompletion)
|
||||||
: TextEditor::CyclicSuggestion(suggestions, sourceDocument, currentCompletion)
|
: TextEditor::CyclicSuggestion(suggestions, sourceDocument, currentCompletion)
|
||||||
@@ -68,28 +38,21 @@ LLMSuggestion::LLMSuggestion(
|
|||||||
int startPos = data.range.begin.toPositionInDocument(sourceDocument);
|
int startPos = data.range.begin.toPositionInDocument(sourceDocument);
|
||||||
int endPos = data.range.end.toPositionInDocument(sourceDocument);
|
int endPos = data.range.end.toPositionInDocument(sourceDocument);
|
||||||
|
|
||||||
startPos = qBound(0, startPos, sourceDocument->characterCount());
|
startPos = qBound(0, startPos, sourceDocument->characterCount() - 1);
|
||||||
endPos = qBound(startPos, endPos, sourceDocument->characterCount());
|
endPos = qBound(startPos, endPos, sourceDocument->characterCount() - 1);
|
||||||
|
|
||||||
QTextCursor cursor(sourceDocument);
|
QTextCursor cursor(sourceDocument);
|
||||||
cursor.setPosition(startPos);
|
cursor.setPosition(startPos);
|
||||||
|
cursor.setPosition(endPos, QTextCursor::KeepAnchor);
|
||||||
|
|
||||||
QTextBlock block = cursor.block();
|
QTextBlock block = cursor.block();
|
||||||
QString blockText = block.text();
|
QString blockText = block.text();
|
||||||
|
|
||||||
int cursorPositionInBlock = cursor.positionInBlock();
|
int startPosInBlock = startPos - block.position();
|
||||||
|
int endPosInBlock = endPos - block.position();
|
||||||
|
|
||||||
QString rightText = blockText.mid(cursorPositionInBlock);
|
blockText.replace(startPosInBlock, endPosInBlock - startPosInBlock, data.text);
|
||||||
|
replacementDocument()->setPlainText(blockText);
|
||||||
if (!data.text.contains('\n')) {
|
|
||||||
QString processedRightText = mergeWithRightText(data.text, rightText);
|
|
||||||
processedRightText = processedRightText.mid(data.text.length());
|
|
||||||
QString displayText = blockText.left(cursorPositionInBlock) + data.text
|
|
||||||
+ processedRightText;
|
|
||||||
replacementDocument()->setPlainText(displayText);
|
|
||||||
} else {
|
|
||||||
QString displayText = blockText.left(cursorPositionInBlock) + data.text;
|
|
||||||
replacementDocument()->setPlainText(displayText);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool LLMSuggestion::applyWord(TextEditor::TextEditorWidget *widget)
|
bool LLMSuggestion::applyWord(TextEditor::TextEditorWidget *widget)
|
||||||
@@ -114,82 +77,31 @@ bool LLMSuggestion::applyPart(Part part, TextEditor::TextEditorWidget *widget)
|
|||||||
|
|
||||||
int next = part == Word ? Utils::endOfNextWord(text, startPos) : text.indexOf('\n', startPos);
|
int next = part == Word ? Utils::endOfNextWord(text, startPos) : text.indexOf('\n', startPos);
|
||||||
|
|
||||||
if (next == -1) {
|
if (next == -1)
|
||||||
if (part == Line) {
|
|
||||||
next = text.length();
|
|
||||||
} else {
|
|
||||||
return apply();
|
return apply();
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (part == Line)
|
if (part == Line)
|
||||||
++next;
|
++next;
|
||||||
|
|
||||||
QString subText = text.mid(startPos, next - startPos);
|
QString subText = text.mid(startPos, next - startPos);
|
||||||
|
if (subText.isEmpty())
|
||||||
if (subText.isEmpty()) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
QTextBlock currentBlock = currentCursor.block();
|
|
||||||
QString textAfterCursor = currentBlock.text().mid(currentCursor.positionInBlock());
|
|
||||||
|
|
||||||
if (!subText.contains('\n')) {
|
|
||||||
QTextCursor deleteCursor = currentCursor;
|
|
||||||
deleteCursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
|
|
||||||
deleteCursor.removeSelectedText();
|
|
||||||
|
|
||||||
QString mergedText = mergeWithRightText(subText, textAfterCursor);
|
|
||||||
currentCursor.insertText(mergedText);
|
|
||||||
} else {
|
|
||||||
currentCursor.insertText(subText);
|
currentCursor.insertText(subText);
|
||||||
|
|
||||||
if (const int seperatorPos = subText.lastIndexOf('\n'); seperatorPos >= 0) {
|
if (const int seperatorPos = subText.lastIndexOf('\n'); seperatorPos >= 0) {
|
||||||
const QString newCompletionText = text.mid(startPos + seperatorPos + 1);
|
const QString newCompletionText = text.mid(startPos + seperatorPos + 1);
|
||||||
if (!newCompletionText.isEmpty()) {
|
if (!newCompletionText.isEmpty()) {
|
||||||
const Utils::Text::Position newStart{int(range.begin.line + subText.count('\n')), 0};
|
const Utils::Text::Position newStart{int(range.begin.line + subText.count('\n')), 0};
|
||||||
const Utils::Text::Position newEnd{newStart.line, int(newCompletionText.length())};
|
const Utils::Text::Position
|
||||||
|
newEnd{newStart.line, int(subText.length() - seperatorPos - 1)};
|
||||||
const Utils::Text::Range newRange{newStart, newEnd};
|
const Utils::Text::Range newRange{newStart, newEnd};
|
||||||
const QList<Data> newSuggestion{{newRange, newEnd, newCompletionText}};
|
const QList<Data> newSuggestion{{newRange, newEnd, newCompletionText}};
|
||||||
widget->insertSuggestion(
|
widget->insertSuggestion(
|
||||||
std::make_unique<LLMSuggestion>(newSuggestion, widget->document(), 0));
|
std::make_unique<LLMSuggestion>(newSuggestion, widget->document(), 0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool LLMSuggestion::apply()
|
|
||||||
{
|
|
||||||
const Utils::Text::Range range = suggestions()[currentSuggestion()].range;
|
|
||||||
const QTextCursor cursor = range.begin.toTextCursor(sourceDocument());
|
|
||||||
const QString text = suggestions()[currentSuggestion()].text;
|
|
||||||
|
|
||||||
QTextBlock currentBlock = cursor.block();
|
|
||||||
QString textAfterCursor = currentBlock.text().mid(cursor.positionInBlock());
|
|
||||||
|
|
||||||
QTextCursor editCursor = cursor;
|
|
||||||
|
|
||||||
int firstLineEnd = text.indexOf('\n');
|
|
||||||
if (firstLineEnd != -1) {
|
|
||||||
QString firstLine = text.left(firstLineEnd);
|
|
||||||
QString restOfText = text.mid(firstLineEnd);
|
|
||||||
|
|
||||||
editCursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
|
|
||||||
editCursor.removeSelectedText();
|
|
||||||
|
|
||||||
QString mergedFirstLine = mergeWithRightText(firstLine, textAfterCursor);
|
|
||||||
editCursor.insertText(mergedFirstLine + restOfText);
|
|
||||||
} else {
|
|
||||||
editCursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
|
|
||||||
editCursor.removeSelectedText();
|
|
||||||
|
|
||||||
QString mergedText = mergeWithRightText(text, textAfterCursor);
|
|
||||||
editCursor.insertText(mergedText);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace QodeAssist
|
} // namespace QodeAssist
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2023 The Qt Company Ltd.
|
* Copyright (C) 2023 The Qt Company Ltd.
|
||||||
* Copyright (C) 2024-2025 Petr Mironychev
|
* Copyright (C) 2024 Petr Mironychev
|
||||||
*
|
*
|
||||||
* This file is part of QodeAssist.
|
* This file is part of QodeAssist.
|
||||||
*
|
*
|
||||||
@@ -40,6 +40,5 @@ public:
|
|||||||
bool applyWord(TextEditor::TextEditorWidget *widget) override;
|
bool applyWord(TextEditor::TextEditorWidget *widget) override;
|
||||||
bool applyLine(TextEditor::TextEditorWidget *widget) override;
|
bool applyLine(TextEditor::TextEditorWidget *widget) override;
|
||||||
bool applyPart(Part part, TextEditor::TextEditorWidget *widget);
|
bool applyPart(Part part, TextEditor::TextEditorWidget *widget);
|
||||||
bool apply() override;
|
|
||||||
};
|
};
|
||||||
} // namespace QodeAssist
|
} // namespace QodeAssist
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2023 The Qt Company Ltd.
|
* Copyright (C) 2023 The Qt Company Ltd.
|
||||||
* Copyright (C) 2024-2025 Petr Mironychev
|
* Copyright (C) 2024 Petr Mironychev
|
||||||
*
|
*
|
||||||
* This file is part of QodeAssist.
|
* This file is part of QodeAssist.
|
||||||
*
|
*
|
||||||
@@ -68,8 +68,7 @@ class GetCompletionParams : public LanguageServerProtocol::JsonObject
|
|||||||
public:
|
public:
|
||||||
static constexpr LanguageServerProtocol::Key docKey{"doc"};
|
static constexpr LanguageServerProtocol::Key docKey{"doc"};
|
||||||
|
|
||||||
GetCompletionParams(
|
GetCompletionParams(const LanguageServerProtocol::TextDocumentIdentifier &document,
|
||||||
const LanguageServerProtocol::TextDocumentIdentifier &document,
|
|
||||||
int version,
|
int version,
|
||||||
const LanguageServerProtocol::Position &position)
|
const LanguageServerProtocol::Position &position)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
{
|
{
|
||||||
"Id" : "qodeassist",
|
"Id" : "qodeassist",
|
||||||
"Name" : "QodeAssist",
|
"Name" : "QodeAssist",
|
||||||
"Version" : "0.5.10",
|
"Version" : "0.4.13",
|
||||||
"Vendor" : "Petr Mironychev",
|
"Vendor" : "Petr Mironychev",
|
||||||
"VendorId" : "petrmironychev",
|
"VendorId" : "petrmironychev",
|
||||||
"Copyright" : "(C) ${IDE_COPYRIGHT_YEAR} Petr Mironychev, (C) ${IDE_COPYRIGHT_YEAR} The Qt Company Ltd",
|
"Copyright" : "(C) ${IDE_COPYRIGHT_YEAR} Petr Mironychev, (C) ${IDE_COPYRIGHT_YEAR} The Qt Company Ltd",
|
||||||
"License" : "GPLv3",
|
"License" : "GPLv3",
|
||||||
"Description": "QodeAssist is an AI-powered coding assistant for Qt Creator. It provides intelligent code completion and suggestions for your code. Prerequisites: Requires one of the supported LLM providers installed (e.g., Ollama or LM Studio) and a compatible large language model downloaded for your chosen provider (e.g., CodeLlama, StarCoder2).",
|
"Description": "QodeAssist is an AI-powered coding assistant for Qt Creator. It provides intelligent code completion and suggestions for your code. Prerequisites: Requires one of the supported LLM providers installed (e.g., Ollama or LM Studio) and a compatible large language model downloaded for your chosen provider (e.g., CodeLlama, StarCoder2).",
|
||||||
"Url" : "https://github.com/Palm1r/QodeAssist",
|
"Url" : "https://github.com/Palm1r/QodeAssist",
|
||||||
"DocumentationUrl" : "https://github.com/Palm1r/QodeAssist",
|
"DocumentationUrl" : "",
|
||||||
${IDE_PLUGIN_DEPENDENCIES}
|
${IDE_PLUGIN_DEPENDENCIES}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,11 +2,5 @@
|
|||||||
<qresource prefix="/">
|
<qresource prefix="/">
|
||||||
<file>resources/images/qoderassist-icon@2x.png</file>
|
<file>resources/images/qoderassist-icon@2x.png</file>
|
||||||
<file>resources/images/qoderassist-icon.png</file>
|
<file>resources/images/qoderassist-icon.png</file>
|
||||||
<file>resources/images/repeat-last-instruct-icon@2x.png</file>
|
|
||||||
<file>resources/images/repeat-last-instruct-icon.png</file>
|
|
||||||
<file>resources/images/improve-current-code-icon@2x.png</file>
|
|
||||||
<file>resources/images/improve-current-code-icon.png</file>
|
|
||||||
<file>resources/images/suggest-new-icon.png</file>
|
|
||||||
<file>resources/images/suggest-new-icon@2x.png</file>
|
|
||||||
</qresource>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2023 The Qt Company Ltd.
|
* Copyright (C) 2023 The Qt Company Ltd.
|
||||||
* Copyright (C) 2024-2025 Petr Mironychev
|
* Copyright (C) 2024 Petr Mironychev
|
||||||
*
|
*
|
||||||
* This file is part of QodeAssist.
|
* This file is part of Qode Assist.
|
||||||
*
|
*
|
||||||
* The Qt Company portions:
|
* The Qt Company portions:
|
||||||
* SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
|
* SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
|
||||||
@@ -24,10 +24,8 @@
|
|||||||
|
|
||||||
#include "QodeAssistClient.hpp"
|
#include "QodeAssistClient.hpp"
|
||||||
|
|
||||||
#include <QInputDialog>
|
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
|
|
||||||
#include <coreplugin/icore.h>
|
|
||||||
#include <languageclient/languageclientsettings.h>
|
#include <languageclient/languageclientsettings.h>
|
||||||
#include <projectexplorer/projectmanager.h>
|
#include <projectexplorer/projectmanager.h>
|
||||||
|
|
||||||
@@ -37,7 +35,6 @@
|
|||||||
#include "settings/GeneralSettings.hpp"
|
#include "settings/GeneralSettings.hpp"
|
||||||
#include "settings/ProjectSettings.hpp"
|
#include "settings/ProjectSettings.hpp"
|
||||||
#include <context/ChangesManager.h>
|
#include <context/ChangesManager.h>
|
||||||
#include <logger/Logger.hpp>
|
|
||||||
|
|
||||||
using namespace LanguageServerProtocol;
|
using namespace LanguageServerProtocol;
|
||||||
using namespace TextEditor;
|
using namespace TextEditor;
|
||||||
@@ -47,12 +44,11 @@ using namespace Core;
|
|||||||
|
|
||||||
namespace QodeAssist {
|
namespace QodeAssist {
|
||||||
|
|
||||||
QodeAssistClient::QodeAssistClient(LLMClientInterface *clientInterface)
|
QodeAssistClient::QodeAssistClient()
|
||||||
: LanguageClient::Client(clientInterface)
|
: LanguageClient::Client(new LLMClientInterface())
|
||||||
, m_llmClient(clientInterface)
|
|
||||||
, m_recentCharCount(0)
|
, m_recentCharCount(0)
|
||||||
{
|
{
|
||||||
setName("QodeAssist");
|
setName("Qode Assist");
|
||||||
LanguageClient::LanguageFilter filter;
|
LanguageClient::LanguageFilter filter;
|
||||||
filter.mimeTypes = QStringList() << "*";
|
filter.mimeTypes = QStringList() << "*";
|
||||||
setSupportedLanguage(filter);
|
setSupportedLanguage(filter);
|
||||||
@@ -132,13 +128,6 @@ void QodeAssistClient::openDocument(TextEditor::TextDocument *document)
|
|||||||
scheduleRequest(widget);
|
scheduleRequest(widget);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// auto editors = BaseTextEditor::textEditorsForDocument(document);
|
|
||||||
// connect(
|
|
||||||
// editors.first()->editorWidget(),
|
|
||||||
// &TextEditorWidget::selectionChanged,
|
|
||||||
// this,
|
|
||||||
// [this, editors]() { m_chatButtonHandler.showButton(editors.first()->editorWidget()); });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool QodeAssistClient::canOpenProject(ProjectExplorer::Project *project)
|
bool QodeAssistClient::canOpenProject(ProjectExplorer::Project *project)
|
||||||
@@ -153,26 +142,14 @@ void QodeAssistClient::requestCompletions(TextEditor::TextEditorWidget *editor)
|
|||||||
if (!isEnabled(project))
|
if (!isEnabled(project))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (m_llmClient->contextManager()
|
|
||||||
->ignoreManager()
|
|
||||||
->shouldIgnore(editor->textDocument()->filePath().toUrlishString(), project)) {
|
|
||||||
LOG_MESSAGE(QString("Ignoring file due to .qodeassistignore: %1")
|
|
||||||
.arg(editor->textDocument()->filePath().toUrlishString()));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
MultiTextCursor cursor = editor->multiTextCursor();
|
MultiTextCursor cursor = editor->multiTextCursor();
|
||||||
if (cursor.hasMultipleCursors() || cursor.hasSelection() || editor->suggestionVisible())
|
if (cursor.hasMultipleCursors() || cursor.hasSelection() || editor->suggestionVisible())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const FilePath filePath = editor->textDocument()->filePath();
|
const FilePath filePath = editor->textDocument()->filePath();
|
||||||
GetCompletionRequest request{
|
GetCompletionRequest request{{TextDocumentIdentifier(hostPathToServerUri(filePath)),
|
||||||
{TextDocumentIdentifier(hostPathToServerUri(filePath)),
|
|
||||||
documentVersion(filePath),
|
documentVersion(filePath),
|
||||||
Position(cursor.mainCursor())}};
|
Position(cursor.mainCursor())}};
|
||||||
if (Settings::codeCompletionSettings().showProgressWidget()) {
|
|
||||||
m_progressHandler.showProgress(editor);
|
|
||||||
}
|
|
||||||
request.setResponseCallback([this, editor = QPointer<TextEditorWidget>(editor)](
|
request.setResponseCallback([this, editor = QPointer<TextEditorWidget>(editor)](
|
||||||
const GetCompletionRequest::Response &response) {
|
const GetCompletionRequest::Response &response) {
|
||||||
QTC_ASSERT(editor, return);
|
QTC_ASSERT(editor, return);
|
||||||
@@ -182,35 +159,6 @@ void QodeAssistClient::requestCompletions(TextEditor::TextEditorWidget *editor)
|
|||||||
sendMessage(request);
|
sendMessage(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
void QodeAssistClient::requestQuickRefactor(
|
|
||||||
TextEditor::TextEditorWidget *editor, const QString &instructions)
|
|
||||||
{
|
|
||||||
auto project = ProjectManager::projectForFile(editor->textDocument()->filePath());
|
|
||||||
|
|
||||||
if (!isEnabled(project))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (m_llmClient->contextManager()
|
|
||||||
->ignoreManager()
|
|
||||||
->shouldIgnore(editor->textDocument()->filePath().toUrlishString(), project)) {
|
|
||||||
LOG_MESSAGE(QString("Ignoring file due to .qodeassistignore: %1")
|
|
||||||
.arg(editor->textDocument()->filePath().toUrlishString()));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!m_refactorHandler) {
|
|
||||||
m_refactorHandler = new QuickRefactorHandler(this);
|
|
||||||
connect(
|
|
||||||
m_refactorHandler,
|
|
||||||
&QuickRefactorHandler::refactoringCompleted,
|
|
||||||
this,
|
|
||||||
&QodeAssistClient::handleRefactoringResult);
|
|
||||||
}
|
|
||||||
|
|
||||||
m_progressHandler.showProgress(editor);
|
|
||||||
m_refactorHandler->sendRefactorRequest(editor, instructions);
|
|
||||||
}
|
|
||||||
|
|
||||||
void QodeAssistClient::scheduleRequest(TextEditor::TextEditorWidget *editor)
|
void QodeAssistClient::scheduleRequest(TextEditor::TextEditorWidget *editor)
|
||||||
{
|
{
|
||||||
cancelRunningRequest(editor);
|
cancelRunningRequest(editor);
|
||||||
@@ -240,8 +188,8 @@ void QodeAssistClient::scheduleRequest(TextEditor::TextEditorWidget *editor)
|
|||||||
it.value()->setProperty("cursorPosition", editor->textCursor().position());
|
it.value()->setProperty("cursorPosition", editor->textCursor().position());
|
||||||
it.value()->start(Settings::codeCompletionSettings().startSuggestionTimer());
|
it.value()->start(Settings::codeCompletionSettings().startSuggestionTimer());
|
||||||
}
|
}
|
||||||
void QodeAssistClient::handleCompletions(
|
void QodeAssistClient::handleCompletions(const GetCompletionRequest::Response &response,
|
||||||
const GetCompletionRequest::Response &response, TextEditor::TextEditorWidget *editor)
|
TextEditor::TextEditorWidget *editor)
|
||||||
{
|
{
|
||||||
if (response.error())
|
if (response.error())
|
||||||
log(*response.error());
|
log(*response.error());
|
||||||
@@ -288,7 +236,6 @@ void QodeAssistClient::handleCompletions(
|
|||||||
Text::Position pos{toTextPos(c.position())};
|
Text::Position pos{toTextPos(c.position())};
|
||||||
return TextSuggestion::Data{range, pos, c.text()};
|
return TextSuggestion::Data{range, pos, c.text()};
|
||||||
});
|
});
|
||||||
m_progressHandler.hideProgress();
|
|
||||||
if (completions.isEmpty())
|
if (completions.isEmpty())
|
||||||
return;
|
return;
|
||||||
editor->insertSuggestion(std::make_unique<LLMSuggestion>(suggestions, editor->document()));
|
editor->insertSuggestion(std::make_unique<LLMSuggestion>(suggestions, editor->document()));
|
||||||
@@ -300,7 +247,6 @@ void QodeAssistClient::cancelRunningRequest(TextEditor::TextEditorWidget *editor
|
|||||||
const auto it = m_runningRequests.constFind(editor);
|
const auto it = m_runningRequests.constFind(editor);
|
||||||
if (it == m_runningRequests.constEnd())
|
if (it == m_runningRequests.constEnd())
|
||||||
return;
|
return;
|
||||||
m_progressHandler.hideProgress();
|
|
||||||
cancelRequest(it->id());
|
cancelRequest(it->id());
|
||||||
m_runningRequests.erase(it);
|
m_runningRequests.erase(it);
|
||||||
}
|
}
|
||||||
@@ -321,11 +267,16 @@ void QodeAssistClient::setupConnections()
|
|||||||
openDocument(textDocument);
|
openDocument(textDocument);
|
||||||
};
|
};
|
||||||
|
|
||||||
m_documentOpenedConnection
|
m_documentOpenedConnection = connect(EditorManager::instance(),
|
||||||
= connect(EditorManager::instance(), &EditorManager::documentOpened, this, openDoc);
|
&EditorManager::documentOpened,
|
||||||
m_documentClosedConnection = connect(
|
this,
|
||||||
EditorManager::instance(), &EditorManager::documentClosed, this, [this](IDocument *document) {
|
openDoc);
|
||||||
if (auto textDocument = qobject_cast<TextDocument *>(document))
|
m_documentClosedConnection = connect(EditorManager::instance(),
|
||||||
|
&EditorManager::documentClosed,
|
||||||
|
this,
|
||||||
|
[this](IDocument *document) {
|
||||||
|
if (auto textDocument = qobject_cast<TextDocument *>(
|
||||||
|
document))
|
||||||
closeDocument(textDocument);
|
closeDocument(textDocument);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -342,32 +293,4 @@ void QodeAssistClient::cleanupConnections()
|
|||||||
m_scheduledRequests.clear();
|
m_scheduledRequests.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void QodeAssistClient::handleRefactoringResult(const RefactorResult &result)
|
|
||||||
{
|
|
||||||
if (!result.success) {
|
|
||||||
LOG_MESSAGE(QString("Refactoring failed: %1").arg(result.errorMessage));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto editor = BaseTextEditor::currentTextEditor();
|
|
||||||
if (!editor) {
|
|
||||||
LOG_MESSAGE("Refactoring failed: No active editor found");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto editorWidget = editor->editorWidget();
|
|
||||||
|
|
||||||
QTextCursor cursor = editorWidget->textCursor();
|
|
||||||
cursor.beginEditBlock();
|
|
||||||
|
|
||||||
int startPos = result.insertRange.begin.toPositionInDocument(editorWidget->document());
|
|
||||||
int endPos = result.insertRange.end.toPositionInDocument(editorWidget->document());
|
|
||||||
|
|
||||||
cursor.setPosition(startPos);
|
|
||||||
cursor.setPosition(endPos, QTextCursor::KeepAnchor);
|
|
||||||
|
|
||||||
cursor.insertText(result.newText);
|
|
||||||
cursor.endEditBlock();
|
|
||||||
m_progressHandler.hideProgress();
|
|
||||||
}
|
|
||||||
} // namespace QodeAssist
|
} // namespace QodeAssist
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2023 The Qt Company Ltd.
|
* Copyright (C) 2023 The Qt Company Ltd.
|
||||||
* Copyright (C) 2024-2025 Petr Mironychev
|
* Copyright (C) 2024 Petr Mironychev
|
||||||
*
|
*
|
||||||
* This file is part of QodeAssist.
|
* This file is part of Qode Assist.
|
||||||
*
|
*
|
||||||
* The Qt Company portions:
|
* The Qt Company portions:
|
||||||
* SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
|
* SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
|
||||||
@@ -24,40 +24,32 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "LLMClientInterface.hpp"
|
|
||||||
#include "LSPCompletion.hpp"
|
|
||||||
#include "QuickRefactorHandler.hpp"
|
|
||||||
#include "widgets/CompletionProgressHandler.hpp"
|
|
||||||
#include "widgets/EditorChatButtonHandler.hpp"
|
|
||||||
#include <languageclient/client.h>
|
#include <languageclient/client.h>
|
||||||
#include <llmcore/IPromptProvider.hpp>
|
|
||||||
#include <llmcore/IProviderRegistry.hpp>
|
#include "LSPCompletion.hpp"
|
||||||
|
|
||||||
namespace QodeAssist {
|
namespace QodeAssist {
|
||||||
|
|
||||||
class QodeAssistClient : public LanguageClient::Client
|
class QodeAssistClient : public LanguageClient::Client
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit QodeAssistClient(LLMClientInterface *clientInterface);
|
explicit QodeAssistClient();
|
||||||
~QodeAssistClient() override;
|
~QodeAssistClient() override;
|
||||||
|
|
||||||
void openDocument(TextEditor::TextDocument *document) override;
|
void openDocument(TextEditor::TextDocument *document) override;
|
||||||
bool canOpenProject(ProjectExplorer::Project *project) override;
|
bool canOpenProject(ProjectExplorer::Project *project) override;
|
||||||
|
|
||||||
void requestCompletions(TextEditor::TextEditorWidget *editor);
|
void requestCompletions(TextEditor::TextEditorWidget *editor);
|
||||||
void requestQuickRefactor(
|
|
||||||
TextEditor::TextEditorWidget *editor, const QString &instructions = QString());
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void scheduleRequest(TextEditor::TextEditorWidget *editor);
|
void scheduleRequest(TextEditor::TextEditorWidget *editor);
|
||||||
void handleCompletions(
|
void handleCompletions(const GetCompletionRequest::Response &response,
|
||||||
const GetCompletionRequest::Response &response, TextEditor::TextEditorWidget *editor);
|
TextEditor::TextEditorWidget *editor);
|
||||||
void cancelRunningRequest(TextEditor::TextEditorWidget *editor);
|
void cancelRunningRequest(TextEditor::TextEditorWidget *editor);
|
||||||
bool isEnabled(ProjectExplorer::Project *project) const;
|
bool isEnabled(ProjectExplorer::Project *project) const;
|
||||||
|
|
||||||
void setupConnections();
|
void setupConnections();
|
||||||
void cleanupConnections();
|
void cleanupConnections();
|
||||||
void handleRefactoringResult(const RefactorResult &result);
|
|
||||||
|
|
||||||
QHash<TextEditor::TextEditorWidget *, GetCompletionRequest> m_runningRequests;
|
QHash<TextEditor::TextEditorWidget *, GetCompletionRequest> m_runningRequests;
|
||||||
QHash<TextEditor::TextEditorWidget *, QTimer *> m_scheduledRequests;
|
QHash<TextEditor::TextEditorWidget *, QTimer *> m_scheduledRequests;
|
||||||
@@ -66,10 +58,6 @@ private:
|
|||||||
|
|
||||||
QElapsedTimer m_typingTimer;
|
QElapsedTimer m_typingTimer;
|
||||||
int m_recentCharCount;
|
int m_recentCharCount;
|
||||||
CompletionProgressHandler m_progressHandler;
|
|
||||||
EditorChatButtonHandler m_chatButtonHandler;
|
|
||||||
QuickRefactorHandler *m_refactorHandler{nullptr};
|
|
||||||
LLMClientInterface *m_llmClient;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace QodeAssist
|
} // namespace QodeAssist
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2024-2025 Petr Mironychev
|
* Copyright (C) 2024 Petr Mironychev
|
||||||
*
|
*
|
||||||
* This file is part of QodeAssist.
|
* This file is part of QodeAssist.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2024-2025 Petr Mironychev
|
* Copyright (C) 2024 Petr Mironychev
|
||||||
*
|
*
|
||||||
* This file is part of QodeAssist.
|
* This file is part of QodeAssist.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,293 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2025 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 "QuickRefactorHandler.hpp"
|
|
||||||
|
|
||||||
#include <QJsonArray>
|
|
||||||
#include <QJsonDocument>
|
|
||||||
#include <QUuid>
|
|
||||||
|
|
||||||
#include <context/DocumentContextReader.hpp>
|
|
||||||
#include <context/DocumentReaderQtCreator.hpp>
|
|
||||||
#include <context/Utils.hpp>
|
|
||||||
#include <llmcore/PromptTemplateManager.hpp>
|
|
||||||
#include <llmcore/ProvidersManager.hpp>
|
|
||||||
#include <logger/Logger.hpp>
|
|
||||||
#include <settings/ChatAssistantSettings.hpp>
|
|
||||||
#include <settings/GeneralSettings.hpp>
|
|
||||||
|
|
||||||
namespace QodeAssist {
|
|
||||||
|
|
||||||
QuickRefactorHandler::QuickRefactorHandler(QObject *parent)
|
|
||||||
: QObject(parent)
|
|
||||||
, m_requestHandler(new LLMCore::RequestHandler(this))
|
|
||||||
, m_currentEditor(nullptr)
|
|
||||||
, m_isRefactoringInProgress(false)
|
|
||||||
, m_contextManager(this)
|
|
||||||
{
|
|
||||||
connect(
|
|
||||||
m_requestHandler,
|
|
||||||
&LLMCore::RequestHandler::completionReceived,
|
|
||||||
this,
|
|
||||||
&QuickRefactorHandler::handleLLMResponse);
|
|
||||||
|
|
||||||
connect(
|
|
||||||
m_requestHandler,
|
|
||||||
&LLMCore::RequestHandler::requestFinished,
|
|
||||||
this,
|
|
||||||
[this](const QString &requestId, bool success, const QString &errorString) {
|
|
||||||
if (!success && requestId == m_lastRequestId) {
|
|
||||||
m_isRefactoringInProgress = false;
|
|
||||||
RefactorResult result;
|
|
||||||
result.success = false;
|
|
||||||
result.errorMessage = errorString;
|
|
||||||
emit refactoringCompleted(result);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
QuickRefactorHandler::~QuickRefactorHandler() {}
|
|
||||||
|
|
||||||
void QuickRefactorHandler::sendRefactorRequest(
|
|
||||||
TextEditor::TextEditorWidget *editor, const QString &instructions)
|
|
||||||
{
|
|
||||||
if (m_isRefactoringInProgress) {
|
|
||||||
cancelRequest();
|
|
||||||
}
|
|
||||||
|
|
||||||
m_currentEditor = editor;
|
|
||||||
|
|
||||||
Utils::Text::Range range;
|
|
||||||
if (editor->textCursor().hasSelection()) {
|
|
||||||
QTextCursor cursor = editor->textCursor();
|
|
||||||
int startPos = cursor.selectionStart();
|
|
||||||
int endPos = cursor.selectionEnd();
|
|
||||||
|
|
||||||
QTextBlock startBlock = editor->document()->findBlock(startPos);
|
|
||||||
int startLine = startBlock.blockNumber() + 1;
|
|
||||||
int startColumn = startPos - startBlock.position();
|
|
||||||
|
|
||||||
QTextBlock endBlock = editor->document()->findBlock(endPos);
|
|
||||||
int endLine = endBlock.blockNumber() + 1;
|
|
||||||
int endColumn = endPos - endBlock.position();
|
|
||||||
|
|
||||||
Utils::Text::Position startPosition;
|
|
||||||
startPosition.line = startLine;
|
|
||||||
startPosition.column = startColumn;
|
|
||||||
|
|
||||||
Utils::Text::Position endPosition;
|
|
||||||
endPosition.line = endLine;
|
|
||||||
endPosition.column = endColumn;
|
|
||||||
|
|
||||||
range = Utils::Text::Range();
|
|
||||||
range.begin = startPosition;
|
|
||||||
range.end = endPosition;
|
|
||||||
} else {
|
|
||||||
QTextCursor cursor = editor->textCursor();
|
|
||||||
int cursorPos = cursor.position();
|
|
||||||
|
|
||||||
QTextBlock block = editor->document()->findBlock(cursorPos);
|
|
||||||
int line = block.blockNumber() + 1;
|
|
||||||
int column = cursorPos - block.position();
|
|
||||||
|
|
||||||
Utils::Text::Position cursorPosition;
|
|
||||||
cursorPosition.line = line;
|
|
||||||
cursorPosition.column = column;
|
|
||||||
range = Utils::Text::Range();
|
|
||||||
range.begin = cursorPosition;
|
|
||||||
range.end = cursorPosition;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_currentRange = range;
|
|
||||||
prepareAndSendRequest(editor, instructions, range);
|
|
||||||
}
|
|
||||||
|
|
||||||
void QuickRefactorHandler::prepareAndSendRequest(
|
|
||||||
TextEditor::TextEditorWidget *editor,
|
|
||||||
const QString &instructions,
|
|
||||||
const Utils::Text::Range &range)
|
|
||||||
{
|
|
||||||
auto &settings = Settings::generalSettings();
|
|
||||||
|
|
||||||
auto &providerRegistry = LLMCore::ProvidersManager::instance();
|
|
||||||
auto &promptManager = LLMCore::PromptTemplateManager::instance();
|
|
||||||
|
|
||||||
const auto providerName = settings.caProvider();
|
|
||||||
auto provider = providerRegistry.getProviderByName(providerName);
|
|
||||||
|
|
||||||
if (!provider) {
|
|
||||||
LOG_MESSAGE(QString("No provider found with name: %1").arg(providerName));
|
|
||||||
RefactorResult result;
|
|
||||||
result.success = false;
|
|
||||||
result.errorMessage = QString("No provider found with name: %1").arg(providerName);
|
|
||||||
emit refactoringCompleted(result);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto templateName = settings.caTemplate();
|
|
||||||
auto promptTemplate = promptManager.getChatTemplateByName(templateName);
|
|
||||||
|
|
||||||
if (!promptTemplate) {
|
|
||||||
LOG_MESSAGE(QString("No template found with name: %1").arg(templateName));
|
|
||||||
RefactorResult result;
|
|
||||||
result.success = false;
|
|
||||||
result.errorMessage = QString("No template found with name: %1").arg(templateName);
|
|
||||||
emit refactoringCompleted(result);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
LLMCore::LLMConfig config;
|
|
||||||
config.requestType = LLMCore::RequestType::Chat;
|
|
||||||
config.provider = provider;
|
|
||||||
config.promptTemplate = promptTemplate;
|
|
||||||
config.url = QString("%1%2").arg(settings.caUrl(), provider->chatEndpoint());
|
|
||||||
config.providerRequest
|
|
||||||
= {{"model", settings.caModel()}, {"stream", Settings::chatAssistantSettings().stream()}};
|
|
||||||
config.apiKey = provider->apiKey();
|
|
||||||
|
|
||||||
LLMCore::ContextData context = prepareContext(editor, range, instructions);
|
|
||||||
|
|
||||||
provider
|
|
||||||
->prepareRequest(config.providerRequest, promptTemplate, context, LLMCore::RequestType::Chat);
|
|
||||||
|
|
||||||
QString requestId = QUuid::createUuid().toString();
|
|
||||||
m_lastRequestId = requestId;
|
|
||||||
QJsonObject request{{"id", requestId}};
|
|
||||||
|
|
||||||
m_isRefactoringInProgress = true;
|
|
||||||
|
|
||||||
m_requestHandler->sendLLMRequest(config, request);
|
|
||||||
}
|
|
||||||
|
|
||||||
LLMCore::ContextData QuickRefactorHandler::prepareContext(
|
|
||||||
TextEditor::TextEditorWidget *editor,
|
|
||||||
const Utils::Text::Range &range,
|
|
||||||
const QString &instructions)
|
|
||||||
{
|
|
||||||
LLMCore::ContextData context;
|
|
||||||
|
|
||||||
auto textDocument = editor->textDocument();
|
|
||||||
Context::DocumentReaderQtCreator documentReader;
|
|
||||||
auto documentInfo = documentReader.readDocument(textDocument->filePath().toUrlishString());
|
|
||||||
|
|
||||||
if (!documentInfo.document) {
|
|
||||||
LOG_MESSAGE("Error: Document is not available");
|
|
||||||
return context;
|
|
||||||
}
|
|
||||||
|
|
||||||
QTextCursor cursor = editor->textCursor();
|
|
||||||
int cursorPos = cursor.position();
|
|
||||||
|
|
||||||
// TODO add selecting content before and after cursor/selection
|
|
||||||
QString fullContent = documentInfo.document->toPlainText();
|
|
||||||
QString taggedContent = fullContent;
|
|
||||||
|
|
||||||
if (cursor.hasSelection()) {
|
|
||||||
int selEnd = cursor.selectionEnd();
|
|
||||||
int selStart = cursor.selectionStart();
|
|
||||||
taggedContent
|
|
||||||
.insert(selEnd, selEnd == cursorPos ? "<selection_end><cursor>" : "<selection_end>");
|
|
||||||
taggedContent.insert(
|
|
||||||
selStart, selStart == cursorPos ? "<cursor><selection_start>" : "<selection_start>");
|
|
||||||
} else {
|
|
||||||
taggedContent.insert(cursorPos, "<cursor>");
|
|
||||||
}
|
|
||||||
|
|
||||||
QString systemPrompt = Settings::codeCompletionSettings().quickRefactorSystemPrompt();
|
|
||||||
systemPrompt += "\n\nFile information:";
|
|
||||||
systemPrompt += "\nLanguage: " + documentInfo.mimeType;
|
|
||||||
systemPrompt += "\nFile path: " + documentInfo.filePath;
|
|
||||||
|
|
||||||
systemPrompt += "\n\nCode context with position markers:";
|
|
||||||
systemPrompt += taggedContent;
|
|
||||||
|
|
||||||
systemPrompt += "\n\nOutput format:";
|
|
||||||
systemPrompt += "\n- Generate ONLY the code that should replace the current selection "
|
|
||||||
"between<selection_start><selection_end> or be "
|
|
||||||
"inserted at cursor position<cursor>";
|
|
||||||
systemPrompt += "\n- Do not include any explanations, comments about the code, or markdown "
|
|
||||||
"code block markers";
|
|
||||||
systemPrompt += "\n- The output should be ready to insert directly into the editor";
|
|
||||||
systemPrompt += "\n- Follow the existing code style and indentation patterns";
|
|
||||||
|
|
||||||
if (Settings::codeCompletionSettings().useOpenFilesInQuickRefactor()) {
|
|
||||||
systemPrompt += "\n\n" + m_contextManager.openedFilesContext({documentInfo.filePath});
|
|
||||||
}
|
|
||||||
|
|
||||||
context.systemPrompt = systemPrompt;
|
|
||||||
|
|
||||||
QVector<LLMCore::Message> messages;
|
|
||||||
messages.append(
|
|
||||||
{"user",
|
|
||||||
instructions.isEmpty() ? "Refactor the code to improve its quality and maintainability."
|
|
||||||
: instructions});
|
|
||||||
context.history = messages;
|
|
||||||
|
|
||||||
return context;
|
|
||||||
}
|
|
||||||
|
|
||||||
void QuickRefactorHandler::handleLLMResponse(
|
|
||||||
const QString &response, const QJsonObject &request, bool isComplete)
|
|
||||||
{
|
|
||||||
if (request["id"].toString() != m_lastRequestId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isComplete) {
|
|
||||||
QString cleanedResponse = response.trimmed();
|
|
||||||
if (cleanedResponse.startsWith("```")) {
|
|
||||||
int firstNewLine = cleanedResponse.indexOf('\n');
|
|
||||||
int lastFence = cleanedResponse.lastIndexOf("```");
|
|
||||||
|
|
||||||
if (firstNewLine != -1 && lastFence > firstNewLine) {
|
|
||||||
cleanedResponse
|
|
||||||
= cleanedResponse.mid(firstNewLine + 1, lastFence - firstNewLine - 1).trimmed();
|
|
||||||
} else if (lastFence != -1) {
|
|
||||||
cleanedResponse = cleanedResponse.mid(3, lastFence - 3).trimmed();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
RefactorResult result;
|
|
||||||
result.newText = cleanedResponse;
|
|
||||||
result.insertRange = m_currentRange;
|
|
||||||
result.success = true;
|
|
||||||
|
|
||||||
LOG_MESSAGE("Refactoring completed successfully. New code to insert: ");
|
|
||||||
LOG_MESSAGE("---------- BEGIN REFACTORED CODE ----------");
|
|
||||||
LOG_MESSAGE(cleanedResponse);
|
|
||||||
LOG_MESSAGE("----------- END REFACTORED CODE -----------");
|
|
||||||
|
|
||||||
emit refactoringCompleted(result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void QuickRefactorHandler::cancelRequest()
|
|
||||||
{
|
|
||||||
if (m_isRefactoringInProgress) {
|
|
||||||
m_requestHandler->cancelRequest(m_lastRequestId);
|
|
||||||
m_isRefactoringInProgress = false;
|
|
||||||
|
|
||||||
RefactorResult result;
|
|
||||||
result.success = false;
|
|
||||||
result.errorMessage = "Refactoring request was cancelled";
|
|
||||||
emit refactoringCompleted(result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace QodeAssist
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2025 Petr Mironychev
|
|
||||||
*
|
|
||||||
* This file is part of QodeAssist.
|
|
||||||
*
|
|
||||||
* QodeAssist is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* QodeAssist is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <QJsonObject>
|
|
||||||
#include <QObject>
|
|
||||||
|
|
||||||
#include <texteditor/texteditor.h>
|
|
||||||
#include <utils/textutils.h>
|
|
||||||
|
|
||||||
#include <context/ContextManager.hpp>
|
|
||||||
#include <context/IDocumentReader.hpp>
|
|
||||||
#include <llmcore/RequestHandler.hpp>
|
|
||||||
|
|
||||||
namespace QodeAssist {
|
|
||||||
|
|
||||||
struct RefactorResult
|
|
||||||
{
|
|
||||||
QString newText;
|
|
||||||
Utils::Text::Range insertRange;
|
|
||||||
bool success;
|
|
||||||
QString errorMessage;
|
|
||||||
};
|
|
||||||
|
|
||||||
class QuickRefactorHandler : public QObject
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit QuickRefactorHandler(QObject *parent = nullptr);
|
|
||||||
~QuickRefactorHandler() override;
|
|
||||||
|
|
||||||
void sendRefactorRequest(TextEditor::TextEditorWidget *editor, const QString &instructions);
|
|
||||||
|
|
||||||
void cancelRequest();
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void refactoringCompleted(const QodeAssist::RefactorResult &result);
|
|
||||||
|
|
||||||
private:
|
|
||||||
void prepareAndSendRequest(
|
|
||||||
TextEditor::TextEditorWidget *editor,
|
|
||||||
const QString &instructions,
|
|
||||||
const Utils::Text::Range &range);
|
|
||||||
|
|
||||||
void handleLLMResponse(const QString &response, const QJsonObject &request, bool isComplete);
|
|
||||||
LLMCore::ContextData prepareContext(
|
|
||||||
TextEditor::TextEditorWidget *editor,
|
|
||||||
const Utils::Text::Range &range,
|
|
||||||
const QString &instructions);
|
|
||||||
|
|
||||||
LLMCore::RequestHandler *m_requestHandler;
|
|
||||||
TextEditor::TextEditorWidget *m_currentEditor;
|
|
||||||
Utils::Text::Range m_currentRange;
|
|
||||||
bool m_isRefactoringInProgress;
|
|
||||||
QString m_lastRequestId;
|
|
||||||
Context::ContextManager m_contextManager;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace QodeAssist
|
|
||||||
169
README.md
169
README.md
@@ -2,7 +2,7 @@
|
|||||||
[](https://github.com/Palm1r/QodeAssist/actions/workflows/build_cmake.yml)
|
[](https://github.com/Palm1r/QodeAssist/actions/workflows/build_cmake.yml)
|
||||||

|

|
||||||

|

|
||||||

|

|
||||||
[](https://discord.gg/BGMkUsXUgf)
|
[](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.
|
 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.
|
||||||
@@ -13,30 +13,34 @@
|
|||||||
> - The QodeAssist developer bears no responsibility for any charges incurred
|
> - The QodeAssist developer bears no responsibility for any charges incurred
|
||||||
> - Please carefully review the provider's pricing and your account settings before use
|
> - Please carefully review the provider's pricing and your account settings before use
|
||||||
|
|
||||||
|
⚠️ **Commercial Support and Custom Development**
|
||||||
|
> The QodeAssist developer offers commercial services for:
|
||||||
|
> - Adapting the plugin for specific Qt Creator versions
|
||||||
|
> - Custom development for particular operating systems
|
||||||
|
> - Integration with specific language models
|
||||||
|
> - Implementing custom features and modifications
|
||||||
|
>
|
||||||
|
> For commercial inquiries, please contact: qodeassist.dev@pm.me
|
||||||
|
|
||||||
## Table of Contents
|
## Table of Contents
|
||||||
1. [Overview](#overview)
|
1. [Overview](#overview)
|
||||||
2. [Install plugin to QtCreator](#install-plugin-to-qtcreator)
|
2. [Install plugin to QtCreator](#install-plugin-to-qtcreator)
|
||||||
3. [Configure for Anthropic Claude](#configure-for-anthropic-claude)
|
3. [Configure for Anthropic Claude](#configure-for-anthropic-claude)
|
||||||
4. [Configure for OpenAI](#configure-for-openai)
|
4. [Configure for OpenAI](#configure-for-openai)
|
||||||
5. [Configure for Mistral AI](#configure-for-mistral-ai)
|
5. [Configure for using Ollama](#configure-for-using-ollama)
|
||||||
6. [Configure for Google AI](#configure-for-google-ai)
|
6. [System Prompt Configuration](#system-prompt-configuration)
|
||||||
7. [Configure for Ollama](#configure-for-ollama)
|
7. [File Context Features](#file-context-features)
|
||||||
8. [Configure for llama.cpp](#configure-for-llamacpp)
|
8. [Template-Model Compatibility](#template-model-compatibility)
|
||||||
9. [System Prompt Configuration](#system-prompt-configuration)
|
9. [QtCreator Version Compatibility](#qtcreator-version-compatibility)
|
||||||
10. [File Context Features](#file-context-features)
|
10. [Development Progress](#development-progress)
|
||||||
11. [QtCreator Version Compatibility](#qtcreator-version-compatibility)
|
11. [Hotkeys](#hotkeys)
|
||||||
12. [Development Progress](#development-progress)
|
12. [Troubleshooting](#troubleshooting)
|
||||||
13. [Hotkeys](#hotkeys)
|
13. [Support the Development](#support-the-development-of-qodeassist)
|
||||||
14. [Ignoring Files](#ignoring-files)
|
14. [How to Build](#how-to-build)
|
||||||
14. [Troubleshooting](#troubleshooting)
|
|
||||||
15. [Support the Development](#support-the-development-of-qodeassist)
|
|
||||||
16. [How to Build](#how-to-build)
|
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
- AI-powered code completion
|
- AI-powered code completion
|
||||||
- Sharing IDE opened files with model context (disabled by default, need enable in settings)
|
|
||||||
- Quick refactor code via fast chat command and opened files
|
|
||||||
- Chat functionality:
|
- Chat functionality:
|
||||||
- Side and Bottom panels
|
- Side and Bottom panels
|
||||||
- Chat history autosave and restore
|
- Chat history autosave and restore
|
||||||
@@ -46,28 +50,19 @@
|
|||||||
- Automatic syncing with open editor files (optional)
|
- Automatic syncing with open editor files (optional)
|
||||||
- Support for multiple LLM providers:
|
- Support for multiple LLM providers:
|
||||||
- Ollama
|
- Ollama
|
||||||
- llama.cpp
|
|
||||||
- OpenAI
|
- OpenAI
|
||||||
- Anthropic Claude
|
- Anthropic Claude
|
||||||
- LM Studio
|
- LM Studio
|
||||||
- Mistral AI
|
- OpenAI-compatible providers(eg. https://openrouter.ai)
|
||||||
- Google AI
|
|
||||||
- OpenAI-compatible providers(eg. llama.cpp, https://openrouter.ai)
|
|
||||||
- Extensive library of model-specific templates
|
- Extensive library of model-specific templates
|
||||||
|
- Custom template support
|
||||||
- Easy configuration and model selection
|
- 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>
|
<details>
|
||||||
<summary>Code completion: (click to expand)</summary>
|
<summary>Code completion: (click to expand)</summary>
|
||||||
<img src="https://github.com/user-attachments/assets/255a52f1-5cc0-4ca3-b05c-c4cf9cdbe25a" width="600" alt="QodeAssistPreview">
|
<img src="https://github.com/user-attachments/assets/255a52f1-5cc0-4ca3-b05c-c4cf9cdbe25a" width="600" alt="QodeAssistPreview">
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>Quick refactor in code: (click to expand)</summary>
|
|
||||||
<img src="https://github.com/user-attachments/assets/4a9092e0-429f-41eb-8723-cbb202fd0a8c" width="600" alt="QodeAssistPreview">
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>Multiline Code completion: (click to expand)</summary>
|
<summary>Multiline Code completion: (click to expand)</summary>
|
||||||
<img src="https://github.com/user-attachments/assets/c18dfbd2-8c54-4a7b-90d1-66e3bb51adb0" width="600" alt="QodeAssistPreview">
|
<img src="https://github.com/user-attachments/assets/c18dfbd2-8c54-4a7b-90d1-66e3bb51adb0" width="600" alt="QodeAssistPreview">
|
||||||
@@ -92,8 +87,6 @@ Join our Discord Community: Have questions or want to discuss QodeAssist? Join o
|
|||||||
1. Install Latest Qt Creator
|
1. Install Latest Qt Creator
|
||||||
2. Download the QodeAssist plugin for your Qt Creator
|
2. Download the QodeAssist plugin for your Qt Creator
|
||||||
- Remove old version plugin if already was installed
|
- Remove old version plugin if already was installed
|
||||||
- on macOS for QtCreator 16: ~/Library/Application Support/QtProject/Qt Creator/plugins/16.0.0/petrmironychev.qodeassist
|
|
||||||
- on windows for QtCreator 16: C:\Users\<user>\AppData\Local\QtProject\qtcreator\plugins\16.0.0\petrmironychev.qodeassist\lib\qtcreator\plugins
|
|
||||||
3. Launch Qt Creator and install the plugin:
|
3. Launch Qt Creator and install the plugin:
|
||||||
- Go to:
|
- Go to:
|
||||||
- MacOS: Qt Creator -> About Plugins...
|
- MacOS: Qt Creator -> About Plugins...
|
||||||
@@ -127,33 +120,7 @@ Join our Discord Community: Have questions or want to discuss QodeAssist? Join o
|
|||||||
<img width="829" alt="OpenAI Settings" src="https://github.com/user-attachments/assets/4716f790-6159-44d0-a8f4-565ccb6eb713" />
|
<img width="829" alt="OpenAI Settings" src="https://github.com/user-attachments/assets/4716f790-6159-44d0-a8f4-565ccb6eb713" />
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## Configure for Mistral AI
|
## Configure for using Ollama
|
||||||
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.
|
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:
|
2. Install a language models in Ollama via terminal. For example, you can run:
|
||||||
@@ -172,7 +139,7 @@ ollama run qwen2.5-coder:32b
|
|||||||
```
|
```
|
||||||
|
|
||||||
1. Open Qt Creator settings (Edit > Preferences on Linux/Windows, Qt Creator > Preferences on macOS)
|
1. Open Qt Creator settings (Edit > Preferences on Linux/Windows, Qt Creator > Preferences on macOS)
|
||||||
2. Navigate to the "QodeAssist" tab
|
2. Navigate to the "Qode Assist" tab
|
||||||
3. On the "General" page, verify:
|
3. On the "General" page, verify:
|
||||||
- Ollama is selected as your LLM provider
|
- Ollama is selected as your LLM provider
|
||||||
- The URL is set to http://localhost:11434
|
- The URL is set to http://localhost:11434
|
||||||
@@ -186,18 +153,6 @@ 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" />
|
<img width="824" alt="Ollama Settings" src="https://github.com/user-attachments/assets/ed64e03a-a923-467a-aa44-4f790e315b53" />
|
||||||
</details>
|
</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
|
## 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.
|
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.
|
||||||
@@ -235,11 +190,25 @@ Linked files provide persistent context throughout the conversation:
|
|||||||
- Supports automatic syncing with open editor files (can be enabled in settings)
|
- Supports automatic syncing with open editor files (can be enabled in settings)
|
||||||
- Files can be added/removed at any time during the conversation
|
- 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 Version Compatibility
|
||||||
|
|
||||||
- QtCreator 16.0.1 - 0.5.7 - 0.x.x
|
- QtCreator 15.0.1 - 0.4.8 - 0.4.x
|
||||||
- QtCreator 16.0.0 - 0.5.2 - 0.5.6
|
|
||||||
- QtCreator 15.0.1 - 0.4.8 - 0.5.1
|
|
||||||
- QtCreator 15.0.0 - 0.4.0 - 0.4.7
|
- QtCreator 15.0.0 - 0.4.0 - 0.4.7
|
||||||
- QtCreator 14.0.2 - 0.2.3 - 0.3.x
|
- QtCreator 14.0.2 - 0.2.3 - 0.3.x
|
||||||
- QtCreator 14.0.1 - 0.2.2 plugin version and below
|
- QtCreator 14.0.1 - 0.2.2 plugin version and below
|
||||||
@@ -258,49 +227,8 @@ Linked files provide persistent context throughout the conversation:
|
|||||||
- To call manual request to suggestion, you can use or change it in settings
|
- To call manual request to suggestion, you can use or change it in settings
|
||||||
- on Mac: Option + Command + Q
|
- on Mac: Option + Command + Q
|
||||||
- on Windows: Ctrl + Alt + Q
|
- on Windows: Ctrl + Alt + Q
|
||||||
- on Linux with KDE Plasma: Ctrl + Alt + Q
|
|
||||||
- To insert the full suggestion, you can use the TAB key
|
- To insert the full suggestion, you can use the TAB key
|
||||||
- To insert word of suggistion, you can use Alt + Right Arrow for Win/Lin, or Option + Right Arrow for Mac
|
- To insert word of suggistion, you can use Alt + Right Arrow for Win/Lin, or Option + Right Arrow for Mac
|
||||||
- To call Quick Refactor dialog, select some code or place cursor and press
|
|
||||||
- on Mac: Option + Command + R
|
|
||||||
- on Windows: Ctrl + Alt + R
|
|
||||||
- on Linux with KDE Plasma: Ctrl + Alt + R
|
|
||||||
|
|
||||||
## Ignoring Files
|
|
||||||
QodeAssist supports the ability to ignore files in context using a .qodeassistignore file. This allows you to exclude specific files from the context during code completion and in the chat assistant, which is especially useful for large projects.
|
|
||||||
|
|
||||||
### How to Use .qodeassistignore
|
|
||||||
- Create a .qodeassistignore file in the root directory of your project near CMakeLists.txt or pro.
|
|
||||||
- Add patterns for files and directories that should be excluded from the context.
|
|
||||||
- QodeAssist will automatically detect this file and apply the exclusion rules.
|
|
||||||
|
|
||||||
### .qodeassistignore File Format
|
|
||||||
The file format is similar to .gitignore:
|
|
||||||
- Each pattern is written on a separate line
|
|
||||||
- Empty lines are ignored
|
|
||||||
- Lines starting with # are considered comments
|
|
||||||
- Standard wildcards work the same as in .gitignore
|
|
||||||
- To negate a pattern, use ! at the beginning of the line
|
|
||||||
```
|
|
||||||
# Ignore all files in the build directory
|
|
||||||
build/
|
|
||||||
|
|
||||||
# Ignore all temporary files
|
|
||||||
*.tmp
|
|
||||||
*.temp
|
|
||||||
|
|
||||||
# Ignore all files with .log extension
|
|
||||||
*.log
|
|
||||||
|
|
||||||
# Ignore a specific file
|
|
||||||
src/generated/autogen.cpp
|
|
||||||
|
|
||||||
# Ignore nested directories
|
|
||||||
**/node_modules/
|
|
||||||
|
|
||||||
# Negation - DO NOT ignore this file
|
|
||||||
!src/important.cpp
|
|
||||||
```
|
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
@@ -311,17 +239,20 @@ If QodeAssist is having problems connecting to the LLM provider, please check th
|
|||||||
- For Ollama, the default is usually http://localhost:11434
|
- For Ollama, the default is usually http://localhost:11434
|
||||||
- For LM Studio, the default is usually http://localhost:1234
|
- For LM Studio, the default is usually http://localhost:1234
|
||||||
|
|
||||||
2. Confirm that the selected model and template are compatible:
|
2. Check the endpoint:
|
||||||
|
|
||||||
Ensure you've chosen the correct model in the "Select Models" option
|
Make sure the endpoint in the settings matches the one required by your provider
|
||||||
Verify that the selected prompt template matches the model you're using
|
- For Ollama, it should be /api/generate
|
||||||
|
- For LM Studio and OpenAI compatible providers, it's usually /v1/chat/completions
|
||||||
|
|
||||||
3. On Linux the prebuilt binaries support only ubuntu 22.04+ or simililliar os.
|
3. Confirm that the selected model and template are compatible:
|
||||||
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
|
|
||||||
|
Ensure you've chosen the correct model in the "Select Models" option
|
||||||
|
Verify that the selected prompt template matches the model you're using
|
||||||
|
|
||||||
If you're still experiencing issues with QodeAssist, you can try resetting the settings to their default values:
|
If you're still experiencing issues with QodeAssist, you can try resetting the settings to their default values:
|
||||||
1. Open Qt Creator settings
|
1. Open Qt Creator settings
|
||||||
2. Navigate to the "QodeAssist" tab
|
2. Navigate to the "Qode Assist" tab
|
||||||
3. Pick settings page for reset
|
3. Pick settings page for reset
|
||||||
4. Click on the "Reset Page to Defaults" button
|
4. Click on the "Reset Page to Defaults" button
|
||||||
- The API key will not reset
|
- The API key will not reset
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2024-2025 Petr Mironychev
|
* Copyright (C) 2024 Petr Mironychev
|
||||||
*
|
*
|
||||||
* This file is part of QodeAssist.
|
* This file is part of QodeAssist.
|
||||||
*
|
*
|
||||||
@@ -52,10 +52,10 @@ void UpdateStatusWidget::setDefaultAction(QAction *action)
|
|||||||
|
|
||||||
void UpdateStatusWidget::showUpdateAvailable(const QString &version)
|
void UpdateStatusWidget::showUpdateAvailable(const QString &version)
|
||||||
{
|
{
|
||||||
m_versionLabel->setText(tr("New version: v%1").arg(version));
|
m_versionLabel->setText(tr("new version: v%1").arg(version));
|
||||||
m_versionLabel->setVisible(true);
|
m_versionLabel->setVisible(true);
|
||||||
m_updateButton->setVisible(true);
|
m_updateButton->setVisible(true);
|
||||||
m_updateButton->setToolTip(tr("Check update information"));
|
m_updateButton->setToolTip(tr("Update QodeAssist to version %1").arg(version));
|
||||||
}
|
}
|
||||||
|
|
||||||
void UpdateStatusWidget::hideUpdateInfo()
|
void UpdateStatusWidget::hideUpdateInfo()
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2024-2025 Petr Mironychev
|
* Copyright (C) 2024 Petr Mironychev
|
||||||
*
|
*
|
||||||
* This file is part of QodeAssist.
|
* This file is part of QodeAssist.
|
||||||
*
|
*
|
||||||
|
|||||||
28
Version.hpp
28
Version.hpp
@@ -1,28 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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)
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2024-2025 Petr Mironychev
|
* Copyright (C) 2024 Petr Mironychev
|
||||||
*
|
*
|
||||||
* This file is part of QodeAssist.
|
* This file is part of QodeAssist.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2024-2025 Petr Mironychev
|
* Copyright (C) 2024 Petr Mironychev
|
||||||
*
|
*
|
||||||
* This file is part of QodeAssist.
|
* This file is part of QodeAssist.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2024-2025 Petr Mironychev
|
* Copyright (C) 2024 Petr Mironychev
|
||||||
*
|
*
|
||||||
* This file is part of QodeAssist.
|
* This file is part of QodeAssist.
|
||||||
*
|
*
|
||||||
@@ -23,15 +23,16 @@
|
|||||||
|
|
||||||
namespace QodeAssist::Chat {
|
namespace QodeAssist::Chat {
|
||||||
|
|
||||||
NavigationPanel::NavigationPanel()
|
NavigationPanel::NavigationPanel() {
|
||||||
{
|
|
||||||
setDisplayName(tr("QodeAssist Chat"));
|
setDisplayName(tr("QodeAssist Chat"));
|
||||||
setPriority(500);
|
setPriority(500);
|
||||||
setId("QodeAssistChat");
|
setId("QodeAssistChat");
|
||||||
setActivationSequence(QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key_C));
|
setActivationSequence(QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key_C));
|
||||||
}
|
}
|
||||||
|
|
||||||
NavigationPanel::~NavigationPanel() {}
|
NavigationPanel::~NavigationPanel()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
Core::NavigationView NavigationPanel::createWidget()
|
Core::NavigationView NavigationPanel::createWidget()
|
||||||
{
|
{
|
||||||
@@ -41,4 +42,4 @@ Core::NavigationView NavigationPanel::createWidget()
|
|||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace QodeAssist::Chat
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2024-2025 Petr Mironychev
|
* Copyright (C) 2024 Petr Mironychev
|
||||||
*
|
*
|
||||||
* This file is part of QodeAssist.
|
* This file is part of QodeAssist.
|
||||||
*
|
*
|
||||||
@@ -19,8 +19,8 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <coreplugin/inavigationwidgetfactory.h>
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
#include <coreplugin/inavigationwidgetfactory.h>
|
||||||
|
|
||||||
namespace QodeAssist::Chat {
|
namespace QodeAssist::Chat {
|
||||||
|
|
||||||
@@ -34,4 +34,4 @@ public:
|
|||||||
Core::NavigationView createWidget() override;
|
Core::NavigationView createWidget() override;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace QodeAssist::Chat
|
}
|
||||||
|
|||||||
@@ -3,17 +3,22 @@ add_library(Context STATIC
|
|||||||
ChangesManager.h ChangesManager.cpp
|
ChangesManager.h ChangesManager.cpp
|
||||||
ContextManager.hpp ContextManager.cpp
|
ContextManager.hpp ContextManager.cpp
|
||||||
ContentFile.hpp
|
ContentFile.hpp
|
||||||
DocumentReaderQtCreator.hpp
|
|
||||||
IDocumentReader.hpp
|
|
||||||
TokenUtils.hpp TokenUtils.cpp
|
TokenUtils.hpp TokenUtils.cpp
|
||||||
ProgrammingLanguage.hpp ProgrammingLanguage.cpp
|
ProgrammingLanguage.hpp ProgrammingLanguage.cpp
|
||||||
IContextManager.hpp
|
RAGManager.hpp RAGManager.cpp
|
||||||
IgnoreManager.hpp IgnoreManager.cpp
|
RAGStorage.hpp RAGStorage.cpp
|
||||||
|
RAGData.hpp
|
||||||
|
RAGVectorizer.hpp RAGVectorizer.cpp
|
||||||
|
RAGSimilaritySearch.hpp RAGSimilaritySearch.cpp
|
||||||
|
RAGPreprocessor.hpp RAGPreprocessor.cpp
|
||||||
|
EnhancedRAGSimilaritySearch.hpp EnhancedRAGSimilaritySearch.cpp
|
||||||
|
FileChunker.hpp FileChunker.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(Context
|
target_link_libraries(Context
|
||||||
PUBLIC
|
PUBLIC
|
||||||
Qt::Core
|
Qt::Core
|
||||||
|
Qt::Sql
|
||||||
QtCreator::Core
|
QtCreator::Core
|
||||||
QtCreator::TextEditor
|
QtCreator::TextEditor
|
||||||
QtCreator::Utils
|
QtCreator::Utils
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2024-2025 Petr Mironychev
|
* Copyright (C) 2024 Petr Mironychev
|
||||||
*
|
*
|
||||||
* This file is part of QodeAssist.
|
* This file is part of QodeAssist.
|
||||||
*
|
*
|
||||||
@@ -30,12 +30,17 @@ ChangesManager &ChangesManager::instance()
|
|||||||
|
|
||||||
ChangesManager::ChangesManager()
|
ChangesManager::ChangesManager()
|
||||||
: QObject(nullptr)
|
: QObject(nullptr)
|
||||||
{}
|
{
|
||||||
|
}
|
||||||
|
|
||||||
ChangesManager::~ChangesManager() {}
|
ChangesManager::~ChangesManager()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
void ChangesManager::addChange(
|
void ChangesManager::addChange(TextEditor::TextDocument *document,
|
||||||
TextEditor::TextDocument *document, int position, int charsRemoved, int charsAdded)
|
int position,
|
||||||
|
int charsRemoved,
|
||||||
|
int charsAdded)
|
||||||
{
|
{
|
||||||
auto &documentQueue = m_documentChanges[document];
|
auto &documentQueue = m_documentChanges[document];
|
||||||
|
|
||||||
@@ -46,10 +51,9 @@ void ChangesManager::addChange(
|
|||||||
|
|
||||||
ChangeInfo change{fileName, lineNumber, lineContent};
|
ChangeInfo change{fileName, lineNumber, lineContent};
|
||||||
|
|
||||||
auto it
|
auto it = std::find_if(documentQueue.begin(),
|
||||||
= std::find_if(documentQueue.begin(), documentQueue.end(), [lineNumber](const ChangeInfo &c) {
|
documentQueue.end(),
|
||||||
return c.lineNumber == lineNumber;
|
[lineNumber](const ChangeInfo &c) { return c.lineNumber == lineNumber; });
|
||||||
});
|
|
||||||
|
|
||||||
if (it != documentQueue.end()) {
|
if (it != documentQueue.end()) {
|
||||||
it->lineContent = lineContent;
|
it->lineContent = lineContent;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2024-2025 Petr Mironychev
|
* Copyright (C) 2024 Petr Mironychev
|
||||||
*
|
*
|
||||||
* This file is part of QodeAssist.
|
* This file is part of QodeAssist.
|
||||||
*
|
*
|
||||||
@@ -19,11 +19,11 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <texteditor/textdocument.h>
|
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
#include <QHash>
|
#include <QHash>
|
||||||
#include <QQueue>
|
#include <QQueue>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
|
#include <texteditor/textdocument.h>
|
||||||
|
|
||||||
namespace QodeAssist::Context {
|
namespace QodeAssist::Context {
|
||||||
|
|
||||||
@@ -41,8 +41,10 @@ public:
|
|||||||
|
|
||||||
static ChangesManager &instance();
|
static ChangesManager &instance();
|
||||||
|
|
||||||
void addChange(
|
void addChange(TextEditor::TextDocument *document,
|
||||||
TextEditor::TextDocument *document, int position, int charsRemoved, int charsAdded);
|
int position,
|
||||||
|
int charsRemoved,
|
||||||
|
int charsAdded);
|
||||||
QString getRecentChangesContext(const TextEditor::TextDocument *currentDocument) const;
|
QString getRecentChangesContext(const TextEditor::TextDocument *currentDocument) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2024-2025 Petr Mironychev
|
* Copyright (C) 2024 Petr Mironychev
|
||||||
*
|
*
|
||||||
* This file is part of QodeAssist.
|
* This file is part of QodeAssist.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2024-2025 Petr Mironychev
|
* Copyright (C) 2024 Petr Mironychev
|
||||||
*
|
*
|
||||||
* This file is part of QodeAssist.
|
* This file is part of QodeAssist.
|
||||||
*
|
*
|
||||||
@@ -21,23 +21,23 @@
|
|||||||
|
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QJsonObject>
|
|
||||||
#include <QTextStream>
|
#include <QTextStream>
|
||||||
|
|
||||||
#include "settings/GeneralSettings.hpp"
|
|
||||||
#include <coreplugin/editormanager/editormanager.h>
|
|
||||||
#include <projectexplorer/project.h>
|
#include <projectexplorer/project.h>
|
||||||
#include <projectexplorer/projectmanager.h>
|
|
||||||
#include <projectexplorer/projectnodes.h>
|
#include <projectexplorer/projectnodes.h>
|
||||||
#include <texteditor/textdocument.h>
|
|
||||||
|
|
||||||
#include "Logger.hpp"
|
#include "FileChunker.hpp"
|
||||||
|
|
||||||
namespace QodeAssist::Context {
|
namespace QodeAssist::Context {
|
||||||
|
|
||||||
|
ContextManager &ContextManager::instance()
|
||||||
|
{
|
||||||
|
static ContextManager manager;
|
||||||
|
return manager;
|
||||||
|
}
|
||||||
|
|
||||||
ContextManager::ContextManager(QObject *parent)
|
ContextManager::ContextManager(QObject *parent)
|
||||||
: QObject(parent)
|
: QObject(parent)
|
||||||
, m_ignoreManager(new IgnoreManager(this))
|
|
||||||
{}
|
{}
|
||||||
|
|
||||||
QString ContextManager::readFile(const QString &filePath) const
|
QString ContextManager::readFile(const QString &filePath) const
|
||||||
@@ -54,19 +54,51 @@ QList<ContentFile> ContextManager::getContentFiles(const QStringList &filePaths)
|
|||||||
{
|
{
|
||||||
QList<ContentFile> files;
|
QList<ContentFile> files;
|
||||||
for (const QString &path : filePaths) {
|
for (const QString &path : filePaths) {
|
||||||
auto project = ProjectExplorer::ProjectManager::projectForFile(
|
|
||||||
Utils::FilePath::fromString(path));
|
|
||||||
if (project && m_ignoreManager->shouldIgnore(path, project)) {
|
|
||||||
LOG_MESSAGE(QString("Ignoring file in context due to .qodeassistignore: %1").arg(path));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
ContentFile contentFile = createContentFile(path);
|
ContentFile contentFile = createContentFile(path);
|
||||||
files.append(contentFile);
|
files.append(contentFile);
|
||||||
}
|
}
|
||||||
return files;
|
return files;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ContentFile ContextManager::createContentFile(const QString &filePath) const
|
||||||
|
{
|
||||||
|
ContentFile contentFile;
|
||||||
|
QFileInfo fileInfo(filePath);
|
||||||
|
contentFile.filename = fileInfo.fileName();
|
||||||
|
contentFile.content = readFile(filePath);
|
||||||
|
return contentFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ContextManager::isInBuildDirectory(const QString &filePath) const
|
||||||
|
{
|
||||||
|
static const QStringList buildDirPatterns
|
||||||
|
= {QString(QDir::separator()) + QLatin1String("build") + QDir::separator(),
|
||||||
|
QString(QDir::separator()) + QLatin1String("Build") + QDir::separator(),
|
||||||
|
QString(QDir::separator()) + QLatin1String("BUILD") + QDir::separator(),
|
||||||
|
QString(QDir::separator()) + QLatin1String("debug") + QDir::separator(),
|
||||||
|
QString(QDir::separator()) + QLatin1String("Debug") + QDir::separator(),
|
||||||
|
QString(QDir::separator()) + QLatin1String("DEBUG") + QDir::separator(),
|
||||||
|
QString(QDir::separator()) + QLatin1String("release") + QDir::separator(),
|
||||||
|
QString(QDir::separator()) + QLatin1String("Release") + QDir::separator(),
|
||||||
|
QString(QDir::separator()) + QLatin1String("RELEASE") + QDir::separator(),
|
||||||
|
QString(QDir::separator()) + QLatin1String("builds") + QDir::separator()};
|
||||||
|
|
||||||
|
// Нормализуем путь
|
||||||
|
QString normalizedPath = QDir::fromNativeSeparators(filePath);
|
||||||
|
|
||||||
|
// Проверяем, содержит ли путь паттерны build-директории
|
||||||
|
for (const QString &pattern : buildDirPatterns) {
|
||||||
|
// Сравниваем с нормализованным паттерном
|
||||||
|
QString normalizedPattern = QDir::fromNativeSeparators(pattern);
|
||||||
|
if (normalizedPath.contains(normalizedPattern)) {
|
||||||
|
qDebug() << "Skipping build file:" << filePath;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
QStringList ContextManager::getProjectSourceFiles(ProjectExplorer::Project *project) const
|
QStringList ContextManager::getProjectSourceFiles(ProjectExplorer::Project *project) const
|
||||||
{
|
{
|
||||||
QStringList sourceFiles;
|
QStringList sourceFiles;
|
||||||
@@ -79,8 +111,11 @@ QStringList ContextManager::getProjectSourceFiles(ProjectExplorer::Project *proj
|
|||||||
|
|
||||||
projectNode->forEachNode(
|
projectNode->forEachNode(
|
||||||
[&sourceFiles, this](ProjectExplorer::FileNode *fileNode) {
|
[&sourceFiles, this](ProjectExplorer::FileNode *fileNode) {
|
||||||
if (fileNode /*&& shouldProcessFile(fileNode->filePath().toString())*/) {
|
if (fileNode) {
|
||||||
sourceFiles.append(fileNode->filePath().toUrlishString());
|
QString filePath = fileNode->filePath().toString();
|
||||||
|
if (shouldProcessFile(filePath) && !isInBuildDirectory(filePath)) {
|
||||||
|
sourceFiles.append(filePath);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
nullptr);
|
nullptr);
|
||||||
@@ -88,98 +123,54 @@ QStringList ContextManager::getProjectSourceFiles(ProjectExplorer::Project *proj
|
|||||||
return sourceFiles;
|
return sourceFiles;
|
||||||
}
|
}
|
||||||
|
|
||||||
ContentFile ContextManager::createContentFile(const QString &filePath) const
|
bool ContextManager::shouldProcessFile(const QString &filePath) const
|
||||||
{
|
{
|
||||||
ContentFile contentFile;
|
static const QStringList supportedExtensions
|
||||||
|
= {"cpp", "hpp", "c", "h", "cc", "hh", "cxx", "hxx", "qml", "js", "py"};
|
||||||
|
|
||||||
QFileInfo fileInfo(filePath);
|
QFileInfo fileInfo(filePath);
|
||||||
contentFile.filename = fileInfo.fileName();
|
return supportedExtensions.contains(fileInfo.suffix().toLower());
|
||||||
contentFile.content = readFile(filePath);
|
|
||||||
return contentFile;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ProgrammingLanguage ContextManager::getDocumentLanguage(const DocumentInfo &documentInfo) const
|
void ContextManager::testProjectChunks(
|
||||||
|
ProjectExplorer::Project *project, const FileChunker::ChunkingConfig &config)
|
||||||
{
|
{
|
||||||
if (!documentInfo.document) {
|
if (!project) {
|
||||||
LOG_MESSAGE("Error: Document is not available for" + documentInfo.filePath);
|
qDebug() << "No project provided";
|
||||||
return Context::ProgrammingLanguage::Unknown;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Context::ProgrammingLanguageUtils::fromMimeType(documentInfo.mimeType);
|
qDebug() << "\nStarting test chunking for project:" << project->displayName();
|
||||||
}
|
|
||||||
|
|
||||||
bool ContextManager::isSpecifyCompletion(const DocumentInfo &documentInfo) const
|
// Get source files
|
||||||
{
|
QStringList sourceFiles = getProjectSourceFiles(project);
|
||||||
const auto &generalSettings = Settings::generalSettings();
|
qDebug() << "Found" << sourceFiles.size() << "source files";
|
||||||
|
|
||||||
Context::ProgrammingLanguage documentLanguage = getDocumentLanguage(documentInfo);
|
// Create chunker
|
||||||
Context::ProgrammingLanguage preset1Language = Context::ProgrammingLanguageUtils::fromString(
|
auto chunker = new FileChunker(config, this);
|
||||||
generalSettings.preset1Language.displayForIndex(generalSettings.preset1Language()));
|
|
||||||
|
|
||||||
return generalSettings.specifyPreset1() && documentLanguage == preset1Language;
|
// Connect progress and error signals
|
||||||
}
|
connect(chunker, &FileChunker::progressUpdated, this, [](int processed, int total) {
|
||||||
|
qDebug() << "Progress:" << processed << "/" << total << "files";
|
||||||
|
});
|
||||||
|
|
||||||
QList<QPair<QString, QString>> ContextManager::openedFiles(const QStringList excludeFiles) const
|
connect(chunker, &FileChunker::error, this, [](const QString &error) {
|
||||||
{
|
qDebug() << "Error:" << error;
|
||||||
auto documents = Core::DocumentModel::openedDocuments();
|
});
|
||||||
|
|
||||||
QList<QPair<QString, QString>> files;
|
// Start chunking and handle results
|
||||||
|
auto future = chunker->chunkFiles(sourceFiles);
|
||||||
|
|
||||||
for (const auto *document : std::as_const(documents)) {
|
// Используем QFutureWatcher для обработки результатов
|
||||||
auto textDocument = qobject_cast<const TextEditor::TextDocument *>(document);
|
auto watcher = new QFutureWatcher<QList<FileChunk>>(this);
|
||||||
if (!textDocument)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
auto filePath = textDocument->filePath().toUrlishString();
|
connect(watcher, &QFutureWatcher<QList<FileChunk>>::finished, this, [watcher, chunker]() {
|
||||||
|
// Очистка
|
||||||
|
watcher->deleteLater();
|
||||||
|
chunker->deleteLater();
|
||||||
|
});
|
||||||
|
|
||||||
auto project = ProjectExplorer::ProjectManager::projectForFile(textDocument->filePath());
|
watcher->setFuture(future);
|
||||||
if (project && m_ignoreManager->shouldIgnore(filePath, project)) {
|
|
||||||
LOG_MESSAGE(
|
|
||||||
QString("Ignoring file in context due to .qodeassistignore: %1").arg(filePath));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!excludeFiles.contains(filePath)) {
|
|
||||||
files.append({filePath, textDocument->plainText()});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return files;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString ContextManager::openedFilesContext(const QStringList excludeFiles)
|
|
||||||
{
|
|
||||||
QString context = "User files context:\n";
|
|
||||||
|
|
||||||
auto documents = Core::DocumentModel::openedDocuments();
|
|
||||||
|
|
||||||
for (const auto *document : documents) {
|
|
||||||
auto textDocument = qobject_cast<const TextEditor::TextDocument *>(document);
|
|
||||||
if (!textDocument)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
auto filePath = textDocument->filePath().toUrlishString();
|
|
||||||
if (excludeFiles.contains(filePath))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
auto project = ProjectExplorer::ProjectManager::projectForFile(textDocument->filePath());
|
|
||||||
if (project && m_ignoreManager->shouldIgnore(filePath, project)) {
|
|
||||||
LOG_MESSAGE(
|
|
||||||
QString("Ignoring file in context due to .qodeassistignore: %1").arg(filePath));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
context += QString("File: %1\n").arg(filePath);
|
|
||||||
context += textDocument->plainText();
|
|
||||||
|
|
||||||
context += "\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
return context;
|
|
||||||
}
|
|
||||||
|
|
||||||
IgnoreManager *ContextManager::ignoreManager() const
|
|
||||||
{
|
|
||||||
return m_ignoreManager;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace QodeAssist::Context
|
} // namespace QodeAssist::Context
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2025 Petr Mironychev
|
* Copyright (C) 2024 Petr Mironychev
|
||||||
*
|
*
|
||||||
* This file is part of QodeAssist.
|
* This file is part of QodeAssist.
|
||||||
*
|
*
|
||||||
@@ -19,13 +19,12 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "ContentFile.hpp"
|
||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
|
||||||
#include "ContentFile.hpp"
|
#include "FileChunker.hpp"
|
||||||
#include "IContextManager.hpp"
|
|
||||||
#include "IgnoreManager.hpp"
|
|
||||||
#include "ProgrammingLanguage.hpp"
|
|
||||||
|
|
||||||
namespace ProjectExplorer {
|
namespace ProjectExplorer {
|
||||||
class Project;
|
class Project;
|
||||||
@@ -33,28 +32,29 @@ class Project;
|
|||||||
|
|
||||||
namespace QodeAssist::Context {
|
namespace QodeAssist::Context {
|
||||||
|
|
||||||
class ContextManager : public QObject, public IContextManager
|
class ContextManager : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit ContextManager(QObject *parent = nullptr);
|
static ContextManager &instance();
|
||||||
~ContextManager() override = default;
|
|
||||||
|
|
||||||
QString readFile(const QString &filePath) const override;
|
QString readFile(const QString &filePath) const;
|
||||||
QList<ContentFile> getContentFiles(const QStringList &filePaths) const override;
|
QList<ContentFile> getContentFiles(const QStringList &filePaths) const;
|
||||||
QStringList getProjectSourceFiles(ProjectExplorer::Project *project) const override;
|
QStringList getProjectSourceFiles(ProjectExplorer::Project *project) const;
|
||||||
ContentFile createContentFile(const QString &filePath) const override;
|
|
||||||
|
|
||||||
ProgrammingLanguage getDocumentLanguage(const DocumentInfo &documentInfo) const override;
|
void testProjectChunks(
|
||||||
bool isSpecifyCompletion(const DocumentInfo &documentInfo) const override;
|
ProjectExplorer::Project *project, const FileChunker::ChunkingConfig &config);
|
||||||
QList<QPair<QString, QString>> openedFiles(const QStringList excludeFiles = QStringList{}) const;
|
|
||||||
QString openedFilesContext(const QStringList excludeFiles = QStringList{});
|
|
||||||
|
|
||||||
IgnoreManager *ignoreManager() const;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
IgnoreManager *m_ignoreManager;
|
explicit ContextManager(QObject *parent = nullptr);
|
||||||
|
~ContextManager() = default;
|
||||||
|
ContextManager(const ContextManager &) = delete;
|
||||||
|
ContextManager &operator=(const ContextManager &) = delete;
|
||||||
|
|
||||||
|
ContentFile createContentFile(const QString &filePath) const;
|
||||||
|
bool shouldProcessFile(const QString &filePath) const;
|
||||||
|
bool isInBuildDirectory(const QString &filePath) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace QodeAssist::Context
|
} // namespace QodeAssist::Context
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2024-2025 Petr Mironychev
|
* Copyright (C) 2024 Petr Mironychev
|
||||||
*
|
*
|
||||||
* This file is part of QodeAssist.
|
* This file is part of QodeAssist.
|
||||||
*
|
*
|
||||||
@@ -19,9 +19,9 @@
|
|||||||
|
|
||||||
#include "DocumentContextReader.hpp"
|
#include "DocumentContextReader.hpp"
|
||||||
|
|
||||||
#include <languageserverprotocol/lsptypes.h>
|
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QTextBlock>
|
#include <QTextBlock>
|
||||||
|
#include <languageserverprotocol/lsptypes.h>
|
||||||
|
|
||||||
#include "CodeCompletionSettings.hpp"
|
#include "CodeCompletionSettings.hpp"
|
||||||
|
|
||||||
@@ -41,18 +41,17 @@ const QRegularExpression &getNameRegex()
|
|||||||
|
|
||||||
const QRegularExpression &getCommentRegex()
|
const QRegularExpression &getCommentRegex()
|
||||||
{
|
{
|
||||||
static const QRegularExpression commentRegex(
|
static const QRegularExpression
|
||||||
R"((/\*[\s\S]*?\*/|//.*$|#.*$|//{2,}[\s\S]*?//{2,}))", QRegularExpression::MultilineOption);
|
commentRegex(R"((/\*[\s\S]*?\*/|//.*$|#.*$|//{2,}[\s\S]*?//{2,}))",
|
||||||
|
QRegularExpression::MultilineOption);
|
||||||
return commentRegex;
|
return commentRegex;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace QodeAssist::Context {
|
namespace QodeAssist::Context {
|
||||||
|
|
||||||
DocumentContextReader::DocumentContextReader(
|
DocumentContextReader::DocumentContextReader(TextEditor::TextDocument *textDocument)
|
||||||
QTextDocument *document, const QString &mimeType, const QString &filePath)
|
: m_textDocument(textDocument)
|
||||||
: m_document(document)
|
, m_document(textDocument->document())
|
||||||
, m_mimeType(mimeType)
|
|
||||||
, m_filePath(filePath)
|
|
||||||
{
|
{
|
||||||
m_copyrightInfo = findCopyright();
|
m_copyrightInfo = findCopyright();
|
||||||
}
|
}
|
||||||
@@ -81,26 +80,26 @@ QString DocumentContextReader::getLineText(int lineNumber, int cursorPosition) c
|
|||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString DocumentContextReader::getContextBefore(
|
QString DocumentContextReader::getContextBefore(int lineNumber,
|
||||||
int lineNumber, int cursorPosition, int linesCount) const
|
int cursorPosition,
|
||||||
|
int linesCount) const
|
||||||
{
|
{
|
||||||
int startLine = lineNumber - linesCount + 1;
|
int effectiveStartLine;
|
||||||
if (m_copyrightInfo.found) {
|
if (m_copyrightInfo.found) {
|
||||||
startLine = qMax(m_copyrightInfo.endLine + 1, startLine);
|
effectiveStartLine = qMax(m_copyrightInfo.endLine + 1, lineNumber - linesCount);
|
||||||
|
} else {
|
||||||
|
effectiveStartLine = qMax(0, lineNumber - linesCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
return getContextBetween(startLine, -1, lineNumber, cursorPosition);
|
return getContextBetween(effectiveStartLine, lineNumber, cursorPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString DocumentContextReader::getContextAfter(
|
QString DocumentContextReader::getContextAfter(int lineNumber,
|
||||||
int lineNumber, int cursorPosition, int linesCount) const
|
int cursorPosition,
|
||||||
|
int linesCount) const
|
||||||
{
|
{
|
||||||
int endLine = lineNumber + linesCount - 1;
|
int endLine = qMin(m_document->blockCount() - 1, lineNumber + linesCount);
|
||||||
if (m_copyrightInfo.found && m_copyrightInfo.endLine >= lineNumber) {
|
return getContextBetween(lineNumber + 1, endLine, cursorPosition);
|
||||||
lineNumber = m_copyrightInfo.endLine + 1;
|
|
||||||
cursorPosition = -1;
|
|
||||||
}
|
|
||||||
return getContextBetween(lineNumber, cursorPosition, endLine, -1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString DocumentContextReader::readWholeFileBefore(int lineNumber, int cursorPosition) const
|
QString DocumentContextReader::readWholeFileBefore(int lineNumber, int cursorPosition) const
|
||||||
@@ -110,26 +109,31 @@ QString DocumentContextReader::readWholeFileBefore(int lineNumber, int cursorPos
|
|||||||
startLine = m_copyrightInfo.endLine + 1;
|
startLine = m_copyrightInfo.endLine + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return getContextBetween(startLine, -1, lineNumber, cursorPosition);
|
startLine = qMin(startLine, lineNumber);
|
||||||
|
|
||||||
|
QString result = getContextBetween(startLine, lineNumber, cursorPosition);
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString DocumentContextReader::readWholeFileAfter(int lineNumber, int cursorPosition) const
|
QString DocumentContextReader::readWholeFileAfter(int lineNumber, int cursorPosition) const
|
||||||
{
|
{
|
||||||
int endLine = m_document->blockCount() - 1;
|
return getContextBetween(lineNumber, m_document->blockCount() - 1, cursorPosition);
|
||||||
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
|
QString DocumentContextReader::getLanguageAndFileInfo() const
|
||||||
{
|
{
|
||||||
QString language = LanguageServerProtocol::TextDocumentItem::mimeTypeToLanguageId(m_mimeType);
|
if (!m_textDocument)
|
||||||
QString fileExtension = QFileInfo(m_filePath).suffix();
|
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();
|
||||||
|
|
||||||
return QString("Language: %1 (MIME: %2) filepath: %3(%4)\n\n")
|
return QString("Language: %1 (MIME: %2) filepath: %3(%4)\n\n")
|
||||||
.arg(language, m_mimeType, m_filePath, fileExtension);
|
.arg(language, mimeType, filePath, fileExtension);
|
||||||
}
|
}
|
||||||
|
|
||||||
CopyrightInfo DocumentContextReader::findCopyright()
|
CopyrightInfo DocumentContextReader::findCopyright()
|
||||||
@@ -145,24 +149,9 @@ CopyrightInfo DocumentContextReader::findCopyright()
|
|||||||
QRegularExpressionMatch match = matchIterator.next();
|
QRegularExpressionMatch match = matchIterator.next();
|
||||||
QString matchedText = match.captured().toLower();
|
QString matchedText = match.captured().toLower();
|
||||||
|
|
||||||
bool hasCopyrightIndicator = matchedText.contains("copyright")
|
if (matchedText.contains("copyright") || matchedText.contains("(C)")
|
||||||
|| matchedText.contains("(c)") || matchedText.contains("©")
|
|| matchedText.contains("(c)") || matchedText.contains("©")
|
||||||
|| matchedText.contains("copr.")
|
|| getYearRegex().match(text).hasMatch() || getNameRegex().match(text).hasMatch()) {
|
||||||
|| matchedText.contains("all rights reserved")
|
|
||||||
|| matchedText.contains("proprietary")
|
|
||||||
|| matchedText.contains("licensed under")
|
|
||||||
|| matchedText.contains("license:")
|
|
||||||
|| matchedText.contains("gpl") || matchedText.contains("lgpl")
|
|
||||||
|| matchedText.contains("mit license")
|
|
||||||
|| matchedText.contains("apache license")
|
|
||||||
|| matchedText.contains("bsd license")
|
|
||||||
|| matchedText.contains("mozilla public license")
|
|
||||||
|| matchedText.contains("copyleft");
|
|
||||||
|
|
||||||
bool hasYear = getYearRegex().match(matchedText).hasMatch();
|
|
||||||
bool hasName = getNameRegex().match(matchedText).hasMatch();
|
|
||||||
|
|
||||||
if ((hasCopyrightIndicator && (hasYear || hasName)) || (hasYear && hasName)) {
|
|
||||||
int startPos = match.capturedStart();
|
int startPos = match.capturedStart();
|
||||||
int endPos = match.capturedEnd();
|
int endPos = match.capturedEnd();
|
||||||
|
|
||||||
@@ -190,75 +179,21 @@ CopyrightInfo DocumentContextReader::findCopyright()
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString DocumentContextReader::getContextBetween(
|
QString DocumentContextReader::getContextBetween(int startLine,
|
||||||
int startLine, int startCursorPosition, int endLine, int endCursorPosition) const
|
int endLine,
|
||||||
|
int cursorPosition) const
|
||||||
{
|
{
|
||||||
QString context;
|
QString context;
|
||||||
|
for (int i = startLine; i <= endLine; ++i) {
|
||||||
startLine = qMax(startLine, 0);
|
QTextBlock block = m_document->findBlockByNumber(i);
|
||||||
endLine = qMin(endLine, m_document->blockCount() - 1);
|
|
||||||
|
|
||||||
if (startLine > endLine) {
|
|
||||||
return context;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (startLine == endLine) {
|
|
||||||
auto block = m_document->findBlockByNumber(startLine);
|
|
||||||
if (!block.isValid()) {
|
if (!block.isValid()) {
|
||||||
return context;
|
break;
|
||||||
}
|
}
|
||||||
|
if (i == endLine) {
|
||||||
auto text = block.text();
|
context += block.text().left(cursorPosition);
|
||||||
|
|
||||||
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 {
|
} else {
|
||||||
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";
|
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return context;
|
return context;
|
||||||
@@ -269,31 +204,47 @@ CopyrightInfo DocumentContextReader::copyrightInfo() const
|
|||||||
return m_copyrightInfo;
|
return m_copyrightInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
LLMCore::ContextData DocumentContextReader::prepareContext(
|
LLMCore::ContextData DocumentContextReader::prepareContext(int lineNumber, int cursorPosition) const
|
||||||
int lineNumber, int cursorPosition, const Settings::CodeCompletionSettings &settings) const
|
|
||||||
{
|
{
|
||||||
QString contextBefore;
|
QString contextBefore = getContextBefore(lineNumber, cursorPosition);
|
||||||
QString contextAfter;
|
QString contextAfter = getContextAfter(lineNumber, cursorPosition);
|
||||||
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;
|
QString fileContext;
|
||||||
|
if (Settings::codeCompletionSettings().useFilePathInContext())
|
||||||
fileContext.append("\n ").append(getLanguageAndFileInfo());
|
fileContext.append("\n ").append(getLanguageAndFileInfo());
|
||||||
|
|
||||||
if (settings.useProjectChangesCache())
|
if (Settings::codeCompletionSettings().useProjectChangesCache())
|
||||||
fileContext.append("Recent Project Changes Context:\n ")
|
fileContext.append("\n ").append(
|
||||||
.append(ChangesManager::instance().getRecentChangesContext(m_textDocument));
|
ChangesManager::instance().getRecentChangesContext(m_textDocument));
|
||||||
|
|
||||||
return {.prefix = contextBefore, .suffix = contextAfter, .fileContext = fileContext};
|
return {contextBefore, contextAfter, fileContext};
|
||||||
|
}
|
||||||
|
|
||||||
|
QString DocumentContextReader::getContextBefore(int lineNumber, int cursorPosition) const
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace QodeAssist::Context
|
} // namespace QodeAssist::Context
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2024-2025 Petr Mironychev
|
* Copyright (C) 2024 Petr Mironychev
|
||||||
*
|
*
|
||||||
* This file is part of QodeAssist.
|
* This file is part of QodeAssist.
|
||||||
*
|
*
|
||||||
@@ -23,7 +23,6 @@
|
|||||||
#include <QTextDocument>
|
#include <QTextDocument>
|
||||||
|
|
||||||
#include <llmcore/ContextData.hpp>
|
#include <llmcore/ContextData.hpp>
|
||||||
#include <settings/CodeCompletionSettings.hpp>
|
|
||||||
|
|
||||||
namespace QodeAssist::Context {
|
namespace QodeAssist::Context {
|
||||||
|
|
||||||
@@ -37,55 +36,28 @@ struct CopyrightInfo
|
|||||||
class DocumentContextReader
|
class DocumentContextReader
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
DocumentContextReader(
|
DocumentContextReader(TextEditor::TextDocument *textDocument);
|
||||||
QTextDocument *m_document, const QString &mimeType, const QString &filePath);
|
|
||||||
|
|
||||||
QString getLineText(int lineNumber, int cursorPosition = -1) const;
|
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;
|
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;
|
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;
|
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 readWholeFileAfter(int lineNumber, int cursorPosition) const;
|
||||||
|
|
||||||
QString getLanguageAndFileInfo() const;
|
QString getLanguageAndFileInfo() const;
|
||||||
CopyrightInfo findCopyright();
|
CopyrightInfo findCopyright();
|
||||||
QString getContextBetween(
|
QString getContextBetween(int startLine, int endLine, int cursorPosition) const;
|
||||||
int startLine, int startCursorPosition, int endLine, int endCursorPosition) const;
|
|
||||||
|
|
||||||
CopyrightInfo copyrightInfo() const;
|
CopyrightInfo copyrightInfo() const;
|
||||||
|
|
||||||
LLMCore::ContextData prepareContext(
|
LLMCore::ContextData prepareContext(int lineNumber, int cursorPosition) const;
|
||||||
int lineNumber, int cursorPosition, const Settings::CodeCompletionSettings &settings) const;
|
|
||||||
|
private:
|
||||||
|
QString getContextBefore(int lineNumber, int cursorPosition) const;
|
||||||
|
QString getContextAfter(int lineNumber, int cursorPosition) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
TextEditor::TextDocument *m_textDocument;
|
TextEditor::TextDocument *m_textDocument;
|
||||||
QTextDocument *m_document;
|
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;
|
CopyrightInfo m_copyrightInfo;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
265
context/EnhancedRAGSimilaritySearch.cpp
Normal file
265
context/EnhancedRAGSimilaritySearch.cpp
Normal file
@@ -0,0 +1,265 @@
|
|||||||
|
#include "EnhancedRAGSimilaritySearch.hpp"
|
||||||
|
|
||||||
|
#include <QSet>
|
||||||
|
|
||||||
|
namespace QodeAssist::Context {
|
||||||
|
|
||||||
|
// Static regex getters
|
||||||
|
const QRegularExpression &EnhancedRAGSimilaritySearch::getNamespaceRegex()
|
||||||
|
{
|
||||||
|
static const QRegularExpression regex(R"(namespace\s+(?:\w+\s*::\s*)*\w+\s*\{)");
|
||||||
|
return regex;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QRegularExpression &EnhancedRAGSimilaritySearch::getClassRegex()
|
||||||
|
{
|
||||||
|
static const QRegularExpression regex(
|
||||||
|
R"((?:template\s*<[^>]*>\s*)?(?:class|struct)\s+(\w+)\s*(?:final\s*)?(?::\s*(?:public|protected|private)\s+\w+(?:\s*,\s*(?:public|protected|private)\s+\w+)*\s*)?{)");
|
||||||
|
return regex;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QRegularExpression &EnhancedRAGSimilaritySearch::getFunctionRegex()
|
||||||
|
{
|
||||||
|
static const QRegularExpression regex(
|
||||||
|
R"((?:virtual\s+)?(?:static\s+)?(?:inline\s+)?(?:explicit\s+)?(?:constexpr\s+)?(?:[\w:]+\s+)?(?:\w+\s*::\s*)*\w+\s*\([^)]*\)\s*(?:const\s*)?(?:noexcept\s*)?(?:override\s*)?(?:final\s*)?(?:=\s*0\s*)?(?:=\s*default\s*)?(?:=\s*delete\s*)?(?:\s*->.*?)?\s*{)");
|
||||||
|
return regex;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QRegularExpression &EnhancedRAGSimilaritySearch::getTemplateRegex()
|
||||||
|
{
|
||||||
|
static const QRegularExpression regex(R"(template\s*<[^>]*>\s*(?:class|struct|typename)\s+\w+)");
|
||||||
|
return regex;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache getters
|
||||||
|
QCache<QString, EnhancedRAGSimilaritySearch::SimilarityScore> &
|
||||||
|
EnhancedRAGSimilaritySearch::getScoreCache()
|
||||||
|
{
|
||||||
|
static QCache<QString, SimilarityScore> cache(1000); // Cache size of 1000 entries
|
||||||
|
return cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
QCache<QString, QStringList> &EnhancedRAGSimilaritySearch::getStructureCache()
|
||||||
|
{
|
||||||
|
static QCache<QString, QStringList> cache(500); // Cache size of 500 entries
|
||||||
|
return cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main public interface
|
||||||
|
EnhancedRAGSimilaritySearch::SimilarityScore EnhancedRAGSimilaritySearch::calculateSimilarity(
|
||||||
|
const RAGVector &v1, const RAGVector &v2, const QString &code1, const QString &code2)
|
||||||
|
{
|
||||||
|
// Generate cache key based on content hashes
|
||||||
|
QString cacheKey = QString("%1_%2").arg(qHash(code1)).arg(qHash(code2));
|
||||||
|
|
||||||
|
// Check cache first
|
||||||
|
auto &scoreCache = getScoreCache();
|
||||||
|
if (auto *cached = scoreCache.object(cacheKey)) {
|
||||||
|
return *cached;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate new similarity score
|
||||||
|
SimilarityScore score = calculateSimilarityInternal(v1, v2, code1, code2);
|
||||||
|
|
||||||
|
// Cache the result
|
||||||
|
scoreCache.insert(cacheKey, new SimilarityScore(score));
|
||||||
|
|
||||||
|
return score;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal implementation
|
||||||
|
EnhancedRAGSimilaritySearch::SimilarityScore EnhancedRAGSimilaritySearch::calculateSimilarityInternal(
|
||||||
|
const RAGVector &v1, const RAGVector &v2, const QString &code1, const QString &code2)
|
||||||
|
{
|
||||||
|
if (v1.empty() || v2.empty()) {
|
||||||
|
LOG_MESSAGE("Warning: Empty vectors in similarity calculation");
|
||||||
|
return SimilarityScore(0.0f, 0.0f, 0.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (v1.size() != v2.size()) {
|
||||||
|
LOG_MESSAGE(QString("Vector size mismatch: %1 vs %2").arg(v1.size()).arg(v2.size()));
|
||||||
|
return SimilarityScore(0.0f, 0.0f, 0.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate semantic similarity using vector embeddings
|
||||||
|
float semantic_similarity = 0.0f;
|
||||||
|
|
||||||
|
#if defined(__SSE__) || defined(_M_X64) || defined(_M_AMD64)
|
||||||
|
if (v1.size() >= 4) { // Use SSE for vectors of 4 or more elements
|
||||||
|
semantic_similarity = calculateCosineSimilaritySSE(v1, v2);
|
||||||
|
} else {
|
||||||
|
semantic_similarity = calculateCosineSimilarity(v1, v2);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
semantic_similarity = calculateCosineSimilarity(v1, v2);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// If semantic similarity is very low, skip structural analysis
|
||||||
|
if (semantic_similarity < 0.0001f) {
|
||||||
|
return SimilarityScore(0.0f, 0.0f, 0.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate structural similarity
|
||||||
|
float structural_similarity = calculateStructuralSimilarity(code1, code2);
|
||||||
|
|
||||||
|
// Calculate combined score with dynamic weights
|
||||||
|
float semantic_weight = 0.7f;
|
||||||
|
const int large_file_threshold = 10000;
|
||||||
|
|
||||||
|
if (code1.size() > large_file_threshold || code2.size() > large_file_threshold) {
|
||||||
|
semantic_weight = 0.8f; // Increase semantic weight for large files
|
||||||
|
}
|
||||||
|
|
||||||
|
float combined_score = semantic_weight * semantic_similarity
|
||||||
|
+ (1.0f - semantic_weight) * structural_similarity;
|
||||||
|
|
||||||
|
return SimilarityScore(semantic_similarity, structural_similarity, combined_score);
|
||||||
|
}
|
||||||
|
|
||||||
|
float EnhancedRAGSimilaritySearch::calculateCosineSimilarity(const RAGVector &v1, const RAGVector &v2)
|
||||||
|
{
|
||||||
|
float dotProduct = 0.0f;
|
||||||
|
float norm1 = 0.0f;
|
||||||
|
float norm2 = 0.0f;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < v1.size(); ++i) {
|
||||||
|
dotProduct += v1[i] * v2[i];
|
||||||
|
norm1 += v1[i] * v1[i];
|
||||||
|
norm2 += v2[i] * v2[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
norm1 = std::sqrt(norm1);
|
||||||
|
norm2 = std::sqrt(norm2);
|
||||||
|
|
||||||
|
if (norm1 == 0.0f || norm2 == 0.0f) {
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
return dotProduct / (norm1 * norm2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(__SSE__) || defined(_M_X64) || defined(_M_AMD64)
|
||||||
|
float EnhancedRAGSimilaritySearch::calculateCosineSimilaritySSE(
|
||||||
|
const RAGVector &v1, const RAGVector &v2)
|
||||||
|
{
|
||||||
|
const float *p1 = v1.data();
|
||||||
|
const float *p2 = v2.data();
|
||||||
|
const size_t size = v1.size();
|
||||||
|
const size_t alignedSize = size & ~3ULL; // Round down to multiple of 4
|
||||||
|
|
||||||
|
__m128 sum = _mm_setzero_ps();
|
||||||
|
__m128 norm1 = _mm_setzero_ps();
|
||||||
|
__m128 norm2 = _mm_setzero_ps();
|
||||||
|
|
||||||
|
// Process 4 elements at a time using SSE
|
||||||
|
for (size_t i = 0; i < alignedSize; i += 4) {
|
||||||
|
__m128 v1_vec = _mm_loadu_ps(p1 + i); // Use unaligned load for safety
|
||||||
|
__m128 v2_vec = _mm_loadu_ps(p2 + i);
|
||||||
|
|
||||||
|
sum = _mm_add_ps(sum, _mm_mul_ps(v1_vec, v2_vec));
|
||||||
|
norm1 = _mm_add_ps(norm1, _mm_mul_ps(v1_vec, v1_vec));
|
||||||
|
norm2 = _mm_add_ps(norm2, _mm_mul_ps(v2_vec, v2_vec));
|
||||||
|
}
|
||||||
|
|
||||||
|
float dotProduct = horizontalSum(sum);
|
||||||
|
float n1 = std::sqrt(horizontalSum(norm1));
|
||||||
|
float n2 = std::sqrt(horizontalSum(norm2));
|
||||||
|
|
||||||
|
// Process remaining elements
|
||||||
|
for (size_t i = alignedSize; i < size; ++i) {
|
||||||
|
dotProduct += v1[i] * v2[i];
|
||||||
|
n1 += v1[i] * v1[i];
|
||||||
|
n2 += v2[i] * v2[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (n1 == 0.0f || n2 == 0.0f) {
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
return dotProduct / (std::sqrt(n1) * std::sqrt(n2));
|
||||||
|
}
|
||||||
|
|
||||||
|
float EnhancedRAGSimilaritySearch::horizontalSum(__m128 x)
|
||||||
|
{
|
||||||
|
__m128 shuf = _mm_shuffle_ps(x, x, _MM_SHUFFLE(2, 3, 0, 1));
|
||||||
|
__m128 sums = _mm_add_ps(x, shuf);
|
||||||
|
shuf = _mm_movehl_ps(shuf, sums);
|
||||||
|
sums = _mm_add_ss(sums, shuf);
|
||||||
|
return _mm_cvtss_f32(sums);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
float EnhancedRAGSimilaritySearch::calculateStructuralSimilarity(
|
||||||
|
const QString &code1, const QString &code2)
|
||||||
|
{
|
||||||
|
QStringList structures1 = extractStructures(code1);
|
||||||
|
QStringList structures2 = extractStructures(code2);
|
||||||
|
|
||||||
|
return calculateJaccardSimilarity(structures1, structures2);
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList EnhancedRAGSimilaritySearch::extractStructures(const QString &code)
|
||||||
|
{
|
||||||
|
// Check cache first
|
||||||
|
auto &structureCache = getStructureCache();
|
||||||
|
QString cacheKey = QString::number(qHash(code));
|
||||||
|
|
||||||
|
if (auto *cached = structureCache.object(cacheKey)) {
|
||||||
|
return *cached;
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList structures;
|
||||||
|
structures.reserve(100); // Reserve space for typical file
|
||||||
|
|
||||||
|
// Extract namespaces
|
||||||
|
auto namespaceMatches = getNamespaceRegex().globalMatch(code);
|
||||||
|
while (namespaceMatches.hasNext()) {
|
||||||
|
structures.append(namespaceMatches.next().captured().trimmed());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract classes
|
||||||
|
auto classMatches = getClassRegex().globalMatch(code);
|
||||||
|
while (classMatches.hasNext()) {
|
||||||
|
structures.append(classMatches.next().captured().trimmed());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract functions
|
||||||
|
auto functionMatches = getFunctionRegex().globalMatch(code);
|
||||||
|
while (functionMatches.hasNext()) {
|
||||||
|
structures.append(functionMatches.next().captured().trimmed());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract templates
|
||||||
|
auto templateMatches = getTemplateRegex().globalMatch(code);
|
||||||
|
while (templateMatches.hasNext()) {
|
||||||
|
structures.append(templateMatches.next().captured().trimmed());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache the result
|
||||||
|
structureCache.insert(cacheKey, new QStringList(structures));
|
||||||
|
|
||||||
|
return structures;
|
||||||
|
}
|
||||||
|
|
||||||
|
float EnhancedRAGSimilaritySearch::calculateJaccardSimilarity(
|
||||||
|
const QStringList &set1, const QStringList &set2)
|
||||||
|
{
|
||||||
|
if (set1.isEmpty() && set2.isEmpty()) {
|
||||||
|
return 1.0f; // Пустые множества считаем идентичными
|
||||||
|
}
|
||||||
|
if (set1.isEmpty() || set2.isEmpty()) {
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
QSet<QString> set1Unique = QSet<QString>(set1.begin(), set1.end());
|
||||||
|
QSet<QString> set2Unique = QSet<QString>(set2.begin(), set2.end());
|
||||||
|
|
||||||
|
QSet<QString> intersection = set1Unique;
|
||||||
|
intersection.intersect(set2Unique);
|
||||||
|
|
||||||
|
QSet<QString> union_set = set1Unique;
|
||||||
|
union_set.unite(set2Unique);
|
||||||
|
|
||||||
|
return static_cast<float>(intersection.size()) / union_set.size();
|
||||||
|
}
|
||||||
|
} // namespace QodeAssist::Context
|
||||||
74
context/EnhancedRAGSimilaritySearch.hpp
Normal file
74
context/EnhancedRAGSimilaritySearch.hpp
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QCache>
|
||||||
|
#include <QHash>
|
||||||
|
#include <QRegularExpression>
|
||||||
|
#include <QString>
|
||||||
|
#include <QStringList>
|
||||||
|
#include <QtGlobal>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
#include <optional>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#if defined(__SSE__) || defined(_M_X64) || defined(_M_AMD64)
|
||||||
|
#include <emmintrin.h>
|
||||||
|
#include <xmmintrin.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "RAGData.hpp"
|
||||||
|
#include "logger/Logger.hpp"
|
||||||
|
|
||||||
|
namespace QodeAssist::Context {
|
||||||
|
|
||||||
|
class EnhancedRAGSimilaritySearch
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
struct SimilarityScore
|
||||||
|
{
|
||||||
|
float semantic_similarity{0.0f};
|
||||||
|
float structural_similarity{0.0f};
|
||||||
|
float combined_score{0.0f};
|
||||||
|
|
||||||
|
SimilarityScore() = default;
|
||||||
|
SimilarityScore(float sem, float str, float comb)
|
||||||
|
: semantic_similarity(sem)
|
||||||
|
, structural_similarity(str)
|
||||||
|
, combined_score(comb)
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
|
||||||
|
static SimilarityScore calculateSimilarity(
|
||||||
|
const RAGVector &v1, const RAGVector &v2, const QString &code1, const QString &code2);
|
||||||
|
|
||||||
|
private:
|
||||||
|
static SimilarityScore calculateSimilarityInternal(
|
||||||
|
const RAGVector &v1, const RAGVector &v2, const QString &code1, const QString &code2);
|
||||||
|
|
||||||
|
static float calculateCosineSimilarity(const RAGVector &v1, const RAGVector &v2);
|
||||||
|
|
||||||
|
#if defined(__SSE__) || defined(_M_X64) || defined(_M_AMD64)
|
||||||
|
static float calculateCosineSimilaritySSE(const RAGVector &v1, const RAGVector &v2);
|
||||||
|
static float horizontalSum(__m128 x);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static float calculateStructuralSimilarity(const QString &code1, const QString &code2);
|
||||||
|
static QStringList extractStructures(const QString &code);
|
||||||
|
static float calculateJaccardSimilarity(const QStringList &set1, const QStringList &set2);
|
||||||
|
|
||||||
|
static const QRegularExpression &getNamespaceRegex();
|
||||||
|
static const QRegularExpression &getClassRegex();
|
||||||
|
static const QRegularExpression &getFunctionRegex();
|
||||||
|
static const QRegularExpression &getTemplateRegex();
|
||||||
|
|
||||||
|
// Cache for similarity scores
|
||||||
|
static QCache<QString, SimilarityScore> &getScoreCache();
|
||||||
|
|
||||||
|
// Cache for extracted structures
|
||||||
|
static QCache<QString, QStringList> &getStructureCache();
|
||||||
|
|
||||||
|
EnhancedRAGSimilaritySearch() = delete; // Prevent instantiation
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace QodeAssist::Context
|
||||||
198
context/FileChunker.cpp
Normal file
198
context/FileChunker.cpp
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
// FileChunker.cpp
|
||||||
|
#include "FileChunker.hpp"
|
||||||
|
|
||||||
|
#include <coreplugin/idocument.h>
|
||||||
|
#include <texteditor/syntaxhighlighter.h>
|
||||||
|
#include <texteditor/textdocument.h>
|
||||||
|
#include <texteditor/texteditorconstants.h>
|
||||||
|
|
||||||
|
#include <QFutureWatcher>
|
||||||
|
#include <QTimer>
|
||||||
|
|
||||||
|
namespace QodeAssist::Context {
|
||||||
|
|
||||||
|
FileChunker::FileChunker(QObject *parent)
|
||||||
|
: QObject(parent)
|
||||||
|
{}
|
||||||
|
|
||||||
|
FileChunker::FileChunker(const ChunkingConfig &config, QObject *parent)
|
||||||
|
: QObject(parent)
|
||||||
|
, m_config(config)
|
||||||
|
{}
|
||||||
|
|
||||||
|
QFuture<QList<FileChunk>> FileChunker::chunkFiles(const QStringList &filePaths)
|
||||||
|
{
|
||||||
|
qDebug() << "\nStarting chunking process for" << filePaths.size() << "files";
|
||||||
|
qDebug() << "Configuration:"
|
||||||
|
<< "\n Max lines per chunk:" << m_config.maxLinesPerChunk
|
||||||
|
<< "\n Overlap lines:" << m_config.overlapLines
|
||||||
|
<< "\n Skip empty lines:" << m_config.skipEmptyLines
|
||||||
|
<< "\n Preserve functions:" << m_config.preserveFunctions
|
||||||
|
<< "\n Preserve classes:" << m_config.preserveClasses
|
||||||
|
<< "\n Batch size:" << m_config.batchSize;
|
||||||
|
|
||||||
|
auto promise = std::make_shared<QPromise<QList<FileChunk>>>();
|
||||||
|
promise->start();
|
||||||
|
|
||||||
|
if (filePaths.isEmpty()) {
|
||||||
|
qDebug() << "No files to process";
|
||||||
|
promise->addResult({});
|
||||||
|
promise->finish();
|
||||||
|
return promise->future();
|
||||||
|
}
|
||||||
|
|
||||||
|
processNextBatch(promise, filePaths, 0);
|
||||||
|
return promise->future();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileChunker::processNextBatch(
|
||||||
|
std::shared_ptr<QPromise<QList<FileChunk>>> promise, const QStringList &files, int startIndex)
|
||||||
|
{
|
||||||
|
if (startIndex >= files.size()) {
|
||||||
|
emit chunkingComplete();
|
||||||
|
promise->finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int endIndex = qMin(startIndex + m_config.batchSize, files.size());
|
||||||
|
QList<FileChunk> batchChunks;
|
||||||
|
|
||||||
|
for (int i = startIndex; i < endIndex; ++i) {
|
||||||
|
try {
|
||||||
|
auto chunks = processFile(files[i]);
|
||||||
|
batchChunks.append(chunks);
|
||||||
|
} catch (const std::exception &e) {
|
||||||
|
emit error(QString("Error processing file %1: %2").arg(files[i], e.what()));
|
||||||
|
}
|
||||||
|
emit progressUpdated(i + 1, files.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
promise->addResult(batchChunks);
|
||||||
|
|
||||||
|
// Планируем обработку следующего батча
|
||||||
|
QTimer::singleShot(0, this, [this, promise, files, endIndex]() {
|
||||||
|
processNextBatch(promise, files, endIndex);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<FileChunk> FileChunker::processFile(const QString &filePath)
|
||||||
|
{
|
||||||
|
qDebug() << "\nProcessing file:" << filePath;
|
||||||
|
|
||||||
|
auto document = new TextEditor::TextDocument;
|
||||||
|
auto filePathObj = Utils::FilePath::fromString(filePath);
|
||||||
|
auto result = document->open(&m_error, filePathObj, filePathObj);
|
||||||
|
if (result != Core::IDocument::OpenResult::Success) {
|
||||||
|
qDebug() << "Failed to open document:" << filePath << "-" << m_error;
|
||||||
|
emit error(QString("Failed to open document: %1 - %2").arg(filePath, m_error));
|
||||||
|
delete document;
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "Document opened successfully. Line count:" << document->document()->blockCount();
|
||||||
|
|
||||||
|
auto chunks = createChunksForDocument(document);
|
||||||
|
qDebug() << "Created" << chunks.size() << "chunks for file";
|
||||||
|
|
||||||
|
delete document;
|
||||||
|
return chunks;
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<FileChunk> FileChunker::createChunksForDocument(TextEditor::TextDocument *document)
|
||||||
|
{
|
||||||
|
QList<FileChunk> chunks;
|
||||||
|
QString filePath = document->filePath().toString();
|
||||||
|
qDebug() << "\nCreating chunks for document:" << filePath << "\nConfiguration:"
|
||||||
|
<< "\n Max lines per chunk:" << m_config.maxLinesPerChunk
|
||||||
|
<< "\n Min lines per chunk:" << m_config.minLinesPerChunk
|
||||||
|
<< "\n Overlap lines:" << m_config.overlapLines;
|
||||||
|
// Если файл меньше минимального размера чанка, создаем один чанк
|
||||||
|
if (document->document()->blockCount() <= m_config.minLinesPerChunk) {
|
||||||
|
FileChunk chunk;
|
||||||
|
chunk.filePath = filePath;
|
||||||
|
chunk.startLine = 0;
|
||||||
|
chunk.endLine = document->document()->blockCount() - 1;
|
||||||
|
chunk.createdAt = QDateTime::currentDateTime();
|
||||||
|
chunk.updatedAt = chunk.createdAt;
|
||||||
|
|
||||||
|
QString content;
|
||||||
|
QTextBlock block = document->document()->firstBlock();
|
||||||
|
while (block.isValid()) {
|
||||||
|
content += block.text() + "\n";
|
||||||
|
block = block.next();
|
||||||
|
}
|
||||||
|
chunk.content = content;
|
||||||
|
|
||||||
|
qDebug() << "File is smaller than minimum chunk size. Creating single chunk:"
|
||||||
|
<< "\n Lines:" << chunk.lineCount() << "\n Content size:" << chunk.content.size()
|
||||||
|
<< "bytes";
|
||||||
|
|
||||||
|
chunks.append(chunk);
|
||||||
|
return chunks;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Для больших файлов создаем чанки фиксированного размера с перекрытием
|
||||||
|
int currentStartLine = 0;
|
||||||
|
int lineCount = 0;
|
||||||
|
QString content;
|
||||||
|
QTextBlock block = document->document()->firstBlock();
|
||||||
|
|
||||||
|
while (block.isValid()) {
|
||||||
|
content += block.text() + "\n";
|
||||||
|
lineCount++;
|
||||||
|
|
||||||
|
// Если достигли размера чанка или это последний блок
|
||||||
|
if (lineCount >= m_config.maxLinesPerChunk || !block.next().isValid()) {
|
||||||
|
FileChunk chunk;
|
||||||
|
chunk.filePath = filePath;
|
||||||
|
chunk.startLine = currentStartLine;
|
||||||
|
chunk.endLine = currentStartLine + lineCount - 1;
|
||||||
|
chunk.content = content;
|
||||||
|
chunk.createdAt = QDateTime::currentDateTime();
|
||||||
|
chunk.updatedAt = chunk.createdAt;
|
||||||
|
|
||||||
|
qDebug() << "Creating chunk:"
|
||||||
|
<< "\n Start line:" << chunk.startLine << "\n End line:" << chunk.endLine
|
||||||
|
<< "\n Lines:" << chunk.lineCount()
|
||||||
|
<< "\n Content size:" << chunk.content.size() << "bytes";
|
||||||
|
|
||||||
|
chunks.append(chunk);
|
||||||
|
|
||||||
|
// Начинаем новый чанк с учетом перекрытия
|
||||||
|
if (block.next().isValid()) {
|
||||||
|
// Отступаем назад на размер перекрытия
|
||||||
|
int overlapLines = qMin(m_config.overlapLines, lineCount);
|
||||||
|
currentStartLine = chunk.endLine - overlapLines + 1;
|
||||||
|
|
||||||
|
// Сбрасываем контент, но добавляем перекрывающиеся строки
|
||||||
|
content.clear();
|
||||||
|
QTextBlock overlapBlock = document->document()->findBlockByLineNumber(
|
||||||
|
currentStartLine);
|
||||||
|
while (overlapBlock.isValid() && overlapBlock.blockNumber() <= chunk.endLine) {
|
||||||
|
content += overlapBlock.text() + "\n";
|
||||||
|
overlapBlock = overlapBlock.next();
|
||||||
|
}
|
||||||
|
lineCount = overlapLines;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
block = block.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "Finished creating chunks for file:" << filePath
|
||||||
|
<< "\nTotal chunks:" << chunks.size();
|
||||||
|
|
||||||
|
return chunks;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileChunker::setConfig(const ChunkingConfig &config)
|
||||||
|
{
|
||||||
|
m_config = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
FileChunker::ChunkingConfig FileChunker::config() const
|
||||||
|
{
|
||||||
|
return m_config;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace QodeAssist::Context
|
||||||
68
context/FileChunker.hpp
Normal file
68
context/FileChunker.hpp
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
// FileChunker.hpp
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <texteditor/textdocument.h>
|
||||||
|
#include <QDateTime>
|
||||||
|
#include <QFuture>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
namespace QodeAssist::Context {
|
||||||
|
|
||||||
|
struct FileChunk
|
||||||
|
{
|
||||||
|
QString filePath; // Path to the source file
|
||||||
|
int startLine; // Starting line of the chunk
|
||||||
|
int endLine; // Ending line of the chunk
|
||||||
|
QDateTime createdAt; // When the chunk was created
|
||||||
|
QDateTime updatedAt; // When the chunk was last updated
|
||||||
|
QString content; // Content of the chunk
|
||||||
|
|
||||||
|
// Helper methods
|
||||||
|
int lineCount() const { return endLine - startLine + 1; }
|
||||||
|
bool isValid() const { return !filePath.isEmpty() && startLine >= 0 && endLine >= startLine; }
|
||||||
|
};
|
||||||
|
|
||||||
|
class FileChunker : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
struct ChunkingConfig
|
||||||
|
{
|
||||||
|
int maxLinesPerChunk = 80;
|
||||||
|
int minLinesPerChunk = 40;
|
||||||
|
int overlapLines = 20;
|
||||||
|
bool skipEmptyLines = true;
|
||||||
|
bool preserveFunctions = true;
|
||||||
|
bool preserveClasses = true;
|
||||||
|
int batchSize = 10;
|
||||||
|
};
|
||||||
|
|
||||||
|
explicit FileChunker(QObject *parent = nullptr);
|
||||||
|
explicit FileChunker(const ChunkingConfig &config, QObject *parent = nullptr);
|
||||||
|
|
||||||
|
// Main chunking method
|
||||||
|
QFuture<QList<FileChunk>> chunkFiles(const QStringList &filePaths);
|
||||||
|
|
||||||
|
// Configuration
|
||||||
|
void setConfig(const ChunkingConfig &config);
|
||||||
|
ChunkingConfig config() const;
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void progressUpdated(int processedFiles, int totalFiles);
|
||||||
|
void chunkingComplete();
|
||||||
|
void error(const QString &errorMessage);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QList<FileChunk> processFile(const QString &filePath);
|
||||||
|
QList<FileChunk> createChunksForDocument(TextEditor::TextDocument *document);
|
||||||
|
void processNextBatch(
|
||||||
|
std::shared_ptr<QPromise<QList<FileChunk>>> promise,
|
||||||
|
const QStringList &files,
|
||||||
|
int startIndex);
|
||||||
|
|
||||||
|
ChunkingConfig m_config;
|
||||||
|
QString m_error;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace QodeAssist::Context
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2025 Petr Mironychev
|
|
||||||
*
|
|
||||||
* This file is part of QodeAssist.
|
|
||||||
*
|
|
||||||
* QodeAssist is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* QodeAssist is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <QObject>
|
|
||||||
#include <QString>
|
|
||||||
|
|
||||||
#include "ContentFile.hpp"
|
|
||||||
#include "IDocumentReader.hpp"
|
|
||||||
#include "ProgrammingLanguage.hpp"
|
|
||||||
|
|
||||||
namespace ProjectExplorer {
|
|
||||||
class Project;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace QodeAssist::Context {
|
|
||||||
|
|
||||||
class IContextManager
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual ~IContextManager() = default;
|
|
||||||
|
|
||||||
virtual QString readFile(const QString &filePath) const = 0;
|
|
||||||
virtual QList<ContentFile> getContentFiles(const QStringList &filePaths) const = 0;
|
|
||||||
virtual QStringList getProjectSourceFiles(ProjectExplorer::Project *project) const = 0;
|
|
||||||
virtual ContentFile createContentFile(const QString &filePath) const = 0;
|
|
||||||
|
|
||||||
virtual ProgrammingLanguage getDocumentLanguage(const DocumentInfo &documentInfo) const = 0;
|
|
||||||
virtual bool isSpecifyCompletion(const DocumentInfo &documentInfo) const = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace QodeAssist::Context
|
|
||||||
@@ -1,274 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2025 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 "IgnoreManager.hpp"
|
|
||||||
|
|
||||||
#include <projectexplorer/project.h>
|
|
||||||
#include <projectexplorer/projectmanager.h>
|
|
||||||
#include <QCoreApplication>
|
|
||||||
#include <QDir>
|
|
||||||
#include <QFile>
|
|
||||||
#include <QFileInfo>
|
|
||||||
#include <QRegularExpression>
|
|
||||||
#include <QTextStream>
|
|
||||||
|
|
||||||
#include "logger/Logger.hpp"
|
|
||||||
|
|
||||||
namespace QodeAssist::Context {
|
|
||||||
|
|
||||||
IgnoreManager::IgnoreManager(QObject *parent)
|
|
||||||
: QObject(parent)
|
|
||||||
{
|
|
||||||
auto projectManager = ProjectExplorer::ProjectManager::instance();
|
|
||||||
if (projectManager) {
|
|
||||||
connect(
|
|
||||||
projectManager,
|
|
||||||
&ProjectExplorer::ProjectManager::projectRemoved,
|
|
||||||
this,
|
|
||||||
&IgnoreManager::removeIgnorePatterns);
|
|
||||||
}
|
|
||||||
|
|
||||||
connect(
|
|
||||||
QCoreApplication::instance(),
|
|
||||||
&QCoreApplication::aboutToQuit,
|
|
||||||
this,
|
|
||||||
&IgnoreManager::cleanupConnections);
|
|
||||||
}
|
|
||||||
|
|
||||||
IgnoreManager::~IgnoreManager()
|
|
||||||
{
|
|
||||||
cleanupConnections();
|
|
||||||
}
|
|
||||||
|
|
||||||
void IgnoreManager::cleanupConnections()
|
|
||||||
{
|
|
||||||
QList<ProjectExplorer::Project *> projects = m_projectConnections.keys();
|
|
||||||
for (ProjectExplorer::Project *project : projects) {
|
|
||||||
if (project) {
|
|
||||||
disconnect(m_projectConnections.take(project));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
m_projectConnections.clear();
|
|
||||||
m_projectIgnorePatterns.clear();
|
|
||||||
m_ignoreCache.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IgnoreManager::shouldIgnore(const QString &filePath, ProjectExplorer::Project *project) const
|
|
||||||
{
|
|
||||||
if (!project)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (!m_projectIgnorePatterns.contains(project)) {
|
|
||||||
const_cast<IgnoreManager *>(this)->reloadIgnorePatterns(project);
|
|
||||||
}
|
|
||||||
|
|
||||||
const QStringList &patterns = m_projectIgnorePatterns[project];
|
|
||||||
if (patterns.isEmpty())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
QDir projectDir(project->projectDirectory().toUrlishString());
|
|
||||||
QString relativePath = projectDir.relativeFilePath(filePath);
|
|
||||||
|
|
||||||
return matchesIgnorePatterns(relativePath, patterns);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IgnoreManager::matchesIgnorePatterns(const QString &path, const QStringList &patterns) const
|
|
||||||
{
|
|
||||||
QString cacheKey = path + ":" + patterns.join("|");
|
|
||||||
if (m_ignoreCache.contains(cacheKey))
|
|
||||||
return m_ignoreCache[cacheKey];
|
|
||||||
|
|
||||||
bool result = isPathExcluded(path, patterns);
|
|
||||||
m_ignoreCache.insert(cacheKey, result);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IgnoreManager::isPathExcluded(const QString &path, const QStringList &patterns) const
|
|
||||||
{
|
|
||||||
bool excluded = false;
|
|
||||||
|
|
||||||
for (const QString &pattern : patterns) {
|
|
||||||
if (pattern.isEmpty() || pattern.startsWith('#'))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
bool isNegative = pattern.startsWith('!');
|
|
||||||
QString actualPattern = isNegative ? pattern.mid(1) : pattern;
|
|
||||||
|
|
||||||
bool matches = matchPathWithPattern(path, actualPattern);
|
|
||||||
|
|
||||||
if (matches) {
|
|
||||||
excluded = !isNegative;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return excluded;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IgnoreManager::matchPathWithPattern(const QString &path, const QString &pattern) const
|
|
||||||
{
|
|
||||||
QString adjustedPattern = pattern.trimmed();
|
|
||||||
|
|
||||||
bool matchFromRoot = adjustedPattern.startsWith('/');
|
|
||||||
if (matchFromRoot)
|
|
||||||
adjustedPattern = adjustedPattern.mid(1);
|
|
||||||
|
|
||||||
bool matchDirOnly = adjustedPattern.endsWith('/');
|
|
||||||
if (matchDirOnly)
|
|
||||||
adjustedPattern.chop(1);
|
|
||||||
|
|
||||||
QString regexPattern = QRegularExpression::escape(adjustedPattern);
|
|
||||||
|
|
||||||
regexPattern.replace("\\*\\*", ".*");
|
|
||||||
|
|
||||||
regexPattern.replace("\\*", "[^/]*");
|
|
||||||
|
|
||||||
regexPattern.replace("\\?", ".");
|
|
||||||
|
|
||||||
if (matchFromRoot)
|
|
||||||
regexPattern = QString("^%1").arg(regexPattern);
|
|
||||||
else
|
|
||||||
regexPattern = QString("(^|/)%1").arg(regexPattern);
|
|
||||||
|
|
||||||
if (matchDirOnly)
|
|
||||||
regexPattern = QString("%1$").arg(regexPattern);
|
|
||||||
else
|
|
||||||
regexPattern = QString("%1($|/)").arg(regexPattern);
|
|
||||||
|
|
||||||
QRegularExpression regex(regexPattern);
|
|
||||||
QRegularExpressionMatch match = regex.match(path);
|
|
||||||
return match.hasMatch();
|
|
||||||
}
|
|
||||||
|
|
||||||
QStringList IgnoreManager::loadIgnorePatterns(ProjectExplorer::Project *project)
|
|
||||||
{
|
|
||||||
QStringList patterns;
|
|
||||||
if (!project)
|
|
||||||
return patterns;
|
|
||||||
|
|
||||||
QString ignoreFile = ignoreFilePath(project);
|
|
||||||
if (ignoreFile.isEmpty() || !QFile::exists(ignoreFile)) {
|
|
||||||
// LOG_MESSAGE(
|
|
||||||
// QString("No .qodeassistignore file found for project: %1").arg(project->displayName()));
|
|
||||||
return patterns;
|
|
||||||
}
|
|
||||||
|
|
||||||
QFile file(ignoreFile);
|
|
||||||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
|
||||||
LOG_MESSAGE(QString("Could not open .qodeassistignore file: %1").arg(ignoreFile));
|
|
||||||
return patterns;
|
|
||||||
}
|
|
||||||
|
|
||||||
QTextStream in(&file);
|
|
||||||
while (!in.atEnd()) {
|
|
||||||
QString line = in.readLine().trimmed();
|
|
||||||
if (!line.isEmpty() && !line.startsWith('#'))
|
|
||||||
patterns << line;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_MESSAGE(QString("Successfully loaded .qodeassistignore file: %1 with %2 patterns")
|
|
||||||
.arg(ignoreFile)
|
|
||||||
.arg(patterns.size()));
|
|
||||||
|
|
||||||
return patterns;
|
|
||||||
}
|
|
||||||
|
|
||||||
void IgnoreManager::reloadIgnorePatterns(ProjectExplorer::Project *project)
|
|
||||||
{
|
|
||||||
if (!project)
|
|
||||||
return;
|
|
||||||
|
|
||||||
QStringList patterns = loadIgnorePatterns(project);
|
|
||||||
m_projectIgnorePatterns[project] = patterns;
|
|
||||||
|
|
||||||
QStringList keysToRemove;
|
|
||||||
QString projectPath = project->projectDirectory().toUrlishString();
|
|
||||||
for (auto it = m_ignoreCache.begin(); it != m_ignoreCache.end(); ++it) {
|
|
||||||
if (it.key().contains(projectPath))
|
|
||||||
keysToRemove << it.key();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const QString &key : keysToRemove)
|
|
||||||
m_ignoreCache.remove(key);
|
|
||||||
|
|
||||||
if (!m_projectConnections.contains(project)) {
|
|
||||||
QPointer<ProjectExplorer::Project> projectPtr(project);
|
|
||||||
auto connection = connect(project, &QObject::destroyed, this, [this, projectPtr]() {
|
|
||||||
if (projectPtr) {
|
|
||||||
m_projectIgnorePatterns.remove(projectPtr);
|
|
||||||
m_projectConnections.remove(projectPtr);
|
|
||||||
|
|
||||||
QStringList keysToRemove;
|
|
||||||
for (auto it = m_ignoreCache.begin(); it != m_ignoreCache.end(); ++it) {
|
|
||||||
if (it.key().contains(projectPtr->projectDirectory().toUrlishString()))
|
|
||||||
keysToRemove << it.key();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const QString &key : keysToRemove)
|
|
||||||
m_ignoreCache.remove(key);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
m_projectConnections[project] = connection;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void IgnoreManager::removeIgnorePatterns(ProjectExplorer::Project *project)
|
|
||||||
{
|
|
||||||
m_projectIgnorePatterns.remove(project);
|
|
||||||
|
|
||||||
QStringList keysToRemove;
|
|
||||||
for (auto it = m_ignoreCache.begin(); it != m_ignoreCache.end(); ++it) {
|
|
||||||
if (it.key().contains(project->projectDirectory().toUrlishString()))
|
|
||||||
keysToRemove << it.key();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const QString &key : keysToRemove)
|
|
||||||
m_ignoreCache.remove(key);
|
|
||||||
|
|
||||||
if (m_projectConnections.contains(project)) {
|
|
||||||
disconnect(m_projectConnections[project]);
|
|
||||||
m_projectConnections.remove(project);
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_MESSAGE(QString("Removed ignore patterns for project: %1").arg(project->displayName()));
|
|
||||||
}
|
|
||||||
|
|
||||||
void IgnoreManager::reloadAllPatterns()
|
|
||||||
{
|
|
||||||
QList<ProjectExplorer::Project *> projects = m_projectIgnorePatterns.keys();
|
|
||||||
|
|
||||||
for (ProjectExplorer::Project *project : projects) {
|
|
||||||
if (project) {
|
|
||||||
reloadIgnorePatterns(project);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
m_ignoreCache.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
QString IgnoreManager::ignoreFilePath(ProjectExplorer::Project *project) const
|
|
||||||
{
|
|
||||||
if (!project) {
|
|
||||||
return QString();
|
|
||||||
}
|
|
||||||
|
|
||||||
return project->projectDirectory().toUrlishString() + "/.qodeassistignore";
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace QodeAssist::Context
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2025 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 <QHash>
|
|
||||||
#include <QObject>
|
|
||||||
#include <QPointer>
|
|
||||||
#include <QStringList>
|
|
||||||
|
|
||||||
namespace ProjectExplorer {
|
|
||||||
class Project;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace QodeAssist::Context {
|
|
||||||
|
|
||||||
class IgnoreManager : public QObject
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit IgnoreManager(QObject *parent = nullptr);
|
|
||||||
~IgnoreManager() override;
|
|
||||||
|
|
||||||
bool shouldIgnore(const QString &filePath, ProjectExplorer::Project *project = nullptr) const;
|
|
||||||
void reloadIgnorePatterns(ProjectExplorer::Project *project);
|
|
||||||
void removeIgnorePatterns(ProjectExplorer::Project *project);
|
|
||||||
|
|
||||||
void reloadAllPatterns();
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
void cleanupConnections();
|
|
||||||
|
|
||||||
private:
|
|
||||||
bool matchesIgnorePatterns(const QString &path, const QStringList &patterns) const;
|
|
||||||
bool isPathExcluded(const QString &path, const QStringList &patterns) const;
|
|
||||||
bool matchPathWithPattern(const QString &path, const QString &pattern) const;
|
|
||||||
QStringList loadIgnorePatterns(ProjectExplorer::Project *project);
|
|
||||||
QString ignoreFilePath(ProjectExplorer::Project *project) const;
|
|
||||||
|
|
||||||
QHash<ProjectExplorer::Project *, QStringList> m_projectIgnorePatterns;
|
|
||||||
mutable QHash<QString, bool> m_ignoreCache;
|
|
||||||
QHash<ProjectExplorer::Project *, QMetaObject::Connection> m_projectConnections;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace QodeAssist::Context
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2024-2025 Petr Mironychev
|
* Copyright (C) 2024 Petr Mironychev
|
||||||
*
|
*
|
||||||
* This file is part of QodeAssist.
|
* This file is part of QodeAssist.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2024-2025 Petr Mironychev
|
* Copyright (C) 2024 Petr Mironychev
|
||||||
*
|
*
|
||||||
* This file is part of QodeAssist.
|
* This file is part of QodeAssist.
|
||||||
*
|
*
|
||||||
@@ -24,8 +24,8 @@
|
|||||||
namespace QodeAssist::Context {
|
namespace QodeAssist::Context {
|
||||||
|
|
||||||
enum class ProgrammingLanguage {
|
enum class ProgrammingLanguage {
|
||||||
QML, // QML/JavaScript
|
QML,
|
||||||
Cpp, // C/C++
|
Cpp,
|
||||||
Python,
|
Python,
|
||||||
Unknown,
|
Unknown,
|
||||||
};
|
};
|
||||||
|
|||||||
7
context/RAGData.hpp
Normal file
7
context/RAGData.hpp
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace QodeAssist::Context {
|
||||||
|
using RAGVector = std::vector<float>;
|
||||||
|
}
|
||||||
443
context/RAGManager.cpp
Normal file
443
context/RAGManager.cpp
Normal file
@@ -0,0 +1,443 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2024 Petr Mironychev
|
||||||
|
*
|
||||||
|
* This file is part of QodeAssist.
|
||||||
|
*
|
||||||
|
* QodeAssist is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* QodeAssist is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "RAGManager.hpp"
|
||||||
|
#include "EnhancedRAGSimilaritySearch.hpp"
|
||||||
|
#include "RAGPreprocessor.hpp"
|
||||||
|
#include "RAGSimilaritySearch.hpp"
|
||||||
|
#include "logger/Logger.hpp"
|
||||||
|
|
||||||
|
#include <coreplugin/icore.h>
|
||||||
|
#include <projectexplorer/project.h>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QtConcurrent>
|
||||||
|
|
||||||
|
namespace QodeAssist::Context {
|
||||||
|
|
||||||
|
RAGManager &RAGManager::instance()
|
||||||
|
{
|
||||||
|
static RAGManager manager;
|
||||||
|
return manager;
|
||||||
|
}
|
||||||
|
|
||||||
|
RAGManager::RAGManager(QObject *parent)
|
||||||
|
: QObject(parent)
|
||||||
|
, m_vectorizer(std::make_unique<RAGVectorizer>())
|
||||||
|
{}
|
||||||
|
|
||||||
|
RAGManager::~RAGManager() {}
|
||||||
|
|
||||||
|
QString RAGManager::getStoragePath(ProjectExplorer::Project *project) const
|
||||||
|
{
|
||||||
|
return QString("%1/qodeassist/%2/rag/vectors.db")
|
||||||
|
.arg(Core::ICore::userResourcePath().toString(), project->displayName());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<QString> RAGManager::loadFileContent(const QString &filePath)
|
||||||
|
{
|
||||||
|
QFile file(filePath);
|
||||||
|
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||||
|
qDebug() << "ERROR: Failed to open file for reading:" << filePath
|
||||||
|
<< "Error:" << file.errorString();
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
QFileInfo fileInfo(filePath);
|
||||||
|
qDebug() << "Loading content from file:" << fileInfo.fileName() << "Size:" << fileInfo.size()
|
||||||
|
<< "bytes";
|
||||||
|
|
||||||
|
QString content = QString::fromUtf8(file.readAll());
|
||||||
|
if (content.isEmpty()) {
|
||||||
|
qDebug() << "WARNING: Empty content read from file:" << filePath;
|
||||||
|
}
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RAGManager::ensureStorageForProject(ProjectExplorer::Project *project) const
|
||||||
|
{
|
||||||
|
qDebug() << "Ensuring storage for project:" << project->displayName();
|
||||||
|
|
||||||
|
if (m_currentProject == project && m_currentStorage) {
|
||||||
|
qDebug() << "Using existing storage";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "Creating new storage";
|
||||||
|
m_currentStorage.reset();
|
||||||
|
m_currentProject = project;
|
||||||
|
|
||||||
|
if (project) {
|
||||||
|
QString storagePath = getStoragePath(project);
|
||||||
|
qDebug() << "Storage path:" << storagePath;
|
||||||
|
|
||||||
|
StorageOptions options;
|
||||||
|
m_currentStorage = std::make_unique<RAGStorage>(storagePath, options);
|
||||||
|
|
||||||
|
qDebug() << "Initializing storage...";
|
||||||
|
if (!m_currentStorage->init()) {
|
||||||
|
qDebug() << "Failed to initialize storage";
|
||||||
|
m_currentStorage.reset();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
qDebug() << "Storage initialized successfully";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QFuture<void> RAGManager::processProjectFiles(
|
||||||
|
ProjectExplorer::Project *project,
|
||||||
|
const QStringList &filePaths,
|
||||||
|
const FileChunker::ChunkingConfig &config)
|
||||||
|
{
|
||||||
|
qDebug() << "\nStarting batch processing of" << filePaths.size()
|
||||||
|
<< "files for project:" << project->displayName();
|
||||||
|
|
||||||
|
auto promise = std::make_shared<QPromise<void>>();
|
||||||
|
promise->start();
|
||||||
|
|
||||||
|
qDebug() << "Initializing storage...";
|
||||||
|
ensureStorageForProject(project);
|
||||||
|
|
||||||
|
if (!m_currentStorage) {
|
||||||
|
qDebug() << "Failed to initialize storage for project:" << project->displayName();
|
||||||
|
promise->finish();
|
||||||
|
return promise->future();
|
||||||
|
}
|
||||||
|
qDebug() << "Storage initialized successfully";
|
||||||
|
|
||||||
|
qDebug() << "Checking files for processing...";
|
||||||
|
QSet<QString> uniqueFiles;
|
||||||
|
for (const QString &filePath : filePaths) {
|
||||||
|
qDebug() << "Checking file:" << filePath;
|
||||||
|
if (isFileStorageOutdated(project, filePath)) {
|
||||||
|
qDebug() << "File needs processing:" << filePath;
|
||||||
|
uniqueFiles.insert(filePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList filesToProcess = uniqueFiles.values();
|
||||||
|
|
||||||
|
if (filesToProcess.isEmpty()) {
|
||||||
|
qDebug() << "No files need processing";
|
||||||
|
emit vectorizationFinished();
|
||||||
|
promise->finish();
|
||||||
|
return promise->future();
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "Starting to process" << filesToProcess.size() << "files";
|
||||||
|
const int batchSize = 10;
|
||||||
|
processNextFileBatch(promise, project, filesToProcess, config, 0, batchSize);
|
||||||
|
|
||||||
|
return promise->future();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RAGManager::processNextFileBatch(
|
||||||
|
std::shared_ptr<QPromise<void>> promise,
|
||||||
|
ProjectExplorer::Project *project,
|
||||||
|
const QStringList &files,
|
||||||
|
const FileChunker::ChunkingConfig &config,
|
||||||
|
int startIndex,
|
||||||
|
int batchSize)
|
||||||
|
{
|
||||||
|
if (startIndex >= files.size()) {
|
||||||
|
qDebug() << "All batches processed successfully";
|
||||||
|
emit vectorizationFinished();
|
||||||
|
promise->finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int endIndex = qMin(startIndex + batchSize, files.size());
|
||||||
|
auto currentBatch = files.mid(startIndex, endIndex - startIndex);
|
||||||
|
|
||||||
|
qDebug() << "\nProcessing batch" << (startIndex / batchSize + 1) << "(" << currentBatch.size()
|
||||||
|
<< "files)"
|
||||||
|
<< "\nProgress:" << startIndex << "to" << endIndex << "of" << files.size();
|
||||||
|
|
||||||
|
for (const QString &filePath : currentBatch) {
|
||||||
|
qDebug() << "Starting processing file:" << filePath;
|
||||||
|
auto future = processFileWithChunks(project, filePath, config);
|
||||||
|
auto watcher = new QFutureWatcher<bool>;
|
||||||
|
watcher->setFuture(future);
|
||||||
|
|
||||||
|
connect(
|
||||||
|
watcher,
|
||||||
|
&QFutureWatcher<bool>::finished,
|
||||||
|
this,
|
||||||
|
[this,
|
||||||
|
watcher,
|
||||||
|
promise,
|
||||||
|
project,
|
||||||
|
files,
|
||||||
|
startIndex,
|
||||||
|
endIndex,
|
||||||
|
batchSize,
|
||||||
|
config,
|
||||||
|
filePath]() {
|
||||||
|
bool success = watcher->result();
|
||||||
|
qDebug() << "File processed:" << filePath << "success:" << success;
|
||||||
|
|
||||||
|
bool isLastFileInBatch = (filePath == files[endIndex - 1]);
|
||||||
|
if (isLastFileInBatch) {
|
||||||
|
qDebug() << "Batch completed, moving to next batch";
|
||||||
|
emit vectorizationProgress(endIndex, files.size());
|
||||||
|
processNextFileBatch(promise, project, files, config, endIndex, batchSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
watcher->deleteLater();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QFuture<bool> RAGManager::processFileWithChunks(
|
||||||
|
ProjectExplorer::Project *project,
|
||||||
|
const QString &filePath,
|
||||||
|
const FileChunker::ChunkingConfig &config)
|
||||||
|
{
|
||||||
|
auto promise = std::make_shared<QPromise<bool>>();
|
||||||
|
promise->start();
|
||||||
|
|
||||||
|
ensureStorageForProject(project);
|
||||||
|
if (!m_currentStorage) {
|
||||||
|
qDebug() << "Storage not initialized for file:" << filePath;
|
||||||
|
promise->addResult(false);
|
||||||
|
promise->finish();
|
||||||
|
return promise->future();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto fileContent = loadFileContent(filePath);
|
||||||
|
if (!fileContent) {
|
||||||
|
qDebug() << "Failed to load content for file:" << filePath;
|
||||||
|
promise->addResult(false);
|
||||||
|
promise->finish();
|
||||||
|
return promise->future();
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "Creating chunks for file:" << filePath;
|
||||||
|
auto chunksFuture = m_chunker.chunkFiles({filePath});
|
||||||
|
auto chunks = chunksFuture.result();
|
||||||
|
|
||||||
|
if (chunks.isEmpty()) {
|
||||||
|
qDebug() << "No chunks created for file:" << filePath;
|
||||||
|
promise->addResult(false);
|
||||||
|
promise->finish();
|
||||||
|
return promise->future();
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "Created" << chunks.size() << "chunks for file:" << filePath;
|
||||||
|
|
||||||
|
// Преобразуем FileChunk в FileChunkData
|
||||||
|
QList<FileChunkData> chunkData;
|
||||||
|
for (const auto &chunk : chunks) {
|
||||||
|
FileChunkData data;
|
||||||
|
data.filePath = chunk.filePath;
|
||||||
|
data.startLine = chunk.startLine;
|
||||||
|
data.endLine = chunk.endLine;
|
||||||
|
data.content = chunk.content;
|
||||||
|
chunkData.append(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "Deleting old chunks for file:" << filePath;
|
||||||
|
m_currentStorage->deleteChunksForFile(filePath);
|
||||||
|
|
||||||
|
auto vectorizeFuture = vectorizeAndStoreChunks(filePath, chunkData);
|
||||||
|
auto watcher = new QFutureWatcher<void>;
|
||||||
|
watcher->setFuture(vectorizeFuture);
|
||||||
|
|
||||||
|
connect(watcher, &QFutureWatcher<void>::finished, this, [promise, watcher, filePath]() {
|
||||||
|
qDebug() << "Completed processing file:" << filePath;
|
||||||
|
promise->addResult(true);
|
||||||
|
promise->finish();
|
||||||
|
watcher->deleteLater();
|
||||||
|
});
|
||||||
|
|
||||||
|
return promise->future();
|
||||||
|
}
|
||||||
|
|
||||||
|
QFuture<void> RAGManager::vectorizeAndStoreChunks(
|
||||||
|
const QString &filePath, const QList<FileChunkData> &chunks)
|
||||||
|
{
|
||||||
|
qDebug() << "Vectorizing and storing" << chunks.size() << "chunks for file:" << filePath;
|
||||||
|
|
||||||
|
auto promise = std::make_shared<QPromise<void>>();
|
||||||
|
promise->start();
|
||||||
|
|
||||||
|
// Обрабатываем чанки последовательно
|
||||||
|
processNextChunk(promise, chunks, 0);
|
||||||
|
|
||||||
|
return promise->future();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RAGManager::processNextChunk(
|
||||||
|
std::shared_ptr<QPromise<void>> promise, const QList<FileChunkData> &chunks, int currentIndex)
|
||||||
|
{
|
||||||
|
if (currentIndex >= chunks.size()) {
|
||||||
|
promise->finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto &chunk = chunks[currentIndex];
|
||||||
|
QString processedContent = RAGPreprocessor::preprocessCode(chunk.content);
|
||||||
|
qDebug() << "Processing chunk" << currentIndex + 1 << "of" << chunks.size();
|
||||||
|
|
||||||
|
auto vectorFuture = m_vectorizer->vectorizeText(processedContent);
|
||||||
|
auto watcher = new QFutureWatcher<RAGVector>;
|
||||||
|
watcher->setFuture(vectorFuture);
|
||||||
|
|
||||||
|
connect(
|
||||||
|
watcher,
|
||||||
|
&QFutureWatcher<RAGVector>::finished,
|
||||||
|
this,
|
||||||
|
[this, watcher, promise, chunks, currentIndex, chunk]() {
|
||||||
|
auto vector = watcher->result();
|
||||||
|
|
||||||
|
if (!vector.empty()) {
|
||||||
|
qDebug() << "Storing vector and chunk for file:" << chunk.filePath;
|
||||||
|
bool vectorStored = m_currentStorage->storeVector(chunk.filePath, vector);
|
||||||
|
bool chunkStored = m_currentStorage->storeChunk(chunk);
|
||||||
|
qDebug() << "Storage results - Vector:" << vectorStored << "Chunk:" << chunkStored;
|
||||||
|
} else {
|
||||||
|
qDebug() << "Failed to vectorize chunk content";
|
||||||
|
}
|
||||||
|
|
||||||
|
processNextChunk(promise, chunks, currentIndex + 1);
|
||||||
|
|
||||||
|
watcher->deleteLater();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QFuture<QList<RAGManager::ChunkSearchResult>> RAGManager::findRelevantChunks(
|
||||||
|
const QString &query, ProjectExplorer::Project *project, int topK)
|
||||||
|
{
|
||||||
|
auto promise = std::make_shared<QPromise<QList<ChunkSearchResult>>>();
|
||||||
|
promise->start();
|
||||||
|
|
||||||
|
ensureStorageForProject(project);
|
||||||
|
if (!m_currentStorage) {
|
||||||
|
qDebug() << "Storage not initialized for project:" << project->displayName();
|
||||||
|
promise->addResult({});
|
||||||
|
promise->finish();
|
||||||
|
return promise->future();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString processedQuery = RAGPreprocessor::preprocessCode(query);
|
||||||
|
|
||||||
|
auto vectorFuture = m_vectorizer->vectorizeText(processedQuery);
|
||||||
|
vectorFuture.then([this, promise, project, processedQuery, topK](const RAGVector &queryVector) {
|
||||||
|
if (queryVector.empty()) {
|
||||||
|
qDebug() << "Failed to vectorize query";
|
||||||
|
promise->addResult({});
|
||||||
|
promise->finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto files = m_currentStorage->getFilesWithChunks();
|
||||||
|
QList<FileChunkData> allChunks;
|
||||||
|
|
||||||
|
for (const auto &filePath : files) {
|
||||||
|
auto fileChunks = m_currentStorage->getChunksForFile(filePath);
|
||||||
|
allChunks.append(fileChunks);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto results = rankChunks(queryVector, processedQuery, allChunks);
|
||||||
|
|
||||||
|
if (results.size() > topK) {
|
||||||
|
results = results.mid(0, topK);
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "Found" << results.size() << "relevant chunks";
|
||||||
|
promise->addResult(results);
|
||||||
|
promise->finish();
|
||||||
|
|
||||||
|
closeStorage();
|
||||||
|
});
|
||||||
|
|
||||||
|
return promise->future();
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<RAGManager::ChunkSearchResult> RAGManager::rankChunks(
|
||||||
|
const RAGVector &queryVector, const QString &queryText, const QList<FileChunkData> &chunks)
|
||||||
|
{
|
||||||
|
QList<ChunkSearchResult> results;
|
||||||
|
results.reserve(chunks.size());
|
||||||
|
|
||||||
|
for (const auto &chunk : chunks) {
|
||||||
|
auto chunkVector = m_currentStorage->getVector(chunk.filePath);
|
||||||
|
if (!chunkVector.has_value()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString processedChunk = RAGPreprocessor::preprocessCode(chunk.content);
|
||||||
|
|
||||||
|
auto similarity = EnhancedRAGSimilaritySearch::calculateSimilarity(
|
||||||
|
queryVector, chunkVector.value(), queryText, processedChunk);
|
||||||
|
|
||||||
|
results.append(ChunkSearchResult{
|
||||||
|
chunk.filePath,
|
||||||
|
chunk.startLine,
|
||||||
|
chunk.endLine,
|
||||||
|
chunk.content,
|
||||||
|
similarity.semantic_similarity,
|
||||||
|
similarity.structural_similarity,
|
||||||
|
similarity.combined_score});
|
||||||
|
}
|
||||||
|
|
||||||
|
std::sort(results.begin(), results.end());
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList RAGManager::getStoredFiles(ProjectExplorer::Project *project) const
|
||||||
|
{
|
||||||
|
ensureStorageForProject(project);
|
||||||
|
if (!m_currentStorage) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return m_currentStorage->getAllFiles();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RAGManager::isFileStorageOutdated(
|
||||||
|
ProjectExplorer::Project *project, const QString &filePath) const
|
||||||
|
{
|
||||||
|
ensureStorageForProject(project);
|
||||||
|
if (!m_currentStorage) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return m_currentStorage->needsUpdate(filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<RAGVector> RAGManager::loadVectorFromStorage(
|
||||||
|
ProjectExplorer::Project *project, const QString &filePath)
|
||||||
|
{
|
||||||
|
ensureStorageForProject(project);
|
||||||
|
if (!m_currentStorage) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
return m_currentStorage->getVector(filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RAGManager::closeStorage()
|
||||||
|
{
|
||||||
|
qDebug() << "Closing storage...";
|
||||||
|
if (m_currentStorage) {
|
||||||
|
m_currentStorage.reset();
|
||||||
|
m_currentProject = nullptr;
|
||||||
|
qDebug() << "Storage closed";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace QodeAssist::Context
|
||||||
119
context/RAGManager.hpp
Normal file
119
context/RAGManager.hpp
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2024 Petr Mironychev
|
||||||
|
*
|
||||||
|
* This file is part of QodeAssist.
|
||||||
|
*
|
||||||
|
* QodeAssist is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* QodeAssist is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
|
#include <QFuture>
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
|
#include "FileChunker.hpp"
|
||||||
|
#include "RAGData.hpp"
|
||||||
|
#include "RAGStorage.hpp"
|
||||||
|
#include "RAGVectorizer.hpp"
|
||||||
|
|
||||||
|
namespace ProjectExplorer {
|
||||||
|
class Project;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace QodeAssist::Context {
|
||||||
|
|
||||||
|
class RAGManager : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
struct ChunkSearchResult
|
||||||
|
{
|
||||||
|
QString filePath;
|
||||||
|
int startLine;
|
||||||
|
int endLine;
|
||||||
|
QString content;
|
||||||
|
float semanticScore;
|
||||||
|
float structuralScore;
|
||||||
|
float combinedScore;
|
||||||
|
|
||||||
|
bool operator<(const ChunkSearchResult &other) const
|
||||||
|
{
|
||||||
|
return combinedScore > other.combinedScore;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static RAGManager &instance();
|
||||||
|
|
||||||
|
QFuture<void> processProjectFiles(
|
||||||
|
ProjectExplorer::Project *project,
|
||||||
|
const QStringList &filePaths,
|
||||||
|
const FileChunker::ChunkingConfig &config = FileChunker::ChunkingConfig());
|
||||||
|
|
||||||
|
QFuture<QList<ChunkSearchResult>> findRelevantChunks(
|
||||||
|
const QString &query, ProjectExplorer::Project *project, int topK = 5);
|
||||||
|
|
||||||
|
QStringList getStoredFiles(ProjectExplorer::Project *project) const;
|
||||||
|
bool isFileStorageOutdated(ProjectExplorer::Project *project, const QString &filePath) const;
|
||||||
|
|
||||||
|
void processNextChunk(
|
||||||
|
std::shared_ptr<QPromise<void>> promise,
|
||||||
|
const QList<FileChunkData> &chunks,
|
||||||
|
int currentIndex);
|
||||||
|
void closeStorage();
|
||||||
|
signals:
|
||||||
|
void vectorizationProgress(int processed, int total);
|
||||||
|
void vectorizationFinished();
|
||||||
|
|
||||||
|
private:
|
||||||
|
explicit RAGManager(QObject *parent = nullptr);
|
||||||
|
~RAGManager();
|
||||||
|
RAGManager(const RAGManager &) = delete;
|
||||||
|
RAGManager &operator=(const RAGManager &) = delete;
|
||||||
|
|
||||||
|
QString getStoragePath(ProjectExplorer::Project *project) const;
|
||||||
|
void ensureStorageForProject(ProjectExplorer::Project *project) const;
|
||||||
|
std::optional<QString> loadFileContent(const QString &filePath);
|
||||||
|
std::optional<RAGVector> loadVectorFromStorage(
|
||||||
|
ProjectExplorer::Project *project, const QString &filePath);
|
||||||
|
|
||||||
|
void processNextFileBatch(
|
||||||
|
std::shared_ptr<QPromise<void>> promise,
|
||||||
|
ProjectExplorer::Project *project,
|
||||||
|
const QStringList &files,
|
||||||
|
const FileChunker::ChunkingConfig &config,
|
||||||
|
int startIndex,
|
||||||
|
int batchSize);
|
||||||
|
|
||||||
|
QFuture<bool> processFileWithChunks(
|
||||||
|
ProjectExplorer::Project *project,
|
||||||
|
const QString &filePath,
|
||||||
|
const FileChunker::ChunkingConfig &config);
|
||||||
|
|
||||||
|
QFuture<void> vectorizeAndStoreChunks(
|
||||||
|
const QString &filePath, const QList<FileChunkData> &chunks);
|
||||||
|
|
||||||
|
QList<ChunkSearchResult> rankChunks(
|
||||||
|
const RAGVector &queryVector, const QString &queryText, const QList<FileChunkData> &chunks);
|
||||||
|
|
||||||
|
private:
|
||||||
|
mutable std::unique_ptr<RAGVectorizer> m_vectorizer;
|
||||||
|
mutable std::unique_ptr<RAGStorage> m_currentStorage;
|
||||||
|
mutable ProjectExplorer::Project *m_currentProject{nullptr};
|
||||||
|
FileChunker m_chunker;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace QodeAssist::Context
|
||||||
2
context/RAGPreprocessor.cpp
Normal file
2
context/RAGPreprocessor.cpp
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
#include "RAGPreprocessor.hpp"
|
||||||
|
|
||||||
64
context/RAGPreprocessor.hpp
Normal file
64
context/RAGPreprocessor.hpp
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
#include <QRegularExpression>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
#include "Logger.hpp"
|
||||||
|
|
||||||
|
namespace QodeAssist::Context {
|
||||||
|
|
||||||
|
class RAGPreprocessor
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static const QRegularExpression &getLicenseRegex()
|
||||||
|
{
|
||||||
|
static const QRegularExpression regex(
|
||||||
|
R"((/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)|//[^\n]*(?:\n|$))",
|
||||||
|
QRegularExpression::MultilineOption);
|
||||||
|
return regex;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const QRegularExpression &getClassRegex()
|
||||||
|
{
|
||||||
|
static const QRegularExpression regex(
|
||||||
|
R"((?:template\s*<[^>]*>\s*)?(?:class|struct)\s+(\w+)\s*(?:final\s*)?(?::\s*(?:public|protected|private)\s+\w+(?:\s*,\s*(?:public|protected|private)\s+\w+)*\s*)?{)");
|
||||||
|
return regex;
|
||||||
|
}
|
||||||
|
|
||||||
|
static QString preprocessCode(const QString &code)
|
||||||
|
{
|
||||||
|
if (code.isEmpty()) {
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
QStringList lines = code.split('\n', Qt::SkipEmptyParts);
|
||||||
|
return processLines(lines);
|
||||||
|
} catch (const std::exception &e) {
|
||||||
|
LOG_MESSAGE(QString("Error preprocessing code: %1").arg(e.what()));
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static QString processLines(const QStringList &lines)
|
||||||
|
{
|
||||||
|
const int estimatedAvgLength = 80;
|
||||||
|
QString result;
|
||||||
|
result.reserve(lines.size() * estimatedAvgLength);
|
||||||
|
|
||||||
|
for (const QString &line : lines) {
|
||||||
|
const QString trimmed = line.trimmed();
|
||||||
|
if (!trimmed.isEmpty()) {
|
||||||
|
result += trimmed;
|
||||||
|
result += QLatin1Char('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.endsWith('\n')) {
|
||||||
|
result.chop(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace QodeAssist::Context
|
||||||
67
context/RAGSimilaritySearch.cpp
Normal file
67
context/RAGSimilaritySearch.cpp
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2024 Petr Mironychev
|
||||||
|
*
|
||||||
|
* This file is part of QodeAssist.
|
||||||
|
*
|
||||||
|
* QodeAssist is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* QodeAssist is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "RAGSimilaritySearch.hpp"
|
||||||
|
#include "logger/Logger.hpp"
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
namespace QodeAssist::Context {
|
||||||
|
|
||||||
|
float RAGSimilaritySearch::l2Distance(const RAGVector &v1, const RAGVector &v2)
|
||||||
|
{
|
||||||
|
if (v1.size() != v2.size()) {
|
||||||
|
LOG_MESSAGE(QString("Vector size mismatch: %1 vs %2").arg(v1.size()).arg(v2.size()));
|
||||||
|
return std::numeric_limits<float>::max();
|
||||||
|
}
|
||||||
|
|
||||||
|
float sum = 0.0f;
|
||||||
|
for (size_t i = 0; i < v1.size(); ++i) {
|
||||||
|
float diff = v1[i] - v2[i];
|
||||||
|
sum += diff * diff;
|
||||||
|
}
|
||||||
|
return std::sqrt(sum);
|
||||||
|
}
|
||||||
|
|
||||||
|
float RAGSimilaritySearch::cosineSimilarity(const RAGVector &v1, const RAGVector &v2)
|
||||||
|
{
|
||||||
|
if (v1.size() != v2.size()) {
|
||||||
|
LOG_MESSAGE(QString("Vector size mismatch: %1 vs %2").arg(v1.size()).arg(v2.size()));
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
float dotProduct = 0.0f;
|
||||||
|
float norm1 = 0.0f;
|
||||||
|
float norm2 = 0.0f;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < v1.size(); ++i) {
|
||||||
|
dotProduct += v1[i] * v2[i];
|
||||||
|
norm1 += v1[i] * v1[i];
|
||||||
|
norm2 += v2[i] * v2[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
norm1 = std::sqrt(norm1);
|
||||||
|
norm2 = std::sqrt(norm2);
|
||||||
|
|
||||||
|
if (norm1 == 0.0f || norm2 == 0.0f)
|
||||||
|
return 0.0f;
|
||||||
|
return dotProduct / (norm1 * norm2);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace QodeAssist::Context
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2025 Povilas Kanapickas
|
* Copyright (C) 2024 Petr Mironychev
|
||||||
*
|
*
|
||||||
* This file is part of QodeAssist.
|
* This file is part of QodeAssist.
|
||||||
*
|
*
|
||||||
@@ -19,23 +19,19 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <QTextDocument>
|
#include "RAGData.hpp"
|
||||||
|
|
||||||
namespace QodeAssist::Context {
|
namespace QodeAssist::Context {
|
||||||
|
|
||||||
struct DocumentInfo
|
class RAGSimilaritySearch
|
||||||
{
|
|
||||||
QTextDocument *document = nullptr; // not owned
|
|
||||||
QString mimeType;
|
|
||||||
QString filePath;
|
|
||||||
};
|
|
||||||
|
|
||||||
class IDocumentReader
|
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
virtual ~IDocumentReader() = default;
|
static float l2Distance(const RAGVector &v1, const RAGVector &v2);
|
||||||
|
|
||||||
virtual DocumentInfo readDocument(const QString &path) const = 0;
|
static float cosineSimilarity(const RAGVector &v1, const RAGVector &v2);
|
||||||
|
|
||||||
|
private:
|
||||||
|
RAGSimilaritySearch() = delete;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace QodeAssist::Context
|
} // namespace QodeAssist::Context
|
||||||
1047
context/RAGStorage.cpp
Normal file
1047
context/RAGStorage.cpp
Normal file
File diff suppressed because it is too large
Load Diff
174
context/RAGStorage.hpp
Normal file
174
context/RAGStorage.hpp
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2024 Petr Mironychev
|
||||||
|
*
|
||||||
|
* This file is part of QodeAssist.
|
||||||
|
*
|
||||||
|
* QodeAssist is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* QodeAssist is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// RAGStorage.hpp
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
#include <QDateTime>
|
||||||
|
#include <QMutex>
|
||||||
|
#include <QObject>
|
||||||
|
#include <QSqlDatabase>
|
||||||
|
#include <QString>
|
||||||
|
#include <qsqlquery.h>
|
||||||
|
|
||||||
|
#include <RAGData.hpp>
|
||||||
|
|
||||||
|
namespace QodeAssist::Context {
|
||||||
|
|
||||||
|
struct FileChunkData
|
||||||
|
{
|
||||||
|
QString filePath;
|
||||||
|
int startLine;
|
||||||
|
int endLine;
|
||||||
|
QString content;
|
||||||
|
QDateTime createdAt;
|
||||||
|
QDateTime updatedAt;
|
||||||
|
|
||||||
|
bool isValid() const
|
||||||
|
{
|
||||||
|
return !filePath.isEmpty() && startLine >= 0 && endLine >= startLine && !content.isEmpty();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct StorageOptions
|
||||||
|
{
|
||||||
|
int maxChunkSize = 1024 * 1024;
|
||||||
|
int maxVectorSize = 1024;
|
||||||
|
bool useCompression = false;
|
||||||
|
bool enableLogging = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct StorageStatistics
|
||||||
|
{
|
||||||
|
int totalChunks;
|
||||||
|
int totalVectors;
|
||||||
|
int totalFiles;
|
||||||
|
qint64 totalSize;
|
||||||
|
QDateTime lastUpdate;
|
||||||
|
};
|
||||||
|
|
||||||
|
class RAGStorage : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
static constexpr int CURRENT_VERSION = 1;
|
||||||
|
|
||||||
|
enum class Status { Ok, DatabaseError, ValidationError, VersionError, ConnectionError };
|
||||||
|
|
||||||
|
struct ValidationResult
|
||||||
|
{
|
||||||
|
bool isValid;
|
||||||
|
QString errorMessage;
|
||||||
|
Status errorStatus;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Error
|
||||||
|
{
|
||||||
|
QString message;
|
||||||
|
QString sqlError;
|
||||||
|
QString query;
|
||||||
|
Status status;
|
||||||
|
};
|
||||||
|
|
||||||
|
explicit RAGStorage(
|
||||||
|
const QString &dbPath,
|
||||||
|
const StorageOptions &options = StorageOptions(),
|
||||||
|
QObject *parent = nullptr);
|
||||||
|
~RAGStorage();
|
||||||
|
|
||||||
|
bool init();
|
||||||
|
Status status() const;
|
||||||
|
Error lastError() const;
|
||||||
|
bool isReady() const;
|
||||||
|
QString dbPath() const;
|
||||||
|
|
||||||
|
bool beginTransaction();
|
||||||
|
bool commitTransaction();
|
||||||
|
bool rollbackTransaction();
|
||||||
|
|
||||||
|
bool storeVector(const QString &filePath, const RAGVector &vector);
|
||||||
|
bool updateVector(const QString &filePath, const RAGVector &vector);
|
||||||
|
std::optional<RAGVector> getVector(const QString &filePath);
|
||||||
|
bool needsUpdate(const QString &filePath);
|
||||||
|
QStringList getAllFiles();
|
||||||
|
|
||||||
|
bool storeChunk(const FileChunkData &chunk);
|
||||||
|
bool storeChunks(const QList<FileChunkData> &chunks);
|
||||||
|
bool updateChunk(const FileChunkData &chunk);
|
||||||
|
bool updateChunks(const QList<FileChunkData> &chunks);
|
||||||
|
bool deleteChunksForFile(const QString &filePath);
|
||||||
|
std::optional<FileChunkData> getChunk(const QString &filePath, int startLine, int endLine);
|
||||||
|
QList<FileChunkData> getChunksForFile(const QString &filePath);
|
||||||
|
bool chunkExists(const QString &filePath, int startLine, int endLine);
|
||||||
|
|
||||||
|
int getChunkCount(const QString &filePath);
|
||||||
|
bool deleteOldChunks(const QString &filePath, const QDateTime &olderThan);
|
||||||
|
bool deleteAllChunks();
|
||||||
|
QStringList getFilesWithChunks();
|
||||||
|
bool vacuum();
|
||||||
|
bool backup(const QString &backupPath);
|
||||||
|
bool restore(const QString &backupPath);
|
||||||
|
StorageStatistics getStatistics() const;
|
||||||
|
|
||||||
|
int getStorageVersion() const;
|
||||||
|
bool isVersionCompatible() const;
|
||||||
|
|
||||||
|
bool applyMigration(int version);
|
||||||
|
signals:
|
||||||
|
void errorOccurred(const Error &error);
|
||||||
|
void operationCompleted(const QString &operation);
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool createTables();
|
||||||
|
bool createIndices();
|
||||||
|
bool createVersionTable();
|
||||||
|
bool createChunksTable();
|
||||||
|
bool createVectorsTable();
|
||||||
|
bool openDatabase();
|
||||||
|
bool initializeNewStorage();
|
||||||
|
bool upgradeStorage(int fromVersion);
|
||||||
|
bool validateSchema() const;
|
||||||
|
|
||||||
|
QDateTime getFileLastModified(const QString &filePath);
|
||||||
|
RAGVector blobToVector(const QByteArray &blob);
|
||||||
|
QByteArray vectorToBlob(const RAGVector &vector);
|
||||||
|
|
||||||
|
void setError(const QString &message, Status status = Status::DatabaseError);
|
||||||
|
void clearError();
|
||||||
|
bool prepareStatements();
|
||||||
|
ValidationResult validateChunk(const FileChunkData &chunk) const;
|
||||||
|
ValidationResult validateVector(const RAGVector &vector) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
QSqlDatabase m_db;
|
||||||
|
QString m_dbPath;
|
||||||
|
StorageOptions m_options;
|
||||||
|
mutable QMutex m_mutex;
|
||||||
|
Error m_lastError;
|
||||||
|
Status m_status;
|
||||||
|
|
||||||
|
QSqlQuery m_insertChunkQuery;
|
||||||
|
QSqlQuery m_updateChunkQuery;
|
||||||
|
QSqlQuery m_insertVectorQuery;
|
||||||
|
QSqlQuery m_updateVectorQuery;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace QodeAssist::Context
|
||||||
116
context/RAGVectorizer.cpp
Normal file
116
context/RAGVectorizer.cpp
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2024 Petr Mironychev
|
||||||
|
*
|
||||||
|
* This file is part of QodeAssist.
|
||||||
|
*
|
||||||
|
* QodeAssist is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* QodeAssist is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "RAGVectorizer.hpp"
|
||||||
|
|
||||||
|
#include <QJsonArray>
|
||||||
|
#include <QJsonDocument>
|
||||||
|
#include <QJsonObject>
|
||||||
|
#include <QNetworkReply>
|
||||||
|
|
||||||
|
namespace QodeAssist::Context {
|
||||||
|
|
||||||
|
RAGVectorizer::RAGVectorizer(const QString &providerUrl,
|
||||||
|
const QString &modelName,
|
||||||
|
QObject *parent)
|
||||||
|
: QObject(parent)
|
||||||
|
, m_network(new QNetworkAccessManager(this))
|
||||||
|
, m_embedProviderUrl(providerUrl)
|
||||||
|
, m_model(modelName)
|
||||||
|
{}
|
||||||
|
|
||||||
|
RAGVectorizer::~RAGVectorizer() {}
|
||||||
|
|
||||||
|
QJsonObject RAGVectorizer::prepareEmbeddingRequest(const QString &text) const
|
||||||
|
{
|
||||||
|
return QJsonObject{{"model", m_model}, {"prompt", text}};
|
||||||
|
}
|
||||||
|
|
||||||
|
RAGVector RAGVectorizer::parseEmbeddingResponse(const QByteArray &response) const
|
||||||
|
{
|
||||||
|
QJsonDocument doc = QJsonDocument::fromJson(response);
|
||||||
|
if (doc.isNull()) {
|
||||||
|
qDebug() << "Failed to parse JSON response";
|
||||||
|
return RAGVector();
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject obj = doc.object();
|
||||||
|
if (!obj.contains("embedding")) {
|
||||||
|
qDebug() << "Response does not contain 'embedding' field";
|
||||||
|
// qDebug() << "Response content:" << response;
|
||||||
|
return RAGVector();
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonArray array = obj["embedding"].toArray();
|
||||||
|
if (array.isEmpty()) {
|
||||||
|
qDebug() << "Embedding array is empty";
|
||||||
|
return RAGVector();
|
||||||
|
}
|
||||||
|
|
||||||
|
RAGVector result;
|
||||||
|
result.reserve(array.size());
|
||||||
|
for (const auto &value : array) {
|
||||||
|
result.push_back(value.toDouble());
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "Successfully parsed vector with size:" << result.size();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
QFuture<RAGVector> RAGVectorizer::vectorizeText(const QString &text)
|
||||||
|
{
|
||||||
|
qDebug() << "Vectorizing text, length:" << text.length();
|
||||||
|
qDebug() << "Using embedding provider:" << m_embedProviderUrl;
|
||||||
|
|
||||||
|
auto promise = std::make_shared<QPromise<RAGVector>>();
|
||||||
|
promise->start();
|
||||||
|
|
||||||
|
QNetworkRequest request(QUrl(m_embedProviderUrl + "/api/embeddings"));
|
||||||
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||||
|
|
||||||
|
QJsonObject requestData = prepareEmbeddingRequest(text);
|
||||||
|
QByteArray jsonData = QJsonDocument(requestData).toJson();
|
||||||
|
qDebug() << "Sending request to embeddings API:" << jsonData;
|
||||||
|
|
||||||
|
auto reply = m_network->post(request, jsonData);
|
||||||
|
|
||||||
|
connect(reply, &QNetworkReply::finished, this, [promise, reply, this]() {
|
||||||
|
if (reply->error() == QNetworkReply::NoError) {
|
||||||
|
QByteArray response = reply->readAll();
|
||||||
|
// qDebug() << "Received response from embeddings API:" << response;
|
||||||
|
|
||||||
|
auto vector = parseEmbeddingResponse(response);
|
||||||
|
qDebug() << "Parsed vector size:" << vector.size();
|
||||||
|
promise->addResult(vector);
|
||||||
|
} else {
|
||||||
|
qDebug() << "Network error:" << reply->errorString();
|
||||||
|
qDebug() << "HTTP status code:"
|
||||||
|
<< reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||||
|
qDebug() << "Response:" << reply->readAll();
|
||||||
|
|
||||||
|
promise->addResult(RAGVector());
|
||||||
|
}
|
||||||
|
promise->finish();
|
||||||
|
reply->deleteLater();
|
||||||
|
});
|
||||||
|
|
||||||
|
return promise->future();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace QodeAssist::Context
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2025 Povilas Kanapickas
|
* Copyright (C) 2024 Petr Mironychev
|
||||||
*
|
*
|
||||||
* This file is part of QodeAssist.
|
* This file is part of QodeAssist.
|
||||||
*
|
*
|
||||||
@@ -19,27 +19,33 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "IDocumentReader.hpp"
|
#include <QFuture>
|
||||||
|
#include <QNetworkAccessManager>
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
#include <texteditor/textdocument.h>
|
#include <RAGData.hpp>
|
||||||
|
|
||||||
namespace QodeAssist::Context {
|
namespace QodeAssist::Context {
|
||||||
|
|
||||||
class DocumentReaderQtCreator : public IDocumentReader
|
class RAGVectorizer : public QObject
|
||||||
{
|
{
|
||||||
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
DocumentInfo readDocument(const QString &path) const override
|
explicit RAGVectorizer(
|
||||||
{
|
const QString &providerUrl = "http://localhost:11434",
|
||||||
auto *textDocument = TextEditor::TextDocument::textDocumentForFilePath(
|
const QString &modelName = "all-minilm:33m-l12-v2-fp16",
|
||||||
Utils::FilePath::fromString(path));
|
QObject *parent = nullptr);
|
||||||
if (!textDocument) {
|
~RAGVectorizer();
|
||||||
return {};
|
|
||||||
}
|
QFuture<RAGVector> vectorizeText(const QString &text);
|
||||||
return {
|
|
||||||
.document = textDocument->document(),
|
private:
|
||||||
.mimeType = textDocument->mimeType(),
|
QJsonObject prepareEmbeddingRequest(const QString &text) const;
|
||||||
.filePath = path};
|
RAGVector parseEmbeddingResponse(const QByteArray &response) const;
|
||||||
}
|
|
||||||
|
QNetworkAccessManager *m_network;
|
||||||
|
QString m_embedProviderUrl;
|
||||||
|
QString m_model;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace QodeAssist::Context
|
} // namespace QodeAssist::Context
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2024-2025 Petr Mironychev
|
* Copyright (C) 2024 Petr Mironychev
|
||||||
*
|
*
|
||||||
* This file is part of QodeAssist.
|
* This file is part of QodeAssist.
|
||||||
*
|
*
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
|
|
||||||
namespace QodeAssist::Context {
|
namespace QodeAssist::Context {
|
||||||
|
|
||||||
int TokenUtils::estimateTokens(const QString &text)
|
int TokenUtils::estimateTokens(const QString& text)
|
||||||
{
|
{
|
||||||
if (text.isEmpty()) {
|
if (text.isEmpty()) {
|
||||||
return 0;
|
return 0;
|
||||||
@@ -31,7 +31,7 @@ int TokenUtils::estimateTokens(const QString &text)
|
|||||||
return text.length() / 4;
|
return text.length() / 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
int TokenUtils::estimateFileTokens(const Context::ContentFile &file)
|
int TokenUtils::estimateFileTokens(const Context::ContentFile& file)
|
||||||
{
|
{
|
||||||
int total = 0;
|
int total = 0;
|
||||||
|
|
||||||
@@ -42,13 +42,13 @@ int TokenUtils::estimateFileTokens(const Context::ContentFile &file)
|
|||||||
return total;
|
return total;
|
||||||
}
|
}
|
||||||
|
|
||||||
int TokenUtils::estimateFilesTokens(const QList<Context::ContentFile> &files)
|
int TokenUtils::estimateFilesTokens(const QList<Context::ContentFile>& files)
|
||||||
{
|
{
|
||||||
int total = 0;
|
int total = 0;
|
||||||
for (const auto &file : files) {
|
for (const auto& file : files) {
|
||||||
total += estimateFileTokens(file);
|
total += estimateFileTokens(file);
|
||||||
}
|
}
|
||||||
return total;
|
return total;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace QodeAssist::Context
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2024-2025 Petr Mironychev
|
* Copyright (C) 2024 Petr Mironychev
|
||||||
*
|
*
|
||||||
* This file is part of QodeAssist.
|
* This file is part of QodeAssist.
|
||||||
*
|
*
|
||||||
@@ -19,18 +19,18 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
#include "ContentFile.hpp"
|
#include "ContentFile.hpp"
|
||||||
#include <QList>
|
#include <QList>
|
||||||
#include <QString>
|
|
||||||
|
|
||||||
namespace QodeAssist::Context {
|
namespace QodeAssist::Context {
|
||||||
|
|
||||||
class TokenUtils
|
class TokenUtils
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
static int estimateTokens(const QString &text);
|
static int estimateTokens(const QString& text);
|
||||||
static int estimateFileTokens(const Context::ContentFile &file);
|
static int estimateFileTokens(const Context::ContentFile& file);
|
||||||
static int estimateFilesTokens(const QList<Context::ContentFile> &files);
|
static int estimateFilesTokens(const QList<Context::ContentFile>& files);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace QodeAssist::Context
|
}
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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,19 +3,14 @@ add_library(LLMCore STATIC
|
|||||||
Provider.hpp
|
Provider.hpp
|
||||||
ProvidersManager.hpp ProvidersManager.cpp
|
ProvidersManager.hpp ProvidersManager.cpp
|
||||||
ContextData.hpp
|
ContextData.hpp
|
||||||
IPromptProvider.hpp
|
|
||||||
IProviderRegistry.hpp
|
|
||||||
PromptProviderChat.hpp
|
|
||||||
PromptProviderFim.hpp
|
|
||||||
PromptTemplate.hpp
|
PromptTemplate.hpp
|
||||||
PromptTemplateManager.hpp PromptTemplateManager.cpp
|
PromptTemplateManager.hpp PromptTemplateManager.cpp
|
||||||
RequestConfig.hpp
|
RequestConfig.hpp
|
||||||
RequestHandlerBase.hpp RequestHandlerBase.cpp
|
|
||||||
RequestHandler.hpp RequestHandler.cpp
|
RequestHandler.hpp RequestHandler.cpp
|
||||||
OllamaMessage.hpp OllamaMessage.cpp
|
OllamaMessage.hpp OllamaMessage.cpp
|
||||||
OpenAIMessage.hpp OpenAIMessage.cpp
|
OpenAIMessage.hpp OpenAIMessage.cpp
|
||||||
ValidationUtils.hpp ValidationUtils.cpp
|
ValidationUtils.hpp ValidationUtils.cpp
|
||||||
ProviderID.hpp
|
MessageBuilder.hpp MessageBuilder.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(LLMCore
|
target_link_libraries(LLMCore
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2024-2025 Petr Mironychev
|
* Copyright (C) 2024 Petr Mironychev
|
||||||
*
|
*
|
||||||
* This file is part of QodeAssist.
|
* This file is part of QodeAssist.
|
||||||
*
|
*
|
||||||
@@ -20,37 +20,14 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QVector>
|
|
||||||
|
|
||||||
namespace QodeAssist::LLMCore {
|
namespace QodeAssist::LLMCore {
|
||||||
|
|
||||||
struct Message
|
|
||||||
{
|
|
||||||
QString role;
|
|
||||||
QString content;
|
|
||||||
|
|
||||||
// clang-format off
|
|
||||||
bool operator==(const Message&) const = default;
|
|
||||||
// clang-format on
|
|
||||||
};
|
|
||||||
|
|
||||||
struct FileMetadata
|
|
||||||
{
|
|
||||||
QString filePath;
|
|
||||||
QString content;
|
|
||||||
bool operator==(const FileMetadata &) const = default;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ContextData
|
struct ContextData
|
||||||
{
|
{
|
||||||
std::optional<QString> systemPrompt;
|
QString prefix;
|
||||||
std::optional<QString> prefix;
|
QString suffix;
|
||||||
std::optional<QString> suffix;
|
QString fileContext;
|
||||||
std::optional<QString> fileContext;
|
|
||||||
std::optional<QVector<Message>> history;
|
|
||||||
std::optional<QList<FileMetadata>> filesMetadata;
|
|
||||||
|
|
||||||
bool operator==(const ContextData &) const = default;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace QodeAssist::LLMCore
|
} // namespace QodeAssist::LLMCore
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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
|
|
||||||
93
llmcore/MessageBuilder.cpp
Normal file
93
llmcore/MessageBuilder.cpp
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2024 Petr Mironychev
|
||||||
|
*
|
||||||
|
* This file is part of QodeAssist.
|
||||||
|
*
|
||||||
|
* QodeAssist is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* QodeAssist is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "MessageBuilder.hpp"
|
||||||
|
|
||||||
|
QodeAssist::LLMCore::MessageBuilder &QodeAssist::LLMCore::MessageBuilder::addSystemMessage(
|
||||||
|
const QString &content)
|
||||||
|
{
|
||||||
|
m_systemMessage = content;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
QodeAssist::LLMCore::MessageBuilder &QodeAssist::LLMCore::MessageBuilder::addUserMessage(
|
||||||
|
const QString &content)
|
||||||
|
{
|
||||||
|
m_messages.append({MessageRole::User, content});
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
QodeAssist::LLMCore::MessageBuilder &QodeAssist::LLMCore::MessageBuilder::addSuffix(
|
||||||
|
const QString &content)
|
||||||
|
{
|
||||||
|
m_suffix = content;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
QodeAssist::LLMCore::MessageBuilder &QodeAssist::LLMCore::MessageBuilder::addTokenizer(
|
||||||
|
PromptTemplate *promptTemplate)
|
||||||
|
{
|
||||||
|
m_promptTemplate = promptTemplate;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString QodeAssist::LLMCore::MessageBuilder::roleToString(MessageRole role) const
|
||||||
|
{
|
||||||
|
switch (role) {
|
||||||
|
case MessageRole::System:
|
||||||
|
return ROLE_SYSTEM;
|
||||||
|
case MessageRole::User:
|
||||||
|
return ROLE_USER;
|
||||||
|
case MessageRole::Assistant:
|
||||||
|
return ROLE_ASSISTANT;
|
||||||
|
default:
|
||||||
|
return ROLE_USER;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void QodeAssist::LLMCore::MessageBuilder::saveTo(QJsonObject &request, ProvidersApi api)
|
||||||
|
{
|
||||||
|
if (!m_promptTemplate) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ContextData context{
|
||||||
|
m_messages.isEmpty() ? QString() : m_messages.last().content, m_suffix, m_systemMessage};
|
||||||
|
|
||||||
|
if (api == ProvidersApi::Ollama) {
|
||||||
|
if (m_promptTemplate->type() == TemplateType::Fim) {
|
||||||
|
request["system"] = m_systemMessage;
|
||||||
|
m_promptTemplate->prepareRequest(request, context);
|
||||||
|
} else {
|
||||||
|
QJsonArray messages;
|
||||||
|
|
||||||
|
messages.append(QJsonObject{{"role", "system"}, {"content", m_systemMessage}});
|
||||||
|
messages.append(QJsonObject{{"role", "user"}, {"content", m_messages.last().content}});
|
||||||
|
request["messages"] = messages;
|
||||||
|
m_promptTemplate->prepareRequest(request, context);
|
||||||
|
}
|
||||||
|
} else if (api == ProvidersApi::OpenAI) {
|
||||||
|
QJsonArray messages;
|
||||||
|
|
||||||
|
messages.append(QJsonObject{{"role", "system"}, {"content", m_systemMessage}});
|
||||||
|
messages.append(QJsonObject{{"role", "user"}, {"content", m_messages.last().content}});
|
||||||
|
request["messages"] = messages;
|
||||||
|
m_promptTemplate->prepareRequest(request, context);
|
||||||
|
}
|
||||||
|
}
|
||||||
68
llmcore/MessageBuilder.hpp
Normal file
68
llmcore/MessageBuilder.hpp
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2024 Petr Mironychev
|
||||||
|
*
|
||||||
|
* This file is part of QodeAssist.
|
||||||
|
*
|
||||||
|
* QodeAssist is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* QodeAssist is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QJsonArray>
|
||||||
|
#include <QJsonObject>
|
||||||
|
#include <QString>
|
||||||
|
#include <QVector>
|
||||||
|
|
||||||
|
#include "PromptTemplate.hpp"
|
||||||
|
|
||||||
|
namespace QodeAssist::LLMCore {
|
||||||
|
|
||||||
|
enum class MessageRole { System, User, Assistant };
|
||||||
|
|
||||||
|
enum class OllamaFormat { Messages, Completions };
|
||||||
|
|
||||||
|
enum class ProvidersApi { Ollama, OpenAI, Claude };
|
||||||
|
|
||||||
|
static const QString ROLE_SYSTEM = "system";
|
||||||
|
static const QString ROLE_USER = "user";
|
||||||
|
static const QString ROLE_ASSISTANT = "assistant";
|
||||||
|
|
||||||
|
struct Message
|
||||||
|
{
|
||||||
|
MessageRole role;
|
||||||
|
QString content;
|
||||||
|
};
|
||||||
|
|
||||||
|
class MessageBuilder
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
MessageBuilder &addSystemMessage(const QString &content);
|
||||||
|
|
||||||
|
MessageBuilder &addUserMessage(const QString &content);
|
||||||
|
|
||||||
|
MessageBuilder &addSuffix(const QString &content);
|
||||||
|
|
||||||
|
MessageBuilder &addTokenizer(PromptTemplate *promptTemplate);
|
||||||
|
|
||||||
|
QString roleToString(MessageRole role) const;
|
||||||
|
|
||||||
|
void saveTo(QJsonObject &request, ProvidersApi api);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QString m_systemMessage;
|
||||||
|
QString m_suffix;
|
||||||
|
QVector<Message> m_messages;
|
||||||
|
PromptTemplate *m_promptTemplate;
|
||||||
|
};
|
||||||
|
} // namespace QodeAssist::LLMCore
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2024-2025 Petr Mironychev
|
* Copyright (C) 2024 Petr Mironychev
|
||||||
*
|
*
|
||||||
* This file is part of QodeAssist.
|
* This file is part of QodeAssist.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2024-2025 Petr Mironychev
|
* Copyright (C) 2024 Petr Mironychev
|
||||||
*
|
*
|
||||||
* This file is part of QodeAssist.
|
* This file is part of QodeAssist.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2024-2025 Petr Mironychev
|
* Copyright (C) 2024 Petr Mironychev
|
||||||
*
|
*
|
||||||
* This file is part of QodeAssist.
|
* This file is part of QodeAssist.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2024-2025 Petr Mironychev
|
* Copyright (C) 2024 Petr Mironychev
|
||||||
*
|
*
|
||||||
* This file is part of QodeAssist.
|
* This file is part of QodeAssist.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,53 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2024-2025 Petr Mironychev
|
* Copyright (C) 2024 Petr Mironychev
|
||||||
*
|
*
|
||||||
* This file is part of QodeAssist.
|
* This file is part of QodeAssist.
|
||||||
*
|
*
|
||||||
@@ -24,11 +24,10 @@
|
|||||||
#include <QString>
|
#include <QString>
|
||||||
|
|
||||||
#include "ContextData.hpp"
|
#include "ContextData.hpp"
|
||||||
#include "ProviderID.hpp"
|
|
||||||
|
|
||||||
namespace QodeAssist::LLMCore {
|
namespace QodeAssist::LLMCore {
|
||||||
|
|
||||||
enum class TemplateType { Chat, FIM };
|
enum class TemplateType { Chat, Fim };
|
||||||
|
|
||||||
class PromptTemplate
|
class PromptTemplate
|
||||||
{
|
{
|
||||||
@@ -36,9 +35,9 @@ public:
|
|||||||
virtual ~PromptTemplate() = default;
|
virtual ~PromptTemplate() = default;
|
||||||
virtual TemplateType type() const = 0;
|
virtual TemplateType type() const = 0;
|
||||||
virtual QString name() const = 0;
|
virtual QString name() const = 0;
|
||||||
|
virtual QString promptTemplate() const = 0;
|
||||||
virtual QStringList stopWords() const = 0;
|
virtual QStringList stopWords() const = 0;
|
||||||
virtual void prepareRequest(QJsonObject &request, const ContextData &context) const = 0;
|
virtual void prepareRequest(QJsonObject &request, const ContextData &context) const = 0;
|
||||||
virtual QString description() const = 0;
|
virtual QString description() const = 0;
|
||||||
virtual bool isSupportProvider(ProviderID id) const = 0;
|
|
||||||
};
|
};
|
||||||
} // namespace QodeAssist::LLMCore
|
} // namespace QodeAssist::LLMCore
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2024-2025 Petr Mironychev
|
* Copyright (C) 2024 Petr Mironychev
|
||||||
*
|
*
|
||||||
* This file is part of QodeAssist.
|
* This file is part of QodeAssist.
|
||||||
*
|
*
|
||||||
@@ -37,32 +37,6 @@ QStringList PromptTemplateManager::chatTemplatesNames() const
|
|||||||
return m_chatTemplates.keys();
|
return m_chatTemplates.keys();
|
||||||
}
|
}
|
||||||
|
|
||||||
QStringList PromptTemplateManager::getFimTemplatesForProvider(ProviderID id)
|
|
||||||
{
|
|
||||||
QStringList templateList;
|
|
||||||
|
|
||||||
for (const auto tmpl : m_fimTemplates) {
|
|
||||||
if (tmpl->isSupportProvider(id)) {
|
|
||||||
templateList.append(tmpl->name());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return templateList;
|
|
||||||
}
|
|
||||||
|
|
||||||
QStringList PromptTemplateManager::getChatTemplatesForProvider(ProviderID id)
|
|
||||||
{
|
|
||||||
QStringList templateList;
|
|
||||||
|
|
||||||
for (const auto tmpl : m_chatTemplates) {
|
|
||||||
if (tmpl->isSupportProvider(id)) {
|
|
||||||
templateList.append(tmpl->name());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return templateList;
|
|
||||||
}
|
|
||||||
|
|
||||||
PromptTemplateManager::~PromptTemplateManager()
|
PromptTemplateManager::~PromptTemplateManager()
|
||||||
{
|
{
|
||||||
qDeleteAll(m_fimTemplates);
|
qDeleteAll(m_fimTemplates);
|
||||||
@@ -70,15 +44,11 @@ PromptTemplateManager::~PromptTemplateManager()
|
|||||||
|
|
||||||
PromptTemplate *PromptTemplateManager::getFimTemplateByName(const QString &templateName)
|
PromptTemplate *PromptTemplateManager::getFimTemplateByName(const QString &templateName)
|
||||||
{
|
{
|
||||||
if (!m_fimTemplates.contains(templateName))
|
|
||||||
return m_fimTemplates.first();
|
|
||||||
return m_fimTemplates[templateName];
|
return m_fimTemplates[templateName];
|
||||||
}
|
}
|
||||||
|
|
||||||
PromptTemplate *PromptTemplateManager::getChatTemplateByName(const QString &templateName)
|
PromptTemplate *PromptTemplateManager::getChatTemplateByName(const QString &templateName)
|
||||||
{
|
{
|
||||||
if (!m_chatTemplates.contains(templateName))
|
|
||||||
return m_chatTemplates.first();
|
|
||||||
return m_chatTemplates[templateName];
|
return m_chatTemplates[templateName];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user