mirror of
https://github.com/Palm1r/QodeAssist.git
synced 2026-06-13 17:59:15 -04:00
Compare commits
41 Commits
v0.9.14
...
dev-experi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1c94fe150e | ||
|
|
abb3351246 | ||
|
|
57eeb32ceb | ||
|
|
74eed49fb4 | ||
|
|
43a30281b6 | ||
|
|
bf4307c459 | ||
|
|
6df70e608b | ||
|
|
ee1bf4ffe5 | ||
|
|
aaca9e2a0b | ||
|
|
f2aae9d37f | ||
|
|
dcf5796ad7 | ||
|
|
033c0e8652 | ||
|
|
ea67ba0e2a | ||
|
|
0cf915c4a5 | ||
|
|
99caa853d5 | ||
|
|
278624d412 | ||
|
|
f8adf4d264 | ||
|
|
bfcd8dc1fb | ||
|
|
33321b2499 | ||
|
|
362533a5c0 | ||
|
|
d180d189e4 | ||
|
|
0774084ad9 | ||
|
|
282f48d9fb | ||
|
|
8cbeb7132e | ||
|
|
af898bd255 | ||
|
|
66e25300e8 | ||
|
|
fcc651fd75 | ||
|
|
dc016ce533 | ||
|
|
725de4a2c3 | ||
|
|
8d3313d16b | ||
|
|
abdcab3c7d | ||
|
|
abadc2262c | ||
|
|
31ad99af61 | ||
|
|
fb887967ed | ||
|
|
97236c6069 | ||
|
|
51ebe3e523 | ||
|
|
e193d1e1fa | ||
|
|
ca3baa7597 | ||
|
|
b33a1c2d43 | ||
|
|
c4e34bb3d9 | ||
|
|
b9e0b5a00c |
34
.github/workflows/build_cmake.yml
vendored
34
.github/workflows/build_cmake.yml
vendored
@@ -45,13 +45,17 @@ jobs:
|
|||||||
cc: "clang", cxx: "clang++"
|
cc: "clang", cxx: "clang++"
|
||||||
}
|
}
|
||||||
qt_config:
|
qt_config:
|
||||||
|
# - {
|
||||||
|
# qt_version: "6.10.1",
|
||||||
|
# qt_creator_version: "18.0.2"
|
||||||
|
# }
|
||||||
|
# - {
|
||||||
|
# qt_version: "6.10.3",
|
||||||
|
# qt_creator_version: "19.0.2"
|
||||||
|
# }
|
||||||
- {
|
- {
|
||||||
qt_version: "6.10.1",
|
qt_version: "6.11.1",
|
||||||
qt_creator_version: "18.0.2"
|
qt_creator_version: "20.0.0-rc1"
|
||||||
}
|
|
||||||
- {
|
|
||||||
qt_version: "6.10.3",
|
|
||||||
qt_creator_version: "19.0.2"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@@ -110,10 +114,14 @@ jobs:
|
|||||||
set(qt_creator_version "${{ matrix.qt_config.qt_creator_version }}")
|
set(qt_creator_version "${{ matrix.qt_config.qt_creator_version }}")
|
||||||
|
|
||||||
string(REPLACE "." "" qt_version_dotless "${qt_version}")
|
string(REPLACE "." "" qt_version_dotless "${qt_version}")
|
||||||
|
set(qt_repo_dir "qt6_${qt_version_dotless}")
|
||||||
if ("${{ runner.os }}" STREQUAL "Windows")
|
if ("${{ runner.os }}" STREQUAL "Windows")
|
||||||
set(url_os "windows_x86")
|
set(url_os "windows_x86")
|
||||||
set(qt_package_arch_suffix "win64_msvc2022_64")
|
set(qt_package_arch_suffix "win64_msvc2022_64")
|
||||||
set(qt_dir_prefix "${qt_version}/msvc2022_64")
|
set(qt_dir_prefix "${qt_version}/msvc2022_64")
|
||||||
|
if (qt_version VERSION_GREATER_EQUAL "6.11.0")
|
||||||
|
set(qt_repo_dir "qt6_${qt_version_dotless}_msvc2022_64")
|
||||||
|
endif()
|
||||||
if (qt_creator_version VERSION_GREATER_EQUAL "18.0.0")
|
if (qt_creator_version VERSION_GREATER_EQUAL "18.0.0")
|
||||||
set(qt_package_suffix "-Windows-Windows_11_24H2-MSVC2022-Windows-Windows_11_24H2-X86_64")
|
set(qt_package_suffix "-Windows-Windows_11_24H2-MSVC2022-Windows-Windows_11_24H2-X86_64")
|
||||||
else()
|
else()
|
||||||
@@ -127,7 +135,9 @@ jobs:
|
|||||||
set(qt_package_arch_suffix "linux_gcc_64")
|
set(qt_package_arch_suffix "linux_gcc_64")
|
||||||
endif()
|
endif()
|
||||||
set(qt_dir_prefix "${qt_version}/gcc_64")
|
set(qt_dir_prefix "${qt_version}/gcc_64")
|
||||||
if (qt_creator_version VERSION_GREATER_EQUAL "18.0.0")
|
if (qt_version VERSION_GREATER_EQUAL "6.11.0")
|
||||||
|
set(qt_package_suffix "-Linux-RHEL_9_6-GCC-Linux-RHEL_9_6-X86_64")
|
||||||
|
elseif (qt_creator_version VERSION_GREATER_EQUAL "18.0.0")
|
||||||
set(qt_package_suffix "-Linux-RHEL_9_4-GCC-Linux-RHEL_9_4-X86_64")
|
set(qt_package_suffix "-Linux-RHEL_9_4-GCC-Linux-RHEL_9_4-X86_64")
|
||||||
else()
|
else()
|
||||||
set(qt_package_suffix "-Linux-RHEL_8_10-GCC-Linux-RHEL_8_10-X86_64")
|
set(qt_package_suffix "-Linux-RHEL_8_10-GCC-Linux-RHEL_8_10-X86_64")
|
||||||
@@ -143,7 +153,7 @@ jobs:
|
|||||||
endif()
|
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}/${qt_repo_dir}")
|
||||||
file(DOWNLOAD "${qt_base_url}/Updates.xml" ./Updates.xml SHOW_PROGRESS)
|
file(DOWNLOAD "${qt_base_url}/Updates.xml" ./Updates.xml SHOW_PROGRESS)
|
||||||
|
|
||||||
file(READ ./Updates.xml updates_xml)
|
file(READ ./Updates.xml updates_xml)
|
||||||
@@ -170,7 +180,11 @@ jobs:
|
|||||||
)
|
)
|
||||||
endforeach()
|
endforeach()
|
||||||
|
|
||||||
foreach(package qt5compat qtshadertools)
|
set(qt_addon_packages qt5compat qtshadertools)
|
||||||
|
if (qt_version VERSION_GREATER_EQUAL "6.11.0")
|
||||||
|
list(APPEND qt_addon_packages qttasktree)
|
||||||
|
endif()
|
||||||
|
foreach(package ${qt_addon_packages})
|
||||||
downloadAndExtract(
|
downloadAndExtract(
|
||||||
"${qt_base_url}/qt.qt6.${qt_version_dotless}.addons.${package}.${qt_package_arch_suffix}/${qt_package_version}${package}${qt_package_suffix}.7z"
|
"${qt_base_url}/qt.qt6.${qt_version_dotless}.addons.${package}.${qt_package_arch_suffix}/${qt_package_version}${package}${qt_package_suffix}.7z"
|
||||||
${package}.7z
|
${package}.7z
|
||||||
@@ -236,7 +250,7 @@ jobs:
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
set(build_plugin_py "scripts/build_plugin.py")
|
set(build_plugin_py "scripts/build_plugin.py")
|
||||||
foreach(dir "share/qtcreator/scripts" "Qt Creator.app/Contents/Resources/scripts" "Contents/Resources/scripts")
|
foreach(dir "share/qtcreator/scripts" "Qt Creator.sdk/share/qtcreator/scripts" "Qt Creator.app/Contents/Resources/scripts" "Contents/Resources/scripts")
|
||||||
if(EXISTS "${{ steps.qt_creator.outputs.qtc_dir }}/${dir}/build_plugin.py")
|
if(EXISTS "${{ steps.qt_creator.outputs.qtc_dir }}/${dir}/build_plugin.py")
|
||||||
set(build_plugin_py "${dir}/build_plugin.py")
|
set(build_plugin_py "${dir}/build_plugin.py")
|
||||||
break()
|
break()
|
||||||
|
|||||||
@@ -2,6 +2,10 @@ cmake_minimum_required(VERSION 3.16)
|
|||||||
|
|
||||||
project(QodeAssist)
|
project(QodeAssist)
|
||||||
|
|
||||||
|
option(QODEASSIST_EXPERIMENTAL
|
||||||
|
"Enable experimental features" OFF)
|
||||||
|
message(STATUS "QodeAssist experimental features: ${QODEASSIST_EXPERIMENTAL}")
|
||||||
|
|
||||||
set(CMAKE_AUTOMOC ON)
|
set(CMAKE_AUTOMOC ON)
|
||||||
set(CMAKE_AUTORCC ON)
|
set(CMAKE_AUTORCC ON)
|
||||||
set(CMAKE_AUTOUIC ON)
|
set(CMAKE_AUTOUIC ON)
|
||||||
@@ -14,7 +18,9 @@ find_package(QtCreator REQUIRED COMPONENTS Core)
|
|||||||
find_package(Qt6 COMPONENTS Core Gui Quick Widgets Network Svg Test LinguistTools REQUIRED)
|
find_package(Qt6 COMPONENTS Core Gui Quick Widgets Network Svg Test LinguistTools REQUIRED)
|
||||||
find_package(GTest)
|
find_package(GTest)
|
||||||
|
|
||||||
qt_standard_project_setup(I18N_TRANSLATED_LANGUAGES en)
|
qt_standard_project_setup(I18N_TRANSLATED_LANGUAGES
|
||||||
|
en cs zh_CN zh_TW da de fr hr ja pl ru sl sv uk
|
||||||
|
)
|
||||||
|
|
||||||
# IDE_VERSION is defined by QtCreator package
|
# IDE_VERSION is defined by QtCreator package
|
||||||
string(REGEX MATCH "([0-9]+)\\.([0-9]+)\\.([0-9]+)" version_match ${IDE_VERSION})
|
string(REGEX MATCH "([0-9]+)\\.([0-9]+)\\.([0-9]+)" version_match ${IDE_VERSION})
|
||||||
@@ -34,11 +40,10 @@ add_definitions(
|
|||||||
-DQODEASSIST_QT_CREATOR_VERSION_PATCH=${QODEASSIST_QT_CREATOR_VERSION_PATCH}
|
-DQODEASSIST_QT_CREATOR_VERSION_PATCH=${QODEASSIST_QT_CREATOR_VERSION_PATCH}
|
||||||
)
|
)
|
||||||
|
|
||||||
add_subdirectory(sources/external/llmqore)
|
add_subdirectory(sources)
|
||||||
add_subdirectory(sources/skills)
|
add_subdirectory(logger)
|
||||||
add_subdirectory(pluginllmcore)
|
add_subdirectory(pluginllmcore)
|
||||||
add_subdirectory(settings)
|
add_subdirectory(settings)
|
||||||
add_subdirectory(logger)
|
|
||||||
add_subdirectory(UIControls)
|
add_subdirectory(UIControls)
|
||||||
add_subdirectory(ChatView)
|
add_subdirectory(ChatView)
|
||||||
add_subdirectory(context)
|
add_subdirectory(context)
|
||||||
@@ -65,6 +70,8 @@ add_qtc_plugin(QodeAssist
|
|||||||
QtCreator::CPlusPlus
|
QtCreator::CPlusPlus
|
||||||
LLMQore
|
LLMQore
|
||||||
PluginLLMCore
|
PluginLLMCore
|
||||||
|
ProvidersConfig
|
||||||
|
Agents
|
||||||
Skills
|
Skills
|
||||||
QodeAssistChatViewplugin
|
QodeAssistChatViewplugin
|
||||||
SOURCES
|
SOURCES
|
||||||
@@ -109,6 +116,9 @@ add_qtc_plugin(QodeAssist
|
|||||||
providers/LlamaCppProvider.hpp providers/LlamaCppProvider.cpp
|
providers/LlamaCppProvider.hpp providers/LlamaCppProvider.cpp
|
||||||
providers/CodestralProvider.hpp providers/CodestralProvider.cpp
|
providers/CodestralProvider.hpp providers/CodestralProvider.cpp
|
||||||
providers/OpenAIResponsesProvider.hpp providers/OpenAIResponsesProvider.cpp
|
providers/OpenAIResponsesProvider.hpp providers/OpenAIResponsesProvider.cpp
|
||||||
|
providers/QwenProvider.hpp providers/QwenProvider.cpp
|
||||||
|
providers/QwenResponsesProvider.hpp providers/QwenResponsesProvider.cpp
|
||||||
|
providers/DeepSeekProvider.hpp providers/DeepSeekProvider.cpp
|
||||||
QodeAssist.qrc
|
QodeAssist.qrc
|
||||||
LSPCompletion.hpp
|
LSPCompletion.hpp
|
||||||
LLMSuggestion.hpp LLMSuggestion.cpp
|
LLMSuggestion.hpp LLMSuggestion.cpp
|
||||||
@@ -160,6 +170,11 @@ add_qtc_plugin(QodeAssist
|
|||||||
settings/McpClientsListAspect.hpp settings/McpClientsListAspect.cpp
|
settings/McpClientsListAspect.hpp settings/McpClientsListAspect.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if(QODEASSIST_EXPERIMENTAL)
|
||||||
|
target_compile_definitions(QodeAssist PRIVATE QODEASSIST_EXPERIMENTAL)
|
||||||
|
target_link_libraries(QodeAssist PRIVATE QodeAssistAgentPipelines)
|
||||||
|
endif()
|
||||||
|
|
||||||
get_target_property(QtCreatorCorePath QtCreator::Core LOCATION)
|
get_target_property(QtCreatorCorePath QtCreator::Core LOCATION)
|
||||||
find_program(QtCreatorExecutable
|
find_program(QtCreatorExecutable
|
||||||
NAMES
|
NAMES
|
||||||
@@ -181,5 +196,5 @@ endif()
|
|||||||
qt_add_translations(TARGETS QodeAssist
|
qt_add_translations(TARGETS QodeAssist
|
||||||
TS_FILE_DIR ${CMAKE_CURRENT_LIST_DIR}/resources/translations
|
TS_FILE_DIR ${CMAKE_CURRENT_LIST_DIR}/resources/translations
|
||||||
RESOURCE_PREFIX "/translations"
|
RESOURCE_PREFIX "/translations"
|
||||||
LUPDATE_OPTIONS -no-obsolete
|
LUPDATE_OPTIONS -no-obsolete -locations none
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// Copyright (C) 2024-2026 Petr Mironychev
|
// Copyright (C) 2024-2026 Petr Mironychev
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
#include "AgentRoleController.hpp"
|
#include "AgentRoleController.hpp"
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// Copyright (C) 2024-2026 Petr Mironychev
|
// Copyright (C) 2024-2026 Petr Mironychev
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ qt_add_qml_module(QodeAssistChatView
|
|||||||
qml/controls/Toast.qml
|
qml/controls/Toast.qml
|
||||||
qml/controls/TopBar.qml
|
qml/controls/TopBar.qml
|
||||||
qml/controls/SplitDropZone.qml
|
qml/controls/SplitDropZone.qml
|
||||||
|
qml/controls/MessageNavigator.qml
|
||||||
|
|
||||||
RESOURCES
|
RESOURCES
|
||||||
icons/attach-file-light.svg
|
icons/attach-file-light.svg
|
||||||
@@ -44,9 +45,12 @@ qt_add_qml_module(QodeAssistChatView
|
|||||||
icons/window-unlock.svg
|
icons/window-unlock.svg
|
||||||
icons/chat-icon.svg
|
icons/chat-icon.svg
|
||||||
icons/chat-pause-icon.svg
|
icons/chat-pause-icon.svg
|
||||||
|
icons/warning-icon.svg
|
||||||
|
icons/new-chat-icon.svg
|
||||||
icons/rules-icon.svg
|
icons/rules-icon.svg
|
||||||
icons/context-icon.svg
|
icons/context-icon.svg
|
||||||
icons/open-in-editor.svg
|
icons/open-in-editor.svg
|
||||||
|
icons/open-in-window.svg
|
||||||
icons/apply-changes-button.svg
|
icons/apply-changes-button.svg
|
||||||
icons/undo-changes-button.svg
|
icons/undo-changes-button.svg
|
||||||
icons/reject-changes-button.svg
|
icons/reject-changes-button.svg
|
||||||
@@ -56,6 +60,7 @@ qt_add_qml_module(QodeAssistChatView
|
|||||||
icons/tools-icon-off.svg
|
icons/tools-icon-off.svg
|
||||||
icons/settings-icon.svg
|
icons/settings-icon.svg
|
||||||
icons/compress-icon.svg
|
icons/compress-icon.svg
|
||||||
|
icons/open-in-code.svg
|
||||||
|
|
||||||
SOURCES
|
SOURCES
|
||||||
ChatWidget.hpp ChatWidget.cpp
|
ChatWidget.hpp ChatWidget.cpp
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// Copyright (C) 2024-2026 Petr Mironychev
|
// Copyright (C) 2024-2026 Petr Mironychev
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
#include "ChatCompressor.hpp"
|
#include "ChatCompressor.hpp"
|
||||||
|
|
||||||
@@ -75,6 +76,8 @@ void ChatCompressor::startCompression(const QString &chatFilePath, ChatModel *ch
|
|||||||
const QString customEndpoint = Settings::generalSettings().caCustomEndpoint();
|
const QString customEndpoint = Settings::generalSettings().caCustomEndpoint();
|
||||||
const QString endpoint = !customEndpoint.isEmpty() ? customEndpoint
|
const QString endpoint = !customEndpoint.isEmpty() ? customEndpoint
|
||||||
: promptTemplate->endpoint();
|
: promptTemplate->endpoint();
|
||||||
|
m_provider->client()->setTransferTimeout(
|
||||||
|
static_cast<int>(Settings::generalSettings().requestTimeout() * 1000));
|
||||||
m_currentRequestId = m_provider->sendRequest(
|
m_currentRequestId = m_provider->sendRequest(
|
||||||
QUrl(Settings::generalSettings().caUrl()), payload, endpoint);
|
QUrl(Settings::generalSettings().caUrl()), payload, endpoint);
|
||||||
LOG_MESSAGE(QString("Starting compression request: %1").arg(m_currentRequestId));
|
LOG_MESSAGE(QString("Starting compression request: %1").arg(m_currentRequestId));
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// Copyright (C) 2024-2026 Petr Mironychev
|
// Copyright (C) 2024-2026 Petr Mironychev
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// Copyright (C) 2024-2026 Petr Mironychev
|
// Copyright (C) 2024-2026 Petr Mironychev
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
#include "ChatConfigurationController.hpp"
|
#include "ChatConfigurationController.hpp"
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// Copyright (C) 2024-2026 Petr Mironychev
|
// Copyright (C) 2024-2026 Petr Mironychev
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// Copyright (C) 2025-2026 Petr Mironychev
|
// Copyright (C) 2025-2026 Petr Mironychev
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// Copyright (C) 2024-2026 Petr Mironychev
|
// Copyright (C) 2024-2026 Petr Mironychev
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
#include "ChatFileManager.hpp"
|
#include "ChatFileManager.hpp"
|
||||||
#include "Logger.hpp"
|
#include "Logger.hpp"
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// Copyright (C) 2024-2026 Petr Mironychev
|
// Copyright (C) 2024-2026 Petr Mironychev
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// Copyright (C) 2024-2026 Petr Mironychev
|
// Copyright (C) 2024-2026 Petr Mironychev
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
#include "ChatHistoryStore.hpp"
|
#include "ChatHistoryStore.hpp"
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// Copyright (C) 2024-2026 Petr Mironychev
|
// Copyright (C) 2024-2026 Petr Mironychev
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// Copyright (C) 2024-2026 Petr Mironychev
|
// Copyright (C) 2024-2026 Petr Mironychev
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
#include "ChatModel.hpp"
|
#include "ChatModel.hpp"
|
||||||
#include <utils/aspects.h>
|
#include <utils/aspects.h>
|
||||||
@@ -335,6 +336,28 @@ void ChatModel::resetModelTo(int index)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QVariantList ChatModel::userMessagePreviews(int maxLength) const
|
||||||
|
{
|
||||||
|
QVariantList result;
|
||||||
|
const int limit = maxLength > 4 ? maxLength : 80;
|
||||||
|
for (int i = 0; i < m_messages.size(); ++i) {
|
||||||
|
if (m_messages[i].role != ChatRole::User)
|
||||||
|
continue;
|
||||||
|
QString preview = m_messages[i].content;
|
||||||
|
preview.replace(QLatin1Char('\n'), QLatin1Char(' '));
|
||||||
|
preview.replace(QLatin1Char('\r'), QLatin1Char(' '));
|
||||||
|
preview.replace(QLatin1Char('\t'), QLatin1Char(' '));
|
||||||
|
preview = preview.simplified();
|
||||||
|
if (preview.size() > limit)
|
||||||
|
preview = preview.left(limit - 1).trimmed() + QChar(0x2026);
|
||||||
|
QVariantMap entry;
|
||||||
|
entry[QStringLiteral("messageIndex")] = i;
|
||||||
|
entry[QStringLiteral("preview")] = preview;
|
||||||
|
result.append(entry);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
void ChatModel::addToolExecutionStatus(
|
void ChatModel::addToolExecutionStatus(
|
||||||
const QString &requestId,
|
const QString &requestId,
|
||||||
const QString &toolId,
|
const QString &toolId,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// Copyright (C) 2024-2026 Petr Mironychev
|
// Copyright (C) 2024-2026 Petr Mironychev
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
@@ -94,6 +95,7 @@ public:
|
|||||||
QString lastMessageId() const;
|
QString lastMessageId() const;
|
||||||
|
|
||||||
Q_INVOKABLE void resetModelTo(int index);
|
Q_INVOKABLE void resetModelTo(int index);
|
||||||
|
Q_INVOKABLE QVariantList userMessagePreviews(int maxLength = 80) const;
|
||||||
|
|
||||||
void addToolExecutionStatus(
|
void addToolExecutionStatus(
|
||||||
const QString &requestId,
|
const QString &requestId,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// Copyright (C) 2024-2026 Petr Mironychev
|
// Copyright (C) 2024-2026 Petr Mironychev
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
#include "ChatRootView.hpp"
|
#include "ChatRootView.hpp"
|
||||||
|
|
||||||
@@ -10,6 +11,7 @@
|
|||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QFileDialog>
|
#include <QFileDialog>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
|
#include <QKeySequence>
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
#include <QQmlContext>
|
#include <QQmlContext>
|
||||||
#include <QQmlEngine>
|
#include <QQmlEngine>
|
||||||
@@ -52,6 +54,21 @@ bool isChatEditor(Core::IEditor *editor)
|
|||||||
return editor && editor->document()
|
return editor && editor->document()
|
||||||
&& editor->document()->id() == Utils::Id(Constants::QODE_ASSIST_CHAT_EDITOR_ID);
|
&& editor->document()->id() == Utils::Id(Constants::QODE_ASSIST_CHAT_EDITOR_ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QKeySequence sendMessageKeySequence()
|
||||||
|
{
|
||||||
|
auto command = Core::ActionManager::command(Constants::QODE_ASSIST_CHAT_SEND_MESSAGE);
|
||||||
|
if (!command)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
QKeySequence sequence = command->keySequence();
|
||||||
|
if (sequence.isEmpty()) {
|
||||||
|
const QList<QKeySequence> defaults = command->defaultKeySequences();
|
||||||
|
if (!defaults.isEmpty())
|
||||||
|
sequence = defaults.constFirst();
|
||||||
|
}
|
||||||
|
return sequence;
|
||||||
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
ChatRootView::ChatRootView(QQuickItem *parent)
|
ChatRootView::ChatRootView(QQuickItem *parent)
|
||||||
@@ -76,6 +93,22 @@ ChatRootView::ChatRootView(QQuickItem *parent)
|
|||||||
this,
|
this,
|
||||||
[this]() { setIsSyncOpenFiles(Settings::chatAssistantSettings().linkOpenFiles()); });
|
[this]() { setIsSyncOpenFiles(Settings::chatAssistantSettings().linkOpenFiles()); });
|
||||||
|
|
||||||
|
QMetaObject::invokeMethod(
|
||||||
|
this,
|
||||||
|
[this] {
|
||||||
|
if (auto sendCommand
|
||||||
|
= Core::ActionManager::command(Constants::QODE_ASSIST_CHAT_SEND_MESSAGE)) {
|
||||||
|
connect(
|
||||||
|
sendCommand,
|
||||||
|
&Core::Command::keySequenceChanged,
|
||||||
|
this,
|
||||||
|
&ChatRootView::sendShortcutTextChanged,
|
||||||
|
Qt::UniqueConnection);
|
||||||
|
}
|
||||||
|
emit sendShortcutTextChanged();
|
||||||
|
},
|
||||||
|
Qt::QueuedConnection);
|
||||||
|
|
||||||
auto &settings = Settings::generalSettings();
|
auto &settings = Settings::generalSettings();
|
||||||
|
|
||||||
connect(
|
connect(
|
||||||
@@ -112,6 +145,18 @@ ChatRootView::ChatRootView(QQuickItem *parent)
|
|||||||
setRecentFilePath(QString{});
|
setRecentFilePath(QString{});
|
||||||
m_fileEditController->clearCurrentRequestId();
|
m_fileEditController->clearCurrentRequestId();
|
||||||
});
|
});
|
||||||
|
auto maybeEmitTitle = [this] {
|
||||||
|
const QString newTitle = computeChatTitle();
|
||||||
|
if (newTitle == m_cachedChatTitle)
|
||||||
|
return;
|
||||||
|
m_cachedChatTitle = newTitle;
|
||||||
|
emit chatTitleChanged();
|
||||||
|
};
|
||||||
|
connect(m_chatModel, &ChatModel::modelReseted, this, maybeEmitTitle);
|
||||||
|
connect(m_chatModel, &QAbstractItemModel::modelReset, this, maybeEmitTitle);
|
||||||
|
connect(m_chatModel, &QAbstractItemModel::rowsInserted, this, maybeEmitTitle);
|
||||||
|
connect(m_chatModel, &QAbstractItemModel::rowsRemoved, this, maybeEmitTitle);
|
||||||
|
connect(m_chatModel, &QAbstractItemModel::dataChanged, this, maybeEmitTitle);
|
||||||
connect(this, &ChatRootView::attachmentFilesChanged, this, [this]() {
|
connect(this, &ChatRootView::attachmentFilesChanged, this, [this]() {
|
||||||
m_tokenCounter->setAttachments(m_attachmentFiles);
|
m_tokenCounter->setAttachments(m_attachmentFiles);
|
||||||
});
|
});
|
||||||
@@ -731,6 +776,32 @@ void ChatRootView::calculateMessageTokensCount(const QString &message)
|
|||||||
m_tokenCounter->setMessage(message);
|
m_tokenCounter->setMessage(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ChatRootView::isSendShortcut(int key, int modifiers) const
|
||||||
|
{
|
||||||
|
const QKeySequence sequence = sendMessageKeySequence();
|
||||||
|
if (sequence.isEmpty())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const QKeyCombination combination = sequence[0];
|
||||||
|
const int sequenceKey = combination.key();
|
||||||
|
|
||||||
|
const int relevantMask = Qt::ShiftModifier | Qt::ControlModifier | Qt::AltModifier
|
||||||
|
| Qt::MetaModifier;
|
||||||
|
const int sequenceModifiers = combination.keyboardModifiers() & relevantMask;
|
||||||
|
const int eventModifiers = modifiers & relevantMask;
|
||||||
|
|
||||||
|
const bool isReturnLike = sequenceKey == Qt::Key_Return || sequenceKey == Qt::Key_Enter;
|
||||||
|
const bool keyMatches = key == sequenceKey
|
||||||
|
|| (isReturnLike && (key == Qt::Key_Return || key == Qt::Key_Enter));
|
||||||
|
|
||||||
|
return keyMatches && eventModifiers == sequenceModifiers;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ChatRootView::sendShortcutText() const
|
||||||
|
{
|
||||||
|
return sendMessageKeySequence().toString(QKeySequence::NativeText);
|
||||||
|
}
|
||||||
|
|
||||||
void ChatRootView::setIsSyncOpenFiles(bool state)
|
void ChatRootView::setIsSyncOpenFiles(bool state)
|
||||||
{
|
{
|
||||||
if (m_isSyncOpenFiles != state) {
|
if (m_isSyncOpenFiles != state) {
|
||||||
@@ -792,6 +863,51 @@ void ChatRootView::triggerOpenChatCommand(Utils::Id commandId)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ChatRootView::isInEditor() const
|
||||||
|
{
|
||||||
|
return m_isInEditor;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatRootView::setInEditor(bool value)
|
||||||
|
{
|
||||||
|
if (m_isInEditor == value)
|
||||||
|
return;
|
||||||
|
m_isInEditor = value;
|
||||||
|
emit isInEditorChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatRootView::requestNewChat()
|
||||||
|
{
|
||||||
|
triggerOpenChatCommand(Constants::QODE_ASSIST_NEW_CHAT_ACTION);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ChatRootView::chatTitle() const
|
||||||
|
{
|
||||||
|
if (m_cachedChatTitle.isEmpty())
|
||||||
|
m_cachedChatTitle = computeChatTitle();
|
||||||
|
return m_cachedChatTitle;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ChatRootView::computeChatTitle() const
|
||||||
|
{
|
||||||
|
if (!m_chatModel)
|
||||||
|
return {};
|
||||||
|
const auto history = m_chatModel->getChatHistory();
|
||||||
|
for (const auto &msg : history) {
|
||||||
|
if (msg.role != ChatModel::User)
|
||||||
|
continue;
|
||||||
|
const QString content = msg.content.trimmed();
|
||||||
|
if (content.isEmpty())
|
||||||
|
continue;
|
||||||
|
const QString firstLine = content.section(QChar('\n'), 0, 0).trimmed();
|
||||||
|
constexpr int maxLen = 60;
|
||||||
|
if (firstLine.length() > maxLen)
|
||||||
|
return firstLine.left(maxLen - 1) + QChar(0x2026);
|
||||||
|
return firstLine;
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
void ChatRootView::handOffSession()
|
void ChatRootView::handOffSession()
|
||||||
{
|
{
|
||||||
if (m_chatModel->rowCount() > 0) {
|
if (m_chatModel->rowCount() > 0) {
|
||||||
@@ -824,7 +940,7 @@ void ChatRootView::consumePendingChatFile()
|
|||||||
void ChatRootView::relocateToSplit()
|
void ChatRootView::relocateToSplit()
|
||||||
{
|
{
|
||||||
handOffSession();
|
handOffSession();
|
||||||
triggerOpenChatCommand(Constants::QODE_ASSIST_SHOW_CHAT_ACTION);
|
triggerOpenChatCommand(Constants::QODE_ASSIST_NEW_CHAT_ACTION);
|
||||||
clearMessages();
|
clearMessages();
|
||||||
clearAttachmentFiles();
|
clearAttachmentFiles();
|
||||||
emit closeHostRequested();
|
emit closeHostRequested();
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// Copyright (C) 2024-2026 Petr Mironychev
|
// Copyright (C) 2024-2026 Petr Mironychev
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
@@ -49,6 +50,7 @@ class ChatRootView : public QQuickItem
|
|||||||
Q_PROPERTY(int activeRulesCount READ activeRulesCount NOTIFY activeRulesCountChanged FINAL)
|
Q_PROPERTY(int activeRulesCount READ activeRulesCount NOTIFY activeRulesCountChanged FINAL)
|
||||||
Q_PROPERTY(bool useTools READ useTools WRITE setUseTools NOTIFY useToolsChanged FINAL)
|
Q_PROPERTY(bool useTools READ useTools WRITE setUseTools NOTIFY useToolsChanged FINAL)
|
||||||
Q_PROPERTY(bool useThinking READ useThinking WRITE setUseThinking NOTIFY useThinkingChanged FINAL)
|
Q_PROPERTY(bool useThinking READ useThinking WRITE setUseThinking NOTIFY useThinkingChanged FINAL)
|
||||||
|
Q_PROPERTY(QString sendShortcutText READ sendShortcutText NOTIFY sendShortcutTextChanged FINAL)
|
||||||
|
|
||||||
Q_PROPERTY(int currentMessageTotalEdits READ currentMessageTotalEdits NOTIFY currentMessageEditsStatsChanged FINAL)
|
Q_PROPERTY(int currentMessageTotalEdits READ currentMessageTotalEdits NOTIFY currentMessageEditsStatsChanged FINAL)
|
||||||
Q_PROPERTY(int currentMessageAppliedEdits READ currentMessageAppliedEdits NOTIFY currentMessageEditsStatsChanged FINAL)
|
Q_PROPERTY(int currentMessageAppliedEdits READ currentMessageAppliedEdits NOTIFY currentMessageEditsStatsChanged FINAL)
|
||||||
@@ -63,6 +65,8 @@ class ChatRootView : public QQuickItem
|
|||||||
Q_PROPERTY(QString currentAgentRoleDescription READ currentAgentRoleDescription NOTIFY currentAgentRoleChanged FINAL)
|
Q_PROPERTY(QString currentAgentRoleDescription READ currentAgentRoleDescription NOTIFY currentAgentRoleChanged FINAL)
|
||||||
Q_PROPERTY(QString currentAgentRoleSystemPrompt READ currentAgentRoleSystemPrompt NOTIFY currentAgentRoleChanged FINAL)
|
Q_PROPERTY(QString currentAgentRoleSystemPrompt READ currentAgentRoleSystemPrompt NOTIFY currentAgentRoleChanged FINAL)
|
||||||
Q_PROPERTY(bool isCompressing READ isCompressing NOTIFY isCompressingChanged FINAL)
|
Q_PROPERTY(bool isCompressing READ isCompressing NOTIFY isCompressingChanged FINAL)
|
||||||
|
Q_PROPERTY(bool isInEditor READ isInEditor NOTIFY isInEditorChanged FINAL)
|
||||||
|
Q_PROPERTY(QString chatTitle READ chatTitle NOTIFY chatTitleChanged FINAL)
|
||||||
|
|
||||||
QML_ELEMENT
|
QML_ELEMENT
|
||||||
|
|
||||||
@@ -96,6 +100,8 @@ public:
|
|||||||
Q_INVOKABLE void showAddImageDialog();
|
Q_INVOKABLE void showAddImageDialog();
|
||||||
Q_INVOKABLE bool isImageFile(const QString &filePath) const;
|
Q_INVOKABLE bool isImageFile(const QString &filePath) const;
|
||||||
Q_INVOKABLE void calculateMessageTokensCount(const QString &message);
|
Q_INVOKABLE void calculateMessageTokensCount(const QString &message);
|
||||||
|
Q_INVOKABLE bool isSendShortcut(int key, int modifiers) const;
|
||||||
|
QString sendShortcutText() const;
|
||||||
Q_INVOKABLE void setIsSyncOpenFiles(bool state);
|
Q_INVOKABLE void setIsSyncOpenFiles(bool state);
|
||||||
Q_INVOKABLE void openChatHistoryFolder();
|
Q_INVOKABLE void openChatHistoryFolder();
|
||||||
Q_INVOKABLE void openRulesFolder();
|
Q_INVOKABLE void openRulesFolder();
|
||||||
@@ -183,6 +189,13 @@ public:
|
|||||||
|
|
||||||
bool isCompressing() const;
|
bool isCompressing() const;
|
||||||
|
|
||||||
|
bool isInEditor() const;
|
||||||
|
void setInEditor(bool value);
|
||||||
|
|
||||||
|
QString chatTitle() const;
|
||||||
|
|
||||||
|
Q_INVOKABLE void requestNewChat();
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void sendMessage(const QString &message);
|
void sendMessage(const QString &message);
|
||||||
void copyToClipboard(const QString &text);
|
void copyToClipboard(const QString &text);
|
||||||
@@ -209,6 +222,7 @@ signals:
|
|||||||
|
|
||||||
void lastErrorMessageChanged();
|
void lastErrorMessageChanged();
|
||||||
void lastInfoMessageChanged();
|
void lastInfoMessageChanged();
|
||||||
|
void sendShortcutTextChanged();
|
||||||
void activeRulesChanged();
|
void activeRulesChanged();
|
||||||
void activeRulesCountChanged();
|
void activeRulesCountChanged();
|
||||||
|
|
||||||
@@ -228,11 +242,15 @@ signals:
|
|||||||
void compressionCompleted(const QString &compressedChatPath);
|
void compressionCompleted(const QString &compressedChatPath);
|
||||||
void compressionFailed(const QString &error);
|
void compressionFailed(const QString &error);
|
||||||
|
|
||||||
|
void isInEditorChanged();
|
||||||
|
void chatTitleChanged();
|
||||||
|
|
||||||
void openFilesChanged();
|
void openFilesChanged();
|
||||||
|
|
||||||
void closeHostRequested();
|
void closeHostRequested();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
QString computeChatTitle() const;
|
||||||
void triggerOpenChatCommand(Utils::Id commandId);
|
void triggerOpenChatCommand(Utils::Id commandId);
|
||||||
void handOffSession();
|
void handOffSession();
|
||||||
bool deferSendForAutoCompress(
|
bool deferSendForAutoCompress(
|
||||||
@@ -271,6 +289,8 @@ private:
|
|||||||
};
|
};
|
||||||
PendingSend m_pendingSend;
|
PendingSend m_pendingSend;
|
||||||
bool m_isSyncOpenFiles;
|
bool m_isSyncOpenFiles;
|
||||||
|
bool m_isInEditor = false;
|
||||||
|
mutable QString m_cachedChatTitle;
|
||||||
QList<Core::IEditor *> m_currentEditors;
|
QList<Core::IEditor *> m_currentEditors;
|
||||||
bool m_isRequestInProgress;
|
bool m_isRequestInProgress;
|
||||||
QString m_lastErrorMessage;
|
QString m_lastErrorMessage;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// Copyright (C) 2024-2026 Petr Mironychev
|
// Copyright (C) 2024-2026 Petr Mironychev
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
#include "ChatSerializer.hpp"
|
#include "ChatSerializer.hpp"
|
||||||
#include "Logger.hpp"
|
#include "Logger.hpp"
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// Copyright (C) 2024-2026 Petr Mironychev
|
// Copyright (C) 2024-2026 Petr Mironychev
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// Copyright (C) 2024-2026 Petr Mironychev
|
// Copyright (C) 2024-2026 Petr Mironychev
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
#include "ChatUtils.h"
|
#include "ChatUtils.h"
|
||||||
|
|
||||||
@@ -19,22 +20,34 @@ QString ChatUtils::getSafeMarkdownText(const QString &text) const
|
|||||||
return text;
|
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;
|
QString safeText;
|
||||||
safeText.reserve(text.size());
|
safeText.reserve(text.size() + 16);
|
||||||
|
|
||||||
|
bool inFenced = false;
|
||||||
|
bool inInline = false;
|
||||||
|
|
||||||
|
for (int i = 0; i < text.size(); ++i) {
|
||||||
|
const QChar ch = text[i];
|
||||||
|
|
||||||
|
if (!inInline && i + 2 < text.size()
|
||||||
|
&& text[i] == '`' && text[i + 1] == '`' && text[i + 2] == '`') {
|
||||||
|
safeText.append(QStringLiteral("```"));
|
||||||
|
inFenced = !inFenced;
|
||||||
|
i += 2;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!inFenced && ch == '`') {
|
||||||
|
safeText.append(ch);
|
||||||
|
inInline = !inInline;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!inFenced && !inInline && ch == '<') {
|
||||||
|
safeText.append(QStringLiteral("<"));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
for (QChar ch : text) {
|
|
||||||
if (ch.isNull()) {
|
if (ch.isNull()) {
|
||||||
safeText.append(' ');
|
safeText.append(' ');
|
||||||
} else if (ch == '\n' || ch == '\t' || ch == '\r' || ch == ' ') {
|
} else if (ch == '\n' || ch == '\t' || ch == '\r' || ch == ' ') {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// Copyright (C) 2024-2026 Petr Mironychev
|
// Copyright (C) 2024-2026 Petr Mironychev
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// Copyright (C) 2024-2026 Petr Mironychev
|
// Copyright (C) 2024-2026 Petr Mironychev
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
#include "ChatView.hpp"
|
#include "ChatView.hpp"
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// Copyright (C) 2024-2026 Petr Mironychev
|
// Copyright (C) 2024-2026 Petr Mironychev
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
// Copyright (C) 2024-2026 Petr Mironychev
|
// Copyright (C) 2024-2026 Petr Mironychev
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
#include "ChatWidget.hpp"
|
#include "ChatWidget.hpp"
|
||||||
|
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
|
#include <QMouseEvent>
|
||||||
#include <QQmlContext>
|
#include <QQmlContext>
|
||||||
#include <QQmlEngine>
|
#include <QQmlEngine>
|
||||||
#include <QQuickItem>
|
#include <QQuickItem>
|
||||||
@@ -21,6 +23,7 @@ ChatWidget::ChatWidget(
|
|||||||
QQmlEngine *engine,
|
QQmlEngine *engine,
|
||||||
SessionFileRegistry *sessionFileRegistry,
|
SessionFileRegistry *sessionFileRegistry,
|
||||||
Skills::SkillsManager *skillsManager,
|
Skills::SkillsManager *skillsManager,
|
||||||
|
bool registerOwnContext,
|
||||||
QWidget *parent)
|
QWidget *parent)
|
||||||
: QQuickWidget{engine, parent}
|
: QQuickWidget{engine, parent}
|
||||||
{
|
{
|
||||||
@@ -37,10 +40,29 @@ ChatWidget::ChatWidget(
|
|||||||
setResizeMode(QQuickWidget::SizeRootObjectToView);
|
setResizeMode(QQuickWidget::SizeRootObjectToView);
|
||||||
setFocusPolicy(Qt::StrongFocus);
|
setFocusPolicy(Qt::StrongFocus);
|
||||||
|
|
||||||
auto ideContext = new Core::IContext{this};
|
setAttribute(Qt::WA_NoMousePropagation, true);
|
||||||
ideContext->setWidget(this);
|
|
||||||
ideContext->setContext(Core::Context{Constants::QODE_ASSIST_CHAT_CONTEXT});
|
if (registerOwnContext) {
|
||||||
Core::ICore::addContextObject(ideContext);
|
auto ideContext = new Core::IContext{this};
|
||||||
|
ideContext->setWidget(this);
|
||||||
|
ideContext->setContext(Core::Context{Constants::QODE_ASSIST_CHAT_CONTEXT});
|
||||||
|
Core::ICore::addContextObject(ideContext);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatWidget::focusInEvent(QFocusEvent *event)
|
||||||
|
{
|
||||||
|
QQuickWidget::focusInEvent(event);
|
||||||
|
if (rootObject())
|
||||||
|
QMetaObject::invokeMethod(rootObject(), "focusInput");
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatWidget::mousePressEvent(QMouseEvent *event)
|
||||||
|
{
|
||||||
|
if (!hasFocus())
|
||||||
|
setFocus(Qt::MouseFocusReason);
|
||||||
|
|
||||||
|
QQuickWidget::mousePressEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChatWidget::clear()
|
void ChatWidget::clear()
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// Copyright (C) 2024-2026 Petr Mironychev
|
// Copyright (C) 2024-2026 Petr Mironychev
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
@@ -22,6 +23,7 @@ public:
|
|||||||
QQmlEngine *engine,
|
QQmlEngine *engine,
|
||||||
SessionFileRegistry *sessionFileRegistry,
|
SessionFileRegistry *sessionFileRegistry,
|
||||||
Skills::SkillsManager *skillsManager,
|
Skills::SkillsManager *skillsManager,
|
||||||
|
bool registerOwnContext = true,
|
||||||
QWidget *parent = nullptr);
|
QWidget *parent = nullptr);
|
||||||
~ChatWidget() = default;
|
~ChatWidget() = default;
|
||||||
|
|
||||||
@@ -38,6 +40,10 @@ public:
|
|||||||
|
|
||||||
signals:
|
signals:
|
||||||
void clearPressed();
|
void clearPressed();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void focusInEvent(QFocusEvent *event) override;
|
||||||
|
void mousePressEvent(QMouseEvent *event) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace QodeAssist::Chat
|
} // namespace QodeAssist::Chat
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// Copyright (C) 2024-2026 Petr Mironychev
|
// Copyright (C) 2024-2026 Petr Mironychev
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
#include "ClientInterface.hpp"
|
#include "ClientInterface.hpp"
|
||||||
|
|
||||||
@@ -174,14 +175,21 @@ void ClientInterface::sendMessage(
|
|||||||
auto project = PluginLLMCore::RulesLoader::getActiveProject();
|
auto project = PluginLLMCore::RulesLoader::getActiveProject();
|
||||||
|
|
||||||
if (project) {
|
if (project) {
|
||||||
systemPrompt += QString("\n# Active project name: %1").arg(project->displayName());
|
systemPrompt += QString("\n# Active project: %1").arg(project->displayName());
|
||||||
systemPrompt += QString("\n# Active Project path: %1")
|
systemPrompt += QString(
|
||||||
|
"\n# Project source root: %1"
|
||||||
|
"\n# All new source files, headers, QML and CMake edits MUST be "
|
||||||
|
"created or modified under this directory. Use absolute paths "
|
||||||
|
"rooted here, or project-relative paths.")
|
||||||
.arg(project->projectDirectory().toUrlishString());
|
.arg(project->projectDirectory().toUrlishString());
|
||||||
|
|
||||||
if (auto target = project->activeTarget()) {
|
if (auto target = project->activeTarget()) {
|
||||||
if (auto buildConfig = target->activeBuildConfiguration()) {
|
if (auto buildConfig = target->activeBuildConfiguration()) {
|
||||||
systemPrompt += QString("\n# Active Build directory: %1")
|
systemPrompt
|
||||||
.arg(buildConfig->buildDirectory().toUrlishString());
|
+= QString(
|
||||||
|
"\n# Build output directory (compiler artifacts only — do NOT "
|
||||||
|
"create or edit source files here): %1")
|
||||||
|
.arg(buildConfig->buildDirectory().toUrlishString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -330,6 +338,9 @@ void ClientInterface::sendMessage(
|
|||||||
provider->client()->setMaxToolContinuations(
|
provider->client()->setMaxToolContinuations(
|
||||||
Settings::toolsSettings().maxToolContinuations());
|
Settings::toolsSettings().maxToolContinuations());
|
||||||
|
|
||||||
|
provider->client()->setTransferTimeout(
|
||||||
|
static_cast<int>(Settings::generalSettings().requestTimeout() * 1000));
|
||||||
|
|
||||||
connect(
|
connect(
|
||||||
provider->client(),
|
provider->client(),
|
||||||
&::LLMQore::BaseClient::chunkReceived,
|
&::LLMQore::BaseClient::chunkReceived,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// Copyright (C) 2024-2026 Petr Mironychev
|
// Copyright (C) 2024-2026 Petr Mironychev
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// Copyright (C) 2024-2026 Petr Mironychev
|
// Copyright (C) 2024-2026 Petr Mironychev
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
#include "FileEditController.hpp"
|
#include "FileEditController.hpp"
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// Copyright (C) 2024-2026 Petr Mironychev
|
// Copyright (C) 2024-2026 Petr Mironychev
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// Copyright (C) 2025-2026 Petr Mironychev
|
// Copyright (C) 2025-2026 Petr Mironychev
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
#include "FileItem.hpp"
|
#include "FileItem.hpp"
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// Copyright (C) 2025-2026 Petr Mironychev
|
// Copyright (C) 2025-2026 Petr Mironychev
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// Copyright (C) 2026 Petr Mironychev
|
// Copyright (C) 2026 Petr Mironychev
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
#include "FileMentionItem.hpp"
|
#include "FileMentionItem.hpp"
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// Copyright (C) 2026 Petr Mironychev
|
// Copyright (C) 2026 Petr Mironychev
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// Copyright (C) 2024-2026 Petr Mironychev
|
// Copyright (C) 2024-2026 Petr Mironychev
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
#include "InputTokenCounter.hpp"
|
#include "InputTokenCounter.hpp"
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// Copyright (C) 2024-2026 Petr Mironychev
|
// Copyright (C) 2024-2026 Petr Mironychev
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// Copyright (C) 2024-2026 Petr Mironychev
|
// Copyright (C) 2024-2026 Petr Mironychev
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// Copyright (C) 2024-2026 Petr Mironychev
|
// Copyright (C) 2024-2026 Petr Mironychev
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
#include "SessionFileRegistry.hpp"
|
#include "SessionFileRegistry.hpp"
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// Copyright (C) 2024-2026 Petr Mironychev
|
// Copyright (C) 2024-2026 Petr Mironychev
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
|||||||
5
ChatView/icons/new-chat-icon.svg
Normal file
5
ChatView/icons/new-chat-icon.svg
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<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"/>
|
||||||
|
<rect x="11" y="5" width="2" height="9" rx="0.5" fill="black"/>
|
||||||
|
<rect x="7.5" y="8.5" width="9" height="2" rx="0.5" fill="black"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 526 B |
17
ChatView/icons/open-in-code.svg
Normal file
17
ChatView/icons/open-in-code.svg
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<svg width="44" height="44" viewBox="0 0 44 44" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g clip-path="url(#clip0_74_52)">
|
||||||
|
<mask id="mask0_74_52" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="0" width="44" height="44">
|
||||||
|
<path d="M44 0H0V44H44V0Z" fill="white"/>
|
||||||
|
</mask>
|
||||||
|
<g mask="url(#mask0_74_52)">
|
||||||
|
<path d="M18 31C25.1797 31 31 25.1797 31 18C31 10.8203 25.1797 5 18 5C10.8203 5 5 10.8203 5 18C5 25.1797 10.8203 31 18 31Z" stroke="black" stroke-width="3.5"/>
|
||||||
|
<path d="M27 27L38 38" stroke="black" stroke-width="3.5" stroke-linecap="round"/>
|
||||||
|
<path d="M16.375 23L18.2841 11.3636H20.1023L18.1932 23H16.375ZM11.1648 20.1136L11.4659 18.2955H20.5568L20.2557 20.1136H11.1648ZM12.2841 23L14.1932 11.3636H16.0114L14.1023 23H12.2841ZM11.8295 16.0682L12.1364 14.25H21.2273L20.9205 16.0682H11.8295Z" fill="black"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="clip0_74_52">
|
||||||
|
<rect width="44" height="44" fill="white"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 943 B |
@@ -1,17 +1,6 @@
|
|||||||
<svg width="44" height="44" viewBox="0 0 44 44" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="44" height="44" viewBox="0 0 44 44" xmlns="http://www.w3.org/2000/svg">
|
||||||
<g clip-path="url(#clip0_74_52)">
|
<g transform="translate(10 8) skewX(-15)" stroke="black" stroke-width="2" stroke-linejoin="round">
|
||||||
<mask id="mask0_74_52" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="0" width="44" height="44">
|
<rect x="10" y="0" width="22" height="15" rx="3" ry="3" fill="black"/>
|
||||||
<path d="M44 0H0V44H44V0Z" fill="white"/>
|
<rect x="0" y="12" width="22" height="15" rx="3" ry="3" fill="none"/>
|
||||||
</mask>
|
</g>
|
||||||
<g mask="url(#mask0_74_52)">
|
|
||||||
<path d="M18 31C25.1797 31 31 25.1797 31 18C31 10.8203 25.1797 5 18 5C10.8203 5 5 10.8203 5 18C5 25.1797 10.8203 31 18 31Z" stroke="black" stroke-width="3.5"/>
|
|
||||||
<path d="M27 27L38 38" stroke="black" stroke-width="3.5" stroke-linecap="round"/>
|
|
||||||
<path d="M16.375 23L18.2841 11.3636H20.1023L18.1932 23H16.375ZM11.1648 20.1136L11.4659 18.2955H20.5568L20.2557 20.1136H11.1648ZM12.2841 23L14.1932 11.3636H16.0114L14.1023 23H12.2841ZM11.8295 16.0682L12.1364 14.25H21.2273L20.9205 16.0682H11.8295Z" fill="black"/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<defs>
|
|
||||||
<clipPath id="clip0_74_52">
|
|
||||||
<rect width="44" height="44" fill="white"/>
|
|
||||||
</clipPath>
|
|
||||||
</defs>
|
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 943 B After Width: | Height: | Size: 348 B |
6
ChatView/icons/open-in-window.svg
Normal file
6
ChatView/icons/open-in-window.svg
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<svg width="44" height="44" viewBox="0 0 44 44" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g transform="translate(10 8) skewX(-15)" stroke="black" stroke-width="2" stroke-linejoin="round">
|
||||||
|
<rect x="10" y="0" width="22" height="15" rx="3" ry="3" fill="none"/>
|
||||||
|
<rect x="0" y="12" width="22" height="15" rx="3" ry="3" fill="black"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 348 B |
5
ChatView/icons/warning-icon.svg
Normal file
5
ChatView/icons/warning-icon.svg
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M12 3L22 20H2L12 3Z" stroke="black" stroke-width="2" stroke-linejoin="round"/>
|
||||||
|
<path d="M12 10V14" stroke="black" stroke-width="2" stroke-linecap="round"/>
|
||||||
|
<path d="M12 17H12.01" stroke="black" stroke-width="2.4" stroke-linecap="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 350 B |
@@ -1,5 +1,6 @@
|
|||||||
// Copyright (C) 2024-2026 Petr Mironychev
|
// Copyright (C) 2024-2026 Petr Mironychev
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
@@ -19,6 +20,9 @@ ChatRootView {
|
|||||||
colorGroup: SystemPalette.Active
|
colorGroup: SystemPalette.Active
|
||||||
}
|
}
|
||||||
|
|
||||||
|
property bool hasActiveError: false
|
||||||
|
readonly property color errorColor: "#d32f2f"
|
||||||
|
|
||||||
palette {
|
palette {
|
||||||
window: sysPalette.window
|
window: sysPalette.window
|
||||||
windowText: sysPalette.windowText
|
windowText: sysPalette.windowText
|
||||||
@@ -87,21 +91,25 @@ ChatRootView {
|
|||||||
Layout.preferredWidth: parent.width
|
Layout.preferredWidth: parent.width
|
||||||
Layout.preferredHeight: childrenRect.height + 10
|
Layout.preferredHeight: childrenRect.height + 10
|
||||||
|
|
||||||
|
isInEditor: root.isInEditor
|
||||||
saveButton.onClicked: root.showSaveDialog()
|
saveButton.onClicked: root.showSaveDialog()
|
||||||
loadButton.onClicked: root.showLoadDialog()
|
loadButton.onClicked: root.showLoadDialog()
|
||||||
clearButton.onClicked: root.clearChat()
|
clearButton.onClicked: root.clearChat()
|
||||||
|
newChatButton.onClicked: root.requestNewChat()
|
||||||
tokensBadge {
|
tokensBadge {
|
||||||
readonly property int sessionCached: root.chatModel.sessionCachedPromptTokens
|
readonly property int sessionPrompt: root.chatModel.sessionPromptTokens || 0
|
||||||
|
readonly property int sessionCompletion: root.chatModel.sessionCompletionTokens || 0
|
||||||
|
readonly property int sessionCached: root.chatModel.sessionCachedPromptTokens || 0
|
||||||
text: sessionCached > 0
|
text: sessionCached > 0
|
||||||
? qsTr("next ~%1 · session ↑%2 ↓%3 ↻%4")
|
? qsTr("next ~%1 · session ↑%2 ↓%3 ↻%4")
|
||||||
.arg(root.inputTokensCount)
|
.arg(root.inputTokensCount)
|
||||||
.arg(root.chatModel.sessionPromptTokens)
|
.arg(sessionPrompt)
|
||||||
.arg(root.chatModel.sessionCompletionTokens)
|
.arg(sessionCompletion)
|
||||||
.arg(sessionCached)
|
.arg(sessionCached)
|
||||||
: qsTr("next ~%1 · session ↑%2 ↓%3")
|
: qsTr("next ~%1 · session ↑%2 ↓%3")
|
||||||
.arg(root.inputTokensCount)
|
.arg(root.inputTokensCount)
|
||||||
.arg(root.chatModel.sessionPromptTokens)
|
.arg(sessionPrompt)
|
||||||
.arg(root.chatModel.sessionCompletionTokens)
|
.arg(sessionCompletion)
|
||||||
ToolTip.text: sessionCached > 0
|
ToolTip.text: sessionCached > 0
|
||||||
? qsTr("next request (estimate) · session prompt ↑ / completion ↓ / cached ↻ (provider cache hits)")
|
? qsTr("next request (estimate) · session prompt ↑ / completion ↓ / cached ↻ (provider cache hits)")
|
||||||
: qsTr("next request (estimate) · session prompt ↑ / completion ↓")
|
: qsTr("next request (estimate) · session prompt ↑ / completion ↓")
|
||||||
@@ -117,9 +125,9 @@ ChatRootView {
|
|||||||
onCheckedChanged: _chatview.isPin = topBar.pinButton.checked
|
onCheckedChanged: _chatview.isPin = topBar.pinButton.checked
|
||||||
}
|
}
|
||||||
relocateButton {
|
relocateButton {
|
||||||
ToolTip.text: (typeof _chatview !== 'undefined')
|
icon.source: (typeof _chatview !== 'undefined')
|
||||||
? qsTr("Move this chat to an editor split")
|
? "qrc:/qt/qml/ChatView/icons/open-in-editor.svg"
|
||||||
: qsTr("Move this chat to a separate window")
|
: "qrc:/qt/qml/ChatView/icons/open-in-window.svg"
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (typeof _chatview !== 'undefined')
|
if (typeof _chatview !== 'undefined')
|
||||||
root.relocateToSplit()
|
root.relocateToSplit()
|
||||||
@@ -127,6 +135,9 @@ ChatRootView {
|
|||||||
root.relocateToWindow()
|
root.relocateToWindow()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
relocateTooltip.text: (typeof _chatview !== 'undefined')
|
||||||
|
? qsTr("Move this chat to an editor tab")
|
||||||
|
: qsTr("Move this chat to a separate window")
|
||||||
toolsButton {
|
toolsButton {
|
||||||
checked: root.useTools
|
checked: root.useTools
|
||||||
onCheckedChanged: {
|
onCheckedChanged: {
|
||||||
@@ -149,39 +160,67 @@ ChatRootView {
|
|||||||
root.applyConfiguration(root.availableConfigurations[index])
|
root.applyConfiguration(root.availableConfigurations[index])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
popup.onAboutToShow: {
|
popup.onAboutToShow: {
|
||||||
root.loadAvailableConfigurations()
|
root.loadAvailableConfigurations()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
roleSelector {
|
roleSelector {
|
||||||
model: root.availableAgentRoles
|
model: root.availableAgentRoles
|
||||||
displayText: root.currentAgentRole
|
displayText: root.currentAgentRole
|
||||||
onActivated: function(index) {
|
onActivated: function(index) {
|
||||||
root.applyAgentRole(root.availableAgentRoles[index])
|
root.applyAgentRole(root.availableAgentRoles[index])
|
||||||
}
|
}
|
||||||
|
|
||||||
popup.onAboutToShow: {
|
popup.onAboutToShow: {
|
||||||
root.loadAvailableAgentRoles()
|
root.loadAvailableAgentRoles()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
spacing: 2
|
||||||
|
|
||||||
|
MessageNavigator {
|
||||||
|
id: messageNavigator
|
||||||
|
|
||||||
|
Layout.preferredWidth: 16
|
||||||
|
Layout.fillHeight: true
|
||||||
|
Layout.topMargin: 4
|
||||||
|
Layout.bottomMargin: 4
|
||||||
|
|
||||||
|
chatModel: root.chatModel
|
||||||
|
|
||||||
|
onMessageClicked: function(messageIndex) {
|
||||||
|
chatListView.userScrolledUp = true
|
||||||
|
chatListView.positionViewAtIndex(messageIndex, ListView.Beginning)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ListView {
|
ListView {
|
||||||
id: chatListView
|
id: chatListView
|
||||||
|
|
||||||
property bool userScrolledUp: false
|
property bool userScrolledUp: false
|
||||||
|
|
||||||
|
function syncNavigatorCurrent() {
|
||||||
|
const top = indexAt(10, contentY + 4)
|
||||||
|
messageNavigator.updateCurrentFromModelIndex(top)
|
||||||
|
}
|
||||||
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
leftMargin: 5
|
leftMargin: 3
|
||||||
model: root.chatModel
|
model: root.chatModel
|
||||||
clip: true
|
clip: true
|
||||||
spacing: 0
|
spacing: 0
|
||||||
boundsBehavior: Flickable.StopAtBounds
|
boundsBehavior: Flickable.StopAtBounds
|
||||||
cacheBuffer: 2000
|
cacheBuffer: 2000
|
||||||
|
|
||||||
|
onContentYChanged: Qt.callLater(syncNavigatorCurrent)
|
||||||
|
|
||||||
onMovingChanged: {
|
onMovingChanged: {
|
||||||
if (moving) {
|
if (moving) {
|
||||||
userScrolledUp = !atYEnd
|
userScrolledUp = !atYEnd
|
||||||
@@ -268,6 +307,7 @@ ChatRootView {
|
|||||||
if (!userScrolledUp) {
|
if (!userScrolledUp) {
|
||||||
root.scrollToBottom()
|
root.scrollToBottom()
|
||||||
}
|
}
|
||||||
|
Qt.callLater(syncNavigatorCurrent)
|
||||||
}
|
}
|
||||||
|
|
||||||
onContentHeightChanged: {
|
onContentHeightChanged: {
|
||||||
@@ -363,6 +403,7 @@ ChatRootView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ScrollView {
|
ScrollView {
|
||||||
id: view
|
id: view
|
||||||
@@ -374,11 +415,10 @@ ChatRootView {
|
|||||||
QQC.TextArea {
|
QQC.TextArea {
|
||||||
id: messageInput
|
id: messageInput
|
||||||
|
|
||||||
placeholderText: Qt.platform.os === "osx"
|
placeholderText: qsTr("Type your message here... (%1 to send)").arg(root.sendShortcutText)
|
||||||
? 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
|
||||||
|
wrapMode: TextArea.Wrap
|
||||||
background: Rectangle {
|
background: Rectangle {
|
||||||
radius: 2
|
radius: 2
|
||||||
color: palette.base
|
color: palette.base
|
||||||
@@ -457,6 +497,9 @@ ChatRootView {
|
|||||||
skillCommandPopup.dismiss()
|
skillCommandPopup.dismiss()
|
||||||
event.accepted = true
|
event.accepted = true
|
||||||
}
|
}
|
||||||
|
} else if (root.isSendShortcut(event.key, event.modifiers)) {
|
||||||
|
root.sendChatMessage()
|
||||||
|
event.accepted = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -549,13 +592,21 @@ ChatRootView {
|
|||||||
Layout.preferredHeight: 40
|
Layout.preferredHeight: 40
|
||||||
|
|
||||||
isCompressing: root.isCompressing
|
isCompressing: root.isCompressing
|
||||||
|
isProcessing: root.isRequestInProgress
|
||||||
sendButton.onClicked: !root.isRequestInProgress ? root.sendChatMessage()
|
sendButton.onClicked: !root.isRequestInProgress ? root.sendChatMessage()
|
||||||
: root.cancelRequest()
|
: root.cancelRequest()
|
||||||
sendButton.icon.source: !root.isRequestInProgress ? "qrc:/qt/qml/ChatView/icons/chat-icon.svg"
|
sendButton.icon.source: root.isRequestInProgress
|
||||||
: "qrc:/qt/qml/ChatView/icons/chat-pause-icon.svg"
|
? ""
|
||||||
sendButton.text: !root.isRequestInProgress ? qsTr("Send") : qsTr("Stop")
|
: (root.hasActiveError ? "qrc:/qt/qml/ChatView/icons/warning-icon.svg"
|
||||||
sendButton.ToolTip.text: !root.isRequestInProgress ? qsTr("Send message to LLM %1").arg(Qt.platform.os === "osx" ? "Cmd+Return" : "Ctrl+Return")
|
: "qrc:/qt/qml/ChatView/icons/chat-icon.svg")
|
||||||
: qsTr("Stop")
|
sendButton.text: root.isRequestInProgress ? qsTr("Stop") : qsTr("Send")
|
||||||
|
sendButton.accentColor: (root.hasActiveError && !root.isRequestInProgress)
|
||||||
|
? root.errorColor : "transparent"
|
||||||
|
sendButtonTooltip.text: root.isRequestInProgress
|
||||||
|
? qsTr("Stop")
|
||||||
|
: (root.hasActiveError
|
||||||
|
? root.lastErrorMessage
|
||||||
|
: qsTr("Send message to LLM %1").arg(root.sendShortcutText))
|
||||||
compressButton.onClicked: compressConfirmDialog.open()
|
compressButton.onClicked: compressConfirmDialog.open()
|
||||||
cancelCompressButton.onClicked: root.cancelCompression()
|
cancelCompressButton.onClicked: root.cancelCompression()
|
||||||
syncOpenFiles {
|
syncOpenFiles {
|
||||||
@@ -582,6 +633,27 @@ ChatRootView {
|
|||||||
messageInput.forceActiveFocus()
|
messageInput.forceActiveFocus()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
property Item focusGuard: Window.activeFocusItem
|
||||||
|
onFocusGuardChanged: Qt.callLater(returnFocusToInputIfNeeded)
|
||||||
|
|
||||||
|
function returnFocusToInputIfNeeded() {
|
||||||
|
var item = Window.activeFocusItem
|
||||||
|
if (!item || item === messageInput)
|
||||||
|
return
|
||||||
|
if (item.cursorVisible !== undefined || item.selectByMouse !== undefined)
|
||||||
|
return
|
||||||
|
if (item.popup !== undefined)
|
||||||
|
return
|
||||||
|
var p = item
|
||||||
|
while (p) {
|
||||||
|
if (p === root) {
|
||||||
|
messageInput.forceActiveFocus()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p = p.parent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function applyMentionSelection() {
|
function applyMentionSelection() {
|
||||||
var result = fileMentionPopup.applyCurrentSelection(
|
var result = fileMentionPopup.applyCurrentSelection(
|
||||||
messageInput.text, messageInput.cursorPosition, root.useTools)
|
messageInput.text, messageInput.cursorPosition, root.useTools)
|
||||||
@@ -609,6 +681,7 @@ ChatRootView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function sendChatMessage() {
|
function sendChatMessage() {
|
||||||
|
root.hasActiveError = false
|
||||||
root.sendMessage(fileMentionPopup.expandMentions(messageInput.text))
|
root.sendMessage(fileMentionPopup.expandMentions(messageInput.text))
|
||||||
messageInput.text = ""
|
messageInput.text = ""
|
||||||
fileMentionPopup.clearMentions()
|
fileMentionPopup.clearMentions()
|
||||||
@@ -631,13 +704,122 @@ ChatRootView {
|
|||||||
onAccepted: root.compressCurrentChat()
|
onAccepted: root.compressCurrentChat()
|
||||||
}
|
}
|
||||||
|
|
||||||
Toast {
|
Rectangle {
|
||||||
id: errorToast
|
id: errorBanner
|
||||||
z: 1000
|
|
||||||
|
|
||||||
color: Qt.rgba(0.8, 0.2, 0.2, 0.9)
|
z: 1000
|
||||||
border.color: Qt.darker(infoToast.color, 1.3)
|
visible: root.hasActiveError && root.lastErrorMessage.length > 0
|
||||||
toastTextColor: "#FFFFFF"
|
|
||||||
|
width: parent.width / 2
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.rightMargin: 10
|
||||||
|
anchors.bottomMargin: bottomBar.height + 48
|
||||||
|
|
||||||
|
height: visible ? errorRow.implicitHeight + 12 : 0
|
||||||
|
|
||||||
|
color: Qt.rgba(0.83, 0.18, 0.18, 0.96)
|
||||||
|
radius: 6
|
||||||
|
border.color: Qt.darker(color, 1.3)
|
||||||
|
border.width: 1
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
id: errorRow
|
||||||
|
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
anchors.leftMargin: 10
|
||||||
|
anchors.rightMargin: 6
|
||||||
|
spacing: 8
|
||||||
|
|
||||||
|
TextEdit {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: root.lastErrorMessage
|
||||||
|
color: "#FFFFFF"
|
||||||
|
font.pixelSize: 12
|
||||||
|
wrapMode: TextEdit.Wrap
|
||||||
|
readOnly: true
|
||||||
|
selectByMouse: true
|
||||||
|
selectionColor: Qt.darker(errorBanner.color, 1.3)
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: copyErrorButton
|
||||||
|
|
||||||
|
property bool copied: false
|
||||||
|
|
||||||
|
Layout.alignment: Qt.AlignTop
|
||||||
|
implicitWidth: copyErrorLabel.implicitWidth + 18
|
||||||
|
implicitHeight: 22
|
||||||
|
radius: 4
|
||||||
|
color: copyErrorMouse.containsMouse ? Qt.rgba(1, 1, 1, 0.28)
|
||||||
|
: Qt.rgba(1, 1, 1, 0.16)
|
||||||
|
border.color: Qt.rgba(1, 1, 1, 0.45)
|
||||||
|
border.width: 1
|
||||||
|
|
||||||
|
Behavior on color { ColorAnimation { duration: 120 } }
|
||||||
|
|
||||||
|
Text {
|
||||||
|
id: copyErrorLabel
|
||||||
|
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: copyErrorButton.copied ? qsTr("Copied") : qsTr("Copy")
|
||||||
|
color: "#FFFFFF"
|
||||||
|
font.pixelSize: 12
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: copyErrorMouse
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
root.copyToClipboard(root.lastErrorMessage)
|
||||||
|
copyErrorButton.copied = true
|
||||||
|
copyErrorResetTimer.restart()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: copyErrorResetTimer
|
||||||
|
|
||||||
|
interval: 1500
|
||||||
|
onTriggered: copyErrorButton.copied = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: closeErrorButton
|
||||||
|
|
||||||
|
Layout.alignment: Qt.AlignTop
|
||||||
|
implicitWidth: 22
|
||||||
|
implicitHeight: 22
|
||||||
|
radius: 4
|
||||||
|
color: closeErrorMouse.containsMouse ? Qt.rgba(1, 1, 1, 0.28) : "transparent"
|
||||||
|
border.color: Qt.rgba(1, 1, 1, 0.45)
|
||||||
|
border.width: closeErrorMouse.containsMouse ? 1 : 0
|
||||||
|
|
||||||
|
Behavior on color { ColorAnimation { duration: 120 } }
|
||||||
|
|
||||||
|
Text {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: "✕"
|
||||||
|
color: "#FFFFFF"
|
||||||
|
font.pixelSize: 12
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: closeErrorMouse
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: root.hasActiveError = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Toast {
|
Toast {
|
||||||
@@ -677,7 +859,7 @@ ChatRootView {
|
|||||||
target: root
|
target: root
|
||||||
function onLastErrorMessageChanged() {
|
function onLastErrorMessageChanged() {
|
||||||
if (root.lastErrorMessage.length > 0) {
|
if (root.lastErrorMessage.length > 0) {
|
||||||
errorToast.show(root.lastErrorMessage)
|
root.hasActiveError = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function onLastInfoMessageChanged() {
|
function onLastInfoMessageChanged() {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// Copyright (C) 2024-2026 Petr Mironychev
|
// Copyright (C) 2024-2026 Petr Mironychev
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import ChatView
|
import ChatView
|
||||||
@@ -355,10 +356,9 @@ Rectangle {
|
|||||||
smooth: true
|
smooth: true
|
||||||
mipmap: true
|
mipmap: true
|
||||||
|
|
||||||
BusyIndicator {
|
QoABusyIndicator {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
running: imageDisplay.status === Image.Loading
|
running: imageDisplay.status === Image.Loading
|
||||||
visible: running
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// Copyright (C) 2024-2026 Petr Mironychev
|
// Copyright (C) 2024-2026 Petr Mironychev
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// Copyright (C) 2025-2026 Petr Mironychev
|
// Copyright (C) 2025-2026 Petr Mironychev
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
@@ -193,9 +194,24 @@ Rectangle {
|
|||||||
color: root.statusColor
|
color: root.statusColor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QoAButton {
|
||||||
|
icon {
|
||||||
|
source: "qrc:/qt/qml/ChatView/icons/open-in-code.svg"
|
||||||
|
height: 15
|
||||||
|
width: 15
|
||||||
|
}
|
||||||
|
hoverEnabled: true
|
||||||
|
onClicked: root.openInEditor(editData.edit_id)
|
||||||
|
|
||||||
|
QoAToolTip {
|
||||||
|
visible: parent.hovered
|
||||||
|
delay: 500
|
||||||
|
text: qsTr("Open file in editor and navigate to changes")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
id: headerText
|
id: headerText
|
||||||
Layout.fillWidth: true
|
|
||||||
text: {
|
text: {
|
||||||
var modeText = root.oldContent.length > 0 ? qsTr("Replace") : qsTr("Append")
|
var modeText = root.oldContent.length > 0 ? qsTr("Replace") : qsTr("Append")
|
||||||
if (root.oldContent.length > 0) {
|
if (root.oldContent.length > 0) {
|
||||||
@@ -223,6 +239,19 @@ Rectangle {
|
|||||||
color: palette.mid
|
color: palette.mid
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Item { Layout.fillWidth: true }
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
id: actionButtons
|
||||||
|
|
||||||
|
anchors {
|
||||||
|
right: parent.right
|
||||||
|
rightMargin: 5
|
||||||
|
verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
spacing: 6
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
visible: !root.isPending
|
visible: !root.isPending
|
||||||
Layout.preferredWidth: badgeText.width + 12
|
Layout.preferredWidth: badgeText.width + 12
|
||||||
@@ -239,31 +268,6 @@ Rectangle {
|
|||||||
color: root.isArchived ? Qt.rgba(0.6, 0.6, 0.6, 1.0) : palette.text
|
color: root.isArchived ? Qt.rgba(0.6, 0.6, 0.6, 1.0) : palette.text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Row {
|
|
||||||
id: actionButtons
|
|
||||||
|
|
||||||
anchors {
|
|
||||||
right: parent.right
|
|
||||||
rightMargin: 5
|
|
||||||
verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
spacing: 6
|
|
||||||
|
|
||||||
QoAButton {
|
|
||||||
icon {
|
|
||||||
source: "qrc:/qt/qml/ChatView/icons/open-in-editor.svg"
|
|
||||||
height: 15
|
|
||||||
width: 15
|
|
||||||
}
|
|
||||||
hoverEnabled: true
|
|
||||||
onClicked: root.openInEditor(editData.edit_id)
|
|
||||||
|
|
||||||
ToolTip.visible: hovered
|
|
||||||
ToolTip.text: qsTr("Open file in editor and navigate to changes")
|
|
||||||
ToolTip.delay: 500
|
|
||||||
}
|
|
||||||
|
|
||||||
QoAButton {
|
QoAButton {
|
||||||
icon {
|
icon {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// Copyright (C) 2024-2026 Petr Mironychev
|
// Copyright (C) 2024-2026 Petr Mironychev
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import Qt.labs.platform as Platform
|
import Qt.labs.platform as Platform
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// Copyright (C) 2025-2026 Petr Mironychev
|
// Copyright (C) 2025-2026 Petr Mironychev
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import Qt.labs.platform as Platform
|
import Qt.labs.platform as Platform
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// Copyright (C) 2025-2026 Petr Mironychev
|
// Copyright (C) 2025-2026 Petr Mironychev
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import Qt.labs.platform as Platform
|
import Qt.labs.platform as Platform
|
||||||
|
|||||||
@@ -1,15 +1,17 @@
|
|||||||
// Copyright (C) 2024-2026 Petr Mironychev
|
// Copyright (C) 2024-2026 Petr Mironychev
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
|
import Qt.labs.platform as Platform
|
||||||
import ChatView
|
import ChatView
|
||||||
import UIControls
|
import UIControls
|
||||||
|
|
||||||
Flow {
|
Flow {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property alias attachedFilesModel: attachRepeater.model
|
property alias attachedFilesModel: attachRepeater.model
|
||||||
property color accentColor: palette.mid
|
property color accentColor: palette.mid
|
||||||
property string iconPath
|
property string iconPath
|
||||||
@@ -21,10 +23,10 @@ Flow {
|
|||||||
rightPadding: 5
|
rightPadding: 5
|
||||||
topPadding: attachRepeater.model.length > 0 ? 2 : 0
|
topPadding: attachRepeater.model.length > 0 ? 2 : 0
|
||||||
bottomPadding: attachRepeater.model.length > 0 ? 2 : 0
|
bottomPadding: attachRepeater.model.length > 0 ? 2 : 0
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
id: attachRepeater
|
id: attachRepeater
|
||||||
|
|
||||||
delegate: FileItem {
|
delegate: FileItem {
|
||||||
id: fileItem
|
id: fileItem
|
||||||
|
|
||||||
@@ -32,7 +34,7 @@ Flow {
|
|||||||
required property string modelData
|
required property string modelData
|
||||||
|
|
||||||
filePath: modelData
|
filePath: modelData
|
||||||
|
|
||||||
height: 30
|
height: 30
|
||||||
width: contentRow.width + 10
|
width: contentRow.width + 10
|
||||||
|
|
||||||
@@ -52,7 +54,7 @@ Flow {
|
|||||||
acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton
|
acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton
|
||||||
onClicked: (mouse) => {
|
onClicked: (mouse) => {
|
||||||
if (mouse.button === Qt.RightButton) {
|
if (mouse.button === Qt.RightButton) {
|
||||||
contextMenu.popup()
|
contextMenu.open()
|
||||||
} else if (mouse.button === Qt.MiddleButton ||
|
} else if (mouse.button === Qt.MiddleButton ||
|
||||||
(mouse.button === Qt.LeftButton && (mouse.modifiers & Qt.ControlModifier))) {
|
(mouse.button === Qt.LeftButton && (mouse.modifiers & Qt.ControlModifier))) {
|
||||||
root.removeFileFromListByIndex(fileItem.index)
|
root.removeFileFromListByIndex(fileItem.index)
|
||||||
@@ -70,27 +72,27 @@ Flow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Menu {
|
Platform.Menu {
|
||||||
id: contextMenu
|
id: contextMenu
|
||||||
|
|
||||||
MenuItem {
|
Platform.MenuItem {
|
||||||
text: "Open in Qt Creator"
|
text: qsTr("Open in Qt Creator")
|
||||||
onTriggered: fileItem.openFileInEditor()
|
onTriggered: fileItem.openFileInEditor()
|
||||||
}
|
}
|
||||||
|
|
||||||
MenuItem {
|
Platform.MenuItem {
|
||||||
text: "Open in External Editor"
|
text: qsTr("Open in External Editor")
|
||||||
onTriggered: fileItem.openFileInExternalEditor()
|
onTriggered: fileItem.openFileInExternalEditor()
|
||||||
}
|
}
|
||||||
|
|
||||||
MenuSeparator {}
|
Platform.MenuSeparator {}
|
||||||
|
|
||||||
MenuItem {
|
Platform.MenuItem {
|
||||||
text: "Remove"
|
text: qsTr("Remove")
|
||||||
onTriggered: root.removeFileFromListByIndex(fileItem.index)
|
onTriggered: root.removeFileFromListByIndex(fileItem.index)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
id: contentRow
|
id: contentRow
|
||||||
|
|
||||||
@@ -107,31 +109,31 @@ Flow {
|
|||||||
sourceSize.width: 8
|
sourceSize.width: 8
|
||||||
sourceSize.height: 15
|
sourceSize.height: 15
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
id: fileNameText
|
id: fileNameText
|
||||||
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
color: palette.buttonText
|
color: palette.buttonText
|
||||||
|
|
||||||
text: {
|
text: {
|
||||||
const parts = modelData.split('/');
|
const parts = modelData.split('/');
|
||||||
return parts[parts.length - 1];
|
return parts[parts.length - 1];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
id: closeButton
|
id: closeButton
|
||||||
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
width: closeIcon.width + 5
|
width: closeIcon.width + 5
|
||||||
height: closeButton.width + 5
|
height: closeButton.width + 5
|
||||||
|
|
||||||
onClicked: root.removeFileFromListByIndex(index)
|
onClicked: root.removeFileFromListByIndex(index)
|
||||||
|
|
||||||
Image {
|
Image {
|
||||||
id: closeIcon
|
id: closeIcon
|
||||||
|
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
source: palette.window.hslLightness > 0.5 ? "qrc:/qt/qml/ChatView/icons/close-dark.svg"
|
source: palette.window.hslLightness > 0.5 ? "qrc:/qt/qml/ChatView/icons/close-dark.svg"
|
||||||
: "qrc:/qt/qml/ChatView/icons/close-light.svg"
|
: "qrc:/qt/qml/ChatView/icons/close-light.svg"
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// Copyright (C) 2024-2026 Petr Mironychev
|
// Copyright (C) 2024-2026 Petr Mironychev
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
@@ -19,6 +20,8 @@ Rectangle {
|
|||||||
property alias cancelCompressButton: cancelCompressButtonId
|
property alias cancelCompressButton: cancelCompressButtonId
|
||||||
|
|
||||||
property bool isCompressing: false
|
property bool isCompressing: false
|
||||||
|
property bool isProcessing: false
|
||||||
|
property alias sendButtonTooltip: sendButtonTooltipId
|
||||||
|
|
||||||
color: palette.window.hslLightness > 0.5 ?
|
color: palette.window.hslLightness > 0.5 ?
|
||||||
Qt.darker(palette.window, 1.1) :
|
Qt.darker(palette.window, 1.1) :
|
||||||
@@ -45,9 +48,12 @@ Rectangle {
|
|||||||
height: 15
|
height: 15
|
||||||
width: 8
|
width: 8
|
||||||
}
|
}
|
||||||
ToolTip.visible: hovered
|
|
||||||
ToolTip.delay: 250
|
QoAToolTip {
|
||||||
ToolTip.text: qsTr("Attach file to message")
|
visible: attachFilesId.hovered
|
||||||
|
delay: 250
|
||||||
|
text: qsTr("Attach file to message")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QoAButton {
|
QoAButton {
|
||||||
@@ -58,9 +64,12 @@ Rectangle {
|
|||||||
height: 15
|
height: 15
|
||||||
width: 15
|
width: 15
|
||||||
}
|
}
|
||||||
ToolTip.visible: hovered
|
|
||||||
ToolTip.delay: 250
|
QoAToolTip {
|
||||||
ToolTip.text: qsTr("Attach image to message")
|
visible: attachImagesId.hovered
|
||||||
|
delay: 250
|
||||||
|
text: qsTr("Attach image to message")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QoAButton {
|
QoAButton {
|
||||||
@@ -71,9 +80,12 @@ Rectangle {
|
|||||||
height: 15
|
height: 15
|
||||||
width: 8
|
width: 8
|
||||||
}
|
}
|
||||||
ToolTip.visible: hovered
|
|
||||||
ToolTip.delay: 250
|
QoAToolTip {
|
||||||
ToolTip.text: qsTr("Link file to context")
|
visible: linkFilesId.hovered
|
||||||
|
delay: 250
|
||||||
|
text: qsTr("Link file to context")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CheckBox {
|
CheckBox {
|
||||||
@@ -81,8 +93,10 @@ Rectangle {
|
|||||||
|
|
||||||
text: qsTr("Sync open files")
|
text: qsTr("Sync open files")
|
||||||
|
|
||||||
ToolTip.visible: syncOpenFilesId.hovered
|
QoAToolTip {
|
||||||
ToolTip.text: qsTr("Automatically synchronize currently opened files with the model context")
|
visible: syncOpenFilesId.hovered
|
||||||
|
text: qsTr("Automatically synchronize currently opened files with the model context")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
@@ -95,7 +109,7 @@ Rectangle {
|
|||||||
visible: root.isCompressing
|
visible: root.isCompressing
|
||||||
spacing: 6
|
spacing: 6
|
||||||
|
|
||||||
BusyIndicator {
|
QoABusyIndicator {
|
||||||
id: compressBusyIndicator
|
id: compressBusyIndicator
|
||||||
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
@@ -117,9 +131,11 @@ Rectangle {
|
|||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
text: qsTr("Cancel")
|
text: qsTr("Cancel")
|
||||||
|
|
||||||
ToolTip.visible: hovered
|
QoAToolTip {
|
||||||
ToolTip.delay: 250
|
visible: cancelCompressButtonId.hovered
|
||||||
ToolTip.text: qsTr("Cancel compression")
|
delay: 250
|
||||||
|
text: qsTr("Cancel compression")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,20 +150,41 @@ Rectangle {
|
|||||||
height: 15
|
height: 15
|
||||||
width: 15
|
width: 15
|
||||||
}
|
}
|
||||||
ToolTip.visible: hovered
|
|
||||||
ToolTip.delay: 250
|
QoAToolTip {
|
||||||
ToolTip.text: qsTr("Compress chat (create summarized copy using LLM)")
|
visible: compressButtonId.hovered
|
||||||
|
delay: 250
|
||||||
|
text: qsTr("Compress chat (create summarized copy using LLM)")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QoAButton {
|
QoAButton {
|
||||||
id: sendButtonId
|
id: sendButtonId
|
||||||
|
|
||||||
|
leftPadding: root.isProcessing ? 22 : 4
|
||||||
|
|
||||||
icon {
|
icon {
|
||||||
height: 15
|
height: 15
|
||||||
width: 15
|
width: 15
|
||||||
}
|
}
|
||||||
ToolTip.visible: hovered
|
|
||||||
ToolTip.delay: 250
|
QoABusyIndicator {
|
||||||
|
id: sendBusyIndicator
|
||||||
|
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: 5
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
width: 14
|
||||||
|
height: 14
|
||||||
|
running: root.isProcessing
|
||||||
|
}
|
||||||
|
|
||||||
|
QoAToolTip {
|
||||||
|
id: sendButtonTooltipId
|
||||||
|
|
||||||
|
visible: sendButtonId.hovered
|
||||||
|
delay: 250
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// Copyright (C) 2025-2026 Petr Mironychev
|
// Copyright (C) 2025-2026 Petr Mironychev
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// Copyright (C) 2025-2026 Petr Mironychev
|
// Copyright (C) 2025-2026 Petr Mironychev
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
@@ -117,12 +118,14 @@ Rectangle {
|
|||||||
text: root.hasPendingEdits
|
text: root.hasPendingEdits
|
||||||
? qsTr("Apply All (%1)").arg(root.pendingEdits + root.rejectedEdits)
|
? qsTr("Apply All (%1)").arg(root.pendingEdits + root.rejectedEdits)
|
||||||
: qsTr("Reapply All (%1)").arg(root.rejectedEdits)
|
: qsTr("Reapply All (%1)").arg(root.rejectedEdits)
|
||||||
|
|
||||||
ToolTip.visible: hovered
|
QoAToolTip {
|
||||||
ToolTip.delay: 250
|
visible: applyAllButton.hovered
|
||||||
ToolTip.text: root.hasPendingEdits
|
delay: 250
|
||||||
? qsTr("Apply all pending and rejected edits in this message")
|
text: root.hasPendingEdits
|
||||||
: qsTr("Reapply all rejected edits in this message")
|
? qsTr("Apply all pending and rejected edits in this message")
|
||||||
|
: qsTr("Reapply all rejected edits in this message")
|
||||||
|
}
|
||||||
|
|
||||||
onClicked: root.applyAllClicked()
|
onClicked: root.applyAllClicked()
|
||||||
}
|
}
|
||||||
@@ -133,10 +136,12 @@ Rectangle {
|
|||||||
visible: root.hasAppliedEdits
|
visible: root.hasAppliedEdits
|
||||||
enabled: root.hasAppliedEdits
|
enabled: root.hasAppliedEdits
|
||||||
text: qsTr("Undo All (%1)").arg(root.appliedEdits)
|
text: qsTr("Undo All (%1)").arg(root.appliedEdits)
|
||||||
|
|
||||||
ToolTip.visible: hovered
|
QoAToolTip {
|
||||||
ToolTip.delay: 250
|
visible: undoAllButton.hovered
|
||||||
ToolTip.text: qsTr("Undo all applied edits in this message")
|
delay: 250
|
||||||
|
text: qsTr("Undo all applied edits in this message")
|
||||||
|
}
|
||||||
|
|
||||||
onClicked: root.undoAllClicked()
|
onClicked: root.undoAllClicked()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// Copyright (C) 2026 Petr Mironychev
|
// Copyright (C) 2026 Petr Mironychev
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
|
|||||||
188
ChatView/qml/controls/MessageNavigator.qml
Normal file
188
ChatView/qml/controls/MessageNavigator.qml
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
// Copyright (C) 2024-2026 Petr Mironychev
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import ChatView
|
||||||
|
import UIControls
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: nav
|
||||||
|
|
||||||
|
property var chatModel
|
||||||
|
property var entries: []
|
||||||
|
property color dotColor: "#92BD6C"
|
||||||
|
property int currentMessageIndex: -1
|
||||||
|
|
||||||
|
readonly property int dotCount: entries.length
|
||||||
|
readonly property int verticalPadding: 8
|
||||||
|
readonly property int minDotSpacing: 18
|
||||||
|
readonly property real availableHeight: Math.max(0, height - 2 * verticalPadding)
|
||||||
|
readonly property real naturalHeight: dotCount > 1 ? (dotCount - 1) * minDotSpacing : 0
|
||||||
|
readonly property bool needsScrolling: naturalHeight > availableHeight
|
||||||
|
readonly property real contentHeight: needsScrolling
|
||||||
|
? naturalHeight + 2 * verticalPadding
|
||||||
|
: Math.max(height, 2 * verticalPadding)
|
||||||
|
|
||||||
|
signal messageClicked(int messageIndex)
|
||||||
|
|
||||||
|
implicitWidth: 16
|
||||||
|
|
||||||
|
function rebuild() {
|
||||||
|
entries = chatModel ? chatModel.userMessagePreviews(80) : []
|
||||||
|
Qt.callLater(scrollCurrentIntoView)
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateCurrentFromModelIndex(modelIdx) {
|
||||||
|
if (modelIdx < 0) {
|
||||||
|
currentMessageIndex = -1
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let best = -1
|
||||||
|
for (let i = 0; i < entries.length; ++i) {
|
||||||
|
const e = entries[i]
|
||||||
|
if (!e)
|
||||||
|
continue
|
||||||
|
const mi = e.messageIndex
|
||||||
|
if (mi <= modelIdx)
|
||||||
|
best = mi
|
||||||
|
else
|
||||||
|
break
|
||||||
|
}
|
||||||
|
currentMessageIndex = best
|
||||||
|
}
|
||||||
|
|
||||||
|
function uiIndexOf(messageIndex) {
|
||||||
|
for (let i = 0; i < entries.length; ++i) {
|
||||||
|
const e = entries[i]
|
||||||
|
if (e && e.messageIndex === messageIndex)
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
function dotCenterY(uiIndex) {
|
||||||
|
const count = dotCount
|
||||||
|
if (count <= 1)
|
||||||
|
return contentHeight / 2
|
||||||
|
const spacing = needsScrolling
|
||||||
|
? minDotSpacing
|
||||||
|
: availableHeight / (count - 1)
|
||||||
|
return verticalPadding + spacing * uiIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
function scrollCurrentIntoView() {
|
||||||
|
if (!needsScrolling || currentMessageIndex < 0)
|
||||||
|
return
|
||||||
|
const ui = uiIndexOf(currentMessageIndex)
|
||||||
|
if (ui < 0)
|
||||||
|
return
|
||||||
|
const y = dotCenterY(ui)
|
||||||
|
const margin = 24
|
||||||
|
if (y < flick.contentY + margin)
|
||||||
|
flick.contentY = Math.max(0, y - margin)
|
||||||
|
else if (y > flick.contentY + flick.height - margin)
|
||||||
|
flick.contentY = Math.min(
|
||||||
|
Math.max(0, flick.contentHeight - flick.height),
|
||||||
|
y - flick.height + margin)
|
||||||
|
}
|
||||||
|
|
||||||
|
onChatModelChanged: rebuild()
|
||||||
|
onCurrentMessageIndexChanged: scrollCurrentIntoView()
|
||||||
|
Component.onCompleted: rebuild()
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: nav.chatModel
|
||||||
|
ignoreUnknownSignals: true
|
||||||
|
function onRowsInserted() { nav.rebuild() }
|
||||||
|
function onRowsRemoved() { nav.rebuild() }
|
||||||
|
function onModelReset() { nav.rebuild() }
|
||||||
|
function onModelReseted() { nav.rebuild() }
|
||||||
|
function onDataChanged() { nav.rebuild() }
|
||||||
|
}
|
||||||
|
|
||||||
|
Flickable {
|
||||||
|
id: flick
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
contentWidth: width
|
||||||
|
contentHeight: nav.contentHeight
|
||||||
|
interactive: nav.needsScrolling
|
||||||
|
clip: true
|
||||||
|
boundsBehavior: Flickable.StopAtBounds
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: spine
|
||||||
|
|
||||||
|
visible: nav.dotCount > 1
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
y: nav.verticalPadding
|
||||||
|
width: 1
|
||||||
|
height: Math.max(0, flick.contentHeight - 2 * nav.verticalPadding)
|
||||||
|
color: palette.mid
|
||||||
|
opacity: 0.4
|
||||||
|
}
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: nav.entries
|
||||||
|
|
||||||
|
delegate: Item {
|
||||||
|
id: dotItem
|
||||||
|
|
||||||
|
required property var modelData
|
||||||
|
required property int index
|
||||||
|
|
||||||
|
readonly property int msgIndex: modelData && modelData.messageIndex !== undefined
|
||||||
|
? modelData.messageIndex : -1
|
||||||
|
readonly property string preview: modelData && modelData.preview !== undefined
|
||||||
|
? modelData.preview : ""
|
||||||
|
readonly property bool isCurrent: nav.currentMessageIndex === msgIndex
|
||||||
|
|
||||||
|
width: 16
|
||||||
|
height: 14
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
y: nav.dotCenterY(index) - height / 2
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: dot
|
||||||
|
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: dotItem.isCurrent ? 11 : (dotArea.containsMouse ? 10 : 7)
|
||||||
|
height: width
|
||||||
|
radius: width / 2
|
||||||
|
color: dotArea.containsMouse
|
||||||
|
? Qt.lighter(nav.dotColor, 1.2)
|
||||||
|
: nav.dotColor
|
||||||
|
border.color: dotItem.isCurrent
|
||||||
|
? Qt.darker(nav.dotColor, 1.7)
|
||||||
|
: Qt.darker(nav.dotColor, 1.4)
|
||||||
|
border.width: dotItem.isCurrent ? 2 : 1
|
||||||
|
opacity: dotItem.isCurrent || dotArea.containsMouse ? 1.0 : 0.55
|
||||||
|
|
||||||
|
Behavior on width { NumberAnimation { duration: 120 } }
|
||||||
|
Behavior on opacity { NumberAnimation { duration: 120 } }
|
||||||
|
Behavior on color { ColorAnimation { duration: 120 } }
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: dotArea
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: nav.messageClicked(dotItem.msgIndex)
|
||||||
|
|
||||||
|
QoAToolTip {
|
||||||
|
visible: dotArea.containsMouse
|
||||||
|
delay: 350
|
||||||
|
text: dotItem.preview.length > 0
|
||||||
|
? qsTr("#%1 · %2").arg(dotItem.index + 1).arg(dotItem.preview)
|
||||||
|
: qsTr("Jump to message #%1").arg(dotItem.index + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
// Copyright (C) 2026 Petr Mironychev
|
// Copyright (C) 2026 Petr Mironychev
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// Copyright (C) 2025-2026 Petr Mironychev
|
// Copyright (C) 2025-2026 Petr Mironychev
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// Copyright (C) 2025-2026 Petr Mironychev
|
// Copyright (C) 2025-2026 Petr Mironychev
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// Copyright (C) 2024-2026 Petr Mironychev
|
// Copyright (C) 2024-2026 Petr Mironychev
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
@@ -10,9 +11,12 @@ import UIControls
|
|||||||
Rectangle {
|
Rectangle {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
|
property bool isInEditor: false
|
||||||
|
|
||||||
property alias saveButton: saveButtonId
|
property alias saveButton: saveButtonId
|
||||||
property alias loadButton: loadButtonId
|
property alias loadButton: loadButtonId
|
||||||
property alias clearButton: clearButtonId
|
property alias clearButton: clearButtonId
|
||||||
|
property alias newChatButton: newChatButtonId
|
||||||
property alias tokensBadge: tokensBadgeId
|
property alias tokensBadge: tokensBadgeId
|
||||||
property alias recentPath: recentPathId
|
property alias recentPath: recentPathId
|
||||||
property alias openChatHistory: openChatHistoryId
|
property alias openChatHistory: openChatHistoryId
|
||||||
@@ -24,6 +28,7 @@ Rectangle {
|
|||||||
property alias settingsButton: settingsButtonId
|
property alias settingsButton: settingsButtonId
|
||||||
property alias configSelector: configSelectorId
|
property alias configSelector: configSelectorId
|
||||||
property alias roleSelector: roleSelector
|
property alias roleSelector: roleSelector
|
||||||
|
property alias relocateTooltip: relocateTooltipId
|
||||||
|
|
||||||
color: palette.window.hslLightness > 0.5 ?
|
color: palette.window.hslLightness > 0.5 ?
|
||||||
Qt.darker(palette.window, 1.1) :
|
Qt.darker(palette.window, 1.1) :
|
||||||
@@ -56,10 +61,13 @@ Rectangle {
|
|||||||
height: 15
|
height: 15
|
||||||
width: 15
|
width: 15
|
||||||
}
|
}
|
||||||
ToolTip.visible: hovered
|
|
||||||
ToolTip.delay: 250
|
QoAToolTip {
|
||||||
ToolTip.text: checked ? qsTr("Unpin chat window")
|
visible: pinButtonId.hovered
|
||||||
: qsTr("Pin chat window to the top")
|
delay: 250
|
||||||
|
text: pinButtonId.checked ? qsTr("Unpin chat window")
|
||||||
|
: qsTr("Pin chat window to the top")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QoAButton {
|
QoAButton {
|
||||||
@@ -73,8 +81,56 @@ Rectangle {
|
|||||||
height: 15
|
height: 15
|
||||||
width: 15
|
width: 15
|
||||||
}
|
}
|
||||||
ToolTip.visible: hovered
|
|
||||||
ToolTip.delay: 250
|
QoAToolTip {
|
||||||
|
id: relocateTooltipId
|
||||||
|
|
||||||
|
visible: relocateButtonId.hovered
|
||||||
|
delay: 250
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QoASeparator {
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
QoAButton {
|
||||||
|
id: clearButtonId
|
||||||
|
|
||||||
|
icon {
|
||||||
|
source: "qrc:/qt/qml/ChatView/icons/clean-icon-dark.svg"
|
||||||
|
height: 15
|
||||||
|
width: 8
|
||||||
|
}
|
||||||
|
|
||||||
|
QoAToolTip {
|
||||||
|
visible: clearButtonId.hovered
|
||||||
|
delay: 250
|
||||||
|
text: qsTr("Clean chat")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QoASeparator {
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
QoAButton {
|
||||||
|
id: newChatButtonId
|
||||||
|
|
||||||
|
visible: root.isInEditor
|
||||||
|
|
||||||
|
icon {
|
||||||
|
source: "qrc:/qt/qml/ChatView/icons/new-chat-icon.svg"
|
||||||
|
color: palette.window.hslLightness > 0.5 ? "#000000" : "#FFFFFF"
|
||||||
|
height: 15
|
||||||
|
width: 15
|
||||||
|
}
|
||||||
|
|
||||||
|
QoAToolTip {
|
||||||
|
visible: newChatButtonId.hovered
|
||||||
|
delay: 250
|
||||||
|
text: qsTr("Open new chat in a new tab")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QoAComboBox {
|
QoAComboBox {
|
||||||
@@ -85,9 +141,11 @@ Rectangle {
|
|||||||
model: []
|
model: []
|
||||||
currentIndex: 0
|
currentIndex: 0
|
||||||
|
|
||||||
ToolTip.visible: hovered
|
QoAToolTip {
|
||||||
ToolTip.delay: 250
|
visible: configSelectorId.hovered
|
||||||
ToolTip.text: qsTr("Switch saved AI configuration")
|
delay: 250
|
||||||
|
text: qsTr("Switch saved AI configuration")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QoAComboBox {
|
QoAComboBox {
|
||||||
@@ -98,9 +156,11 @@ Rectangle {
|
|||||||
model: []
|
model: []
|
||||||
currentIndex: 0
|
currentIndex: 0
|
||||||
|
|
||||||
ToolTip.visible: hovered
|
QoAToolTip {
|
||||||
ToolTip.delay: 250
|
visible: roleSelector.hovered
|
||||||
ToolTip.text: qsTr("Switch agent role (different system prompts)")
|
delay: 250
|
||||||
|
text: qsTr("Switch agent role (different system prompts)")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,15 +183,17 @@ Rectangle {
|
|||||||
width: 15
|
width: 15
|
||||||
}
|
}
|
||||||
|
|
||||||
ToolTip.visible: hovered
|
QoAToolTip {
|
||||||
ToolTip.delay: 250
|
visible: toolsButtonId.hovered
|
||||||
ToolTip.text: {
|
delay: 250
|
||||||
if (!toolsButtonId.enabled) {
|
text: {
|
||||||
return qsTr("Tools are disabled in General Settings")
|
if (!toolsButtonId.enabled) {
|
||||||
|
return qsTr("Tools are disabled in General Settings")
|
||||||
|
}
|
||||||
|
return toolsButtonId.checked
|
||||||
|
? qsTr("Tools enabled: AI can use tools to read files, search project, and build code")
|
||||||
|
: qsTr("Tools disabled: Simple conversation without tool access")
|
||||||
}
|
}
|
||||||
return checked
|
|
||||||
? qsTr("Tools enabled: AI can use tools to read files, search project, and build code")
|
|
||||||
: qsTr("Tools disabled: Simple conversation without tool access")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,11 +213,14 @@ Rectangle {
|
|||||||
width: 15
|
width: 15
|
||||||
}
|
}
|
||||||
|
|
||||||
ToolTip.visible: hovered
|
QoAToolTip {
|
||||||
ToolTip.delay: 250
|
visible: thinkingModeId.hovered
|
||||||
ToolTip.text: enabled ? (checked ? qsTr("Thinking Mode enabled (Check model list support it)")
|
delay: 250
|
||||||
: qsTr("Thinking Mode disabled"))
|
text: thinkingModeId.enabled
|
||||||
: qsTr("Thinking Mode is not available for this provider")
|
? (thinkingModeId.checked ? qsTr("Thinking Mode enabled (Check model list support it)")
|
||||||
|
: qsTr("Thinking Mode disabled"))
|
||||||
|
: qsTr("Thinking Mode is not available for this provider")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QoAButton {
|
QoAButton {
|
||||||
@@ -170,9 +235,11 @@ Rectangle {
|
|||||||
width: 15
|
width: 15
|
||||||
}
|
}
|
||||||
|
|
||||||
ToolTip.visible: hovered
|
QoAToolTip {
|
||||||
ToolTip.delay: 250
|
visible: settingsButtonId.hovered
|
||||||
ToolTip.text: qsTr("Open Chat Assistant Settings")
|
delay: 250
|
||||||
|
text: qsTr("Open Chat Assistant Settings")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QoASeparator {
|
QoASeparator {
|
||||||
@@ -198,9 +265,11 @@ Rectangle {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
|
|
||||||
ToolTip.visible: containsMouse
|
QoAToolTip {
|
||||||
ToolTip.delay: 500
|
visible: parent.containsMouse && recentPathId.text.length > 0
|
||||||
ToolTip.text: recentPathId.text
|
text: recentPathId.text
|
||||||
|
delay: 500
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -221,9 +290,12 @@ Rectangle {
|
|||||||
height: 15
|
height: 15
|
||||||
width: 8
|
width: 8
|
||||||
}
|
}
|
||||||
ToolTip.visible: hovered
|
|
||||||
ToolTip.delay: 250
|
QoAToolTip {
|
||||||
ToolTip.text: qsTr("Save chat to *.json file")
|
visible: saveButtonId.hovered
|
||||||
|
delay: 250
|
||||||
|
text: qsTr("Save chat to *.json file")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QoAButton {
|
QoAButton {
|
||||||
@@ -234,9 +306,12 @@ Rectangle {
|
|||||||
height: 15
|
height: 15
|
||||||
width: 8
|
width: 8
|
||||||
}
|
}
|
||||||
ToolTip.visible: hovered
|
|
||||||
ToolTip.delay: 250
|
QoAToolTip {
|
||||||
ToolTip.text: qsTr("Load chat from *.json file")
|
visible: loadButtonId.hovered
|
||||||
|
delay: 250
|
||||||
|
text: qsTr("Load chat from *.json file")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QoAButton {
|
QoAButton {
|
||||||
@@ -247,9 +322,12 @@ Rectangle {
|
|||||||
height: 15
|
height: 15
|
||||||
width: 15
|
width: 15
|
||||||
}
|
}
|
||||||
ToolTip.visible: hovered
|
|
||||||
ToolTip.delay: 250
|
QoAToolTip {
|
||||||
ToolTip.text: qsTr("Show in system")
|
visible: openChatHistoryId.hovered
|
||||||
|
delay: 250
|
||||||
|
text: qsTr("Show in system")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QoASeparator {}
|
QoASeparator {}
|
||||||
@@ -264,9 +342,11 @@ Rectangle {
|
|||||||
width: 15
|
width: 15
|
||||||
}
|
}
|
||||||
|
|
||||||
ToolTip.visible: hovered
|
QoAToolTip {
|
||||||
ToolTip.delay: 250
|
visible: contextButtonId.hovered
|
||||||
ToolTip.text: qsTr("View chat context (system prompt, role, rules)")
|
delay: 250
|
||||||
|
text: qsTr("View chat context (system prompt, role, rules)")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Badge {
|
Badge {
|
||||||
@@ -276,21 +356,6 @@ Rectangle {
|
|||||||
ToolTip.delay: 250
|
ToolTip.delay: 250
|
||||||
ToolTip.text: qsTr("Current amount tokens in chat and LLM limit threshold")
|
ToolTip.text: qsTr("Current amount tokens in chat and LLM limit threshold")
|
||||||
}
|
}
|
||||||
|
|
||||||
QoASeparator {}
|
|
||||||
|
|
||||||
QoAButton {
|
|
||||||
id: clearButtonId
|
|
||||||
|
|
||||||
icon {
|
|
||||||
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")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
// Copyright (C) 2024-2026 Petr Mironychev
|
// Copyright (C) 2024-2026 Petr Mironychev
|
||||||
// Copyright (C) 2025 Povilas Kanapickas <povilas@radix.lt>
|
// Copyright (C) 2025 Povilas Kanapickas <povilas@radix.lt>
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
#include "CodeHandler.hpp"
|
#include "CodeHandler.hpp"
|
||||||
#include <settings/CodeCompletionSettings.hpp>
|
#include <settings/CodeCompletionSettings.hpp>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// Copyright (C) 2024-2026 Petr Mironychev
|
// Copyright (C) 2024-2026 Petr Mironychev
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// Copyright (C) 2025-2026 Petr Mironychev
|
// Copyright (C) 2025-2026 Petr Mironychev
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
#include "ConfigurationManager.hpp"
|
#include "ConfigurationManager.hpp"
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// Copyright (C) 2025-2026 Petr Mironychev
|
// Copyright (C) 2025-2026 Petr Mironychev
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
|||||||
21
LICENSE
21
LICENSE
@@ -1,3 +1,24 @@
|
|||||||
|
===============================================================
|
||||||
|
ADDITIONAL TERMS UNDER GPLv3 SECTION 7(b)
|
||||||
|
===============================================================
|
||||||
|
|
||||||
|
In accordance with Section 7(b) of the GNU General Public License v3.0,
|
||||||
|
the following additional attribution term applies to QodeAssist:
|
||||||
|
|
||||||
|
You must preserve all author attributions, copyright notices, and the
|
||||||
|
project name "QodeAssist" in all copies and modified versions,
|
||||||
|
including in source file headers, the plugin metadata
|
||||||
|
(QodeAssist.json.in), and the About dialog or equivalent user-facing
|
||||||
|
identification. Modified versions must be clearly marked as different
|
||||||
|
from the original.
|
||||||
|
|
||||||
|
This is a reasonable attribution requirement permitted under GPLv3
|
||||||
|
§7(b) and §7(c). It supplements the notice-preservation obligations of
|
||||||
|
§4 and §5.
|
||||||
|
|
||||||
|
Copyright (C) 2024-2026 Petr Mironychev
|
||||||
|
===============================================================
|
||||||
|
|
||||||
GNU GENERAL PUBLIC LICENSE
|
GNU GENERAL PUBLIC LICENSE
|
||||||
Version 3, 29 June 2007
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// Copyright (C) 2024-2026 Petr Mironychev
|
// Copyright (C) 2024-2026 Petr Mironychev
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
#include "LLMClientInterface.hpp"
|
#include "LLMClientInterface.hpp"
|
||||||
|
|
||||||
@@ -353,6 +354,9 @@ void LLMClientInterface::handleCompletion(const QJsonObject &request)
|
|||||||
&LLMClientInterface::handleRequestFailed,
|
&LLMClientInterface::handleRequestFailed,
|
||||||
Qt::UniqueConnection);
|
Qt::UniqueConnection);
|
||||||
|
|
||||||
|
provider->client()->setTransferTimeout(
|
||||||
|
static_cast<int>(m_generalSettings.requestTimeout() * 1000));
|
||||||
|
|
||||||
auto requestId
|
auto requestId
|
||||||
= provider->sendRequest(QUrl(url), payload, resolveEndpoint(promptTemplate, isPreset1Active));
|
= provider->sendRequest(QUrl(url), payload, resolveEndpoint(promptTemplate, isPreset1Active));
|
||||||
m_activeRequests[requestId] = {request, provider};
|
m_activeRequests[requestId] = {request, provider};
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// Copyright (C) 2024-2026 Petr Mironychev
|
// Copyright (C) 2024-2026 Petr Mironychev
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
// Copyright (C) 2023 The Qt Company Ltd.
|
// Copyright (C) 2023 The Qt Company Ltd.
|
||||||
// Copyright (C) 2024-2026 Petr Mironychev
|
// Copyright (C) 2024-2026 Petr Mironychev
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
#include "LLMSuggestion.hpp"
|
#include "LLMSuggestion.hpp"
|
||||||
#include <texteditor/texteditor.h>
|
#include <texteditor/texteditor.h>
|
||||||
|
|||||||
@@ -20,6 +20,8 @@
|
|||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|||||||
@@ -20,6 +20,8 @@
|
|||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"Id" : "qodeassist",
|
"Id" : "qodeassist",
|
||||||
"Name" : "QodeAssist",
|
"Name" : "QodeAssist",
|
||||||
"Version" : "0.9.14",
|
"Version" : "0.9.20",
|
||||||
"CompatVersion" : "${IDE_VERSION}",
|
"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 with additional attribution terms (§7b) — see LICENSE",
|
||||||
"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" : "https://github.com/Palm1r/QodeAssist",
|
||||||
|
|||||||
@@ -20,6 +20,8 @@
|
|||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "QodeAssistClient.hpp"
|
#include "QodeAssistClient.hpp"
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
// Copyright (C) 2023 The Qt Company Ltd.
|
// Copyright (C) 2023 The Qt Company Ltd.
|
||||||
// Copyright (C) 2024-2026 Petr Mironychev
|
// Copyright (C) 2024-2026 Petr Mironychev
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// Copyright (C) 2024-2026 Petr Mironychev
|
// Copyright (C) 2024-2026 Petr Mironychev
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
@@ -16,6 +17,7 @@ const char QODE_ASSIST_CHAT_EDITOR_ID[] = "QodeAssist.ChatEditor";
|
|||||||
|
|
||||||
const char QODE_ASSIST_SHOW_CHAT_ACTION[] = "QodeAssist.ShowChatView";
|
const char QODE_ASSIST_SHOW_CHAT_ACTION[] = "QodeAssist.ShowChatView";
|
||||||
const char QODE_ASSIST_OPEN_CHAT_WINDOW_ACTION[] = "QodeAssist.OpenChatWindow";
|
const char QODE_ASSIST_OPEN_CHAT_WINDOW_ACTION[] = "QodeAssist.OpenChatWindow";
|
||||||
|
const char QODE_ASSIST_NEW_CHAT_ACTION[] = "QodeAssist.NewChat";
|
||||||
|
|
||||||
const char QODE_ASSIST_CHAT_SEND_MESSAGE[] = "QodeAssist.Chat.SendMessage";
|
const char QODE_ASSIST_CHAT_SEND_MESSAGE[] = "QodeAssist.Chat.SendMessage";
|
||||||
const char QODE_ASSIST_CHAT_CLEAR_SESSION[] = "QodeAssist.Chat.ClearSession";
|
const char QODE_ASSIST_CHAT_CLEAR_SESSION[] = "QodeAssist.Chat.ClearSession";
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// Copyright (C) 2024-2026 Petr Mironychev
|
// Copyright (C) 2024-2026 Petr Mironychev
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// Copyright (C) 2025-2026 Petr Mironychev
|
// Copyright (C) 2025-2026 Petr Mironychev
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
#include "QuickRefactorHandler.hpp"
|
#include "QuickRefactorHandler.hpp"
|
||||||
|
|
||||||
@@ -143,6 +144,9 @@ void QuickRefactorHandler::prepareAndSendRequest(
|
|||||||
provider->client()->setMaxToolContinuations(
|
provider->client()->setMaxToolContinuations(
|
||||||
Settings::toolsSettings().maxToolContinuations());
|
Settings::toolsSettings().maxToolContinuations());
|
||||||
|
|
||||||
|
provider->client()->setTransferTimeout(
|
||||||
|
static_cast<int>(Settings::generalSettings().requestTimeout() * 1000));
|
||||||
|
|
||||||
m_isRefactoringInProgress = true;
|
m_isRefactoringInProgress = true;
|
||||||
|
|
||||||
connect(
|
connect(
|
||||||
@@ -385,25 +389,25 @@ void QuickRefactorHandler::handleLLMResponse(
|
|||||||
|
|
||||||
void QuickRefactorHandler::cancelRequest()
|
void QuickRefactorHandler::cancelRequest()
|
||||||
{
|
{
|
||||||
if (m_isRefactoringInProgress) {
|
if (!m_isRefactoringInProgress)
|
||||||
auto id = m_lastRequestId;
|
return;
|
||||||
|
|
||||||
for (auto it = m_activeRequests.begin(); it != m_activeRequests.end(); ++it) {
|
const auto id = m_lastRequestId;
|
||||||
if (it.key() == id) {
|
m_isRefactoringInProgress = false;
|
||||||
const RequestContext &ctx = it.value();
|
m_lastRequestId.clear();
|
||||||
ctx.provider->cancelRequest(id);
|
|
||||||
m_activeRequests.erase(it);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
m_isRefactoringInProgress = false;
|
auto it = m_activeRequests.find(id);
|
||||||
|
if (it != m_activeRequests.end()) {
|
||||||
RefactorResult result;
|
auto provider = it.value().provider;
|
||||||
result.success = false;
|
m_activeRequests.erase(it);
|
||||||
result.errorMessage = "Refactoring request was cancelled";
|
if (provider)
|
||||||
emit refactoringCompleted(result);
|
provider->cancelRequest(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RefactorResult result;
|
||||||
|
result.success = false;
|
||||||
|
result.errorMessage = "Refactoring request was cancelled";
|
||||||
|
emit refactoringCompleted(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
void QuickRefactorHandler::handleFullResponse(const QString &requestId, const QString &fullText)
|
void QuickRefactorHandler::handleFullResponse(const QString &requestId, const QString &fullText)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// Copyright (C) 2025-2026 Petr Mironychev
|
// Copyright (C) 2025-2026 Petr Mironychev
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
|||||||
79
README.md
79
README.md
@@ -6,7 +6,7 @@
|
|||||||
[](https://www.gnu.org/licenses/gpl-3.0)
|
[](https://www.gnu.org/licenses/gpl-3.0)
|
||||||
[](https://discord.gg/BGMkUsXUgf)
|
[](https://discord.gg/BGMkUsXUgf)
|
||||||
|
|
||||||
 **QodeAssist** brings a full AI coding workflow to Qt Creator for C++ and QML — smart code completion, multi-panel chat, inline quick refactoring, and project-aware tool calling. It works with local runtimes (Ollama, llama.cpp, LM Studio) and cloud providers (Claude, OpenAI, Google AI, Mistral), can run as an **MCP server** so other clients reuse its project context, and can also act as an **MCP client** to consume tools from external MCP servers (authenticated MCP servers are not supported yet).
|
 **QodeAssist** brings a full AI coding workflow to Qt Creator for C++ and QML — smart code completion, multi-panel chat, inline quick refactoring, and project-aware tool calling. It works with local runtimes (Ollama, llama.cpp, LM Studio) and cloud providers (Claude, OpenAI, Google AI, Mistral, Qwen, DeepSeek), can run as an **MCP server** so other clients reuse its project context, and can also act as an **MCP client** to consume tools from external MCP servers (authenticated MCP servers are not supported yet).
|
||||||
|
|
||||||
⚠️ **Important Notice About Paid Providers**
|
⚠️ **Important Notice About Paid Providers**
|
||||||
> When using paid providers like Claude, OpenRouter or OpenAI-compatible services:
|
> When using paid providers like Claude, OpenRouter or OpenAI-compatible services:
|
||||||
@@ -39,7 +39,8 @@ QodeAssist enhances Qt Creator with AI-powered coding assistance:
|
|||||||
- **MCP Server** — expose QodeAssist's project-aware tools to external MCP clients (Claude Code, VS Code, Claude Desktop via bridge)
|
- **MCP Server** — expose QodeAssist's project-aware tools to external MCP clients (Claude Code, VS Code, Claude Desktop via bridge)
|
||||||
- **MCP Client Hub** — connect QodeAssist to external MCP servers and use their tools in Chat and Quick Refactor (authenticated MCP servers are not supported yet)
|
- **MCP Client Hub** — connect QodeAssist to external MCP servers and use their tools in Chat and Quick Refactor (authenticated MCP servers are not supported yet)
|
||||||
- **File Context** — attach, link, or auto-sync open editor files for richer prompts
|
- **File Context** — attach, link, or auto-sync open editor files for richer prompts
|
||||||
- **Many Providers** — Ollama, llama.cpp, LM Studio (Chat + Responses), Claude, OpenAI (Chat + Responses), Google AI, Mistral, Codestral, OpenRouter, any OpenAI-compatible endpoint
|
- **Many Providers** — Ollama, llama.cpp, LM Studio (Chat + Responses), Claude, OpenAI (Chat + Responses), Google AI, Mistral, Codestral, OpenRouter, Qwen (OpenAI + Responses), DeepSeek, any OpenAI-compatible endpoint
|
||||||
|
- **Reasoning / Thinking** — streamed chain-of-thought is shown for reasoning models across Claude, Google, OpenAI Responses, and any OpenAI-compatible endpoint that returns `reasoning_content` (DeepSeek, Qwen QwQ/Qwen3-Thinking, LM Studio, OpenRouter, …)
|
||||||
- **Customizable** — per-project rules (`.qodeassist/rules/`), agent roles, reusable refactor templates, full prompt-template control
|
- **Customizable** — per-project rules (`.qodeassist/rules/`), agent roles, reusable refactor templates, full prompt-template control
|
||||||
|
|
||||||
**Join our [Discord Community](https://discord.gg/BGMkUsXUgf)** to get support and connect with other users!
|
**Join our [Discord Community](https://discord.gg/BGMkUsXUgf)** to get support and connect with other users!
|
||||||
@@ -54,6 +55,11 @@ QodeAssist enhances Qt Creator with AI-powered coding assistance:
|
|||||||
<img src="https://github.com/user-attachments/assets/4a9092e0-429f-41eb-8723-cbb202fd0a8c" width="600" alt="QodeAssistPreview">
|
<img src="https://github.com/user-attachments/assets/4a9092e0-429f-41eb-8723-cbb202fd0a8c" width="600" alt="QodeAssistPreview">
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Chat View Mode: (click to expand)</summary>
|
||||||
|
<img src="https://github.com/user-attachments/assets/5914dd78-c8a4-4d35-889a-10ec493d4c4b" width="600" alt="QodeAssistChat2">
|
||||||
|
</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">
|
||||||
@@ -86,7 +92,27 @@ QodeAssist enhances Qt Creator with AI-powered coding assistance:
|
|||||||
|
|
||||||
## Install plugin to QtCreator
|
## Install plugin to QtCreator
|
||||||
|
|
||||||
### Method 1: Using QodeAssistUpdater (Beta)
|
### Method 1: Using the Extension Registry (Recommended)
|
||||||
|
|
||||||
|
You can install and update QodeAssist directly from within Qt Creator by adding the QodeAssist registry as an external extension repository.
|
||||||
|
|
||||||
|
1. Open the Extensions page (`Qt Creator → Extensions`) and switch to the **Browser** tab
|
||||||
|
2. Enable **Use External Repository**
|
||||||
|
3. Next to **Repository URLs**, click **Add** and paste the registry archive URL matching your Qt Creator version:
|
||||||
|
- **Latest (QtC 19)**: `https://github.com/Palm1r/extension-registry/archive/refs/heads/qodeassist.tar.gz`
|
||||||
|
- **QtC 19**: `https://github.com/Palm1r/extension-registry/archive/refs/heads/qodeassist-qtc19.tar.gz`
|
||||||
|
- **QtC 18**: `https://github.com/Palm1r/extension-registry/archive/refs/heads/qodeassist-qtc18.tar.gz`
|
||||||
|
<details>
|
||||||
|
<summary>Example of extension registry: (click to expand)</summary>
|
||||||
|
<img width="600" alt="RegistryExample" src="https://github.com/user-attachments/assets/8ab8cf10-72e7-4961-8c5a-21d530378a05">
|
||||||
|
</details>
|
||||||
|
|
||||||
|
4. Click **Apply** — QodeAssist will appear in the extensions list, where you can **Install** it
|
||||||
|
5. Updates can be installed from the same screen when a new version is published
|
||||||
|
|
||||||
|
> **Note:** This is an external repository not maintained by The Qt Company. By adding it you accept responsibility for managing the associated risks, as stated in the Extensions page.
|
||||||
|
|
||||||
|
### Method 2: Using QodeAssistUpdater (Beta)
|
||||||
|
|
||||||
QodeAssistUpdater is a command-line utility that automates plugin installation and updates with automatic Qt Creator version detection and checksum verification.
|
QodeAssistUpdater is a command-line utility that automates plugin installation and updates with automatic Qt Creator version detection and checksum verification.
|
||||||
|
|
||||||
@@ -114,7 +140,7 @@ Download pre-built binary from [QodeAssistUpdater releases](https://github.com/P
|
|||||||
|
|
||||||
For more information, visit the [QodeAssistUpdater repository](https://github.com/Palm1r/QodeAssistUpdater).
|
For more information, visit the [QodeAssistUpdater repository](https://github.com/Palm1r/QodeAssistUpdater).
|
||||||
|
|
||||||
### Method 2: Manual Installation
|
### Method 3: Manual Installation
|
||||||
|
|
||||||
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
|
||||||
@@ -144,6 +170,8 @@ The Quick Setup feature provides one-click configuration for popular cloud AI mo
|
|||||||
- **OpenAI** (gpt-5.2-codex)
|
- **OpenAI** (gpt-5.2-codex)
|
||||||
- **Mistral AI** (Codestral 2501)
|
- **Mistral AI** (Codestral 2501)
|
||||||
- **Google AI** (Gemini 2.5 Flash)
|
- **Google AI** (Gemini 2.5 Flash)
|
||||||
|
- **Qwen** (Qwen3.6 Plus, Qwen3.7 Max)
|
||||||
|
- **DeepSeek** (DeepSeek V4 Flash, DeepSeek V4 Pro)
|
||||||
3. **Configure API Key** - Click "Configure API Key" button and enter your API key in Provider Settings
|
3. **Configure API Key** - Click "Configure API Key" button and enter your API key in Provider Settings
|
||||||
|
|
||||||
All settings (provider, model, template, URL) are configured automatically. Just add your API key and you're ready to go!
|
All settings (provider, model, template, URL) are configured automatically. Just add your API key and you're ready to go!
|
||||||
@@ -164,6 +192,8 @@ For advanced users or local models, choose your preferred provider and follow th
|
|||||||
- **[OpenAI](docs/openai-configuration.md)** — Chat Completions and Responses API
|
- **[OpenAI](docs/openai-configuration.md)** — Chat Completions and Responses API
|
||||||
- **[Mistral AI](docs/mistral-configuration.md)** / **Codestral**
|
- **[Mistral AI](docs/mistral-configuration.md)** / **Codestral**
|
||||||
- **[Google AI](docs/google-ai-configuration.md)** — Gemini
|
- **[Google AI](docs/google-ai-configuration.md)** — Gemini
|
||||||
|
- **Qwen (Alibaba)** — DashScope OpenAI-compatible Chat and Responses endpoints
|
||||||
|
- **DeepSeek** — `deepseek-chat` and `deepseek-reasoner` (reasoning shown as thinking)
|
||||||
- **OpenAI-compatible** — OpenRouter and any custom endpoint
|
- **OpenAI-compatible** — OpenRouter and any custom endpoint
|
||||||
|
|
||||||
### Recommended Models for Best Experience
|
### Recommended Models for Best Experience
|
||||||
@@ -228,7 +258,7 @@ Configure in: `Tools → Options → QodeAssist → Code Completion → General
|
|||||||
- **[Chat Summarization](docs/chat-summarization.md)** - Compress long conversations into AI-generated summaries
|
- **[Chat Summarization](docs/chat-summarization.md)** - Compress long conversations into AI-generated summaries
|
||||||
- **[File Context](docs/file-context.md)** - Attach or link files for better context
|
- **[File Context](docs/file-context.md)** - Attach or link files for better context
|
||||||
- Automatic syncing with open editor files (optional)
|
- Automatic syncing with open editor files (optional)
|
||||||
- Extended thinking mode (Claude, other providers in plan) - Enable deeper reasoning for complex tasks
|
- Extended thinking / reasoning mode - shows streamed chain-of-thought for reasoning models (Claude, Google, OpenAI Responses, and OpenAI-compatible endpoints returning `reasoning_content` such as DeepSeek, Qwen, LM Studio, OpenRouter)
|
||||||
|
|
||||||
### Quick Refactoring
|
### Quick Refactoring
|
||||||
- Inline code refactoring directly in the editor with AI assistance
|
- Inline code refactoring directly in the editor with AI assistance
|
||||||
@@ -560,6 +590,45 @@ cmake --build .
|
|||||||
|
|
||||||
For detailed development guidelines, architecture patterns, and best practices, see the [project workspace rules](.cursor/rules.mdc).
|
For detailed development guidelines, architecture patterns, and best practices, see the [project workspace rules](.cursor/rules.mdc).
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
QodeAssist is licensed under the **GNU General Public License v3.0**
|
||||||
|
(see [`LICENSE`](LICENSE)), with **additional attribution terms under
|
||||||
|
GPLv3 Section 7(b)**.
|
||||||
|
|
||||||
|
You are free to use, modify, and redistribute QodeAssist under GPL-3.0,
|
||||||
|
but you **must preserve** the original author attribution, copyright
|
||||||
|
notices, and project identification — including in source file headers,
|
||||||
|
the plugin metadata (`QodeAssist.json.in`), and the About dialog or
|
||||||
|
equivalent user-facing identification. Modified versions must be clearly
|
||||||
|
marked as different from the original.
|
||||||
|
|
||||||
|
### Commercial licensing
|
||||||
|
|
||||||
|
QodeAssist is also available under a separate commercial license for use
|
||||||
|
in proprietary or closed-source products without GPL-3.0 obligations.
|
||||||
|
For commercial licensing inquiries, contact **palm1r-github-dev@pm.me**.
|
||||||
|
|
||||||
|
### Qt Creator components and attributions
|
||||||
|
|
||||||
|
QodeAssist is a plugin for Qt Creator and incorporates certain components
|
||||||
|
(plugin templates, API headers, and related boilerplate) originating from
|
||||||
|
Qt Creator, which are copyright (C) The Qt Company Ltd.
|
||||||
|
|
||||||
|
These components are provided by The Qt Company under the GNU General
|
||||||
|
Public License version 3, annotated with **The Qt Company GPL Exception
|
||||||
|
1.0**. This exception permits the development and distribution of Qt
|
||||||
|
Creator plugins under licenses of the plugin author's own choosing,
|
||||||
|
notwithstanding the GPL's general linking requirements. It is this
|
||||||
|
exception that allows QodeAssist to be offered under both GPL-3.0 and a
|
||||||
|
separate commercial license.
|
||||||
|
|
||||||
|
The original copyright and license notices of The Qt Company are
|
||||||
|
preserved in the relevant source files and must not be removed.
|
||||||
|
|
||||||
|
For Qt Creator's licensing terms, see
|
||||||
|
[LICENSE.GPL3-EXCEPT](https://github.com/qt-creator/qt-creator/blob/master/LICENSES/LICENSE.GPL3-EXCEPT).
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// Copyright (C) 2025-2026 Petr Mironychev
|
// Copyright (C) 2025-2026 Petr Mironychev
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// Copyright (C) 2024-2026 Petr Mironychev
|
// Copyright (C) 2024-2026 Petr Mironychev
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
#include "RefactorSuggestion.hpp"
|
#include "RefactorSuggestion.hpp"
|
||||||
#include "LLMSuggestion.hpp"
|
#include "LLMSuggestion.hpp"
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// Copyright (C) 2024-2026 Petr Mironychev
|
// Copyright (C) 2024-2026 Petr Mironychev
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// Copyright (C) 2025-2026 Petr Mironychev
|
// Copyright (C) 2025-2026 Petr Mironychev
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
#include "RefactorSuggestionHoverHandler.hpp"
|
#include "RefactorSuggestionHoverHandler.hpp"
|
||||||
#include "RefactorSuggestion.hpp"
|
#include "RefactorSuggestion.hpp"
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// Copyright (C) 2025-2026 Petr Mironychev
|
// Copyright (C) 2025-2026 Petr Mironychev
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// Copyright (C) 2025-2026 Petr Mironychev
|
// Copyright (C) 2025-2026 Petr Mironychev
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
#include "FlowEditor.hpp"
|
#include "FlowEditor.hpp"
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// Copyright (C) 2025-2026 Petr Mironychev
|
// Copyright (C) 2025-2026 Petr Mironychev
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
// Copyright (C) 2025-2026 Petr Mironychev
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
#include "FlowItem.hpp"
|
#include "FlowItem.hpp"
|
||||||
|
|
||||||
namespace QodeAssist::TaskFlow {
|
namespace QodeAssist::TaskFlow {
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
// Copyright (C) 2025-2026 Petr Mironychev
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <QQuickItem>
|
#include <QQuickItem>
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
// Copyright (C) 2025-2026 Petr Mironychev
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
#include "FlowsModel.hpp"
|
#include "FlowsModel.hpp"
|
||||||
|
|
||||||
#include "FlowManager.hpp"
|
#include "FlowManager.hpp"
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
// Copyright (C) 2025-2026 Petr Mironychev
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <QAbstractListModel>
|
#include <QAbstractListModel>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// Copyright (C) 2025-2026 Petr Mironychev
|
// Copyright (C) 2025-2026 Petr Mironychev
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
#include "GridBackground.hpp"
|
#include "GridBackground.hpp"
|
||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// Copyright (C) 2025-2026 Petr Mironychev
|
// Copyright (C) 2025-2026 Petr Mironychev
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
// Copyright (C) 2025-2026 Petr Mironychev
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
#include "TaskConnectionItem.hpp"
|
#include "TaskConnectionItem.hpp"
|
||||||
#include "TaskItem.hpp"
|
#include "TaskItem.hpp"
|
||||||
#include "TaskPortItem.hpp"
|
#include "TaskPortItem.hpp"
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
// Copyright (C) 2025-2026 Petr Mironychev
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "TaskConnection.hpp"
|
#include "TaskConnection.hpp"
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
// Copyright (C) 2025-2026 Petr Mironychev
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
#include "TaskConnectionsModel.hpp"
|
#include "TaskConnectionsModel.hpp"
|
||||||
|
|
||||||
namespace QodeAssist::TaskFlow {
|
namespace QodeAssist::TaskFlow {
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
// Copyright (C) 2025-2026 Petr Mironychev
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <QAbstractListModel>
|
#include <QAbstractListModel>
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
// Copyright (C) 2025-2026 Petr Mironychev
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
#include "TaskItem.hpp"
|
#include "TaskItem.hpp"
|
||||||
|
|
||||||
namespace QodeAssist::TaskFlow {
|
namespace QodeAssist::TaskFlow {
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
// Copyright (C) 2025-2026 Petr Mironychev
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <QQuickItem>
|
#include <QQuickItem>
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
// Copyright (C) 2025-2026 Petr Mironychev
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
#include "TaskModel.hpp"
|
#include "TaskModel.hpp"
|
||||||
|
|
||||||
namespace QodeAssist::TaskFlow {
|
namespace QodeAssist::TaskFlow {
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
// Copyright (C) 2025-2026 Petr Mironychev
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <QAbstractListModel>
|
#include <QAbstractListModel>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user