Compare commits
8 Commits
v0.7.1
...
dev-rag-ba
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
142afa725f | ||
|
|
f36db033e6 | ||
|
|
5dfcf74128 | ||
|
|
02101665ca | ||
|
|
77a03d42ed | ||
|
|
09c38c8b0e | ||
|
|
7b73d7af7b | ||
|
|
5a426b4d9f |
2
.github/FUNDING.yml
vendored
@@ -12,4 +12,4 @@ lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cl
|
|||||||
polar: # Replace with a single Polar username
|
polar: # Replace with a single Polar username
|
||||||
buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
|
buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
|
||||||
thanks_dev: # Replace with a single thanks.dev username
|
thanks_dev: # Replace with a single thanks.dev username
|
||||||
custom: ['https://www.paypal.com/paypalme/palm1r', 'https://github.com/Palm1r/QodeAssist#support-the-development-of-qodeassist']
|
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||||
|
|||||||
61
.github/scripts/plugin.json
vendored
@@ -6,77 +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.11",
|
"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,
|
|
||||||
"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": false,
|
|
||||||
"released_at": "2025-04-17T10:00:00Z"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"version": "0.5.9",
|
|
||||||
"is_latest": false,
|
|
||||||
"released_at": "2025-04-21T10:00:00Z"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"version": "0.5.10",
|
|
||||||
"is_latest": false,
|
|
||||||
"released_at": "2025-04-24T10:00:00Z"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"version": "0.5.11",
|
|
||||||
"is_latest": false,
|
|
||||||
"released_at": "2025-04-24T21:00:00Z"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"version": "0.5.12",
|
|
||||||
"is_latest": true,
|
"is_latest": true,
|
||||||
"released_at": "2025-05-01T17:00:00Z"
|
"released_at": "2024-01-24T15: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",
|
||||||
|
|||||||
120
.github/workflows/build_cmake.yml
vendored
@@ -12,13 +12,16 @@ on:
|
|||||||
|
|
||||||
env:
|
env:
|
||||||
PLUGIN_NAME: QodeAssist
|
PLUGIN_NAME: QodeAssist
|
||||||
|
QT_VERSION: 6.8.1
|
||||||
|
QT_CREATOR_VERSION: 15.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"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: ${{ matrix.config.name }} (Qt ${{ matrix.qt_config.qt_version }}, QtC ${{ matrix.qt_config.qt_creator_version }})
|
name: ${{ matrix.config.name }}
|
||||||
runs-on: ${{ matrix.config.os }}
|
runs-on: ${{ matrix.config.os }}
|
||||||
outputs:
|
outputs:
|
||||||
tag: ${{ steps.git.outputs.tag }}
|
tag: ${{ steps.git.outputs.tag }}
|
||||||
@@ -33,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++"
|
||||||
}
|
}
|
||||||
@@ -44,18 +47,12 @@ jobs:
|
|||||||
platform: mac_x64,
|
platform: mac_x64,
|
||||||
cc: "clang", cxx: "clang++"
|
cc: "clang", cxx: "clang++"
|
||||||
}
|
}
|
||||||
qt_config:
|
|
||||||
- {
|
|
||||||
qt_version: "6.9.2",
|
|
||||||
qt_creator_version: "17.0.2"
|
|
||||||
}
|
|
||||||
- {
|
|
||||||
qt_version: "6.8.3",
|
|
||||||
qt_creator_version: "16.0.2"
|
|
||||||
}
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '20'
|
||||||
|
|
||||||
- name: Checkout submodules
|
- name: Checkout submodules
|
||||||
id: git
|
id: git
|
||||||
@@ -64,21 +61,16 @@ jobs:
|
|||||||
if (${{github.ref}} MATCHES "tags/v(.*)")
|
if (${{github.ref}} MATCHES "tags/v(.*)")
|
||||||
file(APPEND "$ENV{GITHUB_OUTPUT}" "tag=${CMAKE_MATCH_1}")
|
file(APPEND "$ENV{GITHUB_OUTPUT}" "tag=${CMAKE_MATCH_1}")
|
||||||
else()
|
else()
|
||||||
execute_process(
|
file(APPEND "$ENV{GITHUB_OUTPUT}" "tag=${{github.run_id}}")
|
||||||
COMMAND git rev-parse --short HEAD
|
|
||||||
OUTPUT_VARIABLE short_sha
|
|
||||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
|
||||||
)
|
|
||||||
file(APPEND "$ENV{GITHUB_OUTPUT}" "tag=${short_sha}")
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
- name: Download Ninja and CMake
|
- name: Download Ninja and CMake
|
||||||
uses: lukka/get-cmake@2ecc21724e5215b0e567bc399a2602d2ecb48541
|
uses: lukka/get-cmake@latest
|
||||||
with:
|
with:
|
||||||
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")
|
||||||
@@ -86,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)
|
||||||
@@ -104,7 +90,7 @@ jobs:
|
|||||||
id: qt
|
id: qt
|
||||||
shell: cmake -P {0}
|
shell: cmake -P {0}
|
||||||
run: |
|
run: |
|
||||||
set(qt_version "${{ matrix.qt_config.qt_version }}")
|
set(qt_version "$ENV{QT_VERSION}")
|
||||||
|
|
||||||
string(REPLACE "." "" qt_version_dotless "${qt_version}")
|
string(REPLACE "." "" qt_version_dotless "${qt_version}")
|
||||||
if ("${{ runner.os }}" STREQUAL "Windows")
|
if ("${{ runner.os }}" STREQUAL "Windows")
|
||||||
@@ -125,11 +111,7 @@ jobs:
|
|||||||
set(url_os "mac_x64")
|
set(url_os "mac_x64")
|
||||||
set(qt_package_arch_suffix "clang_64")
|
set(qt_package_arch_suffix "clang_64")
|
||||||
set(qt_dir_prefix "${qt_version}/macos")
|
set(qt_dir_prefix "${qt_version}/macos")
|
||||||
if (qt_version VERSION_LESS "6.9.1")
|
|
||||||
set(qt_package_suffix "-MacOS-MacOS_14-Clang-MacOS-MacOS_14-X86_64-ARM64")
|
set(qt_package_suffix "-MacOS-MacOS_14-Clang-MacOS-MacOS_14-X86_64-ARM64")
|
||||||
else()
|
|
||||||
set(qt_package_suffix "-MacOS-MacOS_15-Clang-MacOS-MacOS_15-X86_64-ARM64")
|
|
||||||
endif()
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
set(qt_base_url "https://download.qt.io/online/qtsdkrepository/${url_os}/desktop/qt6_${qt_version_dotless}/qt6_${qt_version_dotless}")
|
set(qt_base_url "https://download.qt.io/online/qtsdkrepository/${url_os}/desktop/qt6_${qt_version_dotless}/qt6_${qt_version_dotless}")
|
||||||
@@ -152,7 +134,7 @@ jobs:
|
|||||||
execute_process(COMMAND ${CMAKE_COMMAND} -E tar xvf ../${archive} WORKING_DIRECTORY qt6)
|
execute_process(COMMAND ${CMAKE_COMMAND} -E tar xvf ../${archive} WORKING_DIRECTORY qt6)
|
||||||
endfunction()
|
endfunction()
|
||||||
|
|
||||||
foreach(package qtbase qtdeclarative qttools)
|
foreach(package qtbase qtdeclarative)
|
||||||
downloadAndExtract(
|
downloadAndExtract(
|
||||||
"${qt_base_url}/qt.qt6.${qt_version_dotless}.${qt_package_arch_suffix}/${qt_package_version}${package}${qt_package_suffix}.7z"
|
"${qt_base_url}/qt.qt6.${qt_version_dotless}.${qt_package_arch_suffix}/${qt_package_version}${package}${qt_package_suffix}.7z"
|
||||||
${package}.7z
|
${package}.7z
|
||||||
@@ -186,11 +168,10 @@ jobs:
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
- name: Download Qt Creator
|
- name: Download Qt Creator
|
||||||
uses: qt-creator/install-dev-package@4046eda2efa77c0fe61d4cde7e622c050a4d65af
|
uses: qt-creator/install-dev-package@v1.2
|
||||||
with:
|
with:
|
||||||
version: ${{ matrix.qt_config.qt_creator_version }}
|
version: ${{ env.QT_CREATOR_VERSION }}
|
||||||
unzip-to: 'qtcreator'
|
unzip-to: 'qtcreator'
|
||||||
platform: ${{ matrix.config.platform }}
|
|
||||||
|
|
||||||
- name: Extract Qt Creator
|
- name: Extract Qt Creator
|
||||||
id: qt_creator
|
id: qt_creator
|
||||||
@@ -236,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${{ matrix.qt_config.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 }}"
|
||||||
@@ -252,24 +233,67 @@ jobs:
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
- name: Upload
|
- name: Upload
|
||||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
path: ./${{ env.PLUGIN_NAME }}-v${{ steps.git.outputs.tag }}-QtC${{ matrix.qt_config.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${{ matrix.qt_config.qt_creator_version }}-${{ matrix.config.artifact }}.7z
|
name: ${{ env.PLUGIN_NAME}}-${{ env.QT_CREATOR_VERSION }}-${{ matrix.config.artifact }}.7z
|
||||||
|
|
||||||
- name: Run unit tests
|
# The json is the same for all platforms, but we need to save one
|
||||||
if: startsWith(matrix.config.os, 'ubuntu')
|
- name: Upload plugin json
|
||||||
|
if: matrix.config.os == 'ubuntu-latest'
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: ${{ env.PLUGIN_NAME }}-origin-json
|
||||||
|
path: ./build/build/${{ env.PLUGIN_NAME }}.json
|
||||||
|
|
||||||
|
update_json:
|
||||||
|
if: contains(github.ref, 'tags/v')
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: build
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '20'
|
||||||
|
|
||||||
|
- name: Download the JSON file
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: ${{ env.PLUGIN_NAME }}-origin-json
|
||||||
|
path: ./${{ env.PLUGIN_NAME }}-origin
|
||||||
|
|
||||||
|
- name: Store Release upload_url
|
||||||
run: |
|
run: |
|
||||||
xvfb-run ./build/build/test/QodeAssistTest
|
RELEASE_HTML_URL=$(echo "${{github.event.repository.html_url}}/releases/download/v${{ needs.build.outputs.tag }}")
|
||||||
|
echo "RELEASE_HTML_URL=${RELEASE_HTML_URL}" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Run the Node.js script to update JSON
|
||||||
|
env:
|
||||||
|
QT_TOKEN: ${{ secrets.TOKEN }}
|
||||||
|
API_URL: ${{ secrets.API_URL }}
|
||||||
|
run: |
|
||||||
|
node .github/scripts/registerPlugin.js ${{ env.RELEASE_HTML_URL }} ${{ env.PLUGIN_NAME }} ${{ env.QT_CREATOR_VERSION }} ${{ env.QT_CREATOR_VERSION_INTERNAL }} ${{ env.QT_TOKEN }} ${{ env.API_URL }}
|
||||||
|
|
||||||
|
- name: Delete previous json artifacts
|
||||||
|
uses: geekyeggo/delete-artifact@v5
|
||||||
|
with:
|
||||||
|
name: ${{ env.PLUGIN_NAME }}*-json
|
||||||
|
|
||||||
|
- name: Upload the modified JSON file as an artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: plugin-json
|
||||||
|
path: .github/scripts/${{ env.PLUGIN_NAME }}.json
|
||||||
|
|
||||||
release:
|
release:
|
||||||
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, update_json]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Download artifacts
|
- name: Download artifacts
|
||||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
path: release-with-dirs
|
path: release-with-dirs
|
||||||
|
|
||||||
@@ -280,7 +304,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Create Release
|
- name: Create Release
|
||||||
id: create_release
|
id: create_release
|
||||||
uses: softprops/action-gh-release@6cbd405e2c4e67a21c47fa9e383d020e4e28b836
|
uses: softprops/action-gh-release@v2
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
with:
|
with:
|
||||||
|
|||||||
1
.gitignore
vendored
@@ -74,4 +74,3 @@ CMakeLists.txt.user*
|
|||||||
*.exe
|
*.exe
|
||||||
|
|
||||||
/build
|
/build
|
||||||
/.qodeassist
|
|
||||||
0
.gitmodules
vendored
@@ -8,41 +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 LinguistTools REQUIRED)
|
find_package(Qt6 COMPONENTS Core Gui Quick Widgets Network REQUIRED)
|
||||||
find_package(GTest)
|
|
||||||
|
|
||||||
qt_standard_project_setup(I18N_TRANSLATED_LANGUAGES en)
|
|
||||||
|
|
||||||
# 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(UIControls)
|
|
||||||
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
|
||||||
@@ -69,34 +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/Qwen25CoderFIM.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
|
|
||||||
templates/Qwen3CoderFIM.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
|
||||||
@@ -106,46 +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
|
|
||||||
tools/ToolsFactory.hpp tools/ToolsFactory.cpp
|
|
||||||
tools/ReadProjectFileByNameTool.hpp tools/ReadProjectFileByNameTool.cpp
|
|
||||||
tools/ReadVisibleFilesTool.hpp tools/ReadVisibleFilesTool.cpp
|
|
||||||
tools/ToolHandler.hpp tools/ToolHandler.cpp
|
|
||||||
tools/ListProjectFilesTool.hpp tools/ListProjectFilesTool.cpp
|
|
||||||
tools/ToolsManager.hpp tools/ToolsManager.cpp
|
|
||||||
tools/SearchInProjectTool.hpp tools/SearchInProjectTool.cpp
|
|
||||||
tools/GetIssuesListTool.hpp tools/GetIssuesListTool.cpp
|
|
||||||
providers/ClaudeMessage.hpp providers/ClaudeMessage.cpp
|
|
||||||
providers/OpenAIMessage.hpp providers/OpenAIMessage.cpp
|
|
||||||
providers/OllamaMessage.hpp providers/OllamaMessage.cpp
|
|
||||||
providers/GoogleMessage.hpp providers/GoogleMessage.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()
|
|
||||||
|
|
||||||
#TODO change to TS_OUTPUT_DIRECTORY after removing Qt6.8
|
|
||||||
qt_add_translations(TARGETS QodeAssist
|
|
||||||
TS_FILE_DIR ${CMAKE_CURRENT_LIST_DIR}/resources/translations
|
|
||||||
RESOURCE_PREFIX "/translations"
|
|
||||||
LUPDATE_OPTIONS -no-obsolete
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -6,19 +6,17 @@ qt_policy(SET QTP0004 NEW)
|
|||||||
qt_add_qml_module(QodeAssistChatView
|
qt_add_qml_module(QodeAssistChatView
|
||||||
URI ChatView
|
URI ChatView
|
||||||
VERSION 1.0
|
VERSION 1.0
|
||||||
DEPENDENCIES
|
DEPENDENCIES QtQuick
|
||||||
QtQuick
|
|
||||||
QML_FILES
|
QML_FILES
|
||||||
qml/RootItem.qml
|
qml/RootItem.qml
|
||||||
qml/ChatItem.qml
|
qml/ChatItem.qml
|
||||||
|
qml/Badge.qml
|
||||||
qml/dialog/CodeBlock.qml
|
qml/dialog/CodeBlock.qml
|
||||||
qml/dialog/TextBlock.qml
|
qml/dialog/TextBlock.qml
|
||||||
|
qml/controls/QoAButton.qml
|
||||||
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/ErrorToast.qml
|
|
||||||
qml/ToolStatusItem.qml
|
|
||||||
|
|
||||||
RESOURCES
|
RESOURCES
|
||||||
icons/attach-file-light.svg
|
icons/attach-file-light.svg
|
||||||
icons/attach-file-dark.svg
|
icons/attach-file-dark.svg
|
||||||
@@ -26,14 +24,6 @@ qt_add_qml_module(QodeAssistChatView
|
|||||||
icons/close-light.svg
|
icons/close-light.svg
|
||||||
icons/link-file-light.svg
|
icons/link-file-light.svg
|
||||||
icons/link-file-dark.svg
|
icons/link-file-dark.svg
|
||||||
icons/load-chat-dark.svg
|
|
||||||
icons/save-chat-dark.svg
|
|
||||||
icons/clean-icon-dark.svg
|
|
||||||
icons/file-in-system.svg
|
|
||||||
icons/window-lock.svg
|
|
||||||
icons/window-unlock.svg
|
|
||||||
icons/chat-icon.svg
|
|
||||||
icons/chat-pause-icon.svg
|
|
||||||
SOURCES
|
SOURCES
|
||||||
ChatWidget.hpp ChatWidget.cpp
|
ChatWidget.hpp ChatWidget.cpp
|
||||||
ChatModel.hpp ChatModel.cpp
|
ChatModel.hpp ChatModel.cpp
|
||||||
@@ -42,8 +32,6 @@ qt_add_qml_module(QodeAssistChatView
|
|||||||
MessagePart.hpp
|
MessagePart.hpp
|
||||||
ChatUtils.h ChatUtils.cpp
|
ChatUtils.h ChatUtils.cpp
|
||||||
ChatSerializer.hpp ChatSerializer.cpp
|
ChatSerializer.hpp ChatSerializer.cpp
|
||||||
ChatView.hpp ChatView.cpp
|
|
||||||
ChatData.hpp
|
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(QodeAssistChatView
|
target_link_libraries(QodeAssistChatView
|
||||||
@@ -57,7 +45,6 @@ target_link_libraries(QodeAssistChatView
|
|||||||
LLMCore
|
LLMCore
|
||||||
QodeAssistSettings
|
QodeAssistSettings
|
||||||
Context
|
Context
|
||||||
QodeAssistUIControlsplugin
|
|
||||||
)
|
)
|
||||||
|
|
||||||
target_include_directories(QodeAssistChatView
|
target_include_directories(QodeAssistChatView
|
||||||
|
|||||||
@@ -1,32 +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 <QtQmlIntegration>
|
|
||||||
|
|
||||||
namespace QodeAssist::Chat {
|
|
||||||
Q_NAMESPACE
|
|
||||||
QML_NAMED_ELEMENT(MessagePartType)
|
|
||||||
|
|
||||||
enum class MessagePartType { Code, Text };
|
|
||||||
Q_ENUM_NS(MessagePartType)
|
|
||||||
|
|
||||||
} // 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.
|
||||||
*
|
*
|
||||||
@@ -18,12 +18,11 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#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"
|
||||||
#include "Logger.hpp"
|
|
||||||
|
|
||||||
namespace QodeAssist::Chat {
|
namespace QodeAssist::Chat {
|
||||||
|
|
||||||
@@ -32,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);
|
||||||
@@ -92,8 +90,7 @@ void ChatModel::addMessage(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!m_messages.isEmpty() && !id.isEmpty() && m_messages.last().id == id
|
if (!m_messages.isEmpty() && !id.isEmpty() && m_messages.last().id == id) {
|
||||||
&& m_messages.last().role == role) {
|
|
||||||
Message &lastMessage = m_messages.last();
|
Message &lastMessage = m_messages.last();
|
||||||
lastMessage.content = content;
|
lastMessage.content = content;
|
||||||
lastMessage.attachments = attachments;
|
lastMessage.attachments = attachments;
|
||||||
@@ -126,39 +123,23 @@ 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({MessagePartType::Text, textBetween, ""});
|
parts.append({MessagePart::Text, textBetween, ""});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
parts.append({MessagePartType::Code, match.captured(2).trimmed(), match.captured(1)});
|
parts.append({MessagePart::Code, match.captured(2).trimmed(), match.captured(1)});
|
||||||
lastIndex = match.capturedEnd();
|
lastIndex = match.capturedEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
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]*)$");
|
parts.append({MessagePart::Text, remainingText, ""});
|
||||||
auto unclosedMatch = unclosedBlockRegex.match(remainingText);
|
|
||||||
|
|
||||||
if (unclosedMatch.hasMatch()) {
|
|
||||||
QString beforeCodeBlock = remainingText.left(unclosedMatch.capturedStart()).trimmed();
|
|
||||||
if (!beforeCodeBlock.isEmpty()) {
|
|
||||||
parts.append({MessagePartType::Text, beforeCodeBlock, ""});
|
|
||||||
}
|
|
||||||
|
|
||||||
parts.append(
|
|
||||||
{MessagePartType::Code,
|
|
||||||
unclosedMatch.captured(2).trimmed(),
|
|
||||||
unclosedMatch.captured(1)});
|
|
||||||
} else if (!remainingText.isEmpty()) {
|
|
||||||
parts.append({MessagePartType::Text, remainingText, ""});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,69 +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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ChatModel::addToolExecutionStatus(
|
|
||||||
const QString &requestId, const QString &toolId, const QString &toolName)
|
|
||||||
{
|
|
||||||
QString content = toolName;
|
|
||||||
|
|
||||||
LOG_MESSAGE(QString("Adding tool execution status: requestId=%1, toolId=%2, toolName=%3")
|
|
||||||
.arg(requestId, toolId, toolName));
|
|
||||||
|
|
||||||
if (!m_messages.isEmpty() && !toolId.isEmpty() && m_messages.last().id == toolId
|
|
||||||
&& m_messages.last().role == ChatRole::Tool) {
|
|
||||||
Message &lastMessage = m_messages.last();
|
|
||||||
lastMessage.content = content;
|
|
||||||
LOG_MESSAGE(QString("Updated existing tool message at index %1").arg(m_messages.size() - 1));
|
|
||||||
emit dataChanged(index(m_messages.size() - 1), index(m_messages.size() - 1));
|
|
||||||
} else {
|
|
||||||
beginInsertRows(QModelIndex(), m_messages.size(), m_messages.size());
|
|
||||||
Message newMessage{ChatRole::Tool, content, toolId};
|
|
||||||
m_messages.append(newMessage);
|
|
||||||
endInsertRows();
|
|
||||||
LOG_MESSAGE(QString("Created new tool message at index %1 with toolId=%2")
|
|
||||||
.arg(m_messages.size() - 1)
|
|
||||||
.arg(toolId));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ChatModel::updateToolResult(
|
|
||||||
const QString &requestId, const QString &toolId, const QString &toolName, const QString &result)
|
|
||||||
{
|
|
||||||
if (m_messages.isEmpty() || toolId.isEmpty()) {
|
|
||||||
LOG_MESSAGE(QString("Cannot update tool result: messages empty=%1, toolId empty=%2")
|
|
||||||
.arg(m_messages.isEmpty())
|
|
||||||
.arg(toolId.isEmpty()));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_MESSAGE(
|
|
||||||
QString("Updating tool result: requestId=%1, toolId=%2, toolName=%3, result length=%4")
|
|
||||||
.arg(requestId, toolId, toolName)
|
|
||||||
.arg(result.length()));
|
|
||||||
|
|
||||||
for (int i = m_messages.size() - 1; i >= 0; --i) {
|
|
||||||
if (m_messages[i].id == toolId && m_messages[i].role == ChatRole::Tool) {
|
|
||||||
m_messages[i].content = toolName + "\n" + result;
|
|
||||||
emit dataChanged(index(i), index(i));
|
|
||||||
LOG_MESSAGE(QString("Updated tool result at index %1").arg(i));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_MESSAGE(QString("WARNING: Tool message with requestId=%1 toolId=%2 not found!")
|
|
||||||
.arg(requestId, toolId));
|
|
||||||
}
|
|
||||||
|
|
||||||
} // 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.
|
||||||
*
|
*
|
||||||
@@ -37,11 +37,10 @@ class ChatModel : public QAbstractListModel
|
|||||||
QML_ELEMENT
|
QML_ELEMENT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
enum ChatRole { System, User, Assistant, Tool };
|
enum ChatRole { System, User, Assistant };
|
||||||
Q_ENUM(ChatRole)
|
Q_ENUM(ChatRole)
|
||||||
|
|
||||||
enum Roles { RoleType = Qt::UserRole, Content, Attachments };
|
enum Roles { RoleType = Qt::UserRole, Content, Attachments };
|
||||||
Q_ENUM(Roles)
|
|
||||||
|
|
||||||
struct Message
|
struct Message
|
||||||
{
|
{
|
||||||
@@ -74,15 +73,6 @@ public:
|
|||||||
QString currentModel() const;
|
QString currentModel() const;
|
||||||
QString lastMessageId() const;
|
QString lastMessageId() const;
|
||||||
|
|
||||||
Q_INVOKABLE void resetModelTo(int index);
|
|
||||||
|
|
||||||
void addToolExecutionStatus(
|
|
||||||
const QString &requestId, const QString &toolId, const QString &toolName);
|
|
||||||
void updateToolResult(
|
|
||||||
const QString &requestId,
|
|
||||||
const QString &toolId,
|
|
||||||
const QString &toolName,
|
|
||||||
const QString &result);
|
|
||||||
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,21 +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_isRequestInProgress(false)
|
|
||||||
{
|
{
|
||||||
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,
|
||||||
@@ -67,10 +70,6 @@ ChatRootView::ChatRootView(QQuickItem *parent)
|
|||||||
this,
|
this,
|
||||||
&ChatRootView::autosave);
|
&ChatRootView::autosave);
|
||||||
|
|
||||||
connect(m_clientInterface, &ClientInterface::messageReceivedCompletely, this, [this]() {
|
|
||||||
this->setRequestProgressStatus(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
connect(
|
connect(
|
||||||
m_clientInterface,
|
m_clientInterface,
|
||||||
&ClientInterface::messageReceivedCompletely,
|
&ClientInterface::messageReceivedCompletely,
|
||||||
@@ -80,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();
|
||||||
|
|
||||||
@@ -107,36 +100,6 @@ ChatRootView::ChatRootView(QQuickItem *parent)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
connect(
|
|
||||||
&Settings::chatAssistantSettings().textFontFamily,
|
|
||||||
&Utils::BaseAspect::changed,
|
|
||||||
this,
|
|
||||||
&ChatRootView::textFamilyChanged);
|
|
||||||
connect(
|
|
||||||
&Settings::chatAssistantSettings().codeFontFamily,
|
|
||||||
&Utils::BaseAspect::changed,
|
|
||||||
this,
|
|
||||||
&ChatRootView::codeFamilyChanged);
|
|
||||||
connect(
|
|
||||||
&Settings::chatAssistantSettings().textFontSize,
|
|
||||||
&Utils::BaseAspect::changed,
|
|
||||||
this,
|
|
||||||
&ChatRootView::textFontSizeChanged);
|
|
||||||
connect(
|
|
||||||
&Settings::chatAssistantSettings().codeFontSize,
|
|
||||||
&Utils::BaseAspect::changed,
|
|
||||||
this,
|
|
||||||
&ChatRootView::codeFontSizeChanged);
|
|
||||||
connect(
|
|
||||||
&Settings::chatAssistantSettings().textFormat,
|
|
||||||
&Utils::BaseAspect::changed,
|
|
||||||
this,
|
|
||||||
&ChatRootView::textFormatChanged);
|
|
||||||
connect(m_clientInterface, &ClientInterface::errorOccurred, this, [this](const QString &error) {
|
|
||||||
this->setRequestProgressStatus(false);
|
|
||||||
m_lastErrorMessage = error;
|
|
||||||
emit lastErrorMessageChanged();
|
|
||||||
});
|
|
||||||
|
|
||||||
updateInputTokensCount();
|
updateInputTokensCount();
|
||||||
}
|
}
|
||||||
@@ -166,7 +129,6 @@ void ChatRootView::sendMessage(const QString &message)
|
|||||||
|
|
||||||
m_clientInterface->sendMessage(message, m_attachmentFiles, m_linkedFiles);
|
m_clientInterface->sendMessage(message, m_attachmentFiles, m_linkedFiles);
|
||||||
clearAttachmentFiles();
|
clearAttachmentFiles();
|
||||||
setRequestProgressStatus(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChatRootView::copyToClipboard(const QString &text)
|
void ChatRootView::copyToClipboard(const QString &text)
|
||||||
@@ -177,7 +139,6 @@ void ChatRootView::copyToClipboard(const QString &text)
|
|||||||
void ChatRootView::cancelRequest()
|
void ChatRootView::cancelRequest()
|
||||||
{
|
{
|
||||||
m_clientInterface->cancelRequest();
|
m_clientInterface->cancelRequest();
|
||||||
setRequestProgressStatus(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChatRootView::clearAttachmentFiles()
|
void ChatRootView::clearAttachmentFiles()
|
||||||
@@ -202,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);
|
||||||
@@ -389,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) {
|
||||||
@@ -423,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) {
|
||||||
@@ -476,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);
|
||||||
@@ -491,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
|
||||||
}
|
}
|
||||||
@@ -533,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();
|
||||||
}
|
}
|
||||||
@@ -546,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();
|
||||||
}
|
}
|
||||||
@@ -574,62 +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;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString ChatRootView::textFontFamily() const
|
|
||||||
{
|
|
||||||
return Settings::chatAssistantSettings().textFontFamily.stringValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
QString ChatRootView::codeFontFamily() const
|
|
||||||
{
|
|
||||||
return Settings::chatAssistantSettings().codeFontFamily.stringValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
int ChatRootView::codeFontSize() const
|
|
||||||
{
|
|
||||||
return Settings::chatAssistantSettings().codeFontSize();
|
|
||||||
}
|
|
||||||
|
|
||||||
int ChatRootView::textFontSize() const
|
|
||||||
{
|
|
||||||
return Settings::chatAssistantSettings().textFontSize();
|
|
||||||
}
|
|
||||||
|
|
||||||
int ChatRootView::textFormat() const
|
|
||||||
{
|
|
||||||
return Settings::chatAssistantSettings().textFormat();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ChatRootView::isRequestInProgress() const
|
|
||||||
{
|
|
||||||
return m_isRequestInProgress;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ChatRootView::setRequestProgressStatus(bool state)
|
|
||||||
{
|
|
||||||
if (m_isRequestInProgress == state)
|
|
||||||
return;
|
|
||||||
m_isRequestInProgress = state;
|
|
||||||
emit isRequestInProgressChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
QString ChatRootView::lastErrorMessage() const
|
|
||||||
{
|
|
||||||
return m_lastErrorMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // 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 {
|
||||||
@@ -38,14 +37,6 @@ class ChatRootView : public QQuickItem
|
|||||||
Q_PROPERTY(QStringList linkedFiles READ linkedFiles NOTIFY linkedFilesChanged FINAL)
|
Q_PROPERTY(QStringList linkedFiles READ linkedFiles NOTIFY linkedFilesChanged FINAL)
|
||||||
Q_PROPERTY(int inputTokensCount READ inputTokensCount NOTIFY inputTokensCountChanged FINAL)
|
Q_PROPERTY(int inputTokensCount READ inputTokensCount NOTIFY inputTokensCountChanged FINAL)
|
||||||
Q_PROPERTY(QString chatFileName READ chatFileName NOTIFY chatFileNameChanged FINAL)
|
Q_PROPERTY(QString chatFileName READ chatFileName NOTIFY chatFileNameChanged FINAL)
|
||||||
Q_PROPERTY(QString textFontFamily READ textFontFamily NOTIFY textFamilyChanged FINAL)
|
|
||||||
Q_PROPERTY(QString codeFontFamily READ codeFontFamily NOTIFY codeFamilyChanged FINAL)
|
|
||||||
Q_PROPERTY(int codeFontSize READ codeFontSize NOTIFY codeFontSizeChanged FINAL)
|
|
||||||
Q_PROPERTY(int textFontSize READ textFontSize NOTIFY textFontSizeChanged FINAL)
|
|
||||||
Q_PROPERTY(int textFormat READ textFormat NOTIFY textFormatChanged FINAL)
|
|
||||||
Q_PROPERTY(
|
|
||||||
bool isRequestInProgress READ isRequestInProgress NOTIFY isRequestInProgressChanged FINAL)
|
|
||||||
Q_PROPERTY(QString lastErrorMessage READ lastErrorMessage NOTIFY lastErrorMessageChanged FINAL)
|
|
||||||
|
|
||||||
QML_ELEMENT
|
QML_ELEMENT
|
||||||
|
|
||||||
@@ -74,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;
|
||||||
@@ -86,19 +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);
|
|
||||||
|
|
||||||
QString textFontFamily() const;
|
|
||||||
QString codeFontFamily() const;
|
|
||||||
|
|
||||||
int codeFontSize() const;
|
|
||||||
int textFontSize() const;
|
|
||||||
int textFormat() const;
|
|
||||||
|
|
||||||
bool isRequestInProgress() const;
|
|
||||||
void setRequestProgressStatus(bool state);
|
|
||||||
|
|
||||||
QString lastErrorMessage() const;
|
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void sendMessage(const QString &message);
|
void sendMessage(const QString &message);
|
||||||
@@ -115,22 +95,12 @@ signals:
|
|||||||
void inputTokensCountChanged();
|
void inputTokensCountChanged();
|
||||||
void isSyncOpenFilesChanged();
|
void isSyncOpenFilesChanged();
|
||||||
void chatFileNameChanged();
|
void chatFileNameChanged();
|
||||||
void textFamilyChanged();
|
|
||||||
void codeFamilyChanged();
|
|
||||||
void codeFontSizeChanged();
|
|
||||||
void textFontSizeChanged();
|
|
||||||
void textFormatChanged();
|
|
||||||
void chatRequestStarted();
|
|
||||||
void isRequestInProgressChanged();
|
|
||||||
|
|
||||||
void lastErrorMessageChanged();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QString getChatsHistoryDir() const;
|
QString getChatsHistoryDir() const;
|
||||||
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;
|
||||||
@@ -140,8 +110,6 @@ private:
|
|||||||
int m_inputTokensCount{0};
|
int m_inputTokensCount{0};
|
||||||
bool m_isSyncOpenFiles;
|
bool m_isSyncOpenFiles;
|
||||||
QList<Core::IEditor *> m_currentEditors;
|
QList<Core::IEditor *> m_currentEditors;
|
||||||
bool m_isRequestInProgress;
|
|
||||||
QString m_lastErrorMessage;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // 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.
|
||||||
*
|
*
|
||||||
@@ -29,40 +29,4 @@ void ChatUtils::copyToClipboard(const QString &text)
|
|||||||
QGuiApplication::clipboard()->setText(text);
|
QGuiApplication::clipboard()->setText(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString ChatUtils::getSafeMarkdownText(const QString &text) const
|
|
||||||
{
|
|
||||||
if (text.isEmpty()) {
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool needsSanitization = false;
|
|
||||||
for (const QChar &ch : text) {
|
|
||||||
if (ch.isNull() || (!ch.isPrint() && ch != '\n' && ch != '\t' && ch != '\r' && ch != ' ')) {
|
|
||||||
needsSanitization = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!needsSanitization) {
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString safeText;
|
|
||||||
safeText.reserve(text.size());
|
|
||||||
|
|
||||||
for (QChar ch : text) {
|
|
||||||
if (ch.isNull()) {
|
|
||||||
safeText.append(' ');
|
|
||||||
} else if (ch == '\n' || ch == '\t' || ch == '\r' || ch == ' ') {
|
|
||||||
safeText.append(ch);
|
|
||||||
} else if (ch.isPrint()) {
|
|
||||||
safeText.append(ch);
|
|
||||||
} else {
|
|
||||||
safeText.append(QChar(0xFFFD));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return safeText;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // 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.
|
||||||
*
|
*
|
||||||
@@ -34,7 +34,6 @@ public:
|
|||||||
: QObject(parent) {};
|
: QObject(parent) {};
|
||||||
|
|
||||||
Q_INVOKABLE void copyToClipboard(const QString &text);
|
Q_INVOKABLE void copyToClipboard(const QString &text);
|
||||||
Q_INVOKABLE QString getSafeMarkdownText(const QString &text) const;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace QodeAssist::Chat
|
} // namespace QodeAssist::Chat
|
||||||
|
|||||||
@@ -1,106 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2024-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 "ChatView.hpp"
|
|
||||||
|
|
||||||
#include <QQmlContext>
|
|
||||||
#include <QQmlEngine>
|
|
||||||
#include <QSettings>
|
|
||||||
#include <QVariantMap>
|
|
||||||
|
|
||||||
#include <coreplugin/actionmanager/actionmanager.h>
|
|
||||||
#include <logger/Logger.hpp>
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
constexpr Qt::WindowFlags baseFlags = Qt::Window | Qt::WindowTitleHint | Qt::WindowSystemMenuHint
|
|
||||||
| Qt::WindowMinimizeButtonHint | Qt::WindowMaximizeButtonHint
|
|
||||||
| Qt::WindowCloseButtonHint;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace QodeAssist::Chat {
|
|
||||||
|
|
||||||
ChatView::ChatView()
|
|
||||||
: m_isPin(false)
|
|
||||||
{
|
|
||||||
setTitle("QodeAssist Chat");
|
|
||||||
engine()->rootContext()->setContextProperty("_chatview", this);
|
|
||||||
setSource(QUrl("qrc:/qt/qml/ChatView/qml/RootItem.qml"));
|
|
||||||
setResizeMode(QQuickView::SizeRootObjectToView);
|
|
||||||
setMinimumSize({400, 300});
|
|
||||||
setFlags(baseFlags);
|
|
||||||
|
|
||||||
if (auto action = Core::ActionManager::command("QodeAssist.CloseChatView")) {
|
|
||||||
m_closeShortcut = new QShortcut(action->keySequence(), this);
|
|
||||||
connect(m_closeShortcut, &QShortcut::activated, this, &QQuickView::close);
|
|
||||||
|
|
||||||
connect(action, &Core::Command::keySequenceChanged, this, [action, this]() {
|
|
||||||
if (m_closeShortcut) {
|
|
||||||
m_closeShortcut->setKey(action->keySequence());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
restoreSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ChatView::closeEvent(QCloseEvent *event)
|
|
||||||
{
|
|
||||||
saveSettings();
|
|
||||||
event->accept();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ChatView::saveSettings()
|
|
||||||
{
|
|
||||||
QSettings settings;
|
|
||||||
settings.setValue("QodeAssist/ChatView/geometry", geometry());
|
|
||||||
settings.setValue("QodeAssist/ChatView/pinned", m_isPin);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ChatView::restoreSettings()
|
|
||||||
{
|
|
||||||
QSettings settings;
|
|
||||||
const QRect savedGeometry
|
|
||||||
= settings.value("QodeAssist/ChatView/geometry", QRect(100, 100, 800, 600)).toRect();
|
|
||||||
setGeometry(savedGeometry);
|
|
||||||
|
|
||||||
const bool pinned = settings.value("QodeAssist/ChatView/pinned", false).toBool();
|
|
||||||
setIsPin(pinned);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ChatView::isPin() const
|
|
||||||
{
|
|
||||||
return m_isPin;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ChatView::setIsPin(bool newIsPin)
|
|
||||||
{
|
|
||||||
if (m_isPin == newIsPin)
|
|
||||||
return;
|
|
||||||
m_isPin = newIsPin;
|
|
||||||
|
|
||||||
if (m_isPin) {
|
|
||||||
setFlags(baseFlags | Qt::WindowStaysOnTopHint);
|
|
||||||
} else {
|
|
||||||
setFlags(baseFlags);
|
|
||||||
}
|
|
||||||
|
|
||||||
emit isPinChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace QodeAssist::Chat
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2024-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 <QQuickView>
|
|
||||||
#include <QShortcut>
|
|
||||||
|
|
||||||
namespace QodeAssist::Chat {
|
|
||||||
|
|
||||||
class ChatView : public QQuickView
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
Q_PROPERTY(bool isPin READ isPin WRITE setIsPin NOTIFY isPinChanged FINAL)
|
|
||||||
public:
|
|
||||||
ChatView();
|
|
||||||
|
|
||||||
bool isPin() const;
|
|
||||||
void setIsPin(bool newIsPin);
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void isPinChanged();
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void closeEvent(QCloseEvent *event) override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
void saveSettings();
|
|
||||||
void restoreSettings();
|
|
||||||
|
|
||||||
bool m_isPin;
|
|
||||||
QShortcut *m_closeShortcut;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // 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.
|
||||||
*
|
*
|
||||||
@@ -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,38 +19,49 @@
|
|||||||
|
|
||||||
#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>
|
||||||
#include <coreplugin/idocument.h>
|
#include <coreplugin/idocument.h>
|
||||||
#include <projectexplorer/project.h>
|
|
||||||
#include <projectexplorer/projectexplorer.h>
|
|
||||||
#include <projectexplorer/projectmanager.h>
|
|
||||||
|
|
||||||
#include <texteditor/textdocument.h>
|
#include <texteditor/textdocument.h>
|
||||||
#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"
|
||||||
#include "RequestConfig.hpp"
|
|
||||||
#include <RulesLoader.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_chatModel(chatModel)
|
, m_chatModel(chatModel)
|
||||||
, m_promptProvider(promptProvider)
|
{
|
||||||
, m_contextManager(new Context::ContextManager(this))
|
connect(m_requestHandler,
|
||||||
{}
|
&LLMCore::RequestHandler::completionReceived,
|
||||||
|
this,
|
||||||
|
[this](const QString &completion, const QJsonObject &request, bool isComplete) {
|
||||||
|
handleLLMResponse(completion, request, isComplete);
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(m_requestHandler,
|
||||||
|
&LLMCore::RequestHandler::requestFinished,
|
||||||
|
this,
|
||||||
|
[this](const QString &, bool success, const QString &errorString) {
|
||||||
|
if (!success) {
|
||||||
|
emit errorOccurred(errorString);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
ClientInterface::~ClientInterface() = default;
|
ClientInterface::~ClientInterface() = default;
|
||||||
|
|
||||||
@@ -58,9 +69,8 @@ void ClientInterface::sendMessage(
|
|||||||
const QString &message, const QList<QString> &attachments, const QList<QString> &linkedFiles)
|
const QString &message, const QList<QString> &attachments, const QList<QString> &linkedFiles)
|
||||||
{
|
{
|
||||||
cancelRequest();
|
cancelRequest();
|
||||||
m_accumulatedResponses.clear();
|
|
||||||
|
|
||||||
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();
|
||||||
@@ -74,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));
|
||||||
@@ -82,98 +93,52 @@ void ClientInterface::sendMessage(
|
|||||||
}
|
}
|
||||||
|
|
||||||
LLMCore::ContextData context;
|
LLMCore::ContextData context;
|
||||||
|
context.prefix = message;
|
||||||
|
context.suffix = "";
|
||||||
|
|
||||||
if (chatAssistantSettings.useSystemPrompt()) {
|
QString systemPrompt;
|
||||||
QString systemPrompt = chatAssistantSettings.systemPrompt();
|
if (chatAssistantSettings.useSystemPrompt())
|
||||||
|
systemPrompt = chatAssistantSettings.systemPrompt();
|
||||||
auto project = LLMCore::RulesLoader::getActiveProject();
|
|
||||||
if (project) {
|
|
||||||
QString projectRules
|
|
||||||
= LLMCore::RulesLoader::loadRulesForProject(project, LLMCore::RulesContext::Chat);
|
|
||||||
|
|
||||||
if (!projectRules.isEmpty()) {
|
|
||||||
systemPrompt += "\n\n# Project Rules\n\n" + projectRules;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 = QString{"streamGenerateContent?alt=sse"};
|
config.providerRequest = providerRequest;
|
||||||
config.url = QUrl(QString("%1/models/%2:%3")
|
config.multiLineCompletion = false;
|
||||||
.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", true}};
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
||||||
|
|
||||||
QString requestId = QUuid::createUuid().toString();
|
auto errors = config.provider->validateRequest(config.providerRequest, promptTemplate->type());
|
||||||
QJsonObject request{{"id", requestId}};
|
if (!errors.isEmpty()) {
|
||||||
|
LOG_MESSAGE("Validate errors for chat request:");
|
||||||
|
LOG_MESSAGES(errors);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
m_activeRequests[requestId] = {request, provider};
|
m_requestHandler->sendLLMRequest(config, request);
|
||||||
|
|
||||||
connect(
|
|
||||||
provider,
|
|
||||||
&LLMCore::Provider::partialResponseReceived,
|
|
||||||
this,
|
|
||||||
&ClientInterface::handlePartialResponse,
|
|
||||||
Qt::UniqueConnection);
|
|
||||||
connect(
|
|
||||||
provider,
|
|
||||||
&LLMCore::Provider::fullResponseReceived,
|
|
||||||
this,
|
|
||||||
&ClientInterface::handleFullResponse,
|
|
||||||
Qt::UniqueConnection);
|
|
||||||
connect(
|
|
||||||
provider,
|
|
||||||
&LLMCore::Provider::requestFailed,
|
|
||||||
this,
|
|
||||||
&ClientInterface::handleRequestFailed,
|
|
||||||
Qt::UniqueConnection);
|
|
||||||
connect(
|
|
||||||
provider,
|
|
||||||
&LLMCore::Provider::toolExecutionStarted,
|
|
||||||
m_chatModel,
|
|
||||||
&ChatModel::addToolExecutionStatus,
|
|
||||||
Qt::UniqueConnection);
|
|
||||||
connect(
|
|
||||||
provider,
|
|
||||||
&LLMCore::Provider::toolExecutionCompleted,
|
|
||||||
m_chatModel,
|
|
||||||
&ChatModel::updateToolResult,
|
|
||||||
Qt::UniqueConnection);
|
|
||||||
connect(
|
|
||||||
provider,
|
|
||||||
&LLMCore::Provider::continuationStarted,
|
|
||||||
this,
|
|
||||||
&ClientInterface::handleCleanAccumulatedData,
|
|
||||||
Qt::UniqueConnection);
|
|
||||||
|
|
||||||
provider->sendRequest(requestId, config.url, config.providerRequest);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClientInterface::clearMessages()
|
void ClientInterface::clearMessages()
|
||||||
@@ -184,21 +149,13 @@ void ClientInterface::clearMessages()
|
|||||||
|
|
||||||
void ClientInterface::cancelRequest()
|
void ClientInterface::cancelRequest()
|
||||||
{
|
{
|
||||||
for (auto it = m_activeRequests.begin(); it != m_activeRequests.end(); ++it) {
|
auto id = m_chatModel->lastMessageId();
|
||||||
const RequestContext &ctx = it.value();
|
m_requestHandler->cancelRequest(id);
|
||||||
if (ctx.provider) {
|
|
||||||
ctx.provider->cancelRequest(it.key());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
m_activeRequests.clear();
|
|
||||||
m_accumulatedResponses.clear();
|
|
||||||
|
|
||||||
LOG_MESSAGE("All requests cancelled and state cleared");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
||||||
|
|
||||||
@@ -229,81 +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;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ClientInterface::handlePartialResponse(const QString &requestId, const QString &partialText)
|
|
||||||
{
|
|
||||||
auto it = m_activeRequests.find(requestId);
|
|
||||||
if (it == m_activeRequests.end())
|
|
||||||
return;
|
|
||||||
|
|
||||||
m_accumulatedResponses[requestId] += partialText;
|
|
||||||
|
|
||||||
const RequestContext &ctx = it.value();
|
|
||||||
handleLLMResponse(m_accumulatedResponses[requestId], ctx.originalRequest, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ClientInterface::handleFullResponse(const QString &requestId, const QString &fullText)
|
|
||||||
{
|
|
||||||
auto it = m_activeRequests.find(requestId);
|
|
||||||
if (it == m_activeRequests.end())
|
|
||||||
return;
|
|
||||||
|
|
||||||
const RequestContext &ctx = it.value();
|
|
||||||
|
|
||||||
QString finalText = !fullText.isEmpty() ? fullText : m_accumulatedResponses[requestId];
|
|
||||||
handleLLMResponse(finalText, ctx.originalRequest, true);
|
|
||||||
|
|
||||||
m_activeRequests.erase(it);
|
|
||||||
m_accumulatedResponses.remove(requestId);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ClientInterface::handleRequestFailed(const QString &requestId, const QString &error)
|
|
||||||
{
|
|
||||||
auto it = m_activeRequests.find(requestId);
|
|
||||||
if (it == m_activeRequests.end())
|
|
||||||
return;
|
|
||||||
|
|
||||||
LOG_MESSAGE(QString("Chat request %1 failed: %2").arg(requestId, error));
|
|
||||||
emit errorOccurred(error);
|
|
||||||
|
|
||||||
m_activeRequests.erase(it);
|
|
||||||
m_accumulatedResponses.remove(requestId);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ClientInterface::handleCleanAccumulatedData(const QString &requestId)
|
|
||||||
{
|
|
||||||
m_accumulatedResponses[requestId].clear();
|
|
||||||
LOG_MESSAGE(QString("Cleared accumulated responses for continuation request %1").arg(requestId));
|
|
||||||
}
|
|
||||||
|
|
||||||
} // 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.
|
||||||
*
|
*
|
||||||
@@ -24,9 +24,7 @@
|
|||||||
#include <QVector>
|
#include <QVector>
|
||||||
|
|
||||||
#include "ChatModel.hpp"
|
#include "ChatModel.hpp"
|
||||||
#include "Provider.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,36 +43,19 @@ 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();
|
||||||
|
|
||||||
private slots:
|
|
||||||
void handlePartialResponse(const QString &requestId, const QString &partialText);
|
|
||||||
void handleFullResponse(const QString &requestId, const QString &fullText);
|
|
||||||
void handleRequestFailed(const QString &requestId, const QString &error);
|
|
||||||
void handleCleanAccumulatedData(const QString &requestId);
|
|
||||||
|
|
||||||
private:
|
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;
|
||||||
|
|
||||||
struct RequestContext
|
LLMCore::RequestHandler *m_requestHandler;
|
||||||
{
|
|
||||||
QJsonObject originalRequest;
|
|
||||||
LLMCore::Provider *provider;
|
|
||||||
};
|
|
||||||
|
|
||||||
LLMCore::IPromptProvider *m_promptProvider = nullptr;
|
|
||||||
ChatModel *m_chatModel;
|
ChatModel *m_chatModel;
|
||||||
Context::ContextManager *m_contextManager;
|
|
||||||
|
|
||||||
QHash<QString, RequestContext> m_activeRequests;
|
|
||||||
QHash<QString, QString> m_accumulatedResponses;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // 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.
|
||||||
*
|
*
|
||||||
@@ -19,24 +19,33 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <QObject>
|
#include <qobject.h>
|
||||||
#include <QtQmlIntegration>
|
#include <qqmlintegration.h>
|
||||||
|
|
||||||
#include "ChatData.hpp"
|
|
||||||
|
|
||||||
namespace QodeAssist::Chat {
|
namespace QodeAssist::Chat {
|
||||||
|
Q_NAMESPACE
|
||||||
|
|
||||||
class MessagePart
|
class MessagePart
|
||||||
{
|
{
|
||||||
Q_GADGET
|
Q_GADGET
|
||||||
Q_PROPERTY(MessagePartType type MEMBER type CONSTANT FINAL)
|
Q_PROPERTY(PartType type MEMBER type CONSTANT FINAL)
|
||||||
Q_PROPERTY(QString text MEMBER text CONSTANT FINAL)
|
Q_PROPERTY(QString text MEMBER text CONSTANT FINAL)
|
||||||
Q_PROPERTY(QString language MEMBER language CONSTANT FINAL)
|
Q_PROPERTY(QString language MEMBER language CONSTANT FINAL)
|
||||||
QML_VALUE_TYPE(messagePart)
|
QML_VALUE_TYPE(messagePart)
|
||||||
public:
|
public:
|
||||||
MessagePartType type;
|
enum PartType { Code, Text };
|
||||||
|
Q_ENUM(PartType)
|
||||||
|
|
||||||
|
PartType type;
|
||||||
QString text;
|
QString text;
|
||||||
QString language;
|
QString language;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class MessagePartType : public MessagePart
|
||||||
|
{
|
||||||
|
Q_GADGET
|
||||||
|
};
|
||||||
|
|
||||||
|
QML_NAMED_ELEMENT(MessagePart)
|
||||||
|
QML_FOREIGN_NAMESPACE(QodeAssist::Chat::MessagePartType)
|
||||||
} // namespace QodeAssist::Chat
|
} // namespace QodeAssist::Chat
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<g clip-path="url(#clip0_5_6)">
|
|
||||||
<path d="M21.6 0H2.4C1.08 0 0 1.08 0 2.4V16.8C0 18.12 1.08 19.2 2.4 19.2H7.2V22.8C7.2 23.46 7.74 24 8.4 24H9C9.3 24 9.6 23.88 9.84 23.652L14.28 19.2H21.6C22.92 19.2 24 18.12 24 16.8V2.4C24 1.08 22.92 0 21.6 0ZM21.6 16.8H13.44L8.76 21.48L8.4 21.6V16.8H2.4V2.4H21.6V16.8Z" fill="black"/>
|
|
||||||
</g>
|
|
||||||
<defs>
|
|
||||||
<clipPath id="clip0_5_6">
|
|
||||||
<rect width="24" height="24" fill="white"/>
|
|
||||||
</clipPath>
|
|
||||||
</defs>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 523 B |
@@ -1,10 +0,0 @@
|
|||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<g clip-path="url(#clip0_5_17)">
|
|
||||||
<path d="M21.6 0H2.4C1.08 0 0 1.08 0 2.4V16.8C0 18.12 1.08 19.2 2.4 19.2H7.2V22.8C7.2 23.46 7.74 24 8.4 24H9C9.3 24 9.6 23.88 9.84 23.652L14.28 19.2H21.6C22.92 19.2 24 18.12 24 16.8V2.4C24 1.08 22.92 0 21.6 0ZM21.6 16.8H13.44L8.76 21.48L8.4 21.6V16.8H2.4V2.4H21.6V16.8ZM8.4 6H15.6V13.2H8.4V6Z" fill="black"/>
|
|
||||||
</g>
|
|
||||||
<defs>
|
|
||||||
<clipPath id="clip0_5_17">
|
|
||||||
<rect width="24" height="24" fill="white"/>
|
|
||||||
</clipPath>
|
|
||||||
</defs>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 548 B |
@@ -1,8 +0,0 @@
|
|||||||
<svg width="20" height="44" viewBox="0 0 20 44" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="M16.75 15H3.25C2.00736 15 1 16.0074 1 17.25V39.75C1 40.9926 2.00736 42 3.25 42H16.75C17.9926 42 19 40.9926 19 39.75V17.25C19 16.0074 17.9926 15 16.75 15Z" stroke="black" stroke-width="2"/>
|
|
||||||
<path d="M1.04316 11.015L18.9554 8.90787" stroke="black" stroke-width="2" stroke-linecap="round"/>
|
|
||||||
<path d="M7.19462 10.363L7.02032 8.59516C6.92446 7.62284 8.18688 6.64116 9.8257 6.41365C11.4645 6.18615 12.8838 6.79555 12.9797 7.76787L13.154 9.53573" stroke="black" stroke-width="2"/>
|
|
||||||
<path d="M6 24V34" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
|
|
||||||
<path d="M10 24V34" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
|
|
||||||
<path d="M14 24V34" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 822 B |
@@ -1,12 +0,0 @@
|
|||||||
<svg width="44" height="44" viewBox="0 0 44 44" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<g clip-path="url(#clip0_59_114)">
|
|
||||||
<path d="M2 8H12L16 4H40C42 4 44 6 44 8V36C44 38 42 40 40 40H6C4 40 2 38 2 36V8Z" fill="black" fill-opacity="0.1" stroke="black" stroke-width="3"/>
|
|
||||||
<path d="M25 37C32.732 37 39 30.732 39 23C39 15.268 32.732 9 25 9C17.268 9 11 15.268 11 23C11 30.732 17.268 37 25 37Z" stroke="black" stroke-width="4"/>
|
|
||||||
<path d="M33 35L42 44" stroke="black" stroke-width="4" stroke-linecap="round"/>
|
|
||||||
</g>
|
|
||||||
<defs>
|
|
||||||
<clipPath id="clip0_59_114">
|
|
||||||
<rect width="44" height="44" fill="white"/>
|
|
||||||
</clipPath>
|
|
||||||
</defs>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 624 B |
@@ -1,5 +0,0 @@
|
|||||||
<svg width="20" height="44" viewBox="0 0 20 44" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="M5 8H15" stroke="black" stroke-width="2.5" stroke-linecap="round"/>
|
|
||||||
<path d="M10 16V36" stroke="black" stroke-width="2.5" stroke-linecap="round"/>
|
|
||||||
<path d="M5 21L10 16L15 21" stroke="black" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 370 B |
@@ -1,5 +0,0 @@
|
|||||||
<svg width="20" height="44" viewBox="0 0 20 44" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="M10 8V28" stroke="black" stroke-width="2.5" stroke-linecap="round"/>
|
|
||||||
<path d="M5 23L10 28L15 23" stroke="black" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
||||||
<path d="M5 36H15" stroke="black" stroke-width="2.5" stroke-linecap="round"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 370 B |
@@ -1,5 +0,0 @@
|
|||||||
<svg width="44" height="44" viewBox="0 0 44 44" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="M31 18H13C11.3431 18 10 19.3431 10 21V37C10 38.6569 11.3431 40 13 40H31C32.6569 40 34 38.6569 34 37V21C34 19.3431 32.6569 18 31 18Z" fill="black" fill-opacity="0.3" stroke="black" stroke-width="4"/>
|
|
||||||
<path d="M14 18V10C14 5.6 17.6 2 22 2C26.4 2 30 5.6 30 10V18" stroke="black" stroke-width="4"/>
|
|
||||||
<path d="M22 32C23.6569 32 25 30.6569 25 29C25 27.3431 23.6569 26 22 26C20.3431 26 19 27.3431 19 29C19 30.6569 20.3431 32 22 32Z" fill="black"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 552 B |
@@ -1,5 +0,0 @@
|
|||||||
<svg width="44" height="44" viewBox="0 0 44 44" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="M31 18H13C11.3431 18 10 19.3431 10 21V37C10 38.6569 11.3431 40 13 40H31C32.6569 40 34 38.6569 34 37V21C34 19.3431 32.6569 18 31 18Z" fill="black" fill-opacity="0.1" stroke="black" stroke-width="4"/>
|
|
||||||
<path d="M14 17V9.5C14 5.375 17.15 2 21 2C24.85 2 27.5 2.875 27.5 7" stroke="black" stroke-width="4"/>
|
|
||||||
<path d="M22 32C23.6569 32 25 30.6569 25 29C25 27.3431 23.6569 26 22 26C20.3431 26 19 27.3431 19 29C19 30.6569 20.3431 32 22 32Z" fill="black"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 559 B |
@@ -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 @@ Rectangle {
|
|||||||
id: root
|
id: root
|
||||||
|
|
||||||
property alias text: badgeText.text
|
property alias text: badgeText.text
|
||||||
property alias hovered: mouse.hovered
|
|
||||||
|
|
||||||
implicitWidth: badgeText.implicitWidth + root.radius
|
implicitWidth: badgeText.implicitWidth + root.radius
|
||||||
implicitHeight: badgeText.implicitHeight + 6
|
implicitHeight: badgeText.implicitHeight + 6
|
||||||
@@ -38,10 +37,4 @@ Rectangle {
|
|||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
color: palette.buttonText
|
color: palette.buttonText
|
||||||
}
|
}
|
||||||
|
|
||||||
HoverHandler {
|
|
||||||
id: mouse
|
|
||||||
|
|
||||||
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -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,8 +20,6 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import ChatView
|
import ChatView
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
import UIControls
|
|
||||||
|
|
||||||
import "./dialog"
|
import "./dialog"
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
@@ -29,42 +27,14 @@ Rectangle {
|
|||||||
|
|
||||||
property alias msgModel: msgCreator.model
|
property alias msgModel: msgCreator.model
|
||||||
property alias messageAttachments: attachmentsModel.model
|
property alias messageAttachments: attachmentsModel.model
|
||||||
property string textFontFamily: Qt.application.font.family
|
|
||||||
property string codeFontFamily: {
|
|
||||||
switch (Qt.platform.os) {
|
|
||||||
case "windows":
|
|
||||||
return "Consolas";
|
|
||||||
case "osx":
|
|
||||||
return "Menlo";
|
|
||||||
case "linux":
|
|
||||||
return "DejaVu Sans Mono";
|
|
||||||
default:
|
|
||||||
return "monospace";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
property int textFontSize: Qt.application.font.pointSize
|
|
||||||
property int codeFontSize: Qt.application.font.pointSize
|
|
||||||
property int textFormat: 0
|
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
@@ -86,8 +56,8 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch(modelData.type) {
|
switch(modelData.type) {
|
||||||
case MessagePartType.Text: return textComponent;
|
case MessagePart.Text: return textComponent;
|
||||||
case MessagePartType.Code: return codeBlockComponent;
|
case MessagePart.Code: return codeBlockComponent;
|
||||||
default: return textComponent;
|
default: return textComponent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -143,59 +113,16 @@ 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
|
||||||
verticalAlignment: Text.AlignVCenter
|
verticalAlignment: Text.AlignVCenter
|
||||||
leftPadding: 10
|
leftPadding: 10
|
||||||
text: textFormat == Text.MarkdownText ? utils.getSafeMarkdownText(itemData.text)
|
text: itemData.text
|
||||||
: itemData.text
|
|
||||||
font.family: root.textFontFamily
|
|
||||||
font.pointSize: root.textFontSize
|
|
||||||
textFormat: {
|
|
||||||
if (root.textFormat == 0) {
|
|
||||||
return Text.MarkdownText
|
|
||||||
} else if (root.textFormat == 1) {
|
|
||||||
return Text.RichText
|
|
||||||
} else {
|
|
||||||
return Text.PlainText
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ChatUtils {
|
|
||||||
id: utils
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
component CodeBlockComponent : CodeBlock {
|
component CodeBlockComponent : CodeBlock {
|
||||||
id: codeblock
|
|
||||||
|
|
||||||
required property var itemData
|
required property var itemData
|
||||||
anchors {
|
anchors {
|
||||||
left: parent.left
|
left: parent.left
|
||||||
@@ -206,7 +133,5 @@ Rectangle {
|
|||||||
|
|
||||||
code: itemData.text
|
code: itemData.text
|
||||||
language: itemData.language
|
language: itemData.language
|
||||||
codeFontFamily: root.codeFontFamily
|
|
||||||
codeFontSize: root.codeFontSize
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,8 +22,7 @@ import QtQuick.Controls
|
|||||||
import QtQuick.Controls.Basic as QQC
|
import QtQuick.Controls.Basic as QQC
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
import ChatView
|
import ChatView
|
||||||
import UIControls
|
import "./controls"
|
||||||
import Qt.labs.platform as Platform
|
|
||||||
import "./parts"
|
import "./parts"
|
||||||
|
|
||||||
ChatRootView {
|
ChatRootView {
|
||||||
@@ -71,17 +70,12 @@ ChatRootView {
|
|||||||
loadButton.onClicked: root.showLoadDialog()
|
loadButton.onClicked: root.showLoadDialog()
|
||||||
clearButton.onClicked: root.clearChat()
|
clearButton.onClicked: root.clearChat()
|
||||||
tokensBadge {
|
tokensBadge {
|
||||||
text: qsTr("%1/%2").arg(root.inputTokensCount).arg(root.chatModel.tokensThreshold)
|
text: qsTr("tokens:%1/%2").arg(root.inputTokensCount).arg(root.chatModel.tokensThreshold)
|
||||||
}
|
}
|
||||||
recentPath {
|
recentPath {
|
||||||
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()
|
||||||
pinButton {
|
|
||||||
visible: typeof _chatview !== 'undefined'
|
|
||||||
checked: typeof _chatview !== 'undefined' ? _chatview.isPin : false
|
|
||||||
onCheckedChanged: _chatview.isPin = topBar.pinButton.checked
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ListView {
|
ListView {
|
||||||
@@ -96,13 +90,14 @@ ChatRootView {
|
|||||||
boundsBehavior: Flickable.StopAtBounds
|
boundsBehavior: Flickable.StopAtBounds
|
||||||
cacheBuffer: 2000
|
cacheBuffer: 2000
|
||||||
|
|
||||||
delegate: Loader {
|
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)
|
||||||
sourceComponent: model.roleType === ChatModel.Tool ? toolMessageComponent : chatItemComponent
|
messageAttachments: model.attachments
|
||||||
|
color: model.roleType === ChatModel.User ? palette.alternateBase
|
||||||
|
: palette.base
|
||||||
}
|
}
|
||||||
|
|
||||||
header: Item {
|
header: Item {
|
||||||
@@ -123,36 +118,6 @@ ChatRootView {
|
|||||||
root.scrollToBottom()
|
root.scrollToBottom()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Component {
|
|
||||||
id: chatItemComponent
|
|
||||||
|
|
||||||
ChatItem {
|
|
||||||
msgModel: root.chatModel.processMessageContent(model.content)
|
|
||||||
messageAttachments: model.attachments
|
|
||||||
isUserMessage: model.roleType === ChatModel.User
|
|
||||||
messageIndex: index
|
|
||||||
textFontFamily: root.textFontFamily
|
|
||||||
codeFontFamily: root.codeFontFamily
|
|
||||||
codeFontSize: root.codeFontSize
|
|
||||||
textFontSize: root.textFontSize
|
|
||||||
textFormat: root.textFormat
|
|
||||||
|
|
||||||
onResetChatToMessage: function(idx) {
|
|
||||||
messageInput.text = model.content
|
|
||||||
messageInput.cursorPosition = model.content.length
|
|
||||||
root.chatModel.resetModelTo(idx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
|
||||||
id: toolMessageComponent
|
|
||||||
|
|
||||||
ToolStatusItem {
|
|
||||||
toolContent: model.content
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ScrollView {
|
ScrollView {
|
||||||
@@ -165,9 +130,7 @@ ChatRootView {
|
|||||||
QQC.TextArea {
|
QQC.TextArea {
|
||||||
id: messageInput
|
id: messageInput
|
||||||
|
|
||||||
placeholderText: Qt.platform.os === "osx"
|
placeholderText: qsTr("Type your message here...")
|
||||||
? qsTr("Type your message here... (⌘+↩ to send)")
|
|
||||||
: qsTr("Type your message here... (Ctrl+Enter to send)")
|
|
||||||
placeholderTextColor: palette.mid
|
placeholderTextColor: palette.mid
|
||||||
color: palette.text
|
color: palette.text
|
||||||
background: Rectangle {
|
background: Rectangle {
|
||||||
@@ -190,51 +153,13 @@ ChatRootView {
|
|||||||
|
|
||||||
onTextChanged: root.calculateMessageTokensCount(messageInput.text)
|
onTextChanged: root.calculateMessageTokensCount(messageInput.text)
|
||||||
|
|
||||||
MouseArea {
|
Keys.onPressed: function(event) {
|
||||||
anchors.fill: parent
|
if ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && !(event.modifiers & Qt.ShiftModifier)) {
|
||||||
acceptedButtons: Qt.RightButton
|
root.sendChatMessage()
|
||||||
onClicked: messageContextMenu.open()
|
event.accepted = true;
|
||||||
propagateComposedEvents: true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Platform.Menu {
|
|
||||||
id: messageContextMenu
|
|
||||||
|
|
||||||
Platform.MenuItem {
|
|
||||||
text: qsTr("Cut")
|
|
||||||
enabled: messageInput.selectedText.length > 0
|
|
||||||
onTriggered: messageInput.cut()
|
|
||||||
}
|
|
||||||
|
|
||||||
Platform.MenuItem {
|
|
||||||
text: qsTr("Copy")
|
|
||||||
enabled: messageInput.selectedText.length > 0
|
|
||||||
onTriggered: messageInput.copy()
|
|
||||||
}
|
|
||||||
|
|
||||||
Platform.MenuItem {
|
|
||||||
text: qsTr("Paste")
|
|
||||||
enabled: messageInput.canPaste
|
|
||||||
onTriggered: messageInput.paste()
|
|
||||||
}
|
|
||||||
|
|
||||||
Platform.MenuSeparator {}
|
|
||||||
|
|
||||||
Platform.MenuItem {
|
|
||||||
text: qsTr("Select All")
|
|
||||||
enabled: messageInput.text.length > 0
|
|
||||||
onTriggered: messageInput.selectAll()
|
|
||||||
}
|
|
||||||
|
|
||||||
Platform.MenuSeparator {}
|
|
||||||
|
|
||||||
Platform.MenuItem {
|
|
||||||
text: qsTr("Clear")
|
|
||||||
enabled: messageInput.text.length > 0
|
|
||||||
onTriggered: messageInput.clear()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AttachedFilesPlace {
|
AttachedFilesPlace {
|
||||||
@@ -265,30 +190,16 @@ ChatRootView {
|
|||||||
Layout.preferredWidth: parent.width
|
Layout.preferredWidth: parent.width
|
||||||
Layout.preferredHeight: 40
|
Layout.preferredHeight: 40
|
||||||
|
|
||||||
sendButton.onClicked: !root.isRequestInProgress ? root.sendChatMessage()
|
sendButton.onClicked: root.sendChatMessage()
|
||||||
: root.cancelRequest()
|
stopButton.onClicked: root.cancelRequest()
|
||||||
sendButton.icon.source: !root.isRequestInProgress ? "qrc:/qt/qml/ChatView/icons/chat-icon.svg"
|
|
||||||
: "qrc:/qt/qml/ChatView/icons/chat-pause-icon.svg"
|
|
||||||
sendButton.ToolTip.text: !root.isRequestInProgress ? qsTr("Send message to LLM %1").arg(Qt.platform.os === "osx" ? "Cmd+Return" : "Ctrl+Return")
|
|
||||||
: qsTr("Stop")
|
|
||||||
syncOpenFiles {
|
syncOpenFiles {
|
||||||
checked: root.isSyncOpenFiles
|
checked: root.isSyncOpenFiles
|
||||||
onCheckedChanged: root.setIsSyncOpenFiles(bottomBar.syncOpenFiles.checked)
|
onCheckedChanged: root.setIsSyncOpenFiles(bottomBar.syncOpenFiles.checked)
|
||||||
}
|
}
|
||||||
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()
|
||||||
|
|
||||||
Shortcut {
|
|
||||||
id: sendMessageShortcut
|
|
||||||
|
|
||||||
sequence: "Ctrl+Return"
|
|
||||||
context: Qt.WindowShortcut
|
|
||||||
onActivated: {
|
|
||||||
if (messageInput.activeFocus && !Qt.inputMethod.visible) {
|
|
||||||
root.sendChatMessage()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -307,22 +218,4 @@ ChatRootView {
|
|||||||
messageInput.text = ""
|
messageInput.text = ""
|
||||||
scrollToBottom()
|
scrollToBottom()
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorToast {
|
|
||||||
id: errorToast
|
|
||||||
z: 1000
|
|
||||||
}
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: root
|
|
||||||
function onLastErrorMessageChanged() {
|
|
||||||
if (root.lastErrorMessage.length > 0) {
|
|
||||||
errorToast.show(root.lastErrorMessage)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component.onCompleted: {
|
|
||||||
messageInput.forceActiveFocus()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,159 +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 Qt.labs.platform as Platform
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
property string toolContent: ""
|
|
||||||
property bool expanded: false
|
|
||||||
|
|
||||||
readonly property int firstNewline: toolContent.indexOf('\n')
|
|
||||||
readonly property string toolName: firstNewline > 0 ? toolContent.substring(0, firstNewline) : toolContent
|
|
||||||
readonly property string toolResult: firstNewline > 0 ? toolContent.substring(firstNewline + 1) : ""
|
|
||||||
|
|
||||||
radius: 6
|
|
||||||
color: palette.base
|
|
||||||
clip: true
|
|
||||||
|
|
||||||
Behavior on implicitHeight {
|
|
||||||
NumberAnimation { duration: 200; easing.type: Easing.InOutQuad }
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: header
|
|
||||||
|
|
||||||
width: parent.width
|
|
||||||
height: headerRow.height + 10
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: root.expanded = !root.expanded
|
|
||||||
|
|
||||||
Row {
|
|
||||||
id: headerRow
|
|
||||||
|
|
||||||
anchors {
|
|
||||||
verticalCenter: parent.verticalCenter
|
|
||||||
left: parent.left
|
|
||||||
leftMargin: 10
|
|
||||||
}
|
|
||||||
width: parent.width
|
|
||||||
spacing: 8
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: qsTr("Tool: %1").arg(root.toolName)
|
|
||||||
font.pixelSize: 13
|
|
||||||
font.bold: true
|
|
||||||
color: palette.text
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
text: root.expanded ? "▼" : "▶"
|
|
||||||
font.pixelSize: 10
|
|
||||||
color: palette.mid
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
id: contentColumn
|
|
||||||
|
|
||||||
anchors {
|
|
||||||
left: parent.left
|
|
||||||
right: parent.right
|
|
||||||
top: header.bottom
|
|
||||||
margins: 10
|
|
||||||
}
|
|
||||||
spacing: 8
|
|
||||||
|
|
||||||
TextEdit {
|
|
||||||
id: resultText
|
|
||||||
|
|
||||||
text: root.toolResult
|
|
||||||
readOnly: true
|
|
||||||
selectByMouse: true
|
|
||||||
color: palette.text
|
|
||||||
wrapMode: Text.WordWrap
|
|
||||||
font.family: "monospace"
|
|
||||||
font.pixelSize: 11
|
|
||||||
selectionColor: palette.highlight
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
acceptedButtons: Qt.RightButton
|
|
||||||
onClicked: contextMenu.open()
|
|
||||||
propagateComposedEvents: true
|
|
||||||
}
|
|
||||||
|
|
||||||
Platform.Menu {
|
|
||||||
id: contextMenu
|
|
||||||
|
|
||||||
Platform.MenuItem {
|
|
||||||
text: qsTr("Copy")
|
|
||||||
enabled: resultText.selectedText.length > 0
|
|
||||||
onTriggered: resultText.copy()
|
|
||||||
}
|
|
||||||
|
|
||||||
Platform.MenuItem {
|
|
||||||
text: qsTr("Select All")
|
|
||||||
enabled: resultText.text.length > 0
|
|
||||||
onTriggered: resultText.selectAll()
|
|
||||||
}
|
|
||||||
|
|
||||||
Platform.MenuSeparator {}
|
|
||||||
|
|
||||||
Platform.MenuItem {
|
|
||||||
text: root.expanded ? qsTr("Collapse") : qsTr("Expand")
|
|
||||||
onTriggered: root.expanded = !root.expanded
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: messageMarker
|
|
||||||
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
width: 3
|
|
||||||
height: root.height - root.radius
|
|
||||||
color: root.color.hslLightness > 0.5 ? Qt.darker(palette.alternateBase, 1.3)
|
|
||||||
: Qt.lighter(palette.alternateBase, 1.3)
|
|
||||||
radius: root.radius
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
states: [
|
|
||||||
State {
|
|
||||||
when: !root.expanded
|
|
||||||
PropertyChanges {
|
|
||||||
target: root
|
|
||||||
implicitHeight: header.height
|
|
||||||
}
|
|
||||||
},
|
|
||||||
State {
|
|
||||||
when: root.expanded
|
|
||||||
PropertyChanges {
|
|
||||||
target: root
|
|
||||||
implicitHeight: header.height + contentColumn.height + 20
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -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.
|
||||||
*
|
*
|
||||||
@@ -20,127 +20,71 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
import ChatView
|
import ChatView
|
||||||
import UIControls
|
|
||||||
import Qt.labs.platform as Platform
|
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property string code: ""
|
property string code: ""
|
||||||
property string language: ""
|
property string language: ""
|
||||||
property bool expanded: false
|
|
||||||
|
|
||||||
property alias codeFontFamily: codeText.font.family
|
readonly property string monospaceFont: {
|
||||||
property alias codeFontSize: codeText.font.pointSize
|
switch (Qt.platform.os) {
|
||||||
readonly property real collapsedHeight: copyButton.height + 10
|
case "windows":
|
||||||
|
return "Consolas";
|
||||||
|
case "osx":
|
||||||
|
return "Menlo";
|
||||||
|
case "linux":
|
||||||
|
return "DejaVu Sans Mono";
|
||||||
|
default:
|
||||||
|
return "monospace";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
color: palette.alternateBase
|
color: palette.alternateBase
|
||||||
border.color: root.color.hslLightness > 0.5 ? Qt.darker(root.color, 1.3)
|
border.color: root.color.hslLightness > 0.5 ? Qt.darker(root.color, 1.3)
|
||||||
: Qt.lighter(root.color, 1.3)
|
: Qt.lighter(root.color, 1.3)
|
||||||
border.width: 2
|
border.width: 2
|
||||||
radius: 4
|
radius: 4
|
||||||
implicitWidth: parent.width
|
|
||||||
clip: true
|
|
||||||
|
|
||||||
Behavior on implicitHeight {
|
implicitWidth: parent.width
|
||||||
NumberAnimation { duration: 200; easing.type: Easing.InOutQuad }
|
implicitHeight: codeText.implicitHeight + 20
|
||||||
}
|
|
||||||
|
|
||||||
ChatUtils {
|
ChatUtils {
|
||||||
id: utils
|
id: utils
|
||||||
}
|
}
|
||||||
|
|
||||||
HoverHandler {
|
|
||||||
id: hoverHandler
|
|
||||||
enabled: true
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: header
|
|
||||||
|
|
||||||
width: parent.width
|
|
||||||
height: root.collapsedHeight
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: root.expanded = !root.expanded
|
|
||||||
|
|
||||||
Row {
|
|
||||||
id: headerRow
|
|
||||||
|
|
||||||
anchors {
|
|
||||||
verticalCenter: parent.verticalCenter
|
|
||||||
left: parent.left
|
|
||||||
leftMargin: 10
|
|
||||||
}
|
|
||||||
spacing: 6
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: root.language ? qsTr("Code (%1)").arg(root.language) :
|
|
||||||
qsTr("Code")
|
|
||||||
font.pixelSize: 12
|
|
||||||
font.bold: true
|
|
||||||
color: palette.text
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
text: root.expanded ? "▼" : "▶"
|
|
||||||
font.pixelSize: 10
|
|
||||||
color: palette.mid
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TextEdit {
|
TextEdit {
|
||||||
id: codeText
|
id: codeText
|
||||||
|
|
||||||
anchors {
|
anchors.fill: parent
|
||||||
left: parent.left
|
anchors.margins: 10
|
||||||
right: parent.right
|
|
||||||
top: header.bottom
|
|
||||||
margins: 10
|
|
||||||
}
|
|
||||||
text: root.code
|
text: root.code
|
||||||
readOnly: true
|
readOnly: true
|
||||||
selectByMouse: true
|
selectByMouse: true
|
||||||
|
font.family: root.monospaceFont
|
||||||
|
font.pointSize: Qt.application.font.pointSize
|
||||||
color: parent.color.hslLightness > 0.5 ? "black" : "white"
|
color: parent.color.hslLightness > 0.5 ? "black" : "white"
|
||||||
wrapMode: Text.WordWrap
|
wrapMode: Text.WordWrap
|
||||||
selectionColor: palette.highlight
|
selectionColor: palette.highlight
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
acceptedButtons: Qt.RightButton
|
|
||||||
onClicked: contextMenu.open()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Platform.Menu {
|
TextEdit {
|
||||||
id: contextMenu
|
anchors.top: parent.top
|
||||||
|
anchors.right: parent.right
|
||||||
Platform.MenuItem {
|
anchors.margins: 5
|
||||||
text: qsTr("Copy")
|
readOnly: true
|
||||||
onTriggered: {
|
selectByMouse: true
|
||||||
const textToCopy = codeText.selectedText || root.code
|
text: root.language
|
||||||
utils.copyToClipboard(textToCopy)
|
color: root.color.hslLightness > 0.5 ? Qt.darker(root.color, 1.1)
|
||||||
}
|
: Qt.lighter(root.color, 1.1)
|
||||||
}
|
font.pointSize: 8
|
||||||
|
|
||||||
Platform.MenuSeparator {}
|
|
||||||
|
|
||||||
Platform.MenuItem {
|
|
||||||
text: root.expanded ? qsTr("Collapse") : qsTr("Expand")
|
|
||||||
onTriggered: root.expanded = !root.expanded
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QoAButton {
|
QoAButton {
|
||||||
id: copyButton
|
anchors.top: parent.top
|
||||||
|
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.rightMargin: 5
|
anchors.margins: 5
|
||||||
|
text: "Copy"
|
||||||
y: 5
|
|
||||||
text: qsTr("Copy")
|
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
utils.copyToClipboard(root.code)
|
utils.copyToClipboard(root.code)
|
||||||
text = qsTr("Copied")
|
text = qsTr("Copied")
|
||||||
@@ -153,21 +97,4 @@ Rectangle {
|
|||||||
onTriggered: parent.text = qsTr("Copy")
|
onTriggered: parent.text = qsTr("Copy")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
states: [
|
|
||||||
State {
|
|
||||||
when: !root.expanded
|
|
||||||
PropertyChanges {
|
|
||||||
target: root
|
|
||||||
implicitHeight: root.collapsedHeight
|
|
||||||
}
|
|
||||||
},
|
|
||||||
State {
|
|
||||||
when: root.expanded
|
|
||||||
PropertyChanges {
|
|
||||||
target: root
|
|
||||||
implicitHeight: header.height + codeText.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.
|
||||||
*
|
*
|
||||||
@@ -18,7 +18,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import Qt.labs.platform as Platform
|
|
||||||
|
|
||||||
TextEdit {
|
TextEdit {
|
||||||
id: root
|
id: root
|
||||||
@@ -26,29 +25,7 @@ TextEdit {
|
|||||||
readOnly: true
|
readOnly: true
|
||||||
selectByMouse: true
|
selectByMouse: true
|
||||||
wrapMode: Text.WordWrap
|
wrapMode: Text.WordWrap
|
||||||
|
textFormat: Text.StyledText
|
||||||
selectionColor: palette.highlight
|
selectionColor: palette.highlight
|
||||||
color: palette.text
|
color: palette.text
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
acceptedButtons: Qt.RightButton
|
|
||||||
onClicked: contextMenu.open()
|
|
||||||
propagateComposedEvents: true
|
|
||||||
}
|
|
||||||
|
|
||||||
Platform.Menu {
|
|
||||||
id: contextMenu
|
|
||||||
|
|
||||||
Platform.MenuItem {
|
|
||||||
text: qsTr("Copy")
|
|
||||||
enabled: root.selectedText.length > 0
|
|
||||||
onTriggered: root.copy()
|
|
||||||
}
|
|
||||||
|
|
||||||
Platform.MenuItem {
|
|
||||||
text: qsTr("Select All")
|
|
||||||
enabled: root.text.length > 0
|
|
||||||
onTriggered: root.selectAll()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,16 +21,17 @@ import QtQuick
|
|||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
import ChatView
|
import ChatView
|
||||||
import UIControls
|
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property alias sendButton: sendButtonId
|
property alias sendButton: sendButtonId
|
||||||
|
property alias stopButton: stopButtonId
|
||||||
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) :
|
||||||
@@ -52,12 +53,13 @@ Rectangle {
|
|||||||
QoAButton {
|
QoAButton {
|
||||||
id: sendButtonId
|
id: sendButtonId
|
||||||
|
|
||||||
icon {
|
text: qsTr("Send")
|
||||||
height: 15
|
|
||||||
width: 15
|
|
||||||
}
|
}
|
||||||
ToolTip.visible: hovered
|
|
||||||
ToolTip.delay: 250
|
QoAButton {
|
||||||
|
id: stopButtonId
|
||||||
|
|
||||||
|
text: qsTr("Stop")
|
||||||
}
|
}
|
||||||
|
|
||||||
QoAButton {
|
QoAButton {
|
||||||
@@ -68,9 +70,7 @@ Rectangle {
|
|||||||
height: 15
|
height: 15
|
||||||
width: 8
|
width: 8
|
||||||
}
|
}
|
||||||
ToolTip.visible: hovered
|
text: qsTr("Attach files")
|
||||||
ToolTip.delay: 250
|
|
||||||
ToolTip.text: qsTr("Attach file to message")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QoAButton {
|
QoAButton {
|
||||||
@@ -81,9 +81,7 @@ Rectangle {
|
|||||||
height: 15
|
height: 15
|
||||||
width: 8
|
width: 8
|
||||||
}
|
}
|
||||||
ToolTip.visible: hovered
|
text: qsTr("Link files")
|
||||||
ToolTip.delay: 250
|
|
||||||
ToolTip.text: qsTr("Link file to context")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CheckBox {
|
CheckBox {
|
||||||
@@ -95,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,100 +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
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: errorToast
|
|
||||||
|
|
||||||
property string errorText: ""
|
|
||||||
property int displayDuration: 5000
|
|
||||||
|
|
||||||
width: Math.min(parent.width - 40, errorTextItem.implicitWidth + radius)
|
|
||||||
height: visible ? (errorTextItem.implicitHeight + 12) : 0
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
anchors.top: parent.top
|
|
||||||
anchors.topMargin: 10
|
|
||||||
|
|
||||||
color: "#d32f2f"
|
|
||||||
radius: height / 2
|
|
||||||
border.color: "#b71c1c"
|
|
||||||
border.width: 1
|
|
||||||
visible: false
|
|
||||||
opacity: 0
|
|
||||||
|
|
||||||
TextEdit {
|
|
||||||
id: errorTextItem
|
|
||||||
|
|
||||||
anchors.centerIn: parent
|
|
||||||
anchors.margins: 6
|
|
||||||
text: errorToast.errorText
|
|
||||||
color: "#ffffff"
|
|
||||||
font.pixelSize: 13
|
|
||||||
wrapMode: TextEdit.Wrap
|
|
||||||
width: Math.min(implicitWidth, errorToast.parent.width - 60)
|
|
||||||
horizontalAlignment: TextEdit.AlignHCenter
|
|
||||||
readOnly: true
|
|
||||||
selectByMouse: true
|
|
||||||
selectByKeyboard: true
|
|
||||||
selectionColor: "#b71c1c"
|
|
||||||
}
|
|
||||||
|
|
||||||
function show(message) {
|
|
||||||
errorText = message
|
|
||||||
visible = true
|
|
||||||
showAnimation.start()
|
|
||||||
hideTimer.restart()
|
|
||||||
}
|
|
||||||
|
|
||||||
function hide() {
|
|
||||||
hideAnimation.start()
|
|
||||||
}
|
|
||||||
|
|
||||||
NumberAnimation {
|
|
||||||
id: showAnimation
|
|
||||||
|
|
||||||
target: errorToast
|
|
||||||
property: "opacity"
|
|
||||||
from: 0
|
|
||||||
to: 1
|
|
||||||
duration: 200
|
|
||||||
easing.type: Easing.OutQuad
|
|
||||||
}
|
|
||||||
|
|
||||||
NumberAnimation {
|
|
||||||
id: hideAnimation
|
|
||||||
|
|
||||||
target: errorToast
|
|
||||||
property: "opacity"
|
|
||||||
from: 1
|
|
||||||
to: 0
|
|
||||||
duration: 200
|
|
||||||
easing.type: Easing.InQuad
|
|
||||||
onFinished: errorToast.visible = false
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer {
|
|
||||||
id: hideTimer
|
|
||||||
|
|
||||||
interval: errorToast.displayDuration
|
|
||||||
running: false
|
|
||||||
repeat: false
|
|
||||||
onTriggered: errorToast.hide()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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,7 @@
|
|||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
import QtQuick.Controls
|
|
||||||
import ChatView
|
import ChatView
|
||||||
import UIControls
|
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: root
|
id: root
|
||||||
@@ -32,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 pinButton: pinButtonId
|
|
||||||
|
|
||||||
color: palette.window.hslLightness > 0.5 ?
|
color: palette.window.hslLightness > 0.5 ?
|
||||||
Qt.darker(palette.window, 1.1) :
|
Qt.darker(palette.window, 1.1) :
|
||||||
@@ -49,61 +46,22 @@ Rectangle {
|
|||||||
|
|
||||||
spacing: 10
|
spacing: 10
|
||||||
|
|
||||||
QoAButton {
|
|
||||||
id: pinButtonId
|
|
||||||
|
|
||||||
checkable: true
|
|
||||||
|
|
||||||
icon {
|
|
||||||
source: checked ? "qrc:/qt/qml/ChatView/icons/window-lock.svg"
|
|
||||||
: "qrc:/qt/qml/ChatView/icons/window-unlock.svg"
|
|
||||||
color: palette.window.hslLightness > 0.5 ? "#000000" : "#FFFFFF"
|
|
||||||
height: 15
|
|
||||||
width: 15
|
|
||||||
}
|
|
||||||
ToolTip.visible: hovered
|
|
||||||
ToolTip.delay: 250
|
|
||||||
ToolTip.text: checked ? qsTr("Unpin chat window")
|
|
||||||
: qsTr("Pin chat window to the top")
|
|
||||||
}
|
|
||||||
|
|
||||||
QoAButton {
|
QoAButton {
|
||||||
id: saveButtonId
|
id: saveButtonId
|
||||||
|
|
||||||
icon {
|
text: qsTr("Save")
|
||||||
source: "qrc:/qt/qml/ChatView/icons/save-chat-dark.svg"
|
|
||||||
height: 15
|
|
||||||
width: 8
|
|
||||||
}
|
|
||||||
ToolTip.visible: hovered
|
|
||||||
ToolTip.delay: 250
|
|
||||||
ToolTip.text: qsTr("Save chat to *.json file")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QoAButton {
|
QoAButton {
|
||||||
id: loadButtonId
|
id: loadButtonId
|
||||||
|
|
||||||
icon {
|
text: qsTr("Load")
|
||||||
source: "qrc:/qt/qml/ChatView/icons/load-chat-dark.svg"
|
|
||||||
height: 15
|
|
||||||
width: 8
|
|
||||||
}
|
|
||||||
ToolTip.visible: hovered
|
|
||||||
ToolTip.delay: 250
|
|
||||||
ToolTip.text: qsTr("Load chat from *.json file")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QoAButton {
|
QoAButton {
|
||||||
id: clearButtonId
|
id: clearButtonId
|
||||||
|
|
||||||
icon {
|
text: qsTr("Clear")
|
||||||
source: "qrc:/qt/qml/ChatView/icons/clean-icon-dark.svg"
|
|
||||||
height: 15
|
|
||||||
width: 8
|
|
||||||
}
|
|
||||||
ToolTip.visible: hovered
|
|
||||||
ToolTip.delay: 250
|
|
||||||
ToolTip.text: qsTr("Clean chat")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
@@ -116,14 +74,7 @@ Rectangle {
|
|||||||
QoAButton {
|
QoAButton {
|
||||||
id: openChatHistoryId
|
id: openChatHistoryId
|
||||||
|
|
||||||
icon {
|
text: qsTr("Show in system")
|
||||||
source: "qrc:/qt/qml/ChatView/icons/file-in-system.svg"
|
|
||||||
height: 15
|
|
||||||
width: 15
|
|
||||||
}
|
|
||||||
ToolTip.visible: hovered
|
|
||||||
ToolTip.delay: 250
|
|
||||||
ToolTip.text: qsTr("Show in system")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
@@ -132,10 +83,6 @@ Rectangle {
|
|||||||
|
|
||||||
Badge {
|
Badge {
|
||||||
id: tokensBadgeId
|
id: tokensBadgeId
|
||||||
|
|
||||||
ToolTip.visible: hovered
|
|
||||||
ToolTip.delay: 250
|
|
||||||
ToolTip.text: qsTr("Current amount tokens in chat and LLM limit threshold")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
203
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,144 +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;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CodeHandler::hasCodeBlocks(const QString &text)
|
|
||||||
{
|
|
||||||
QStringList lines = text.split('\n');
|
|
||||||
|
|
||||||
for (const QString &line : lines) {
|
|
||||||
if (line.trimmed().startsWith("```")) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
||||||
|
|
||||||
@@ -168,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();
|
||||||
@@ -201,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,25 +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);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Detects if text contains code blocks, or returns false if this was not possible
|
|
||||||
*/
|
|
||||||
static bool hasCodeBlocks(const QString &text);
|
|
||||||
|
|
||||||
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,32 @@
|
|||||||
#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/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>
|
|
||||||
#include <llmcore/RulesLoader.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,
|
|
||||||
Context::IDocumentReader &documentReader,
|
|
||||||
IRequestPerformanceLogger &performanceLogger)
|
|
||||||
: m_generalSettings(generalSettings)
|
|
||||||
, m_completeSettings(completeSettings)
|
|
||||||
, m_providerRegistry(providerRegistry)
|
|
||||||
, m_promptProvider(promptProvider)
|
|
||||||
, m_documentReader(documentReader)
|
|
||||||
, m_performanceLogger(performanceLogger)
|
|
||||||
, m_contextManager(new Context::ContextManager(this))
|
|
||||||
{
|
{
|
||||||
|
connect(&m_requestHandler,
|
||||||
|
&LLMCore::RequestHandler::completionReceived,
|
||||||
|
this,
|
||||||
|
&LLMClientInterface::sendCompletionToClient);
|
||||||
}
|
}
|
||||||
|
|
||||||
Utils::FilePath LLMClientInterface::serverDeviceTemplate() const
|
Utils::FilePath LLMClientInterface::serverDeviceTemplate() const
|
||||||
{
|
{
|
||||||
return "QodeAssist";
|
return "Qode Assist";
|
||||||
}
|
}
|
||||||
|
|
||||||
void LLMClientInterface::startImpl()
|
void LLMClientInterface::startImpl()
|
||||||
@@ -61,29 +56,6 @@ void LLMClientInterface::startImpl()
|
|||||||
emit started();
|
emit started();
|
||||||
}
|
}
|
||||||
|
|
||||||
void LLMClientInterface::handleFullResponse(const QString &requestId, const QString &fullText)
|
|
||||||
{
|
|
||||||
auto it = m_activeRequests.find(requestId);
|
|
||||||
if (it == m_activeRequests.end())
|
|
||||||
return;
|
|
||||||
|
|
||||||
const RequestContext &ctx = it.value();
|
|
||||||
sendCompletionToClient(fullText, ctx.originalRequest, true);
|
|
||||||
|
|
||||||
m_activeRequests.erase(it);
|
|
||||||
m_performanceLogger.endTimeMeasurement(requestId);
|
|
||||||
}
|
|
||||||
|
|
||||||
void LLMClientInterface::handleRequestFailed(const QString &requestId, const QString &error)
|
|
||||||
{
|
|
||||||
auto it = m_activeRequests.find(requestId);
|
|
||||||
if (it == m_activeRequests.end())
|
|
||||||
return;
|
|
||||||
|
|
||||||
LOG_MESSAGE(QString("Request %1 failed: %2").arg(requestId, error));
|
|
||||||
m_activeRequests.erase(it);
|
|
||||||
}
|
|
||||||
|
|
||||||
void LLMClientInterface::sendData(const QByteArray &data)
|
void LLMClientInterface::sendData(const QByteArray &data)
|
||||||
{
|
{
|
||||||
QJsonDocument doc = QJsonDocument::fromJson(data);
|
QJsonDocument doc = QJsonDocument::fromJson(data);
|
||||||
@@ -103,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);
|
||||||
@@ -116,16 +88,12 @@ void LLMClientInterface::sendData(const QByteArray &data)
|
|||||||
|
|
||||||
void LLMClientInterface::handleCancelRequest(const QJsonObject &request)
|
void LLMClientInterface::handleCancelRequest(const QJsonObject &request)
|
||||||
{
|
{
|
||||||
for (auto it = m_activeRequests.begin(); it != m_activeRequests.end(); ++it) {
|
QString id = request["params"].toObject()["id"].toString();
|
||||||
const RequestContext &ctx = it.value();
|
if (m_requestHandler.cancelRequest(id)) {
|
||||||
if (ctx.provider) {
|
LOG_MESSAGE(QString("Request %1 cancelled successfully").arg(id));
|
||||||
ctx.provider->cancelRequest(it.key());
|
} else {
|
||||||
|
LOG_MESSAGE(QString("Request %1 not found").arg(id));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
m_activeRequests.clear();
|
|
||||||
|
|
||||||
LOG_MESSAGE("All requests cancelled and state cleared");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void LLMClientInterface::handleInitialize(const QJsonObject &request)
|
void LLMClientInterface::handleInitialize(const QJsonObject &request)
|
||||||
@@ -178,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));
|
||||||
@@ -220,79 +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
|
config.url = QUrl(QString("%1%2").arg(
|
||||||
if (provider->providerID() == LLMCore::ProviderID::GoogleAI) {
|
url,
|
||||||
QString stream = QString{"streamGenerateContent?alt=sse"};
|
promptTemplate->type() == LLMCore::TemplateType::Fim ? provider->completionEndpoint()
|
||||||
config.url = QUrl(QString("%1/models/%2:%3").arg(url, modelName, stream));
|
: provider->chatEndpoint()));
|
||||||
} else {
|
|
||||||
config.url = QUrl(
|
|
||||||
QString("%1%2").arg(url, endpoint(provider, promptTemplate->type(), isPreset1Active)));
|
|
||||||
config.providerRequest = {{"model", modelName}, {"stream", true}};
|
|
||||||
}
|
|
||||||
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());
|
|
||||||
|
|
||||||
auto project = LLMCore::RulesLoader::getActiveProject();
|
|
||||||
if (project) {
|
|
||||||
QString projectRules
|
|
||||||
= LLMCore::RulesLoader::loadRulesForProject(project, LLMCore::RulesContext::Completions);
|
|
||||||
|
|
||||||
if (!projectRules.isEmpty()) {
|
|
||||||
systemPrompt += "\n\n# Project Rules\n\n" + projectRules;
|
|
||||||
LOG_MESSAGE("Loaded project rules for completion");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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()) {
|
||||||
@@ -300,78 +239,62 @@ void LLMClientInterface::handleCompletion(const QJsonObject &request)
|
|||||||
LOG_MESSAGES(errors);
|
LOG_MESSAGES(errors);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
m_requestHandler.sendLLMRequest(config, request);
|
||||||
QString requestId = request["id"].toString();
|
|
||||||
m_performanceLogger.startTimeMeasurement(requestId);
|
|
||||||
|
|
||||||
m_activeRequests[requestId] = {request, provider};
|
|
||||||
|
|
||||||
connect(
|
|
||||||
provider,
|
|
||||||
&LLMCore::Provider::fullResponseReceived,
|
|
||||||
this,
|
|
||||||
&LLMClientInterface::handleFullResponse,
|
|
||||||
Qt::UniqueConnection);
|
|
||||||
connect(
|
|
||||||
provider,
|
|
||||||
&LLMCore::Provider::requestFailed,
|
|
||||||
this,
|
|
||||||
&LLMClientInterface::handleRequestFailed,
|
|
||||||
Qt::UniqueConnection);
|
|
||||||
|
|
||||||
provider->sendRequest(requestId, config.url, config.providerRequest);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString LLMClientInterface::endpoint(
|
Context::ProgrammingLanguage LLMClientInterface::getDocumentLanguage(const QJsonObject &request) const
|
||||||
LLMCore::Provider *provider, LLMCore::TemplateType type, bool isLanguageSpecify)
|
|
||||||
{
|
{
|
||||||
QString endpoint;
|
QJsonObject params = request["params"].toObject();
|
||||||
auto endpointMode = isLanguageSpecify ? m_generalSettings.ccPreset1EndpointMode.stringValue()
|
QJsonObject doc = params["doc"].toObject();
|
||||||
: m_generalSettings.ccEndpointMode.stringValue();
|
QString uri = doc["uri"].toString();
|
||||||
if (endpointMode == "Auto") {
|
|
||||||
endpoint = type == LLMCore::TemplateType::FIM ? provider->completionEndpoint()
|
Utils::FilePath filePath = Utils::FilePath::fromString(QUrl(uri).toLocalFile());
|
||||||
: provider->chatEndpoint();
|
TextEditor::TextDocument *textDocument = TextEditor::TextDocument::textDocumentForFilePath(
|
||||||
} else if (endpointMode == "Custom") {
|
filePath);
|
||||||
endpoint = isLanguageSpecify ? m_generalSettings.ccPreset1CustomEndpoint()
|
|
||||||
: m_generalSettings.ccCustomEndpoint();
|
if (!textDocument) {
|
||||||
} else if (endpointMode == "FIM") {
|
LOG_MESSAGE("Error: Document is not available for" + filePath.toString());
|
||||||
endpoint = provider->completionEndpoint();
|
return Context::ProgrammingLanguage::Unknown;
|
||||||
} else if (endpointMode == "Chat") {
|
|
||||||
endpoint = provider->chatEndpoint();
|
|
||||||
}
|
}
|
||||||
return endpoint;
|
|
||||||
|
return Context::ProgrammingLanguageUtils::fromMimeType(textDocument->mimeType());
|
||||||
}
|
}
|
||||||
|
|
||||||
Context::ContextManager *LLMClientInterface::contextManager() const
|
void LLMClientInterface::sendCompletionToClient(const QString &completion,
|
||||||
|
const QJsonObject &request,
|
||||||
|
bool isComplete)
|
||||||
{
|
{
|
||||||
return m_contextManager;
|
bool isPreset1Active = isSpecifyCompletion(request);
|
||||||
}
|
|
||||||
|
|
||||||
void LLMClientInterface::sendCompletionToClient(
|
auto templateName = !isPreset1Active ? Settings::generalSettings().ccTemplate()
|
||||||
const QString &completion, const QJsonObject &request, bool isComplete)
|
: Settings::generalSettings().ccPreset1Template();
|
||||||
{
|
|
||||||
auto filePath = Context::extractFilePathFromRequest(request);
|
|
||||||
auto documentInfo = m_documentReader.readDocument(filePath);
|
|
||||||
bool isPreset1Active = m_contextManager->isSpecifyCompletion(documentInfo);
|
|
||||||
|
|
||||||
auto templateName = !isPreset1Active ? m_generalSettings.ccTemplate()
|
auto promptTemplate = LLMCore::PromptTemplateManager::instance().getFimTemplateByName(
|
||||||
: m_generalSettings.ccPreset1Template();
|
templateName);
|
||||||
|
|
||||||
auto promptTemplate = m_promptProvider->getTemplateByName(templateName);
|
|
||||||
|
|
||||||
QJsonObject position = request["params"].toObject()["doc"].toObject()["position"].toObject();
|
QJsonObject position = request["params"].toObject()["doc"].toObject()["position"].toObject();
|
||||||
|
|
||||||
@@ -383,23 +306,11 @@ void LLMClientInterface::sendCompletionToClient(
|
|||||||
QJsonArray completions;
|
QJsonArray completions;
|
||||||
QJsonObject completionItem;
|
QJsonObject completionItem;
|
||||||
|
|
||||||
LOG_MESSAGE(QString("Completions before filter: \n%1").arg(completion));
|
QString processedCompletion
|
||||||
|
= promptTemplate->type() == LLMCore::TemplateType::Chat
|
||||||
QString outputHandler = m_completeSettings.modelOutputHandler.stringValue();
|
&& Settings::codeCompletionSettings().smartProcessInstuctText()
|
||||||
QString processedCompletion;
|
? CodeHandler::processText(completion)
|
||||||
|
|
||||||
if (outputHandler == "Raw text") {
|
|
||||||
processedCompletion = completion;
|
|
||||||
} else if (outputHandler == "Force processing") {
|
|
||||||
processedCompletion = CodeHandler::processText(completion,
|
|
||||||
Context::extractFilePathFromRequest(request));
|
|
||||||
} else { // "Auto"
|
|
||||||
processedCompletion = CodeHandler::hasCodeBlocks(completion)
|
|
||||||
? CodeHandler::processText(completion,
|
|
||||||
Context::extractFilePathFromRequest(
|
|
||||||
request))
|
|
||||||
: completion;
|
: completion;
|
||||||
}
|
|
||||||
|
|
||||||
completionItem[LanguageServerProtocol::textKey] = processedCompletion;
|
completionItem[LanguageServerProtocol::textKey] = processedCompletion;
|
||||||
QJsonObject range;
|
QJsonObject range;
|
||||||
@@ -418,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,15 +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/RequestHandler.hpp>
|
||||||
#include <llmcore/IProviderRegistry.hpp>
|
|
||||||
#include <logger/IRequestPerformanceLogger.hpp>
|
|
||||||
#include <settings/CodeCompletionSettings.hpp>
|
|
||||||
#include <settings/GeneralSettings.hpp>
|
|
||||||
|
|
||||||
class QNetworkReply;
|
class QNetworkReply;
|
||||||
class QNetworkAccessManager;
|
class QNetworkAccessManager;
|
||||||
@@ -42,32 +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,
|
|
||||||
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;
|
||||||
private slots:
|
void parseCurrentMessage() override;
|
||||||
void handleFullResponse(const QString &requestId, const QString &fullText);
|
|
||||||
void handleRequestFailed(const QString &requestId, const QString &error);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void handleInitialize(const QJsonObject &request);
|
void handleInitialize(const QJsonObject &request);
|
||||||
@@ -77,25 +59,18 @@ private:
|
|||||||
void handleExit(const QJsonObject &request);
|
void handleExit(const QJsonObject &request);
|
||||||
void handleCancelRequest(const QJsonObject &request);
|
void handleCancelRequest(const QJsonObject &request);
|
||||||
|
|
||||||
struct RequestContext
|
|
||||||
{
|
|
||||||
QJsonObject originalRequest;
|
|
||||||
LLMCore::Provider *provider;
|
|
||||||
};
|
|
||||||
|
|
||||||
LLMCore::ContextData prepareContext(
|
LLMCore::ContextData prepareContext(
|
||||||
const QJsonObject &request, const Context::DocumentInfo &documentInfo);
|
const QJsonObject &request, const QStringView &accumulatedCompletion = QString{});
|
||||||
QString endpoint(LLMCore::Provider *provider, LLMCore::TemplateType type, bool isLanguageSpecify);
|
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;
|
|
||||||
Context::IDocumentReader &m_documentReader;
|
|
||||||
IRequestPerformanceLogger &m_performanceLogger;
|
|
||||||
QElapsedTimer m_completionTimer;
|
QElapsedTimer m_completionTimer;
|
||||||
Context::ContextManager *m_contextManager;
|
QMap<QString, qint64> m_requestStartTimes;
|
||||||
QHash<QString, RequestContext> m_activeRequests;
|
|
||||||
|
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,87 +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;
|
||||||
}
|
|
||||||
|
|
||||||
if (!subText.contains('\n')) {
|
|
||||||
currentCursor.insertText(subText);
|
|
||||||
|
|
||||||
const QString remainingText = text.mid(next);
|
|
||||||
if (!remainingText.isEmpty()) {
|
|
||||||
QTextCursor newCursor = widget->textCursor();
|
|
||||||
const Utils::Text::Position newStart = Utils::Text::Position::fromPositionInDocument(
|
|
||||||
newCursor.document(), newCursor.position());
|
|
||||||
const Utils::Text::Position
|
|
||||||
newEnd{newStart.line, newStart.column + int(remainingText.length())};
|
|
||||||
const Utils::Text::Range newRange{newStart, newEnd};
|
|
||||||
const QList<Data> newSuggestion{{newRange, newStart, remainingText}};
|
|
||||||
widget->insertSuggestion(
|
|
||||||
std::make_unique<LLMSuggestion>(newSuggestion, widget->document(), 0));
|
|
||||||
}
|
|
||||||
} 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,14 +1,13 @@
|
|||||||
{
|
{
|
||||||
"Id" : "qodeassist",
|
"Id" : "qodeassist",
|
||||||
"Name" : "QodeAssist",
|
"Name" : "QodeAssist",
|
||||||
"Version" : "0.7.1",
|
"Version" : "0.4.13",
|
||||||
"CompatVersion" : "${IDE_VERSION}",
|
|
||||||
"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,13 +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>
|
|
||||||
<file>resources/images/qode-assist-chat-icon.png</file>
|
|
||||||
<file>resources/images/qode-assist-chat-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,43 +24,32 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <QObject>
|
|
||||||
|
|
||||||
#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
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
|
||||||
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;
|
||||||
@@ -69,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.
|
||||||
*
|
*
|
||||||
|
|||||||
3
QodeAssist_en_001.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!DOCTYPE TS>
|
||||||
|
<TS version="2.1" language="en_001"></TS>
|
||||||
@@ -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,331 +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 <llmcore/RequestConfig.hpp>
|
|
||||||
#include <llmcore/RulesLoader.hpp>
|
|
||||||
#include <logger/Logger.hpp>
|
|
||||||
#include <settings/ChatAssistantSettings.hpp>
|
|
||||||
#include <settings/GeneralSettings.hpp>
|
|
||||||
|
|
||||||
namespace QodeAssist {
|
|
||||||
|
|
||||||
QuickRefactorHandler::QuickRefactorHandler(QObject *parent)
|
|
||||||
: QObject(parent)
|
|
||||||
, m_currentEditor(nullptr)
|
|
||||||
, m_isRefactoringInProgress(false)
|
|
||||||
, m_contextManager(this)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
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", true}};
|
|
||||||
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_activeRequests[requestId] = {request, provider};
|
|
||||||
|
|
||||||
connect(
|
|
||||||
provider,
|
|
||||||
&LLMCore::Provider::fullResponseReceived,
|
|
||||||
this,
|
|
||||||
&QuickRefactorHandler::handleFullResponse,
|
|
||||||
Qt::UniqueConnection);
|
|
||||||
|
|
||||||
connect(
|
|
||||||
provider,
|
|
||||||
&LLMCore::Provider::requestFailed,
|
|
||||||
this,
|
|
||||||
&QuickRefactorHandler::handleRequestFailed,
|
|
||||||
Qt::UniqueConnection);
|
|
||||||
|
|
||||||
provider->sendRequest(requestId, config.url, config.providerRequest);
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
|
||||||
|
|
||||||
auto project = LLMCore::RulesLoader::getActiveProject();
|
|
||||||
if (project) {
|
|
||||||
QString projectRules = LLMCore::RulesLoader::loadRulesForProject(
|
|
||||||
project, LLMCore::RulesContext::QuickRefactor);
|
|
||||||
|
|
||||||
if (!projectRules.isEmpty()) {
|
|
||||||
systemPrompt += "\n\n# Project Rules\n\n" + projectRules;
|
|
||||||
LOG_MESSAGE("Loaded project rules for quick refactor");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
|
||||||
auto id = m_lastRequestId;
|
|
||||||
|
|
||||||
for (auto it = m_activeRequests.begin(); it != m_activeRequests.end(); ++it) {
|
|
||||||
if (it.key() == id) {
|
|
||||||
const RequestContext &ctx = it.value();
|
|
||||||
ctx.provider->cancelRequest(id);
|
|
||||||
m_activeRequests.erase(it);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
m_isRefactoringInProgress = false;
|
|
||||||
|
|
||||||
RefactorResult result;
|
|
||||||
result.success = false;
|
|
||||||
result.errorMessage = "Refactoring request was cancelled";
|
|
||||||
emit refactoringCompleted(result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void QuickRefactorHandler::handleFullResponse(const QString &requestId, const QString &fullText)
|
|
||||||
{
|
|
||||||
if (requestId == m_lastRequestId) {
|
|
||||||
QJsonObject request{{"id", requestId}};
|
|
||||||
handleLLMResponse(fullText, request, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void QuickRefactorHandler::handleRequestFailed(const QString &requestId, const QString &error)
|
|
||||||
{
|
|
||||||
if (requestId == m_lastRequestId) {
|
|
||||||
m_isRefactoringInProgress = false;
|
|
||||||
RefactorResult result;
|
|
||||||
result.success = false;
|
|
||||||
result.errorMessage = error;
|
|
||||||
emit refactoringCompleted(result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace QodeAssist
|
|
||||||
@@ -1,88 +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/ContextData.hpp>
|
|
||||||
#include <llmcore/Provider.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 slots:
|
|
||||||
void handleFullResponse(const QString &requestId, const QString &fullText);
|
|
||||||
void handleRequestFailed(const QString &requestId, const QString &error);
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
struct RequestContext
|
|
||||||
{
|
|
||||||
QJsonObject originalRequest;
|
|
||||||
LLMCore::Provider *provider;
|
|
||||||
};
|
|
||||||
|
|
||||||
QHash<QString, RequestContext> m_activeRequests;
|
|
||||||
TextEditor::TextEditorWidget *m_currentEditor;
|
|
||||||
Utils::Text::Range m_currentRange;
|
|
||||||
bool m_isRefactoringInProgress;
|
|
||||||
QString m_lastRequestId;
|
|
||||||
Context::ContextManager m_contextManager;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace QodeAssist
|
|
||||||
229
README.md
@@ -2,8 +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.
|
||||||
@@ -14,34 +13,36 @@
|
|||||||
> - 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 Feature](#file-context-feature)
|
10. [Development Progress](#development-progress)
|
||||||
11. [Quick Refactoring Feature](#quick-refactoring-feature)
|
11. [Hotkeys](#hotkeys)
|
||||||
12. [QtCreator Version Compatibility](#qtcreator-version-compatibility)
|
12. [Troubleshooting](#troubleshooting)
|
||||||
13. [Development Progress](#development-progress)
|
13. [Support the Development](#support-the-development-of-qodeassist)
|
||||||
14. [Hotkeys](#hotkeys)
|
14. [How to Build](#how-to-build)
|
||||||
15. [Ignoring Files](#ignoring-files)
|
|
||||||
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(enabling in chat settings due stability reason with QQuickWidget problem)
|
- Side and Bottom panels
|
||||||
- Chat in additional popup window with pinning(recommended)
|
|
||||||
- Chat history autosave and restore
|
- Chat history autosave and restore
|
||||||
- Token usage monitoring and management
|
- Token usage monitoring and management
|
||||||
- Attach files for one-time code analysis
|
- Attach files for one-time code analysis
|
||||||
@@ -49,29 +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
|
||||||
- Support tools/function calling (enabled by default)
|
|
||||||
|
|
||||||
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">
|
||||||
@@ -87,27 +78,15 @@ Join our Discord Community: Have questions or want to discuss QodeAssist? Join o
|
|||||||
<img width="326" alt="QodeAssistBottomPanel" src="https://github.com/user-attachments/assets/4cc64c23-a294-4df8-9153-39ad6fdab34b">
|
<img width="326" alt="QodeAssistBottomPanel" src="https://github.com/user-attachments/assets/4cc64c23-a294-4df8-9153-39ad6fdab34b">
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>Chat in addtional window: (click to expand)</summary>
|
|
||||||
<img width="851" height="865" alt="image" src="https://github.com/user-attachments/assets/a68894b7-886e-4501-a61b-7161ae34b427" />
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>Automatic syncing with open editor files: (click to expand)</summary>
|
<summary>Automatic syncing with open editor files: (click to expand)</summary>
|
||||||
<img width="600" alt="OpenedDocumentsSync" src="https://github.com/user-attachments/assets/08efda2f-dc4d-44c3-927c-e6a975090d2f">
|
<img width="600" alt="OpenedDocumentsSync" src="https://github.com/user-attachments/assets/08efda2f-dc4d-44c3-927c-e6a975090d2f">
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>Example how tools works: (click to expand)</summary>
|
|
||||||
<img width="600" alt="ToolsDemo" src="https://github.com/user-attachments/assets/cf6273ad-d5c8-47fc-81e6-23d929547f6c">
|
|
||||||
</details>
|
|
||||||
|
|
||||||
## Install plugin to QtCreator
|
## Install plugin to QtCreator
|
||||||
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...
|
||||||
@@ -141,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:
|
||||||
@@ -186,13 +139,12 @@ 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
|
||||||
- Your installed model appears in the model selection
|
- Your installed model appears in the model selection
|
||||||
- The prompt template is Ollama Auto FIM or Ollama Auto Chat for chat assistance. You can specify template if it is not work correct
|
- The prompt template is Ollama Auto FIM or Ollama Auto Chat for chat assistance. You can specify template if it is not work correct
|
||||||
- Disable using tools if your model doesn't support tooling
|
|
||||||
4. Click Apply if you made any changes
|
4. Click Apply if you made any changes
|
||||||
|
|
||||||
You're all set! QodeAssist is now ready to use in Qt Creator.
|
You're all set! QodeAssist is now ready to use in Qt Creator.
|
||||||
@@ -201,52 +153,11 @@ 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)
|
|
||||||
- Disable using tools if your model doesn't support tooling
|
|
||||||
<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.
|
||||||
|
|
||||||
## Project Rules Configuration
|
## File Context Features
|
||||||
|
|
||||||
QodeAssist supports project-specific rules to customize AI behavior for your codebase. Create a `.qodeassist/rules/` directory in your project root.
|
|
||||||
|
|
||||||
### Quick Start
|
|
||||||
```bash
|
|
||||||
mkdir -p .qodeassist/rules/{common,completion,chat,quickrefactor}
|
|
||||||
```
|
|
||||||
```
|
|
||||||
.qodeassist/
|
|
||||||
└── rules/
|
|
||||||
├── common/ # Applied to all contexts
|
|
||||||
├── completion/ # Code completion only
|
|
||||||
├── chat/ # Chat assistant only
|
|
||||||
└── quickrefactor/ # Quick refactor only
|
|
||||||
```
|
|
||||||
All .md files in each directory are automatically loaded and added to the system prompt.
|
|
||||||
|
|
||||||
Example
|
|
||||||
Create .qodeassist/rules/common/general.md:
|
|
||||||
```markdown
|
|
||||||
# Project Guidelines
|
|
||||||
- Use snake_case for private members
|
|
||||||
- Prefix interfaces with 'I'
|
|
||||||
- Always document public APIs
|
|
||||||
- Prefer Qt containers over STL
|
|
||||||
```
|
|
||||||
|
|
||||||
## File Context Feature
|
|
||||||
|
|
||||||
QodeAssist provides two powerful ways to include source code files in your chat conversations: Attachments and Linked Files. Each serves a distinct purpose and helps provide better context for the AI assistant.
|
QodeAssist provides two powerful ways to include source code files in your chat conversations: Attachments and Linked Files. Each serves a distinct purpose and helps provide better context for the AI assistant.
|
||||||
|
|
||||||
@@ -279,25 +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
|
||||||
|
|
||||||
## Quick Refactoring Feature
|
## Template-Model Compatibility
|
||||||
### Setup
|
|
||||||
Since this is actually a small chat with redirected output, the main settings of the provider, model and template are taken from the chat settings
|
| Template | Compatible Models | Purpose |
|
||||||
### Using
|
|----------|------------------|----------|
|
||||||
The request to model consist of instructions to model, selection code and cursor position
|
| CodeLlama FIM | `codellama:code` | Code completion |
|
||||||
The default instruction is: "Refactor the code to improve its quality and maintainability." and sending if text field is empty
|
| DeepSeekCoder FIM | `deepseek-coder-v2`, `deepseek-v2.5` | Code completion |
|
||||||
Also there buttons to quick call instractions:
|
| Ollama Auto FIM | `Any Ollama base/fim models` | Code completion |
|
||||||
* Repeat latest instruction, will activate after sending first request in QtCreator session
|
| Qwen FIM | `Qwen 2.5 models(exclude instruct)` | Code completion |
|
||||||
* Improve current selection code
|
| StarCoder2 FIM | `starcoder2 base model` | Code completion |
|
||||||
* Suggestion alternative variant of selection code
|
| Alpaca | `starcoder2:instruct` | Chat assistance |
|
||||||
* Other instructions[TBD]
|
| 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 17.0.0 - 0.6.0 - 0.x.x
|
- QtCreator 15.0.1 - 0.4.8 - 0.4.x
|
||||||
- QtCreator 16.0.2 - 0.5.13 - 0.x.x
|
|
||||||
- QtCreator 16.0.1 - 0.5.7 - 0.5.13
|
|
||||||
- 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
|
||||||
@@ -310,53 +221,14 @@ Linked files provide persistent context throughout the conversation:
|
|||||||
- [x] Sharing diff with model
|
- [x] Sharing diff with model
|
||||||
- [ ] Sharing project source with model
|
- [ ] Sharing project source with model
|
||||||
- [ ] Support for more providers and models
|
- [ ] Support for more providers and models
|
||||||
- [ ] Support MCP
|
|
||||||
|
|
||||||
## Hotkeys
|
## Hotkeys
|
||||||
All hotkeys available in QtCreator Settings
|
|
||||||
Also you can find default hotkeys here:
|
|
||||||
- To call chat with llm in separate window, you can use:
|
|
||||||
- on Mac: Option + Command + W
|
|
||||||
- on Windows: Ctrl + Alt + W
|
|
||||||
- on Linux: Ctrl + Alt + W
|
|
||||||
- To close chat with llm in separate window, you can use:
|
|
||||||
- on Mac: Option + Command + S
|
|
||||||
- on Windows: Ctrl + Alt + S
|
|
||||||
- on Linux: Ctrl + Alt + S
|
|
||||||
- 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
|
|
||||||
*.tmp
|
|
||||||
# Ignore a specific file
|
|
||||||
src/generated/autogen.cpp
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
@@ -367,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,14 +0,0 @@
|
|||||||
add_subdirectory(core)
|
|
||||||
add_subdirectory(Editor)
|
|
||||||
# add_subdirectory(serialization)
|
|
||||||
# add_subdirectory(tasks)
|
|
||||||
|
|
||||||
qt_add_library(TaskFlow STATIC)
|
|
||||||
|
|
||||||
target_link_libraries(TaskFlow
|
|
||||||
PUBLIC
|
|
||||||
TaskFlowCore
|
|
||||||
TaskFlowEditorplugin
|
|
||||||
# TaskFlowSerialization
|
|
||||||
# TaskFlowTasks
|
|
||||||
)
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
qt_add_library(TaskFlowEditor STATIC)
|
|
||||||
|
|
||||||
qt_policy(SET QTP0001 NEW)
|
|
||||||
qt_policy(SET QTP0004 NEW)
|
|
||||||
|
|
||||||
qt_add_qml_module(TaskFlowEditor
|
|
||||||
URI TaskFlow.Editor
|
|
||||||
VERSION 1.0
|
|
||||||
DEPENDENCIES QtQuick
|
|
||||||
RESOURCES
|
|
||||||
QML_FILES
|
|
||||||
qml/FlowEditorView.qml
|
|
||||||
qml/Flow.qml
|
|
||||||
qml/Task.qml
|
|
||||||
qml/TaskPort.qml
|
|
||||||
qml/TaskParameter.qml
|
|
||||||
qml/TaskConnection.qml
|
|
||||||
SOURCES
|
|
||||||
FlowEditor.hpp FlowEditor.cpp
|
|
||||||
FlowsModel.hpp FlowsModel.cpp
|
|
||||||
TaskItem.hpp TaskItem.cpp
|
|
||||||
FlowItem.hpp FlowItem.cpp
|
|
||||||
TaskModel.hpp TaskModel.cpp
|
|
||||||
TaskPortItem.hpp TaskPortItem.cpp
|
|
||||||
TaskPortModel.hpp TaskPortModel.cpp
|
|
||||||
TaskConnectionsModel.hpp TaskConnectionsModel.cpp
|
|
||||||
TaskConnectionItem.hpp TaskConnectionItem.cpp
|
|
||||||
GridBackground.hpp GridBackground.cpp
|
|
||||||
)
|
|
||||||
|
|
||||||
target_link_libraries(TaskFlowEditor
|
|
||||||
PUBLIC
|
|
||||||
Qt::Quick
|
|
||||||
PRIVATE
|
|
||||||
TaskFlowCore
|
|
||||||
)
|
|
||||||
|
|
||||||
target_include_directories(TaskFlowEditor
|
|
||||||
PUBLIC
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}
|
|
||||||
)
|
|
||||||
@@ -1,120 +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:
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "FlowEditor.hpp"
|
|
||||||
|
|
||||||
namespace QodeAssist::TaskFlow {
|
|
||||||
|
|
||||||
FlowEditor::FlowEditor(QQuickItem *parent)
|
|
||||||
: QQuickItem(parent)
|
|
||||||
{}
|
|
||||||
|
|
||||||
void FlowEditor::initialize()
|
|
||||||
{
|
|
||||||
emit availableTaskTypesChanged();
|
|
||||||
emit availableFlowsChanged();
|
|
||||||
|
|
||||||
m_flowsModel = new FlowsModel(m_flowManager, this);
|
|
||||||
|
|
||||||
emit flowsModelChanged();
|
|
||||||
|
|
||||||
if (m_flowsModel->rowCount() > 0) {
|
|
||||||
setCurrentFlowIndex(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// setCurrentFlowId(m_flowManager->flows().begin().value()->flowId());
|
|
||||||
m_currentFlow = m_flowManager->getFlow();
|
|
||||||
emit currentFlowChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
QString FlowEditor::currentFlowId() const
|
|
||||||
{
|
|
||||||
return m_currentFlowId;
|
|
||||||
}
|
|
||||||
|
|
||||||
void FlowEditor::setCurrentFlowId(const QString &newCurrentFlowId)
|
|
||||||
{
|
|
||||||
if (m_currentFlowId == newCurrentFlowId)
|
|
||||||
return;
|
|
||||||
m_currentFlowId = newCurrentFlowId;
|
|
||||||
emit currentFlowIdChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
QStringList FlowEditor::availableTaskTypes() const
|
|
||||||
{
|
|
||||||
if (m_flowManager)
|
|
||||||
return m_flowManager->getAvailableTasksTypes();
|
|
||||||
else {
|
|
||||||
return {"No flow manager"};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QStringList FlowEditor::availableFlows() const
|
|
||||||
{
|
|
||||||
if (m_flowManager) {
|
|
||||||
auto flows = m_flowManager->getAvailableFlows();
|
|
||||||
return flows.size() > 0 ? flows : QStringList{"No flows"};
|
|
||||||
} else {
|
|
||||||
return {"No flow manager"};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void FlowEditor::setFlowManager(FlowManager *newFlowManager)
|
|
||||||
{
|
|
||||||
if (m_flowManager == newFlowManager)
|
|
||||||
return;
|
|
||||||
m_flowManager = newFlowManager;
|
|
||||||
|
|
||||||
initialize();
|
|
||||||
}
|
|
||||||
|
|
||||||
FlowsModel *FlowEditor::flowsModel() const
|
|
||||||
{
|
|
||||||
return m_flowsModel;
|
|
||||||
}
|
|
||||||
|
|
||||||
int FlowEditor::currentFlowIndex() const
|
|
||||||
{
|
|
||||||
return m_currentFlowIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
void FlowEditor::setCurrentFlowIndex(int newCurrentFlowIndex)
|
|
||||||
{
|
|
||||||
if (m_currentFlowIndex == newCurrentFlowIndex)
|
|
||||||
return;
|
|
||||||
m_currentFlowIndex = newCurrentFlowIndex;
|
|
||||||
emit currentFlowIndexChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
Flow *FlowEditor::getFlow(const QString &flowName)
|
|
||||||
{
|
|
||||||
return m_flowManager->getFlow(flowName);
|
|
||||||
}
|
|
||||||
|
|
||||||
Flow *FlowEditor::getCurrentFlow()
|
|
||||||
{
|
|
||||||
return m_flowManager->getFlow(m_currentFlowId);
|
|
||||||
}
|
|
||||||
|
|
||||||
Flow *FlowEditor::currentFlow() const
|
|
||||||
{
|
|
||||||
return m_currentFlow;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace QodeAssist::TaskFlow
|
|
||||||
@@ -1,86 +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:
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <QQuickItem>
|
|
||||||
|
|
||||||
#include "FlowsModel.hpp"
|
|
||||||
#include <FlowManager.hpp>
|
|
||||||
|
|
||||||
namespace QodeAssist::TaskFlow {
|
|
||||||
|
|
||||||
class FlowEditor : public QQuickItem
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
QML_ELEMENT
|
|
||||||
|
|
||||||
Q_PROPERTY(
|
|
||||||
QString currentFlowId READ currentFlowId WRITE setCurrentFlowId NOTIFY currentFlowIdChanged)
|
|
||||||
Q_PROPERTY(
|
|
||||||
QStringList availableTaskTypes READ availableTaskTypes NOTIFY availableTaskTypesChanged)
|
|
||||||
Q_PROPERTY(QStringList availableFlows READ availableFlows NOTIFY availableFlowsChanged)
|
|
||||||
Q_PROPERTY(FlowsModel *flowsModel READ flowsModel NOTIFY flowsModelChanged)
|
|
||||||
Q_PROPERTY(int currentFlowIndex READ currentFlowIndex WRITE setCurrentFlowIndex NOTIFY
|
|
||||||
currentFlowIndexChanged)
|
|
||||||
|
|
||||||
Q_PROPERTY(Flow *currentFlow READ currentFlow NOTIFY currentFlowChanged FINAL)
|
|
||||||
|
|
||||||
public:
|
|
||||||
FlowEditor(QQuickItem *parent = nullptr);
|
|
||||||
|
|
||||||
void initialize();
|
|
||||||
|
|
||||||
QString currentFlowId() const;
|
|
||||||
void setCurrentFlowId(const QString &newCurrentFlowId);
|
|
||||||
|
|
||||||
QStringList availableTaskTypes() const;
|
|
||||||
QStringList availableFlows() const;
|
|
||||||
|
|
||||||
void setFlowManager(FlowManager *newFlowManager);
|
|
||||||
|
|
||||||
FlowsModel *flowsModel() const;
|
|
||||||
|
|
||||||
int currentFlowIndex() const;
|
|
||||||
void setCurrentFlowIndex(int newCurrentFlowIndex);
|
|
||||||
|
|
||||||
Q_INVOKABLE Flow *getFlow(const QString &flowName);
|
|
||||||
Q_INVOKABLE Flow *getCurrentFlow();
|
|
||||||
|
|
||||||
Flow *currentFlow() const;
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void currentFlowIdChanged();
|
|
||||||
void availableTaskTypesChanged();
|
|
||||||
void availableFlowsChanged();
|
|
||||||
void flowsModelChanged();
|
|
||||||
|
|
||||||
void currentFlowIndexChanged();
|
|
||||||
|
|
||||||
void currentFlowChanged();
|
|
||||||
|
|
||||||
private:
|
|
||||||
FlowManager *m_flowManager = nullptr;
|
|
||||||
QString m_currentFlowId;
|
|
||||||
FlowsModel *m_flowsModel;
|
|
||||||
int m_currentFlowIndex;
|
|
||||||
Flow *m_currentFlow = nullptr;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace QodeAssist::TaskFlow
|
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
#include "FlowItem.hpp"
|
|
||||||
|
|
||||||
namespace QodeAssist::TaskFlow {
|
|
||||||
|
|
||||||
FlowItem::FlowItem(QQuickItem *parent)
|
|
||||||
: QQuickItem(parent)
|
|
||||||
{
|
|
||||||
connect(this, &QQuickItem::childrenChanged, this, [this]() { updateFlowLayout(); });
|
|
||||||
}
|
|
||||||
|
|
||||||
QString FlowItem::flowId() const
|
|
||||||
{
|
|
||||||
if (!m_flow)
|
|
||||||
return {"no flow"};
|
|
||||||
return m_flow->flowId();
|
|
||||||
}
|
|
||||||
|
|
||||||
void FlowItem::setFlowId(const QString &newFlowId)
|
|
||||||
{
|
|
||||||
if (m_flow->flowId() == newFlowId)
|
|
||||||
return;
|
|
||||||
m_flow->setFlowId(newFlowId);
|
|
||||||
emit flowIdChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
Flow *FlowItem::flow() const
|
|
||||||
{
|
|
||||||
return m_flow;
|
|
||||||
}
|
|
||||||
|
|
||||||
void FlowItem::setFlow(Flow *newFlow)
|
|
||||||
{
|
|
||||||
if (m_flow == newFlow)
|
|
||||||
return;
|
|
||||||
m_flow = newFlow;
|
|
||||||
emit flowChanged();
|
|
||||||
emit flowIdChanged();
|
|
||||||
qDebug() << "FlowItem::setFlow" << m_flow->flowId() << newFlow;
|
|
||||||
|
|
||||||
m_taskModel = new TaskModel(m_flow, this);
|
|
||||||
m_connectionsModel = new TaskConnectionsModel(m_flow, this);
|
|
||||||
|
|
||||||
emit taskModelChanged();
|
|
||||||
emit connectionsModelChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
TaskModel *FlowItem::taskModel() const
|
|
||||||
{
|
|
||||||
return m_taskModel;
|
|
||||||
}
|
|
||||||
|
|
||||||
TaskConnectionsModel *FlowItem::connectionsModel() const
|
|
||||||
{
|
|
||||||
return m_connectionsModel;
|
|
||||||
}
|
|
||||||
|
|
||||||
QVariantList FlowItem::taskItems() const
|
|
||||||
{
|
|
||||||
return m_taskItems;
|
|
||||||
}
|
|
||||||
|
|
||||||
void FlowItem::setTaskItems(const QVariantList &newTaskItems)
|
|
||||||
{
|
|
||||||
qDebug() << "FlowItem::setTaskItems" << newTaskItems;
|
|
||||||
if (m_taskItems == newTaskItems)
|
|
||||||
return;
|
|
||||||
m_taskItems = newTaskItems;
|
|
||||||
emit taskItemsChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
void FlowItem::updateFlowLayout()
|
|
||||||
{
|
|
||||||
auto allItems = this->childItems();
|
|
||||||
|
|
||||||
for (auto child : allItems) {
|
|
||||||
if (child->objectName() == QString("TaskItem")) {
|
|
||||||
qDebug() << "Found TaskItem:" << child;
|
|
||||||
auto taskItem = qobject_cast<TaskItem *>(child);
|
|
||||||
m_taskItemsList.insert(taskItem, taskItem->task());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (child->objectName() == QString("TaskConnectionItem")) {
|
|
||||||
qDebug() << "Found TaskConnectionItem:" << child;
|
|
||||||
auto connectionItem = qobject_cast<TaskConnectionItem *>(child);
|
|
||||||
m_taskConnectionsList.insert(connectionItem, connectionItem->connection());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace QodeAssist::TaskFlow
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <QQuickItem>
|
|
||||||
|
|
||||||
#include "TaskConnectionItem.hpp"
|
|
||||||
#include "TaskConnectionsModel.hpp"
|
|
||||||
#include "TaskItem.hpp"
|
|
||||||
#include "TaskModel.hpp"
|
|
||||||
#include <Flow.hpp>
|
|
||||||
#include <TaskConnection.hpp>
|
|
||||||
|
|
||||||
namespace QodeAssist::TaskFlow {
|
|
||||||
|
|
||||||
class FlowItem : public QQuickItem
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
QML_ELEMENT
|
|
||||||
|
|
||||||
Q_PROPERTY(QString flowId READ flowId WRITE setFlowId NOTIFY flowIdChanged)
|
|
||||||
Q_PROPERTY(Flow *flow READ flow WRITE setFlow NOTIFY flowChanged)
|
|
||||||
Q_PROPERTY(TaskModel *taskModel READ taskModel NOTIFY taskModelChanged)
|
|
||||||
Q_PROPERTY(
|
|
||||||
TaskConnectionsModel *connectionsModel READ connectionsModel NOTIFY connectionsModelChanged)
|
|
||||||
Q_PROPERTY(QVariantList taskItems READ taskItems WRITE setTaskItems NOTIFY taskItemsChanged)
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit FlowItem(QQuickItem *parent = nullptr);
|
|
||||||
|
|
||||||
QString flowId() const;
|
|
||||||
void setFlowId(const QString &newFlowId);
|
|
||||||
|
|
||||||
Flow *flow() const;
|
|
||||||
void setFlow(Flow *newFlow);
|
|
||||||
|
|
||||||
TaskModel *taskModel() const;
|
|
||||||
|
|
||||||
TaskConnectionsModel *connectionsModel() const;
|
|
||||||
|
|
||||||
QVariantList taskItems() const;
|
|
||||||
void setTaskItems(const QVariantList &newTaskItems);
|
|
||||||
|
|
||||||
void updateFlowLayout();
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void flowIdChanged();
|
|
||||||
void flowChanged();
|
|
||||||
void taskModelChanged();
|
|
||||||
void connectionsModelChanged();
|
|
||||||
void taskItemsChanged();
|
|
||||||
|
|
||||||
private:
|
|
||||||
Flow *m_flow = nullptr;
|
|
||||||
TaskModel *m_taskModel = nullptr;
|
|
||||||
TaskConnectionsModel *m_connectionsModel = nullptr;
|
|
||||||
QVariantList m_taskItems;
|
|
||||||
|
|
||||||
QHash<TaskItem *, BaseTask *> m_taskItemsList;
|
|
||||||
QHash<TaskConnectionItem *, TaskConnection *> m_taskConnectionsList;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace QodeAssist::TaskFlow
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
#include "FlowsModel.hpp"
|
|
||||||
|
|
||||||
#include "FlowManager.hpp"
|
|
||||||
|
|
||||||
namespace QodeAssist::TaskFlow {
|
|
||||||
|
|
||||||
FlowsModel::FlowsModel(FlowManager *flowManager, QObject *parent)
|
|
||||||
: QAbstractListModel(parent)
|
|
||||||
, m_flowManager(flowManager)
|
|
||||||
{
|
|
||||||
connect(m_flowManager, &FlowManager::flowAdded, this, &FlowsModel::onFlowAdded);
|
|
||||||
}
|
|
||||||
|
|
||||||
int FlowsModel::rowCount(const QModelIndex &parent) const
|
|
||||||
{
|
|
||||||
return m_flowManager->flows().size();
|
|
||||||
}
|
|
||||||
|
|
||||||
QVariant FlowsModel::data(const QModelIndex &index, int role) const
|
|
||||||
{
|
|
||||||
if (!index.isValid() || !m_flowManager || index.row() >= m_flowManager->flows().size())
|
|
||||||
return QVariant();
|
|
||||||
|
|
||||||
const auto flows = m_flowManager->flows().values();
|
|
||||||
|
|
||||||
switch (role) {
|
|
||||||
case FlowRoles::FlowIdRole:
|
|
||||||
return flows.at(index.row())->flowId();
|
|
||||||
case FlowRoles::FlowDataRole:
|
|
||||||
return QVariant::fromValue(flows.at(index.row()));
|
|
||||||
default:
|
|
||||||
return QVariant();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QHash<int, QByteArray> FlowsModel::roleNames() const
|
|
||||||
{
|
|
||||||
QHash<int, QByteArray> roles;
|
|
||||||
roles[FlowRoles::FlowIdRole] = "flowId";
|
|
||||||
roles[FlowRoles::FlowDataRole] = "flowData";
|
|
||||||
return roles;
|
|
||||||
}
|
|
||||||
|
|
||||||
void FlowsModel::onFlowAdded(const QString &flowId)
|
|
||||||
{
|
|
||||||
// qDebug() << "FlowsModel::Flow added: " << flowId;
|
|
||||||
// int newIndex = m_flowManager->flows().size();
|
|
||||||
// beginInsertRows(QModelIndex(), newIndex, newIndex);
|
|
||||||
// endInsertRows();
|
|
||||||
}
|
|
||||||
|
|
||||||
void FlowsModel::onFlowRemoved(const QString &flowId) {}
|
|
||||||
|
|
||||||
} // namespace QodeAssist::TaskFlow
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <QAbstractListModel>
|
|
||||||
#include <QObject>
|
|
||||||
|
|
||||||
// #include "tasks/Flow.hpp"
|
|
||||||
#include <FlowManager.hpp>
|
|
||||||
|
|
||||||
namespace QodeAssist::TaskFlow {
|
|
||||||
|
|
||||||
class FlowsModel : public QAbstractListModel
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
enum FlowRoles { FlowIdRole = Qt::UserRole, FlowDataRole };
|
|
||||||
|
|
||||||
FlowsModel(FlowManager *flowManager, QObject *parent = nullptr);
|
|
||||||
|
|
||||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
|
||||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
|
||||||
QHash<int, QByteArray> roleNames() const override;
|
|
||||||
|
|
||||||
public slots:
|
|
||||||
void onFlowAdded(const QString &flowId);
|
|
||||||
void onFlowRemoved(const QString &flowId);
|
|
||||||
|
|
||||||
private:
|
|
||||||
FlowManager *m_flowManager;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace QodeAssist::TaskFlow
|
|
||||||
@@ -1,98 +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 "GridBackground.hpp"
|
|
||||||
#include <QPainter>
|
|
||||||
#include <QPixmap>
|
|
||||||
#include <QQuickWindow>
|
|
||||||
#include <QSGSimpleRectNode>
|
|
||||||
#include <QSGSimpleTextureNode>
|
|
||||||
|
|
||||||
namespace QodeAssist::TaskFlow {
|
|
||||||
|
|
||||||
GridBackground::GridBackground(QQuickItem *parent)
|
|
||||||
: QQuickItem(parent)
|
|
||||||
{
|
|
||||||
setFlag(QQuickItem::ItemHasContents, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
int GridBackground::gridSize() const
|
|
||||||
{
|
|
||||||
return m_gridSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
void GridBackground::setGridSize(int size)
|
|
||||||
{
|
|
||||||
if (m_gridSize != size) {
|
|
||||||
m_gridSize = size;
|
|
||||||
update();
|
|
||||||
emit gridSizeChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QColor GridBackground::gridColor() const
|
|
||||||
{
|
|
||||||
return m_gridColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
void GridBackground::setGridColor(const QColor &color)
|
|
||||||
{
|
|
||||||
if (m_gridColor != color) {
|
|
||||||
m_gridColor = color;
|
|
||||||
update();
|
|
||||||
emit gridColorChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QSGNode *GridBackground::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)
|
|
||||||
{
|
|
||||||
QSGSimpleTextureNode *node = static_cast<QSGSimpleTextureNode *>(oldNode);
|
|
||||||
if (!node) {
|
|
||||||
node = new QSGSimpleTextureNode();
|
|
||||||
}
|
|
||||||
|
|
||||||
QPixmap pixmap(width(), height());
|
|
||||||
pixmap.fill(Qt::transparent);
|
|
||||||
|
|
||||||
QPainter painter(&pixmap);
|
|
||||||
painter.setRenderHint(QPainter::Antialiasing, false);
|
|
||||||
|
|
||||||
QPen pen(m_gridColor);
|
|
||||||
pen.setWidth(1);
|
|
||||||
painter.setPen(pen);
|
|
||||||
painter.setOpacity(this->opacity());
|
|
||||||
|
|
||||||
for (int x = 0; x < width(); x += m_gridSize) {
|
|
||||||
painter.drawLine(x, 0, x, height());
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int y = 0; y < height(); y += m_gridSize) {
|
|
||||||
painter.drawLine(0, y, width(), y);
|
|
||||||
}
|
|
||||||
|
|
||||||
painter.end();
|
|
||||||
|
|
||||||
QSGTexture *texture = window()->createTextureFromImage(pixmap.toImage());
|
|
||||||
node->setTexture(texture);
|
|
||||||
node->setRect(boundingRect());
|
|
||||||
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace QodeAssist::TaskFlow
|
|
||||||
@@ -1,57 +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 <QColor>
|
|
||||||
#include <QPainter>
|
|
||||||
#include <QQuickItem>
|
|
||||||
|
|
||||||
namespace QodeAssist::TaskFlow {
|
|
||||||
|
|
||||||
class GridBackground : public QQuickItem
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
QML_ELEMENT
|
|
||||||
|
|
||||||
Q_PROPERTY(int gridSize READ gridSize WRITE setGridSize NOTIFY gridSizeChanged)
|
|
||||||
Q_PROPERTY(QColor gridColor READ gridColor WRITE setGridColor NOTIFY gridColorChanged)
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit GridBackground(QQuickItem *parent = nullptr);
|
|
||||||
|
|
||||||
int gridSize() const;
|
|
||||||
void setGridSize(int size);
|
|
||||||
|
|
||||||
QColor gridColor() const;
|
|
||||||
void setGridColor(const QColor &color);
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void gridSizeChanged();
|
|
||||||
void gridColorChanged();
|
|
||||||
|
|
||||||
protected:
|
|
||||||
QSGNode *updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *) override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
int m_gridSize = 20;
|
|
||||||
QColor m_gridColor = QColor(128, 128, 128);
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace QodeAssist::TaskFlow
|
|
||||||
@@ -1,153 +0,0 @@
|
|||||||
#include "TaskConnectionItem.hpp"
|
|
||||||
#include "TaskItem.hpp"
|
|
||||||
#include "TaskPortItem.hpp"
|
|
||||||
#include <QDebug>
|
|
||||||
|
|
||||||
namespace QodeAssist::TaskFlow {
|
|
||||||
|
|
||||||
TaskConnectionItem::TaskConnectionItem(QQuickItem *parent)
|
|
||||||
: QQuickItem(parent)
|
|
||||||
{
|
|
||||||
setObjectName("TaskConnectionItem");
|
|
||||||
}
|
|
||||||
|
|
||||||
void TaskConnectionItem::setConnection(TaskConnection *connection)
|
|
||||||
{
|
|
||||||
if (m_connection == connection)
|
|
||||||
return;
|
|
||||||
|
|
||||||
m_connection = connection;
|
|
||||||
emit connectionChanged();
|
|
||||||
|
|
||||||
calculatePositions();
|
|
||||||
}
|
|
||||||
|
|
||||||
void TaskConnectionItem::updatePositions()
|
|
||||||
{
|
|
||||||
// calculatePositions();
|
|
||||||
}
|
|
||||||
|
|
||||||
void TaskConnectionItem::calculatePositions()
|
|
||||||
{
|
|
||||||
if (!m_connection) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find source task item
|
|
||||||
QQuickItem *sourceTaskItem = findTaskItem(m_connection->sourceTask());
|
|
||||||
QQuickItem *targetTaskItem = findTaskItem(m_connection->targetTask());
|
|
||||||
|
|
||||||
if (!sourceTaskItem || !targetTaskItem) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find port items within tasks
|
|
||||||
QQuickItem *sourcePortItem = findPortItem(sourceTaskItem, m_connection->sourcePort());
|
|
||||||
QQuickItem *targetPortItem = findPortItem(targetTaskItem, m_connection->targetPort());
|
|
||||||
|
|
||||||
if (!sourcePortItem || !targetPortItem) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate global positions
|
|
||||||
QPointF sourceGlobal
|
|
||||||
= sourcePortItem
|
|
||||||
->mapToItem(parentItem(), sourcePortItem->width() / 2, sourcePortItem->height() / 2);
|
|
||||||
QPointF targetGlobal
|
|
||||||
= targetPortItem
|
|
||||||
->mapToItem(parentItem(), targetPortItem->width() / 2, targetPortItem->height() / 2);
|
|
||||||
|
|
||||||
if (m_startPoint != sourceGlobal) {
|
|
||||||
m_startPoint = sourceGlobal;
|
|
||||||
emit startPointChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_endPoint != targetGlobal) {
|
|
||||||
m_endPoint = targetGlobal;
|
|
||||||
emit endPointChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QQuickItem *TaskConnectionItem::findTaskItem(BaseTask *task)
|
|
||||||
{
|
|
||||||
for (const QVariant &item : m_taskItems) {
|
|
||||||
QQuickItem *taskItem = qvariant_cast<QQuickItem *>(item);
|
|
||||||
if (!taskItem)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
QVariant taskProp = taskItem->property("task");
|
|
||||||
if (taskProp.isValid() && taskProp.value<BaseTask *>() == task) {
|
|
||||||
return taskItem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
QQuickItem *TaskConnectionItem::findTaskItemRecursive(QQuickItem *item, BaseTask *task)
|
|
||||||
{
|
|
||||||
// Проверяем objectName и task property
|
|
||||||
if (item->objectName() == "TaskItem") {
|
|
||||||
QVariant taskProp = item->property("task");
|
|
||||||
if (taskProp.isValid()) {
|
|
||||||
BaseTask *itemTask = taskProp.value<BaseTask *>();
|
|
||||||
if (itemTask == task) {
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Рекурсивно ищем в детях
|
|
||||||
auto children = item->childItems();
|
|
||||||
|
|
||||||
for (QQuickItem *child : children) {
|
|
||||||
if (QQuickItem *found = findTaskItemRecursive(child, task)) {
|
|
||||||
return found;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
QQuickItem *TaskConnectionItem::findPortItem(QQuickItem *taskItem, TaskPort *port)
|
|
||||||
{
|
|
||||||
std::function<QQuickItem *(QQuickItem *)> findPortRecursive =
|
|
||||||
[&](QQuickItem *item) -> QQuickItem * {
|
|
||||||
// Проверяем objectName и port property
|
|
||||||
if (item->objectName() == "TaskPortItem") {
|
|
||||||
QVariant portProp = item->property("port");
|
|
||||||
if (portProp.isValid()) {
|
|
||||||
TaskPort *itemPort = portProp.value<TaskPort *>();
|
|
||||||
if (itemPort == port) {
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Рекурсивно ищем в детях
|
|
||||||
for (QQuickItem *child : item->childItems()) {
|
|
||||||
if (QQuickItem *found = findPortRecursive(child)) {
|
|
||||||
return found;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
};
|
|
||||||
|
|
||||||
return findPortRecursive(taskItem);
|
|
||||||
}
|
|
||||||
|
|
||||||
QVariantList TaskConnectionItem::taskItems() const
|
|
||||||
{
|
|
||||||
return m_taskItems;
|
|
||||||
}
|
|
||||||
|
|
||||||
void TaskConnectionItem::setTaskItems(const QVariantList &newTaskItems)
|
|
||||||
{
|
|
||||||
if (m_taskItems == newTaskItems)
|
|
||||||
return;
|
|
||||||
m_taskItems = newTaskItems;
|
|
||||||
emit taskItemsChanged();
|
|
||||||
|
|
||||||
calculatePositions();
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace QodeAssist::TaskFlow
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "TaskConnection.hpp"
|
|
||||||
#include <QPointF>
|
|
||||||
#include <QQuickItem>
|
|
||||||
|
|
||||||
namespace QodeAssist::TaskFlow {
|
|
||||||
|
|
||||||
class TaskConnectionItem : public QQuickItem
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
QML_ELEMENT
|
|
||||||
|
|
||||||
Q_PROPERTY(QPointF startPoint READ startPoint NOTIFY startPointChanged)
|
|
||||||
Q_PROPERTY(QPointF endPoint READ endPoint NOTIFY endPointChanged)
|
|
||||||
Q_PROPERTY(
|
|
||||||
TaskConnection *connection READ connection WRITE setConnection NOTIFY connectionChanged)
|
|
||||||
|
|
||||||
Q_PROPERTY(QVariantList taskItems READ taskItems WRITE setTaskItems NOTIFY taskItemsChanged)
|
|
||||||
|
|
||||||
public:
|
|
||||||
TaskConnectionItem(QQuickItem *parent = nullptr);
|
|
||||||
|
|
||||||
QPointF startPoint() const { return m_startPoint; }
|
|
||||||
QPointF endPoint() const { return m_endPoint; }
|
|
||||||
|
|
||||||
TaskConnection *connection() const { return m_connection; }
|
|
||||||
void setConnection(TaskConnection *connection);
|
|
||||||
|
|
||||||
Q_INVOKABLE void updatePositions();
|
|
||||||
|
|
||||||
QVariantList taskItems() const;
|
|
||||||
void setTaskItems(const QVariantList &newTaskItems);
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void startPointChanged();
|
|
||||||
void endPointChanged();
|
|
||||||
void connectionChanged();
|
|
||||||
|
|
||||||
void taskItemsChanged();
|
|
||||||
|
|
||||||
private:
|
|
||||||
void calculatePositions();
|
|
||||||
QQuickItem *findTaskItem(BaseTask *task);
|
|
||||||
QQuickItem *findTaskItemRecursive(QQuickItem *item, BaseTask *task);
|
|
||||||
QQuickItem *findPortItem(QQuickItem *taskItem, TaskPort *port);
|
|
||||||
|
|
||||||
private:
|
|
||||||
TaskConnection *m_connection = nullptr;
|
|
||||||
QPointF m_startPoint;
|
|
||||||
QPointF m_endPoint;
|
|
||||||
QVariantList m_taskItems;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace QodeAssist::TaskFlow
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
#include "TaskConnectionsModel.hpp"
|
|
||||||
|
|
||||||
namespace QodeAssist::TaskFlow {
|
|
||||||
|
|
||||||
TaskConnectionsModel::TaskConnectionsModel(Flow *flow, QObject *parent)
|
|
||||||
: QAbstractListModel(parent)
|
|
||||||
, m_flow(flow)
|
|
||||||
{}
|
|
||||||
|
|
||||||
int TaskConnectionsModel::rowCount(const QModelIndex &parent) const
|
|
||||||
{
|
|
||||||
return m_flow->connections().size();
|
|
||||||
}
|
|
||||||
|
|
||||||
QVariant TaskConnectionsModel::data(const QModelIndex &index, int role) const
|
|
||||||
{
|
|
||||||
if (role == TaskConnectionsRoles::TaskConnectionsRole)
|
|
||||||
return QVariant::fromValue(m_flow->connections().at(index.row()));
|
|
||||||
return QVariant();
|
|
||||||
}
|
|
||||||
|
|
||||||
QHash<int, QByteArray> TaskConnectionsModel::roleNames() const
|
|
||||||
{
|
|
||||||
QHash<int, QByteArray> roles;
|
|
||||||
roles[TaskConnectionsRoles::TaskConnectionsRole] = "connectionData";
|
|
||||||
return roles;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace QodeAssist::TaskFlow
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <QAbstractListModel>
|
|
||||||
#include <QObject>
|
|
||||||
|
|
||||||
#include <Flow.hpp>
|
|
||||||
|
|
||||||
namespace QodeAssist::TaskFlow {
|
|
||||||
|
|
||||||
class TaskConnectionsModel : public QAbstractListModel
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
enum TaskConnectionsRoles { TaskConnectionsRole = Qt::UserRole };
|
|
||||||
|
|
||||||
explicit TaskConnectionsModel(Flow *flow, QObject *parent = nullptr);
|
|
||||||
|
|
||||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
|
||||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
|
||||||
QHash<int, QByteArray> roleNames() const override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
Flow *m_flow;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace QodeAssist::TaskFlow
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
#include "TaskItem.hpp"
|
|
||||||
|
|
||||||
namespace QodeAssist::TaskFlow {
|
|
||||||
|
|
||||||
TaskItem::TaskItem(QQuickItem *parent)
|
|
||||||
: QQuickItem(parent)
|
|
||||||
{
|
|
||||||
setObjectName("TaskItem");
|
|
||||||
}
|
|
||||||
|
|
||||||
QString TaskItem::taskId() const
|
|
||||||
{
|
|
||||||
return m_taskId;
|
|
||||||
}
|
|
||||||
|
|
||||||
void TaskItem::setTaskId(const QString &newTaskId)
|
|
||||||
{
|
|
||||||
if (m_taskId == newTaskId)
|
|
||||||
return;
|
|
||||||
m_taskId = newTaskId;
|
|
||||||
emit taskIdChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
QString TaskItem::taskType() const
|
|
||||||
{
|
|
||||||
return m_task ? m_task->taskType() : QString();
|
|
||||||
}
|
|
||||||
|
|
||||||
BaseTask *TaskItem::task() const
|
|
||||||
{
|
|
||||||
return m_task;
|
|
||||||
}
|
|
||||||
|
|
||||||
void TaskItem::setTask(BaseTask *newTask)
|
|
||||||
{
|
|
||||||
if (m_task == newTask)
|
|
||||||
return;
|
|
||||||
|
|
||||||
m_task = newTask;
|
|
||||||
|
|
||||||
if (m_task) {
|
|
||||||
m_taskId = m_task->taskId();
|
|
||||||
|
|
||||||
// Обновляем модели портов
|
|
||||||
m_inputPorts = new TaskPortModel(m_task->getInputPorts(), this);
|
|
||||||
m_outputPorts = new TaskPortModel(m_task->getOutputPorts(), this);
|
|
||||||
} else {
|
|
||||||
m_inputPorts = nullptr;
|
|
||||||
m_outputPorts = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
emit taskChanged();
|
|
||||||
emit inputPortsChanged();
|
|
||||||
emit outputPortsChanged();
|
|
||||||
emit taskIdChanged();
|
|
||||||
emit taskTypeChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
TaskPortModel *TaskItem::inputPorts() const
|
|
||||||
{
|
|
||||||
return m_inputPorts;
|
|
||||||
}
|
|
||||||
|
|
||||||
TaskPortModel *TaskItem::outputPorts() const
|
|
||||||
{
|
|
||||||
return m_outputPorts;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace QodeAssist::TaskFlow
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <QQuickItem>
|
|
||||||
|
|
||||||
#include "TaskPortModel.hpp"
|
|
||||||
#include <BaseTask.hpp>
|
|
||||||
#include <TaskPort.hpp>
|
|
||||||
|
|
||||||
namespace QodeAssist::TaskFlow {
|
|
||||||
|
|
||||||
class TaskItem : public QQuickItem
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
QML_ELEMENT
|
|
||||||
|
|
||||||
Q_PROPERTY(QString taskId READ taskId WRITE setTaskId NOTIFY taskIdChanged)
|
|
||||||
Q_PROPERTY(QString taskType READ taskType NOTIFY taskTypeChanged)
|
|
||||||
Q_PROPERTY(BaseTask *task READ task WRITE setTask NOTIFY taskChanged)
|
|
||||||
Q_PROPERTY(TaskPortModel *inputPorts READ inputPorts NOTIFY inputPortsChanged)
|
|
||||||
Q_PROPERTY(TaskPortModel *outputPorts READ outputPorts NOTIFY outputPortsChanged)
|
|
||||||
|
|
||||||
public:
|
|
||||||
TaskItem(QQuickItem *parent = nullptr);
|
|
||||||
|
|
||||||
QString taskId() const;
|
|
||||||
void setTaskId(const QString &newTaskId);
|
|
||||||
QString taskType() const;
|
|
||||||
|
|
||||||
BaseTask *task() const;
|
|
||||||
void setTask(BaseTask *newTask);
|
|
||||||
|
|
||||||
TaskPortModel *inputPorts() const;
|
|
||||||
TaskPortModel *outputPorts() const;
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void taskIdChanged();
|
|
||||||
void taskTypeChanged();
|
|
||||||
void taskChanged();
|
|
||||||
void inputPortsChanged();
|
|
||||||
void outputPortsChanged();
|
|
||||||
|
|
||||||
private:
|
|
||||||
QString m_taskId;
|
|
||||||
BaseTask *m_task = nullptr;
|
|
||||||
TaskPortModel *m_inputPorts = nullptr;
|
|
||||||
TaskPortModel *m_outputPorts = nullptr;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace QodeAssist::TaskFlow
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
#include "TaskModel.hpp"
|
|
||||||
|
|
||||||
namespace QodeAssist::TaskFlow {
|
|
||||||
|
|
||||||
TaskModel::TaskModel(Flow *flow, QObject *parent)
|
|
||||||
: QAbstractListModel(parent)
|
|
||||||
, m_flow(flow)
|
|
||||||
{}
|
|
||||||
|
|
||||||
int TaskModel::rowCount(const QModelIndex &parent) const
|
|
||||||
{
|
|
||||||
return m_flow->tasks().size();
|
|
||||||
}
|
|
||||||
|
|
||||||
QVariant TaskModel::data(const QModelIndex &index, int role) const
|
|
||||||
{
|
|
||||||
if (!index.isValid() || !m_flow || index.row() >= m_flow->tasks().size())
|
|
||||||
return QVariant();
|
|
||||||
|
|
||||||
const auto &task = m_flow->tasks().values();
|
|
||||||
|
|
||||||
switch (role) {
|
|
||||||
case TaskRoles::TaskIdRole:
|
|
||||||
return task.at(index.row())->taskId();
|
|
||||||
case TaskRoles::TaskDataRole:
|
|
||||||
return QVariant::fromValue(task.at(index.row()));
|
|
||||||
default:
|
|
||||||
return QVariant();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QHash<int, QByteArray> TaskModel::roleNames() const
|
|
||||||
{
|
|
||||||
QHash<int, QByteArray> roles;
|
|
||||||
roles[TaskRoles::TaskIdRole] = "taskId";
|
|
||||||
roles[TaskRoles::TaskDataRole] = "taskData";
|
|
||||||
return roles;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace QodeAssist::TaskFlow
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <QAbstractListModel>
|
|
||||||
|
|
||||||
#include <Flow.hpp>
|
|
||||||
|
|
||||||
namespace QodeAssist::TaskFlow {
|
|
||||||
|
|
||||||
class TaskModel : public QAbstractListModel
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
enum TaskRoles { TaskIdRole = Qt::UserRole, TaskDataRole };
|
|
||||||
|
|
||||||
TaskModel(Flow *flow, QObject *parent);
|
|
||||||
|
|
||||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
|
||||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
|
||||||
QHash<int, QByteArray> roleNames() const override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
Flow *m_flow;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace QodeAssist::TaskFlow
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
#include "TaskPortItem.hpp"
|
|
||||||
|
|
||||||
namespace QodeAssist::TaskFlow {
|
|
||||||
|
|
||||||
TaskPortItem::TaskPortItem(QQuickItem *parent)
|
|
||||||
: QQuickItem(parent)
|
|
||||||
{
|
|
||||||
setObjectName("TaskPortItem");
|
|
||||||
}
|
|
||||||
|
|
||||||
TaskPort *TaskPortItem::port() const
|
|
||||||
{
|
|
||||||
return m_port;
|
|
||||||
}
|
|
||||||
|
|
||||||
void TaskPortItem::setPort(TaskPort *newPort)
|
|
||||||
{
|
|
||||||
if (m_port == newPort)
|
|
||||||
return;
|
|
||||||
m_port = newPort;
|
|
||||||
emit portChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
QString TaskPortItem::name() const
|
|
||||||
{
|
|
||||||
return m_port->name();
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace QodeAssist::TaskFlow
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <TaskPort.hpp>
|
|
||||||
#include <QQuickItem>
|
|
||||||
|
|
||||||
namespace QodeAssist::TaskFlow {
|
|
||||||
|
|
||||||
class TaskPortItem : public QQuickItem
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
QML_ELEMENT
|
|
||||||
|
|
||||||
Q_PROPERTY(TaskPort *port READ port WRITE setPort NOTIFY portChanged)
|
|
||||||
Q_PROPERTY(QString name READ name CONSTANT)
|
|
||||||
|
|
||||||
public:
|
|
||||||
TaskPortItem(QQuickItem *parent = nullptr);
|
|
||||||
|
|
||||||
TaskPort *port() const;
|
|
||||||
void setPort(TaskPort *newPort);
|
|
||||||
|
|
||||||
QString name() const;
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void portChanged();
|
|
||||||
|
|
||||||
private:
|
|
||||||
TaskPort *m_port = nullptr;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace QodeAssist::TaskFlow
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
#include "TaskPortModel.hpp"
|
|
||||||
#include "TaskPort.hpp"
|
|
||||||
|
|
||||||
namespace QodeAssist::TaskFlow {
|
|
||||||
|
|
||||||
TaskPortModel::TaskPortModel(const QList<TaskPort *> &ports, QObject *parent)
|
|
||||||
: QAbstractListModel(parent)
|
|
||||||
, m_ports(ports)
|
|
||||||
{}
|
|
||||||
|
|
||||||
int TaskPortModel::rowCount(const QModelIndex &parent) const
|
|
||||||
{
|
|
||||||
return m_ports.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
QVariant TaskPortModel::data(const QModelIndex &index, int role) const
|
|
||||||
{
|
|
||||||
if (!index.isValid() || index.row() >= m_ports.size())
|
|
||||||
return QVariant();
|
|
||||||
|
|
||||||
switch (role) {
|
|
||||||
case TaskPortRoles::TaskPortNameRole:
|
|
||||||
return m_ports.at(index.row())->name();
|
|
||||||
case TaskPortRoles::TaskPortDataRole:
|
|
||||||
return QVariant::fromValue(m_ports.at(index.row()));
|
|
||||||
default:
|
|
||||||
return QVariant();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QHash<int, QByteArray> TaskPortModel::roleNames() const
|
|
||||||
{
|
|
||||||
QHash<int, QByteArray> roles;
|
|
||||||
roles[TaskPortRoles::TaskPortNameRole] = "taskPortName";
|
|
||||||
roles[TaskPortRoles::TaskPortDataRole] = "taskPortData";
|
|
||||||
return roles;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace QodeAssist::TaskFlow
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <QAbstractListModel>
|
|
||||||
|
|
||||||
#include <BaseTask.hpp>
|
|
||||||
|
|
||||||
namespace QodeAssist::TaskFlow {
|
|
||||||
|
|
||||||
class TaskPortModel : public QAbstractListModel
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
enum TaskPortRoles { TaskPortNameRole = Qt::UserRole, TaskPortDataRole };
|
|
||||||
|
|
||||||
TaskPortModel(const QList<TaskPort *> &ports, QObject *parent = nullptr);
|
|
||||||
|
|
||||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
|
||||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
|
||||||
QHash<int, QByteArray> roleNames() const override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
QList<TaskPort *> m_ports;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace QodeAssist::TaskFlow
|
|
||||||
@@ -1,134 +0,0 @@
|
|||||||
import QtQuick
|
|
||||||
import TaskFlow.Editor
|
|
||||||
|
|
||||||
FlowItem {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
id: tasks
|
|
||||||
|
|
||||||
model: root.taskModel
|
|
||||||
delegate: Task {
|
|
||||||
// task: taskData
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
id: connections
|
|
||||||
|
|
||||||
model: root.taskModel
|
|
||||||
delegate: TaskConnection {
|
|
||||||
// task: taskData
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// property var qtaskItems: []
|
|
||||||
|
|
||||||
// // Flow container background
|
|
||||||
// Rectangle {
|
|
||||||
// anchors.fill: parent
|
|
||||||
// color: palette.alternateBase
|
|
||||||
// border.color: palette.mid
|
|
||||||
// border.width: 2
|
|
||||||
// radius: 8
|
|
||||||
|
|
||||||
// // Flow header
|
|
||||||
// Rectangle {
|
|
||||||
// id: flowHeader
|
|
||||||
// anchors.top: parent.top
|
|
||||||
// anchors.left: parent.left
|
|
||||||
// anchors.right: parent.right
|
|
||||||
// height: 40
|
|
||||||
// color: palette.button
|
|
||||||
// radius: 6
|
|
||||||
|
|
||||||
// Rectangle {
|
|
||||||
// anchors.bottom: parent.bottom
|
|
||||||
// anchors.left: parent.left
|
|
||||||
// anchors.right: parent.right
|
|
||||||
// height: parent.radius
|
|
||||||
// color: parent.color
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Text {
|
|
||||||
// anchors.centerIn: parent
|
|
||||||
// text: root.flowId
|
|
||||||
// color: palette.buttonText
|
|
||||||
// font.pixelSize: 14
|
|
||||||
// font.bold: true
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // // Tasks container
|
|
||||||
// // Row {
|
|
||||||
// // id: tasksRow
|
|
||||||
// // anchors.top: flowHeader.bottom
|
|
||||||
// // anchors.left: parent.left
|
|
||||||
// // anchors.margins: 25
|
|
||||||
// // anchors.topMargin: 25
|
|
||||||
// // objectName: "FlowTaskRow"
|
|
||||||
|
|
||||||
// // spacing: 40
|
|
||||||
|
|
||||||
// // Repeater {
|
|
||||||
// // model: root.taskModel
|
|
||||||
|
|
||||||
// // delegate: Task {
|
|
||||||
// // task: taskData
|
|
||||||
// // }
|
|
||||||
|
|
||||||
// // onItemAdded: function(index, item){
|
|
||||||
// // console.log("task added", index, item)
|
|
||||||
// // qtaskItems.push(item)
|
|
||||||
// // root.insertTaskItem(index, item)
|
|
||||||
// // }
|
|
||||||
|
|
||||||
// // onItemRemoved: function(index, item){
|
|
||||||
// // console.log("task added", index, item)
|
|
||||||
// // var idx = qtaskItems.indexOf(item)
|
|
||||||
// // if (idx !== -1) qtaskItems.splice(idx, 1)
|
|
||||||
// // }
|
|
||||||
// // }
|
|
||||||
// // }
|
|
||||||
|
|
||||||
// // Repeater {
|
|
||||||
// // model: root.connectionsModel
|
|
||||||
|
|
||||||
// // delegate: TaskConnection {
|
|
||||||
// // connection: connectionData
|
|
||||||
// // }
|
|
||||||
// // }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Flow info tooltip
|
|
||||||
// Rectangle {
|
|
||||||
// id: infoTooltip
|
|
||||||
// anchors.top: parent.bottom
|
|
||||||
// anchors.left: parent.left
|
|
||||||
// anchors.topMargin: 5
|
|
||||||
// width: infoText.width + 20
|
|
||||||
// height: infoText.height + 10
|
|
||||||
// color: palette.base
|
|
||||||
// border.color: palette.shadow
|
|
||||||
// border.width: 1
|
|
||||||
// radius: 4
|
|
||||||
// visible: false
|
|
||||||
|
|
||||||
// Text {
|
|
||||||
// id: infoText
|
|
||||||
// anchors.centerIn: parent
|
|
||||||
// text: "Tasks: " + (root.taskModel ? root.taskModel.rowCount() : 0)
|
|
||||||
// color: palette.text
|
|
||||||
// font.pixelSize: 10
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// MouseArea {
|
|
||||||
// anchors.fill: parent
|
|
||||||
// hoverEnabled: true
|
|
||||||
|
|
||||||
// onEntered: infoTooltip.visible = true
|
|
||||||
// onExited: infoTooltip.visible = false
|
|
||||||
// }
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,140 +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
|
|
||||||
import TaskFlow.Editor
|
|
||||||
|
|
||||||
FlowEditor {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
width: 1200
|
|
||||||
height: 800
|
|
||||||
|
|
||||||
property SystemPalette sysPalette: SystemPalette {
|
|
||||||
colorGroup: SystemPalette.Active
|
|
||||||
}
|
|
||||||
palette {
|
|
||||||
window: sysPalette.window
|
|
||||||
windowText: sysPalette.windowText
|
|
||||||
base: sysPalette.base
|
|
||||||
alternateBase: sysPalette.alternateBase
|
|
||||||
text: sysPalette.text
|
|
||||||
button: sysPalette.button
|
|
||||||
buttonText: sysPalette.buttonText
|
|
||||||
highlight: sysPalette.highlight
|
|
||||||
highlightedText: sysPalette.highlightedText
|
|
||||||
light: sysPalette.light
|
|
||||||
mid: sysPalette.mid
|
|
||||||
dark: sysPalette.dark
|
|
||||||
shadow: sysPalette.shadow
|
|
||||||
brightText: sysPalette.brightText
|
|
||||||
}
|
|
||||||
|
|
||||||
// Background with grid pattern
|
|
||||||
Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
color: palette.window
|
|
||||||
|
|
||||||
// Grid pattern using C++ implementation
|
|
||||||
GridBackground {
|
|
||||||
anchors.fill: parent
|
|
||||||
gridSize: 20
|
|
||||||
gridColor: palette.mid
|
|
||||||
opacity: 0.3
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Header panel
|
|
||||||
Rectangle {
|
|
||||||
id: headerPanel
|
|
||||||
anchors.top: parent.top
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.right: parent.right
|
|
||||||
height: 60
|
|
||||||
color: palette.base
|
|
||||||
border.color: palette.mid
|
|
||||||
border.width: 1
|
|
||||||
|
|
||||||
Row {
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
anchors.leftMargin: 20
|
|
||||||
spacing: 20
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "Flow Editor"
|
|
||||||
color: palette.windowText
|
|
||||||
font.pixelSize: 18
|
|
||||||
font.bold: true
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: 2
|
|
||||||
height: 30
|
|
||||||
color: palette.mid
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "Flow:"
|
|
||||||
color: palette.text
|
|
||||||
font.pixelSize: 14
|
|
||||||
}
|
|
||||||
|
|
||||||
ComboBox {
|
|
||||||
id: flowComboBox
|
|
||||||
|
|
||||||
model: root.flowsModel
|
|
||||||
textRole: "flowId"
|
|
||||||
currentIndex: root.currentFlowIndex
|
|
||||||
|
|
||||||
onActivated: {
|
|
||||||
root.currentFlowIndex = currentIndex
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "Available Tasks: " + root.availableTaskTypes.join(", ")
|
|
||||||
color: palette.text
|
|
||||||
font.pixelSize: 12
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Main flow area
|
|
||||||
ScrollView {
|
|
||||||
id: scrollView
|
|
||||||
anchors.top: headerPanel.bottom
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.bottom: parent.bottom
|
|
||||||
|
|
||||||
contentWidth: flow.width
|
|
||||||
contentHeight: flow.height
|
|
||||||
|
|
||||||
Flow {
|
|
||||||
id: flow
|
|
||||||
|
|
||||||
// flow: root.currentFlow
|
|
||||||
|
|
||||||
width: Math.max(root.width, 0)
|
|
||||||
height: Math.min(root.height, 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,210 +0,0 @@
|
|||||||
import QtQuick
|
|
||||||
import TaskFlow.Editor
|
|
||||||
|
|
||||||
TaskItem{
|
|
||||||
id: root
|
|
||||||
|
|
||||||
width: 280
|
|
||||||
height: Math.max(200, contentColumn.height + 40)
|
|
||||||
|
|
||||||
DragHandler {
|
|
||||||
id: dragHandler
|
|
||||||
|
|
||||||
target: root
|
|
||||||
onActiveChanged: {
|
|
||||||
if (active) {
|
|
||||||
root.z = 1000; // Поднять над остальными
|
|
||||||
} else {
|
|
||||||
root.z = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Task node background
|
|
||||||
Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
color: palette.window
|
|
||||||
border.color: palette.shadow
|
|
||||||
border.width: 1
|
|
||||||
radius: 6
|
|
||||||
|
|
||||||
// Task header
|
|
||||||
Rectangle {
|
|
||||||
id: taskHeader
|
|
||||||
anchors.top: parent.top
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.right: parent.right
|
|
||||||
height: 40
|
|
||||||
color: palette.button
|
|
||||||
radius: 6
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors.bottom: parent.bottom
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.right: parent.right
|
|
||||||
height: parent.radius
|
|
||||||
color: parent.color
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
// text: root.taskType
|
|
||||||
color: palette.buttonText
|
|
||||||
font.pixelSize: 14
|
|
||||||
font.bold: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Task content
|
|
||||||
Column {
|
|
||||||
id: contentColumn
|
|
||||||
anchors.top: taskHeader.bottom
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.margins: 10
|
|
||||||
spacing: 8
|
|
||||||
|
|
||||||
// Task ID
|
|
||||||
Text {
|
|
||||||
text: "ID: " + root.taskId
|
|
||||||
color: palette.text
|
|
||||||
font.pixelSize: 11
|
|
||||||
width: parent.width
|
|
||||||
elide: Text.ElideRight
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parameters section
|
|
||||||
Item {
|
|
||||||
width: parent.width
|
|
||||||
height: paramColumn.height
|
|
||||||
// visible: root.parameters && root.parameters.rowCount() > 0
|
|
||||||
|
|
||||||
Column {
|
|
||||||
id: paramColumn
|
|
||||||
width: parent.width
|
|
||||||
spacing: 6
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "Parameters:"
|
|
||||||
color: palette.text
|
|
||||||
font.pixelSize: 10
|
|
||||||
font.bold: true
|
|
||||||
}
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
model: root.parameters
|
|
||||||
delegate: Rectangle {
|
|
||||||
width: parent.width
|
|
||||||
height: 24
|
|
||||||
color: palette.base
|
|
||||||
radius: 4
|
|
||||||
|
|
||||||
Row {
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
anchors.leftMargin: 8
|
|
||||||
spacing: 6
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: paramKey + ":"
|
|
||||||
color: palette.text
|
|
||||||
font.pixelSize: 9
|
|
||||||
font.bold: true
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: paramValue
|
|
||||||
color: palette.windowText
|
|
||||||
font.pixelSize: 9
|
|
||||||
width: Math.min(150, implicitWidth)
|
|
||||||
elide: Text.ElideRight
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Input ports section (left side)
|
|
||||||
Column {
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
anchors.leftMargin: -8
|
|
||||||
spacing: 6
|
|
||||||
// visible: root.inputPorts && root.inputPorts.rowCount() > 0
|
|
||||||
|
|
||||||
// Input label
|
|
||||||
Text {
|
|
||||||
text: "IN"
|
|
||||||
color: palette.highlight
|
|
||||||
font.pixelSize: 10
|
|
||||||
font.bold: true
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.leftMargin: -20
|
|
||||||
}
|
|
||||||
|
|
||||||
// Repeater {
|
|
||||||
// model: root.inputPorts
|
|
||||||
// delegate: Row {
|
|
||||||
// spacing: 6
|
|
||||||
|
|
||||||
// Text {
|
|
||||||
// text: taskPortName
|
|
||||||
// color: palette.text
|
|
||||||
// font.pixelSize: 9
|
|
||||||
// anchors.verticalCenter: parent.verticalCenter
|
|
||||||
// horizontalAlignment: Text.AlignRight
|
|
||||||
// width: 60
|
|
||||||
// elide: Text.ElideLeft
|
|
||||||
// }
|
|
||||||
|
|
||||||
// TaskPort {
|
|
||||||
// port: taskPortData
|
|
||||||
// isInput: true
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Output ports section (right side)
|
|
||||||
Column {
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
anchors.rightMargin: -10
|
|
||||||
spacing: 8
|
|
||||||
// visible: root.outputPorts && root.outputPorts.rowCount() > 0
|
|
||||||
|
|
||||||
// Output label
|
|
||||||
Text {
|
|
||||||
text: "OUT"
|
|
||||||
color: palette.highlight
|
|
||||||
font.pixelSize: 10
|
|
||||||
font.bold: true
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.rightMargin: -24
|
|
||||||
}
|
|
||||||
|
|
||||||
// Repeater {
|
|
||||||
// model: root.outputPorts
|
|
||||||
// delegate: Row {
|
|
||||||
// spacing: 6
|
|
||||||
|
|
||||||
// TaskPort {
|
|
||||||
// port: taskPortData
|
|
||||||
// isInput: false
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Text {
|
|
||||||
// text: taskPortName
|
|
||||||
// color: palette.text
|
|
||||||
// font.pixelSize: 9
|
|
||||||
// anchors.verticalCenter: parent.verticalCenter
|
|
||||||
// width: 60
|
|
||||||
// elide: Text.ElideRight
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
import QtQuick
|
|
||||||
import QtQuick.Shapes
|
|
||||||
import TaskFlow.Editor
|
|
||||||
|
|
||||||
TaskConnectionItem {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
property color connectionColor: "red"
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: 10
|
|
||||||
height: 10
|
|
||||||
radius: width / 2
|
|
||||||
color: "blue"
|
|
||||||
}
|
|
||||||
|
|
||||||
// width: Math.abs(endPoint.x - startPoint.x) + 40
|
|
||||||
// height: Math.abs(endPoint.y - startPoint.y) + 40
|
|
||||||
// x: Math.min(startPoint.x, endPoint.x) - 20
|
|
||||||
// y: Math.min(startPoint.y, endPoint.y) - 20
|
|
||||||
|
|
||||||
// Shape {
|
|
||||||
// anchors.fill: parent
|
|
||||||
|
|
||||||
// ShapePath {
|
|
||||||
// strokeWidth: 2
|
|
||||||
// strokeColor: connectionColor
|
|
||||||
// fillColor: "transparent"
|
|
||||||
|
|
||||||
// property point localStart: Qt.point(
|
|
||||||
// root.startPoint.x - root.x,
|
|
||||||
// root.startPoint.y - root.y
|
|
||||||
// )
|
|
||||||
// property point localEnd: Qt.point(
|
|
||||||
// root.endPoint.x - root.x,
|
|
||||||
// root.endPoint.y - root.y
|
|
||||||
// )
|
|
||||||
|
|
||||||
// // Bezier curve
|
|
||||||
// property real controlOffset: Math.max(50, Math.abs(localEnd.x - localStart.x) * 0.4)
|
|
||||||
|
|
||||||
// startX: localStart.x
|
|
||||||
// startY: localStart.y
|
|
||||||
|
|
||||||
// PathCubic {
|
|
||||||
// x: parent.localEnd.x
|
|
||||||
// y: parent.localEnd.y
|
|
||||||
// control1X: parent.localStart.x + parent.controlOffset
|
|
||||||
// control1Y: parent.localStart.y
|
|
||||||
// control2X: parent.localEnd.x - parent.controlOffset
|
|
||||||
// control2Y: parent.localEnd.y
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Arrow head
|
|
||||||
// Rectangle {
|
|
||||||
// width: 8
|
|
||||||
// height: 8
|
|
||||||
// color: connectionColor
|
|
||||||
// rotation: 45
|
|
||||||
// x: root.endPoint.x - root.x - 4
|
|
||||||
// y: root.endPoint.y - root.y - 4
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Update positions when tasks might have moved
|
|
||||||
// Component.onCompleted: updatePositions()
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
import QtQuick
|
|
||||||
import TaskFlow.Editor
|
|
||||||
|
|
||||||
Item {
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
import QtQuick
|
|
||||||
import TaskFlow.Editor
|
|
||||||
|
|
||||||
TaskPortItem {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
property bool isInput: true
|
|
||||||
|
|
||||||
width: 20
|
|
||||||
height: 20
|
|
||||||
|
|
||||||
// Port circle
|
|
||||||
Rectangle {
|
|
||||||
id: portCircle
|
|
||||||
anchors.centerIn: parent
|
|
||||||
width: 16
|
|
||||||
height: 16
|
|
||||||
radius: 8
|
|
||||||
color: getPortColor()
|
|
||||||
border.color: palette.windowText
|
|
||||||
border.width: 1
|
|
||||||
|
|
||||||
// Inner circle for connected state simulation
|
|
||||||
Rectangle {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
width: 8
|
|
||||||
height: 8
|
|
||||||
radius: 4
|
|
||||||
color: root.port ? palette.windowText : "transparent"
|
|
||||||
visible: root.port !== null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
|
|
||||||
onEntered: {
|
|
||||||
portCircle.scale = 1.3
|
|
||||||
portCircle.border.width = 2
|
|
||||||
}
|
|
||||||
|
|
||||||
onExited: {
|
|
||||||
portCircle.scale = 1.0
|
|
||||||
portCircle.border.width = 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getPortColor() {
|
|
||||||
if (!root.port) return palette.mid
|
|
||||||
|
|
||||||
// Different colors for input/output using system palette
|
|
||||||
if (root.isInput) {
|
|
||||||
return palette.highlight // System highlight color for inputs
|
|
||||||
} else {
|
|
||||||
return Qt.lighter(palette.highlight, 1.3) // Lighter highlight for outputs
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on scale {
|
|
||||||
NumberAnimation { duration: 100 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,117 +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 "BaseTask.hpp"
|
|
||||||
#include "TaskPort.hpp"
|
|
||||||
#include <QUuid>
|
|
||||||
#include <QtConcurrent>
|
|
||||||
|
|
||||||
namespace QodeAssist::TaskFlow {
|
|
||||||
|
|
||||||
BaseTask::BaseTask(QObject *parent)
|
|
||||||
: QObject(parent)
|
|
||||||
, m_taskId("unknown" + QUuid::createUuid().toString())
|
|
||||||
{}
|
|
||||||
|
|
||||||
BaseTask::~BaseTask()
|
|
||||||
{
|
|
||||||
qDeleteAll(m_inputs);
|
|
||||||
qDeleteAll(m_outputs);
|
|
||||||
}
|
|
||||||
|
|
||||||
QString BaseTask::taskId() const
|
|
||||||
{
|
|
||||||
return m_taskId;
|
|
||||||
}
|
|
||||||
|
|
||||||
void BaseTask::setTaskId(const QString &taskId)
|
|
||||||
{
|
|
||||||
m_taskId = taskId;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString BaseTask::taskType() const
|
|
||||||
{
|
|
||||||
return QString(metaObject()->className()).split("::").last();
|
|
||||||
}
|
|
||||||
|
|
||||||
void BaseTask::addInputPort(const QString &name)
|
|
||||||
{
|
|
||||||
QMutexLocker locker(&m_tasksMutex);
|
|
||||||
m_inputs.append(new TaskPort(name, TaskPort::ValueType::Any, this));
|
|
||||||
}
|
|
||||||
|
|
||||||
void BaseTask::addOutputPort(const QString &name)
|
|
||||||
{
|
|
||||||
QMutexLocker locker(&m_tasksMutex);
|
|
||||||
m_outputs.append(new TaskPort(name, TaskPort::ValueType::Any, this));
|
|
||||||
}
|
|
||||||
|
|
||||||
TaskPort *BaseTask::inputPort(const QString &name) const
|
|
||||||
{
|
|
||||||
QMutexLocker locker(&m_tasksMutex);
|
|
||||||
|
|
||||||
auto it = std::find_if(m_inputs.begin(), m_inputs.end(), [&name](const TaskPort *port) {
|
|
||||||
return port->name() == name;
|
|
||||||
});
|
|
||||||
|
|
||||||
return (it != m_inputs.end()) ? *it : nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
TaskPort *BaseTask::outputPort(const QString &name) const
|
|
||||||
{
|
|
||||||
QMutexLocker locker(&m_tasksMutex);
|
|
||||||
|
|
||||||
auto it = std::find_if(m_outputs.begin(), m_outputs.end(), [&name](const TaskPort *port) {
|
|
||||||
return port->name() == name;
|
|
||||||
});
|
|
||||||
|
|
||||||
return (it != m_outputs.end()) ? *it : nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
QList<TaskPort *> BaseTask::getInputPorts() const
|
|
||||||
{
|
|
||||||
QMutexLocker locker(&m_tasksMutex);
|
|
||||||
return m_inputs;
|
|
||||||
}
|
|
||||||
|
|
||||||
QList<TaskPort *> BaseTask::getOutputPorts() const
|
|
||||||
{
|
|
||||||
QMutexLocker locker(&m_tasksMutex);
|
|
||||||
return m_outputs;
|
|
||||||
}
|
|
||||||
|
|
||||||
QFuture<TaskState> BaseTask::executeAsync()
|
|
||||||
{
|
|
||||||
return QtConcurrent::task([this]() -> TaskState { return execute(); }).spawn();
|
|
||||||
}
|
|
||||||
|
|
||||||
QString BaseTask::taskStateAsString(TaskState state)
|
|
||||||
{
|
|
||||||
switch (state) {
|
|
||||||
case TaskState::Success:
|
|
||||||
return "Success";
|
|
||||||
case TaskState::Failed:
|
|
||||||
return "Failed";
|
|
||||||
case TaskState::Cancelled:
|
|
||||||
return "Cancelled";
|
|
||||||
}
|
|
||||||
return "Unknown";
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace QodeAssist::TaskFlow
|
|
||||||
@@ -1,68 +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 <QFuture>
|
|
||||||
#include <QMetaType>
|
|
||||||
#include <QMutex>
|
|
||||||
#include <QObject>
|
|
||||||
|
|
||||||
namespace QodeAssist::TaskFlow {
|
|
||||||
|
|
||||||
class TaskPort;
|
|
||||||
|
|
||||||
enum class TaskState { Success, Failed, Cancelled };
|
|
||||||
|
|
||||||
class BaseTask : public QObject
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit BaseTask(QObject *parent = nullptr);
|
|
||||||
virtual ~BaseTask();
|
|
||||||
|
|
||||||
QString taskId() const;
|
|
||||||
void setTaskId(const QString &taskId);
|
|
||||||
QString taskType() const;
|
|
||||||
|
|
||||||
void addInputPort(const QString &name);
|
|
||||||
void addOutputPort(const QString &name);
|
|
||||||
|
|
||||||
TaskPort *inputPort(const QString &name) const;
|
|
||||||
TaskPort *outputPort(const QString &name) const;
|
|
||||||
|
|
||||||
QList<TaskPort *> getInputPorts() const;
|
|
||||||
QList<TaskPort *> getOutputPorts() const;
|
|
||||||
|
|
||||||
virtual TaskState execute() = 0;
|
|
||||||
|
|
||||||
static QString taskStateAsString(TaskState state);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
QFuture<TaskState> executeAsync();
|
|
||||||
|
|
||||||
private:
|
|
||||||
QString m_taskId;
|
|
||||||
QList<TaskPort *> m_inputs;
|
|
||||||
QList<TaskPort *> m_outputs;
|
|
||||||
mutable QMutex m_tasksMutex;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace QodeAssist::TaskFlow
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
qt_add_library(TaskFlowCore STATIC
|
|
||||||
BaseTask.hpp BaseTask.cpp
|
|
||||||
TaskConnection.hpp TaskConnection.cpp
|
|
||||||
Flow.hpp Flow.cpp
|
|
||||||
TaskPort.hpp TaskPort.cpp
|
|
||||||
TaskRegistry.hpp TaskRegistry.cpp
|
|
||||||
FlowManager.hpp FlowManager.cpp
|
|
||||||
FlowRegistry.hpp FlowRegistry.cpp
|
|
||||||
)
|
|
||||||
|
|
||||||
target_link_libraries(TaskFlowCore
|
|
||||||
PUBLIC
|
|
||||||
Qt::Core
|
|
||||||
Qt::Concurrent
|
|
||||||
PRIVATE
|
|
||||||
QodeAssistLogger
|
|
||||||
)
|
|
||||||
|
|
||||||
target_include_directories(TaskFlowCore
|
|
||||||
PUBLIC
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}
|
|
||||||
)
|
|
||||||
@@ -1,355 +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 "Flow.hpp"
|
|
||||||
#include "TaskPort.hpp"
|
|
||||||
#include <QUuid>
|
|
||||||
#include <QtConcurrent>
|
|
||||||
|
|
||||||
namespace QodeAssist::TaskFlow {
|
|
||||||
|
|
||||||
Flow::Flow(QObject *parent)
|
|
||||||
: QObject(parent)
|
|
||||||
, m_flowId("flow_" + QUuid::createUuid().toString())
|
|
||||||
{}
|
|
||||||
|
|
||||||
Flow::~Flow()
|
|
||||||
{
|
|
||||||
QMutexLocker locker(&m_flowMutex);
|
|
||||||
qDeleteAll(m_connections);
|
|
||||||
qDeleteAll(m_tasks);
|
|
||||||
}
|
|
||||||
|
|
||||||
QString Flow::flowId() const
|
|
||||||
{
|
|
||||||
return m_flowId;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Flow::setFlowId(const QString &flowId)
|
|
||||||
{
|
|
||||||
if (m_flowId != flowId) {
|
|
||||||
m_flowId = flowId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Flow::addTask(BaseTask *task)
|
|
||||||
{
|
|
||||||
if (!task) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
QMutexLocker locker(&m_flowMutex);
|
|
||||||
|
|
||||||
QString taskId = task->taskId();
|
|
||||||
if (m_tasks.contains(taskId)) {
|
|
||||||
qWarning() << "Flow::addTask - Task with ID" << taskId << "already exists";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_tasks.insert(taskId, task);
|
|
||||||
task->setParent(this);
|
|
||||||
|
|
||||||
emit taskAdded(taskId);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Flow::removeTask(const QString &taskId)
|
|
||||||
{
|
|
||||||
QMutexLocker locker(&m_flowMutex);
|
|
||||||
|
|
||||||
BaseTask *task = m_tasks.value(taskId);
|
|
||||||
if (!task) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto it = m_connections.begin();
|
|
||||||
while (it != m_connections.end()) {
|
|
||||||
TaskConnection *connection = *it;
|
|
||||||
if (connection->sourceTask() == task || connection->targetTask() == task) {
|
|
||||||
it = m_connections.erase(it);
|
|
||||||
emit connectionRemoved(connection);
|
|
||||||
delete connection;
|
|
||||||
} else {
|
|
||||||
++it;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
m_tasks.remove(taskId);
|
|
||||||
emit taskRemoved(taskId);
|
|
||||||
delete task;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Flow::removeTask(BaseTask *task)
|
|
||||||
{
|
|
||||||
if (!task) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
removeTask(task->taskId());
|
|
||||||
}
|
|
||||||
|
|
||||||
BaseTask *Flow::getTask(const QString &taskId) const
|
|
||||||
{
|
|
||||||
QMutexLocker locker(&m_flowMutex);
|
|
||||||
return m_tasks.value(taskId);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Flow::hasTask(const QString &taskId) const
|
|
||||||
{
|
|
||||||
QMutexLocker locker(&m_flowMutex);
|
|
||||||
return m_tasks.contains(taskId);
|
|
||||||
}
|
|
||||||
|
|
||||||
QHash<QString, BaseTask *> Flow::tasks() const
|
|
||||||
{
|
|
||||||
QMutexLocker locker(&m_flowMutex);
|
|
||||||
return m_tasks;
|
|
||||||
}
|
|
||||||
|
|
||||||
TaskConnection *Flow::addConnection(TaskPort *sourcePort, TaskPort *targetPort)
|
|
||||||
{
|
|
||||||
if (!sourcePort || !targetPort) {
|
|
||||||
qWarning() << "Flow::addConnection - Invalid ports";
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify ports belong to tasks in this flow
|
|
||||||
BaseTask *sourceTask = qobject_cast<BaseTask *>(sourcePort->parent());
|
|
||||||
BaseTask *targetTask = qobject_cast<BaseTask *>(targetPort->parent());
|
|
||||||
|
|
||||||
if (!sourceTask || !targetTask) {
|
|
||||||
qWarning() << "Flow::addConnection - Ports don't belong to valid tasks";
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
QMutexLocker locker(&m_flowMutex);
|
|
||||||
|
|
||||||
if (!m_tasks.contains(sourceTask->taskId()) || !m_tasks.contains(targetTask->taskId())) {
|
|
||||||
qWarning() << "Flow::addConnection - Tasks not in this flow";
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (TaskConnection *existingConnection : m_connections) {
|
|
||||||
if (existingConnection->sourcePort() == sourcePort
|
|
||||||
&& existingConnection->targetPort() == targetPort) {
|
|
||||||
qWarning() << "Flow::addConnection - Connection already exists";
|
|
||||||
return existingConnection;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TaskConnection *connection = new TaskConnection(sourcePort, targetPort, this);
|
|
||||||
m_connections.append(connection);
|
|
||||||
|
|
||||||
emit connectionAdded(connection);
|
|
||||||
return connection;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Flow::removeConnection(TaskConnection *connection)
|
|
||||||
{
|
|
||||||
if (!connection) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
QMutexLocker locker(&m_flowMutex);
|
|
||||||
|
|
||||||
if (m_connections.removeOne(connection)) {
|
|
||||||
emit connectionRemoved(connection);
|
|
||||||
delete connection;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QList<TaskConnection *> Flow::connections() const
|
|
||||||
{
|
|
||||||
QMutexLocker locker(&m_flowMutex);
|
|
||||||
return m_connections;
|
|
||||||
}
|
|
||||||
|
|
||||||
QFuture<FlowState> Flow::executeAsync()
|
|
||||||
{
|
|
||||||
return QtConcurrent::run([this]() { return execute(); });
|
|
||||||
}
|
|
||||||
|
|
||||||
FlowState Flow::execute()
|
|
||||||
{
|
|
||||||
emit executionStarted();
|
|
||||||
|
|
||||||
if (!isValid()) {
|
|
||||||
emit executionFinished(FlowState::Failed);
|
|
||||||
return FlowState::Failed;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasCircularDependencies()) {
|
|
||||||
qWarning() << "Flow::execute - Circular dependencies detected";
|
|
||||||
emit executionFinished(FlowState::Failed);
|
|
||||||
return FlowState::Failed;
|
|
||||||
}
|
|
||||||
|
|
||||||
QList<BaseTask *> executionOrder = getExecutionOrder();
|
|
||||||
|
|
||||||
for (BaseTask *task : executionOrder) {
|
|
||||||
TaskState taskResult = task->execute();
|
|
||||||
|
|
||||||
if (taskResult == TaskState::Failed) {
|
|
||||||
qWarning() << "Flow::execute - Task" << task->taskId() << "failed";
|
|
||||||
emit executionFinished(FlowState::Failed);
|
|
||||||
return FlowState::Failed;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (taskResult == TaskState::Cancelled) {
|
|
||||||
qWarning() << "Flow::execute - Task" << task->taskId() << "cancelled";
|
|
||||||
emit executionFinished(FlowState::Cancelled);
|
|
||||||
return FlowState::Cancelled;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
emit executionFinished(FlowState::Success);
|
|
||||||
return FlowState::Success;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Flow::isValid() const
|
|
||||||
{
|
|
||||||
QMutexLocker locker(&m_flowMutex);
|
|
||||||
|
|
||||||
// Check all connections are valid
|
|
||||||
for (TaskConnection *connection : m_connections) {
|
|
||||||
if (!connection->isValid()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Flow::hasCircularDependencies() const
|
|
||||||
{
|
|
||||||
return detectCircularDependencies();
|
|
||||||
}
|
|
||||||
|
|
||||||
QString Flow::flowStateAsString(FlowState state)
|
|
||||||
{
|
|
||||||
switch (state) {
|
|
||||||
case FlowState::Success:
|
|
||||||
return "Success";
|
|
||||||
case FlowState::Failed:
|
|
||||||
return "Failed";
|
|
||||||
case FlowState::Cancelled:
|
|
||||||
return "Cancelled";
|
|
||||||
}
|
|
||||||
return "Unknown";
|
|
||||||
}
|
|
||||||
|
|
||||||
QStringList Flow::getTaskIds() const
|
|
||||||
{
|
|
||||||
QMutexLocker locker(&m_flowMutex);
|
|
||||||
return m_tasks.keys();
|
|
||||||
}
|
|
||||||
|
|
||||||
QList<BaseTask *> Flow::getExecutionOrder() const
|
|
||||||
{
|
|
||||||
QMutexLocker locker(&m_flowMutex);
|
|
||||||
|
|
||||||
QList<BaseTask *> result;
|
|
||||||
QSet<BaseTask *> visited;
|
|
||||||
QList<BaseTask *> allTasks = m_tasks.values();
|
|
||||||
|
|
||||||
std::function<void(BaseTask *)> visit = [&](BaseTask *task) {
|
|
||||||
if (visited.contains(task)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
visited.insert(task);
|
|
||||||
|
|
||||||
QList<BaseTask *> dependencies = getTaskDependencies(task);
|
|
||||||
for (BaseTask *dependency : dependencies) {
|
|
||||||
visit(dependency);
|
|
||||||
}
|
|
||||||
|
|
||||||
result.append(task);
|
|
||||||
};
|
|
||||||
|
|
||||||
for (BaseTask *task : allTasks) {
|
|
||||||
visit(task);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Flow::detectCircularDependencies() const
|
|
||||||
{
|
|
||||||
QMutexLocker locker(&m_flowMutex);
|
|
||||||
|
|
||||||
QSet<BaseTask *> visited;
|
|
||||||
QSet<BaseTask *> recursionStack;
|
|
||||||
bool hasCycle = false;
|
|
||||||
|
|
||||||
for (BaseTask *task : m_tasks.values()) {
|
|
||||||
if (!visited.contains(task)) {
|
|
||||||
visitTask(task, visited, recursionStack, hasCycle);
|
|
||||||
if (hasCycle) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Flow::visitTask(
|
|
||||||
BaseTask *task, QSet<BaseTask *> &visited, QSet<BaseTask *> &recursionStack, bool &hasCycle) const
|
|
||||||
{
|
|
||||||
if (hasCycle) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
visited.insert(task);
|
|
||||||
recursionStack.insert(task);
|
|
||||||
|
|
||||||
for (TaskConnection *connection : m_connections) {
|
|
||||||
if (connection->sourceTask() == task) {
|
|
||||||
BaseTask *dependentTask = connection->targetTask();
|
|
||||||
|
|
||||||
if (recursionStack.contains(dependentTask)) {
|
|
||||||
hasCycle = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!visited.contains(dependentTask)) {
|
|
||||||
visitTask(dependentTask, visited, recursionStack, hasCycle);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
recursionStack.remove(task);
|
|
||||||
}
|
|
||||||
|
|
||||||
QList<BaseTask *> Flow::getTaskDependencies(BaseTask *task) const
|
|
||||||
{
|
|
||||||
QList<BaseTask *> dependencies;
|
|
||||||
|
|
||||||
for (TaskConnection *connection : m_connections) {
|
|
||||||
if (connection->targetTask() == task) {
|
|
||||||
BaseTask *dependencyTask = connection->sourceTask();
|
|
||||||
if (!dependencies.contains(dependencyTask)) {
|
|
||||||
dependencies.append(dependencyTask);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return dependencies;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace QodeAssist::TaskFlow
|
|
||||||
@@ -1,95 +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 <QFuture>
|
|
||||||
#include <QHash>
|
|
||||||
#include <QList>
|
|
||||||
#include <QMetaType>
|
|
||||||
#include <QMutex>
|
|
||||||
#include <QObject>
|
|
||||||
|
|
||||||
#include "BaseTask.hpp"
|
|
||||||
#include "TaskConnection.hpp"
|
|
||||||
|
|
||||||
namespace QodeAssist::TaskFlow {
|
|
||||||
|
|
||||||
enum class FlowState { Success, Failed, Cancelled };
|
|
||||||
|
|
||||||
class Flow : public QObject
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit Flow(QObject *parent = nullptr);
|
|
||||||
~Flow() override;
|
|
||||||
|
|
||||||
QString flowId() const;
|
|
||||||
void setFlowId(const QString &flowId);
|
|
||||||
|
|
||||||
void addTask(BaseTask *task);
|
|
||||||
void removeTask(const QString &taskId);
|
|
||||||
void removeTask(BaseTask *task);
|
|
||||||
|
|
||||||
BaseTask *getTask(const QString &taskId) const;
|
|
||||||
bool hasTask(const QString &taskId) const;
|
|
||||||
QHash<QString, BaseTask *> tasks() const;
|
|
||||||
|
|
||||||
TaskConnection *addConnection(TaskPort *sourcePort, TaskPort *targetPort);
|
|
||||||
void removeConnection(TaskConnection *connection);
|
|
||||||
QList<TaskConnection *> connections() const;
|
|
||||||
|
|
||||||
QFuture<FlowState> executeAsync();
|
|
||||||
virtual FlowState execute();
|
|
||||||
|
|
||||||
bool isValid() const;
|
|
||||||
bool hasCircularDependencies() const;
|
|
||||||
|
|
||||||
static QString flowStateAsString(FlowState state);
|
|
||||||
QStringList getTaskIds() const;
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void taskAdded(const QString &taskId);
|
|
||||||
void taskRemoved(const QString &taskId);
|
|
||||||
void connectionAdded(QodeAssist::TaskFlow::TaskConnection *connection);
|
|
||||||
void connectionRemoved(QodeAssist::TaskFlow::TaskConnection *connection);
|
|
||||||
void executionStarted();
|
|
||||||
void executionFinished(FlowState result);
|
|
||||||
|
|
||||||
private:
|
|
||||||
QString m_flowId;
|
|
||||||
QHash<QString, BaseTask *> m_tasks;
|
|
||||||
QList<TaskConnection *> m_connections;
|
|
||||||
mutable QMutex m_flowMutex;
|
|
||||||
|
|
||||||
QList<BaseTask *> getExecutionOrder() const;
|
|
||||||
bool detectCircularDependencies() const;
|
|
||||||
void visitTask(
|
|
||||||
BaseTask *task,
|
|
||||||
QSet<BaseTask *> &visited,
|
|
||||||
QSet<BaseTask *> &recursionStack,
|
|
||||||
bool &hasCycle) const;
|
|
||||||
QList<BaseTask *> getTaskDependencies(BaseTask *task) const;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace QodeAssist::TaskFlow
|
|
||||||
|
|
||||||
Q_DECLARE_METATYPE(QodeAssist::TaskFlow::Flow *)
|
|
||||||
Q_DECLARE_METATYPE(QodeAssist::TaskFlow::FlowState)
|
|
||||||
@@ -1,112 +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 "FlowManager.hpp"
|
|
||||||
|
|
||||||
#include <Logger.hpp>
|
|
||||||
#include <QFile>
|
|
||||||
#include <QFileInfo>
|
|
||||||
#include <QJsonDocument>
|
|
||||||
|
|
||||||
#include "FlowRegistry.hpp"
|
|
||||||
#include "TaskRegistry.hpp"
|
|
||||||
|
|
||||||
namespace QodeAssist::TaskFlow {
|
|
||||||
|
|
||||||
FlowManager::FlowManager(QObject *parent)
|
|
||||||
: QObject(parent)
|
|
||||||
, m_taskRegistry(new TaskRegistry(this))
|
|
||||||
, m_flowRegistry(new FlowRegistry(this))
|
|
||||||
{
|
|
||||||
LOG_MESSAGE("FlowManager created");
|
|
||||||
}
|
|
||||||
|
|
||||||
FlowManager::~FlowManager()
|
|
||||||
{
|
|
||||||
clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Flow *FlowManager::createFlow(const QString &flowId)
|
|
||||||
// {
|
|
||||||
// Flow *flow = new Flow(flowId, m_taskRegistry, this);
|
|
||||||
// if (!m_flows.contains(flow->flowId())) {
|
|
||||||
// m_flows.insert(flowId, flow);
|
|
||||||
// } else {
|
|
||||||
// LOG_MESSAGE(
|
|
||||||
// QString("FlowManager::createFlow - flow with id %1 already exists").arg(flow->flowId()));
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return flow;
|
|
||||||
// }
|
|
||||||
|
|
||||||
void FlowManager::addFlow(Flow *flow)
|
|
||||||
{
|
|
||||||
qDebug() << "FlowManager::addFlow" << flow->flowId();
|
|
||||||
if (!m_flows.contains(flow->flowId())) {
|
|
||||||
m_flows.insert(flow->flowId(), flow);
|
|
||||||
flow->setParent(this);
|
|
||||||
emit flowAdded(flow->flowId());
|
|
||||||
} else {
|
|
||||||
LOG_MESSAGE(
|
|
||||||
QString("FlowManager::addFlow - flow with id %1 already exists").arg(flow->flowId()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void FlowManager::clear()
|
|
||||||
{
|
|
||||||
LOG_MESSAGE(QString("FlowManager::clear - removing %1 flows").arg(m_flows.size()));
|
|
||||||
|
|
||||||
qDeleteAll(m_flows);
|
|
||||||
m_flows.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
QStringList FlowManager::getAvailableTasksTypes()
|
|
||||||
{
|
|
||||||
return m_taskRegistry->getAvailableTypes();
|
|
||||||
}
|
|
||||||
|
|
||||||
QStringList FlowManager::getAvailableFlows()
|
|
||||||
{
|
|
||||||
return m_flowRegistry->getAvailableTypes();
|
|
||||||
}
|
|
||||||
|
|
||||||
QHash<QString, Flow *> FlowManager::flows() const
|
|
||||||
{
|
|
||||||
return m_flows;
|
|
||||||
}
|
|
||||||
|
|
||||||
TaskRegistry *FlowManager::taskRegistry() const
|
|
||||||
{
|
|
||||||
return m_taskRegistry;
|
|
||||||
}
|
|
||||||
|
|
||||||
FlowRegistry *FlowManager::flowRegistry() const
|
|
||||||
{
|
|
||||||
return m_flowRegistry;
|
|
||||||
}
|
|
||||||
|
|
||||||
Flow *FlowManager::getFlow(const QString &flowId) const
|
|
||||||
{
|
|
||||||
// if (flowId.isEmpty()) {
|
|
||||||
// return m_flows.begin().value();
|
|
||||||
// }
|
|
||||||
// return m_flows.value(flowId, nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace QodeAssist::TaskFlow
|
|
||||||
@@ -1,69 +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 <QJsonArray>
|
|
||||||
#include <QJsonObject>
|
|
||||||
#include <QObject>
|
|
||||||
#include <QString>
|
|
||||||
|
|
||||||
#include "Flow.hpp"
|
|
||||||
|
|
||||||
namespace QodeAssist::TaskFlow {
|
|
||||||
|
|
||||||
class TaskRegistry;
|
|
||||||
class FlowRegistry;
|
|
||||||
|
|
||||||
class FlowManager : public QObject
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit FlowManager(QObject *parent = nullptr);
|
|
||||||
~FlowManager() override;
|
|
||||||
|
|
||||||
// Flow *createFlow(const QString &flowId);
|
|
||||||
void addFlow(Flow *flow);
|
|
||||||
|
|
||||||
void clear();
|
|
||||||
|
|
||||||
QStringList getAvailableTasksTypes();
|
|
||||||
QStringList getAvailableFlows();
|
|
||||||
|
|
||||||
QHash<QString, Flow *> flows() const;
|
|
||||||
|
|
||||||
TaskRegistry *taskRegistry() const;
|
|
||||||
FlowRegistry *flowRegistry() const;
|
|
||||||
|
|
||||||
Flow *getFlow(const QString &flowId = {}) const;
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void flowAdded(const QString &flowId);
|
|
||||||
void flowRemoved(const QString &flowId);
|
|
||||||
|
|
||||||
private:
|
|
||||||
QHash<QString, Flow *> m_flows;
|
|
||||||
|
|
||||||
TaskRegistry *m_taskRegistry;
|
|
||||||
FlowRegistry *m_flowRegistry;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace QodeAssist::TaskFlow
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
#include "FlowRegistry.hpp"
|
|
||||||
#include "Logger.hpp"
|
|
||||||
|
|
||||||
namespace QodeAssist::TaskFlow {
|
|
||||||
|
|
||||||
FlowRegistry::FlowRegistry(QObject *parent)
|
|
||||||
: QObject(parent)
|
|
||||||
{}
|
|
||||||
|
|
||||||
void FlowRegistry::registerFlow(const QString &flowType, FlowCreator creator)
|
|
||||||
{
|
|
||||||
m_flowCreators[flowType] = creator;
|
|
||||||
LOG_MESSAGE(QString("FlowRegistry: Registered flow type '%1'").arg(flowType));
|
|
||||||
}
|
|
||||||
|
|
||||||
Flow *FlowRegistry::createFlow(const QString &flowType, FlowManager *flowManager) const
|
|
||||||
{
|
|
||||||
LOG_MESSAGE(QString("Trying to create flow: %1").arg(flowType));
|
|
||||||
|
|
||||||
if (m_flowCreators.contains(flowType)) {
|
|
||||||
LOG_MESSAGE(QString("Found creator for flow type: %1").arg(flowType));
|
|
||||||
try {
|
|
||||||
Flow *flow = m_flowCreators[flowType](flowManager);
|
|
||||||
if (flow) {
|
|
||||||
LOG_MESSAGE(QString("Successfully created flow: %1").arg(flowType));
|
|
||||||
return flow;
|
|
||||||
}
|
|
||||||
} catch (...) {
|
|
||||||
LOG_MESSAGE(QString("Exception while creating flow of type: %1").arg(flowType));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
LOG_MESSAGE(QString("No creator found for flow type: %1").arg(flowType));
|
|
||||||
}
|
|
||||||
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
QStringList FlowRegistry::getAvailableTypes() const
|
|
||||||
{
|
|
||||||
return m_flowCreators.keys();
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace QodeAssist::TaskFlow
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <functional>
|
|
||||||
#include <QHash>
|
|
||||||
#include <QObject>
|
|
||||||
#include <QString>
|
|
||||||
|
|
||||||
namespace QodeAssist::TaskFlow {
|
|
||||||
|
|
||||||
class Flow;
|
|
||||||
class FlowManager;
|
|
||||||
|
|
||||||
class FlowRegistry : public QObject
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
using FlowCreator = std::function<Flow *(FlowManager *flowManager)>;
|
|
||||||
|
|
||||||
explicit FlowRegistry(QObject *parent = nullptr);
|
|
||||||
|
|
||||||
void registerFlow(const QString &flowType, FlowCreator creator);
|
|
||||||
Flow *createFlow(const QString &flowType, FlowManager *flowManager = nullptr) const;
|
|
||||||
QStringList getAvailableTypes() const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
QHash<QString, FlowCreator> m_flowCreators;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace QodeAssist::TaskFlow
|
|
||||||
@@ -1,125 +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 "TaskConnection.hpp"
|
|
||||||
#include "BaseTask.hpp"
|
|
||||||
#include "TaskPort.hpp"
|
|
||||||
#include <QMetaEnum>
|
|
||||||
|
|
||||||
namespace QodeAssist::TaskFlow {
|
|
||||||
|
|
||||||
TaskConnection::TaskConnection(TaskPort *sourcePort, TaskPort *targetPort, QObject *parent)
|
|
||||||
: QObject(parent)
|
|
||||||
, m_sourcePort(sourcePort)
|
|
||||||
, m_targetPort(targetPort)
|
|
||||||
{
|
|
||||||
setupConnection();
|
|
||||||
}
|
|
||||||
|
|
||||||
TaskConnection::~TaskConnection()
|
|
||||||
{
|
|
||||||
cleanupConnection();
|
|
||||||
}
|
|
||||||
|
|
||||||
BaseTask *TaskConnection::sourceTask() const
|
|
||||||
{
|
|
||||||
return m_sourcePort ? qobject_cast<BaseTask *>(m_sourcePort->parent()) : nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
BaseTask *TaskConnection::targetTask() const
|
|
||||||
{
|
|
||||||
return m_targetPort ? qobject_cast<BaseTask *>(m_targetPort->parent()) : nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
TaskPort *TaskConnection::sourcePort() const
|
|
||||||
{
|
|
||||||
return m_sourcePort;
|
|
||||||
}
|
|
||||||
|
|
||||||
TaskPort *TaskConnection::targetPort() const
|
|
||||||
{
|
|
||||||
return m_targetPort;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool TaskConnection::isValid() const
|
|
||||||
{
|
|
||||||
return m_sourcePort && m_targetPort && m_sourcePort != m_targetPort && sourceTask()
|
|
||||||
&& targetTask() && sourceTask() != targetTask();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool TaskConnection::isTypeCompatible() const
|
|
||||||
{
|
|
||||||
if (!isValid()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return m_targetPort->isConnectionTypeCompatible(m_sourcePort);
|
|
||||||
}
|
|
||||||
|
|
||||||
QString TaskConnection::toString() const
|
|
||||||
{
|
|
||||||
if (!isValid()) {
|
|
||||||
return QString();
|
|
||||||
}
|
|
||||||
|
|
||||||
BaseTask *srcTask = sourceTask();
|
|
||||||
BaseTask *tgtTask = targetTask();
|
|
||||||
|
|
||||||
return QString("%1.%2->%3.%4")
|
|
||||||
.arg(srcTask->taskId())
|
|
||||||
.arg(m_sourcePort->name())
|
|
||||||
.arg(tgtTask->taskId())
|
|
||||||
.arg(m_targetPort->name());
|
|
||||||
}
|
|
||||||
|
|
||||||
bool TaskConnection::operator==(const TaskConnection &other) const
|
|
||||||
{
|
|
||||||
return m_sourcePort == other.m_sourcePort && m_targetPort == other.m_targetPort;
|
|
||||||
}
|
|
||||||
|
|
||||||
void TaskConnection::setupConnection()
|
|
||||||
{
|
|
||||||
if (!isValid()) {
|
|
||||||
qWarning() << "TaskConnection::setupConnection - Invalid connection parameters";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isTypeCompatible()) {
|
|
||||||
QMetaEnum metaEnum = QMetaEnum::fromType<TaskPort::ValueType>();
|
|
||||||
qWarning() << "TaskConnection::setupConnection - Type incompatible connection:"
|
|
||||||
<< metaEnum.valueToKey(static_cast<int>(m_sourcePort->valueType())) << "to"
|
|
||||||
<< metaEnum.valueToKey(static_cast<int>(m_targetPort->valueType()));
|
|
||||||
}
|
|
||||||
|
|
||||||
m_sourcePort->setConnection(this);
|
|
||||||
m_targetPort->setConnection(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
void TaskConnection::cleanupConnection()
|
|
||||||
{
|
|
||||||
if (m_sourcePort && m_sourcePort->connection() == this) {
|
|
||||||
m_sourcePort->setConnection(nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_targetPort && m_targetPort->connection() == this) {
|
|
||||||
m_targetPort->setConnection(nullptr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace QodeAssist::TaskFlow
|
|
||||||
@@ -1,65 +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>
|
|
||||||
|
|
||||||
namespace QodeAssist::TaskFlow {
|
|
||||||
|
|
||||||
class BaseTask;
|
|
||||||
class TaskPort;
|
|
||||||
|
|
||||||
class TaskConnection : public QObject
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
|
||||||
// Constructor automatically sets up the connection
|
|
||||||
explicit TaskConnection(TaskPort *sourcePort, TaskPort *targetPort, QObject *parent = nullptr);
|
|
||||||
|
|
||||||
// Destructor automatically cleans up the connection
|
|
||||||
~TaskConnection() override;
|
|
||||||
|
|
||||||
// Getters
|
|
||||||
BaseTask *sourceTask() const;
|
|
||||||
BaseTask *targetTask() const;
|
|
||||||
TaskPort *sourcePort() const;
|
|
||||||
TaskPort *targetPort() const;
|
|
||||||
|
|
||||||
// Validation
|
|
||||||
bool isValid() const;
|
|
||||||
bool isTypeCompatible() const;
|
|
||||||
|
|
||||||
// Utility
|
|
||||||
QString toString() const;
|
|
||||||
|
|
||||||
// Comparison
|
|
||||||
bool operator==(const TaskConnection &other) const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
TaskPort *m_sourcePort;
|
|
||||||
TaskPort *m_targetPort;
|
|
||||||
|
|
||||||
void setupConnection();
|
|
||||||
void cleanupConnection();
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace QodeAssist::TaskFlow
|
|
||||||