Compare commits

..

41 Commits

Author SHA1 Message Date
Petr Mironychev
1c94fe150e feat: Add QtCreator 20 support 2026-06-08 17:44:18 +02:00
Petr Mironychev
abb3351246 chore: Update version to 0.9.20 2026-06-08 15:51:03 +02:00
Petr Mironychev
57eeb32ceb chore: Update translations 2026-06-08 15:36:09 +02:00
Petr Mironychev
74eed49fb4 feat: Add transfer timeout settings 2026-06-08 15:20:21 +02:00
Petr Mironychev
43a30281b6 feat: Improve BusyIndicator 2026-06-08 12:53:37 +02:00
Petr Mironychev
bf4307c459 doc: Update License in README 2026-06-08 12:31:14 +02:00
Petr Mironychev
6df70e608b chore: Update LICENCE and copyright 2026-06-08 11:25:18 +02:00
Petr Mironychev
ee1bf4ffe5 feat: Improve chat, status and message sending keys (#361) 2026-06-06 11:25:30 +02:00
Petr Mironychev
aaca9e2a0b chore: Update plugin to 0.9.19 2026-06-01 12:09:25 +02:00
Petr Mironychev
f2aae9d37f fix: Using adaptive thinking for Claude opus 4.8 - 4.6 2026-06-01 12:08:03 +02:00
Petr Mironychev
dcf5796ad7 refactor: Move Qwen provider to separate classes 2026-05-29 12:49:32 +02:00
Petr Mironychev
033c0e8652 feat: Add DeepSeek provider 2026-05-29 12:41:18 +02:00
Petr Mironychev
ea67ba0e2a feat: Add Qwen provider 2026-05-29 11:33:06 +02:00
Petr Mironychev
0cf915c4a5 feat: Update dialog with update 2026-05-29 10:35:52 +02:00
Petr Mironychev
99caa853d5 chore: Update plugin to 0.9.18 version 2026-05-29 09:45:34 +02:00
Petr Mironychev
278624d412 fix: Prevent toolbar button clicks being eaten by focus guard 2026-05-29 09:44:44 +02:00
Petr Mironychev
f8adf4d264 fix: Clean request id by clear() 2026-05-28 17:58:33 +02:00
Petr Mironychev
bfcd8dc1fb fix: Prevent crash on cancelling quick refactor via progress widget 2026-05-28 16:00:18 +02:00
Petr Mironychev
33321b2499 Update README with extension registry example
Added an example of the extension registry with an image.
2026-05-28 14:40:28 +02:00
Petr Mironychev
362533a5c0 doc: Add installation from registry 2026-05-28 14:38:28 +02:00
Petr Mironychev
d180d189e4 chore: Update plugin to 0.9.17 version 2026-05-28 11:18:36 +02:00
Petr Mironychev
0774084ad9 fix: Add mouse propogation to qml chat in qquickwidget 2026-05-28 11:17:38 +02:00
Petr Mironychev
282f48d9fb fix: Improve parsing markdown 2026-05-28 10:50:25 +02:00
Petr Mironychev
8cbeb7132e fix: Replace context menu to system specific 2026-05-28 10:49:56 +02:00
Petr Mironychev
af898bd255 feat: Add preview to message navigator 2026-05-27 23:03:10 +02:00
Petr Mironychev
66e25300e8 tr: Update translations 2026-05-27 22:15:19 +02:00
Petr Mironychev
fcc651fd75 feat: Add message navigator 2026-05-27 22:06:41 +02:00
Petr Mironychev
dc016ce533 refactor: Improve file edit panel 2026-05-27 21:54:52 +02:00
Petr Mironychev
725de4a2c3 fix: Replace to custom tooltip 2026-05-27 19:23:28 +02:00
Petr Mironychev
8d3313d16b feat: Add translations to cs zh_CN zh_TW da de fr hr ja pl ru sl sv uk 2026-05-27 18:42:56 +02:00
Petr Mironychev
abdcab3c7d fix: Add focus guard 2026-05-27 15:43:18 +02:00
Petr Mironychev
abadc2262c feat: Add session layout 2026-05-26 18:02:44 +02:00
Petr Mironychev
31ad99af61 refactor: Group sources cmake files to subfolder 2026-05-26 17:17:33 +02:00
Petr Mironychev
fb887967ed feat: Add agents pipelines 2026-05-26 16:44:45 +02:00
Petr Mironychev
97236c6069 feat: Add agents and agents settings 2026-05-26 12:31:00 +02:00
Petr Mironychev
51ebe3e523 Update README.md 2026-05-23 18:16:23 +02:00
Petr Mironychev
e193d1e1fa feat: Add settings page for providers (#353) 2026-05-21 19:30:32 +02:00
Petr Mironychev
ca3baa7597 chore: Update plugin to 0.9.16 2026-05-21 14:59:52 +02:00
Petr Mironychev
b33a1c2d43 fix: Add handling final argument for OpenAI responses tool calling 2026-05-21 14:19:16 +02:00
Petr Mironychev
c4e34bb3d9 chore: Update plugin to 0.9.15 version 2026-05-21 10:49:34 +02:00
Petr Mironychev
b9e0b5a00c fix: Change getting focus to chat in editor and name of title 2026-05-21 10:48:11 +02:00
493 changed files with 60657 additions and 855 deletions

View File

@@ -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()

View File

@@ -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
) )

View File

@@ -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"

View File

@@ -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

View File

@@ -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

View File

@@ -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));

View File

@@ -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

View File

@@ -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"

View File

@@ -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

View File

@@ -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

View File

@@ -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"

View File

@@ -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

View File

@@ -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"

View File

@@ -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

View File

@@ -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,

View File

@@ -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,

View File

@@ -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();

View File

@@ -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;

View File

@@ -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"

View File

@@ -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

View File

@@ -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("&lt;"));
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 == ' ') {

View File

@@ -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

View File

@@ -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"

View File

@@ -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

View File

@@ -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()

View File

@@ -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

View File

@@ -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,

View File

@@ -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

View File

@@ -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"

View File

@@ -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

View File

@@ -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"

View File

@@ -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

View File

@@ -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"

View File

@@ -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

View File

@@ -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"

View File

@@ -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

View File

@@ -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

View File

@@ -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"

View File

@@ -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

View 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

View 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

View File

@@ -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

View 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

View 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

View File

@@ -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() {

View File

@@ -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 {

View File

@@ -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

View File

@@ -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 {

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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"

View File

@@ -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
}
} }
} }
} }

View File

@@ -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

View File

@@ -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()
} }

View File

@@ -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

View 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)
}
}
}
}
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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")
}
} }
} }
} }

View File

@@ -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>

View File

@@ -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

View File

@@ -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"

View File

@@ -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
View File

@@ -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

View File

@@ -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};

View File

@@ -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

View File

@@ -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>

View File

@@ -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

View File

@@ -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

View File

@@ -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",

View File

@@ -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"

View File

@@ -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

View File

@@ -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";

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -6,7 +6,7 @@
[![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)
[![Discord](https://dcbadge.limes.pink/api/server/BGMkUsXUgf?style=flat)](https://discord.gg/BGMkUsXUgf) [![Discord](https://dcbadge.limes.pink/api/server/BGMkUsXUgf?style=flat)](https://discord.gg/BGMkUsXUgf)
![qodeassist-icon](https://github.com/user-attachments/assets/dc336712-83cb-440d-8761-8d0a31de898d) **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-icon](https://github.com/user-attachments/assets/dc336712-83cb-440d-8761-8d0a31de898d) **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).
![qodeassist-icon](https://github.com/user-attachments/assets/dc336712-83cb-440d-8761-8d0a31de898d) ![qodeassist-icon](https://github.com/user-attachments/assets/dc336712-83cb-440d-8761-8d0a31de898d)
![qodeassist-icon-small](https://github.com/user-attachments/assets/8ec241bf-3186-452e-b8db-8d70543c2f41) ![qodeassist-icon-small](https://github.com/user-attachments/assets/8ec241bf-3186-452e-b8db-8d70543c2f41)

View File

@@ -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

View File

@@ -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"

View File

@@ -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

View File

@@ -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"

View File

@@ -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

View File

@@ -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"

View File

@@ -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

View File

@@ -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 {

View File

@@ -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>

View File

@@ -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"

View File

@@ -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>

View File

@@ -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>

View File

@@ -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

View File

@@ -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"

View File

@@ -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"

View File

@@ -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 {

View File

@@ -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>

View File

@@ -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 {

View File

@@ -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>

View File

@@ -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 {

View File

@@ -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