mirror of
https://github.com/Palm1r/QodeAssist.git
synced 2026-06-29 17:49:12 -04:00
refactor: Move to agent-session architecture
This commit is contained in:
@@ -126,7 +126,6 @@ add_qtc_plugin(QodeAssist
|
|||||||
chat/ChatDocument.hpp chat/ChatDocument.cpp
|
chat/ChatDocument.hpp chat/ChatDocument.cpp
|
||||||
chat/ChatEditor.hpp chat/ChatEditor.cpp
|
chat/ChatEditor.hpp chat/ChatEditor.cpp
|
||||||
chat/ChatEditorFactory.hpp chat/ChatEditorFactory.cpp
|
chat/ChatEditorFactory.hpp chat/ChatEditorFactory.cpp
|
||||||
ConfigurationManager.hpp ConfigurationManager.cpp
|
|
||||||
CodeHandler.hpp CodeHandler.cpp
|
CodeHandler.hpp CodeHandler.cpp
|
||||||
UpdateStatusWidget.hpp UpdateStatusWidget.cpp
|
UpdateStatusWidget.hpp UpdateStatusWidget.cpp
|
||||||
widgets/CompletionProgressHandler.hpp widgets/CompletionProgressHandler.cpp
|
widgets/CompletionProgressHandler.hpp widgets/CompletionProgressHandler.cpp
|
||||||
@@ -166,7 +165,7 @@ add_qtc_plugin(QodeAssist
|
|||||||
settings/McpClientsListAspect.hpp settings/McpClientsListAspect.cpp
|
settings/McpClientsListAspect.hpp settings/McpClientsListAspect.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(QodeAssist PRIVATE QodeAssistAgentPipelines)
|
target_link_libraries(QodeAssist PRIVATE QodeAssistAgentPipelines Session)
|
||||||
|
|
||||||
get_target_property(QtCreatorCorePath QtCreator::Core LOCATION)
|
get_target_property(QtCreatorCorePath QtCreator::Core LOCATION)
|
||||||
find_program(QtCreatorExecutable
|
find_program(QtCreatorExecutable
|
||||||
|
|||||||
@@ -1,124 +0,0 @@
|
|||||||
// Copyright (C) 2024-2026 Petr Mironychev
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
|
||||||
|
|
||||||
#include "AgentRoleController.hpp"
|
|
||||||
|
|
||||||
#include <utils/aspects.h>
|
|
||||||
|
|
||||||
#include "AgentRole.hpp"
|
|
||||||
#include "ChatAssistantSettings.hpp"
|
|
||||||
#include "GeneralSettings.hpp"
|
|
||||||
|
|
||||||
namespace QodeAssist::Chat {
|
|
||||||
|
|
||||||
AgentRoleController::AgentRoleController(QObject *parent)
|
|
||||||
: QObject(parent)
|
|
||||||
{
|
|
||||||
connect(
|
|
||||||
&Settings::chatAssistantSettings().systemPrompt,
|
|
||||||
&Utils::BaseAspect::changed,
|
|
||||||
this,
|
|
||||||
&AgentRoleController::baseSystemPromptChanged);
|
|
||||||
|
|
||||||
loadAvailableRoles();
|
|
||||||
}
|
|
||||||
|
|
||||||
QStringList AgentRoleController::availableRoles() const
|
|
||||||
{
|
|
||||||
return m_availableRoles;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString AgentRoleController::currentRole() const
|
|
||||||
{
|
|
||||||
return m_currentRole;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString AgentRoleController::baseSystemPrompt() const
|
|
||||||
{
|
|
||||||
return Settings::chatAssistantSettings().systemPrompt();
|
|
||||||
}
|
|
||||||
|
|
||||||
QString AgentRoleController::currentRoleDescription() const
|
|
||||||
{
|
|
||||||
const QString lastRoleId = Settings::chatAssistantSettings().lastUsedRoleId();
|
|
||||||
if (lastRoleId.isEmpty())
|
|
||||||
return Settings::AgentRolesManager::getNoRole().description;
|
|
||||||
|
|
||||||
const Settings::AgentRole role = Settings::AgentRolesManager::loadRole(lastRoleId);
|
|
||||||
if (role.id.isEmpty())
|
|
||||||
return Settings::AgentRolesManager::getNoRole().description;
|
|
||||||
|
|
||||||
return role.description;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString AgentRoleController::currentRoleSystemPrompt() const
|
|
||||||
{
|
|
||||||
const QString lastRoleId = Settings::chatAssistantSettings().lastUsedRoleId();
|
|
||||||
if (lastRoleId.isEmpty())
|
|
||||||
return QString();
|
|
||||||
|
|
||||||
const Settings::AgentRole role = Settings::AgentRolesManager::loadRole(lastRoleId);
|
|
||||||
if (role.id.isEmpty())
|
|
||||||
return QString();
|
|
||||||
|
|
||||||
return role.systemPrompt;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AgentRoleController::loadAvailableRoles()
|
|
||||||
{
|
|
||||||
const QList<Settings::AgentRole> roles = Settings::AgentRolesManager::loadAllRoles();
|
|
||||||
|
|
||||||
m_availableRoles.clear();
|
|
||||||
m_availableRoles.append(Settings::AgentRolesManager::getNoRole().name);
|
|
||||||
|
|
||||||
for (const auto &role : roles)
|
|
||||||
m_availableRoles.append(role.name);
|
|
||||||
|
|
||||||
const QString lastRoleId = Settings::chatAssistantSettings().lastUsedRoleId();
|
|
||||||
m_currentRole = Settings::AgentRolesManager::getNoRole().name;
|
|
||||||
|
|
||||||
if (!lastRoleId.isEmpty()) {
|
|
||||||
for (const auto &role : roles) {
|
|
||||||
if (role.id == lastRoleId) {
|
|
||||||
m_currentRole = role.name;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
emit availableRolesChanged();
|
|
||||||
emit currentRoleChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AgentRoleController::applyRole(const QString &roleName)
|
|
||||||
{
|
|
||||||
auto &settings = Settings::chatAssistantSettings();
|
|
||||||
|
|
||||||
if (roleName == Settings::AgentRolesManager::getNoRole().name) {
|
|
||||||
settings.lastUsedRoleId.setValue("");
|
|
||||||
settings.writeSettings();
|
|
||||||
m_currentRole = roleName;
|
|
||||||
emit currentRoleChanged();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const QList<Settings::AgentRole> roles = Settings::AgentRolesManager::loadAllRoles();
|
|
||||||
|
|
||||||
for (const auto &role : roles) {
|
|
||||||
if (role.name == roleName) {
|
|
||||||
settings.lastUsedRoleId.setValue(role.id);
|
|
||||||
settings.writeSettings();
|
|
||||||
m_currentRole = role.name;
|
|
||||||
emit currentRoleChanged();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AgentRoleController::openSettings()
|
|
||||||
{
|
|
||||||
Settings::showSettings(Utils::Id("QodeAssist.AgentRoles"));
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace QodeAssist::Chat
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
// Copyright (C) 2024-2026 Petr Mironychev
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <QObject>
|
|
||||||
#include <QStringList>
|
|
||||||
|
|
||||||
namespace QodeAssist::Chat {
|
|
||||||
|
|
||||||
class AgentRoleController : public QObject
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit AgentRoleController(QObject *parent = nullptr);
|
|
||||||
|
|
||||||
QStringList availableRoles() const;
|
|
||||||
QString currentRole() const;
|
|
||||||
QString baseSystemPrompt() const;
|
|
||||||
QString currentRoleDescription() const;
|
|
||||||
QString currentRoleSystemPrompt() const;
|
|
||||||
|
|
||||||
void loadAvailableRoles();
|
|
||||||
void applyRole(const QString &roleName);
|
|
||||||
void openSettings();
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void availableRolesChanged();
|
|
||||||
void currentRoleChanged();
|
|
||||||
void baseSystemPromptChanged();
|
|
||||||
|
|
||||||
private:
|
|
||||||
QStringList m_availableRoles;
|
|
||||||
QString m_currentRole;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace QodeAssist::Chat
|
|
||||||
@@ -75,8 +75,7 @@ qt_add_qml_module(QodeAssistChatView
|
|||||||
FileItem.hpp FileItem.cpp
|
FileItem.hpp FileItem.cpp
|
||||||
ChatFileManager.hpp ChatFileManager.cpp
|
ChatFileManager.hpp ChatFileManager.cpp
|
||||||
ChatCompressor.hpp ChatCompressor.cpp
|
ChatCompressor.hpp ChatCompressor.cpp
|
||||||
AgentRoleController.hpp AgentRoleController.cpp
|
ChatAgentController.hpp ChatAgentController.cpp
|
||||||
ChatConfigurationController.hpp ChatConfigurationController.cpp
|
|
||||||
FileEditController.hpp FileEditController.cpp
|
FileEditController.hpp FileEditController.cpp
|
||||||
InputTokenCounter.hpp InputTokenCounter.cpp
|
InputTokenCounter.hpp InputTokenCounter.cpp
|
||||||
ChatHistoryStore.hpp ChatHistoryStore.cpp
|
ChatHistoryStore.hpp ChatHistoryStore.cpp
|
||||||
@@ -92,13 +91,14 @@ target_link_libraries(QodeAssistChatView
|
|||||||
Qt::Network
|
Qt::Network
|
||||||
QtCreator::Core
|
QtCreator::Core
|
||||||
QtCreator::Utils
|
QtCreator::Utils
|
||||||
PluginLLMCore
|
|
||||||
QodeAssistSettings
|
QodeAssistSettings
|
||||||
Context
|
Context
|
||||||
QodeAssistUIControlsplugin
|
QodeAssistUIControlsplugin
|
||||||
QodeAssistLogger
|
QodeAssistLogger
|
||||||
LLMQore
|
LLMQore
|
||||||
Skills
|
Skills
|
||||||
|
Agents
|
||||||
|
Session
|
||||||
)
|
)
|
||||||
|
|
||||||
target_include_directories(QodeAssistChatView
|
target_include_directories(QodeAssistChatView
|
||||||
|
|||||||
91
ChatView/ChatAgentController.cpp
Normal file
91
ChatView/ChatAgentController.cpp
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
// Copyright (C) 2024-2026 Petr Mironychev
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#include "ChatAgentController.hpp"
|
||||||
|
|
||||||
|
#include <QSettings>
|
||||||
|
|
||||||
|
#include <coreplugin/icore.h>
|
||||||
|
|
||||||
|
#include <AgentConfig.hpp>
|
||||||
|
#include <AgentFactory.hpp>
|
||||||
|
|
||||||
|
namespace QodeAssist::Chat {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
const char kChatAgentKey[] = "QodeAssist.chatActiveAgent";
|
||||||
|
}
|
||||||
|
|
||||||
|
ChatAgentController::ChatAgentController(QObject *parent)
|
||||||
|
: QObject(parent)
|
||||||
|
{
|
||||||
|
if (auto *settings = Core::ICore::settings())
|
||||||
|
m_currentAgent = settings->value(kChatAgentKey).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatAgentController::setAgentFactory(AgentFactory *factory)
|
||||||
|
{
|
||||||
|
m_agentFactory = factory;
|
||||||
|
reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList ChatAgentController::availableAgents() const
|
||||||
|
{
|
||||||
|
return m_availableAgents;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ChatAgentController::currentAgent() const
|
||||||
|
{
|
||||||
|
return m_currentAgent;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatAgentController::setCurrentAgent(const QString &name)
|
||||||
|
{
|
||||||
|
if (name == m_currentAgent || !m_availableAgents.contains(name))
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_currentAgent = name;
|
||||||
|
if (auto *settings = Core::ICore::settings())
|
||||||
|
settings->setValue(kChatAgentKey, m_currentAgent);
|
||||||
|
emit currentAgentChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatAgentController::reload()
|
||||||
|
{
|
||||||
|
m_availableAgents = m_agentFactory ? m_agentFactory->configNames() : QStringList{};
|
||||||
|
emit availableAgentsChanged();
|
||||||
|
ensureValidCurrent();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatAgentController::ensureValidCurrent()
|
||||||
|
{
|
||||||
|
if (m_availableAgents.contains(m_currentAgent))
|
||||||
|
return;
|
||||||
|
|
||||||
|
const QString next = m_availableAgents.isEmpty() ? QString() : m_availableAgents.first();
|
||||||
|
if (next == m_currentAgent)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_currentAgent = next;
|
||||||
|
if (auto *settings = Core::ICore::settings())
|
||||||
|
settings->setValue(kChatAgentKey, m_currentAgent);
|
||||||
|
emit currentAgentChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ChatAgentController::currentSupportsThinking() const
|
||||||
|
{
|
||||||
|
if (!m_agentFactory || m_currentAgent.isEmpty())
|
||||||
|
return false;
|
||||||
|
const AgentConfig *config = m_agentFactory->configByName(m_currentAgent);
|
||||||
|
return config && config->enableThinking;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ChatAgentController::currentSupportsTools() const
|
||||||
|
{
|
||||||
|
if (!m_agentFactory || m_currentAgent.isEmpty())
|
||||||
|
return false;
|
||||||
|
const AgentConfig *config = m_agentFactory->configByName(m_currentAgent);
|
||||||
|
return config && config->enableTools;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace QodeAssist::Chat
|
||||||
47
ChatView/ChatAgentController.hpp
Normal file
47
ChatView/ChatAgentController.hpp
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
// Copyright (C) 2024-2026 Petr Mironychev
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QPointer>
|
||||||
|
#include <QString>
|
||||||
|
#include <QStringList>
|
||||||
|
|
||||||
|
namespace QodeAssist {
|
||||||
|
class AgentFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace QodeAssist::Chat {
|
||||||
|
|
||||||
|
class ChatAgentController : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit ChatAgentController(QObject *parent = nullptr);
|
||||||
|
|
||||||
|
void setAgentFactory(AgentFactory *factory);
|
||||||
|
|
||||||
|
QStringList availableAgents() const;
|
||||||
|
QString currentAgent() const;
|
||||||
|
void setCurrentAgent(const QString &name);
|
||||||
|
|
||||||
|
bool currentSupportsThinking() const;
|
||||||
|
bool currentSupportsTools() const;
|
||||||
|
|
||||||
|
void reload();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void availableAgentsChanged();
|
||||||
|
void currentAgentChanged();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void ensureValidCurrent();
|
||||||
|
|
||||||
|
QPointer<AgentFactory> m_agentFactory;
|
||||||
|
QStringList m_availableAgents;
|
||||||
|
QString m_currentAgent;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace QodeAssist::Chat
|
||||||
@@ -4,13 +4,21 @@
|
|||||||
|
|
||||||
#include "ChatCompressor.hpp"
|
#include "ChatCompressor.hpp"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
#include <LLMQore/BaseClient.hpp>
|
#include <LLMQore/BaseClient.hpp>
|
||||||
|
#include <LLMQore/ContentBlocks.hpp>
|
||||||
|
|
||||||
#include "ChatModel.hpp"
|
#include "ChatModel.hpp"
|
||||||
#include "GeneralSettings.hpp"
|
#include "GeneralSettings.hpp"
|
||||||
#include "PromptTemplateManager.hpp"
|
|
||||||
#include "ProvidersManager.hpp"
|
|
||||||
#include "logger/Logger.hpp"
|
#include "logger/Logger.hpp"
|
||||||
|
|
||||||
|
#include <ConversationHistory.hpp>
|
||||||
|
#include <Message.hpp>
|
||||||
|
#include <Session.hpp>
|
||||||
|
#include <SessionManager.hpp>
|
||||||
|
#include <SystemPromptBuilder.hpp>
|
||||||
|
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
@@ -25,6 +33,16 @@ ChatCompressor::ChatCompressor(QObject *parent)
|
|||||||
: QObject(parent)
|
: QObject(parent)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
|
void ChatCompressor::setSessionManager(SessionManager *sessionManager)
|
||||||
|
{
|
||||||
|
m_sessionManager = sessionManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatCompressor::setActiveAgent(const QString &agentName)
|
||||||
|
{
|
||||||
|
m_activeAgent = agentName;
|
||||||
|
}
|
||||||
|
|
||||||
void ChatCompressor::startCompression(const QString &chatFilePath, ChatModel *chatModel)
|
void ChatCompressor::startCompression(const QString &chatFilePath, ChatModel *chatModel)
|
||||||
{
|
{
|
||||||
if (m_isCompressing) {
|
if (m_isCompressing) {
|
||||||
@@ -42,20 +60,23 @@ void ChatCompressor::startCompression(const QString &chatFilePath, ChatModel *ch
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto providerName = Settings::generalSettings().caProvider();
|
if (!m_sessionManager) {
|
||||||
m_provider = PluginLLMCore::ProvidersManager::instance().getProviderByName(providerName);
|
emit compressionFailed(tr("Chat session manager is not available"));
|
||||||
|
|
||||||
if (!m_provider) {
|
|
||||||
emit compressionFailed(tr("No provider available"));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto templateName = Settings::generalSettings().caTemplate();
|
QString sessionError;
|
||||||
auto promptTemplate = PluginLLMCore::PromptTemplateManager::instance().getChatTemplateByName(
|
Session *session = m_sessionManager->createSession(m_activeAgent, &sessionError);
|
||||||
templateName);
|
if (!session) {
|
||||||
|
emit compressionFailed(
|
||||||
|
sessionError.isEmpty() ? tr("No chat agent selected") : sessionError);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!promptTemplate) {
|
auto *client = session->client();
|
||||||
emit compressionFailed(tr("No template available"));
|
if (!client) {
|
||||||
|
m_sessionManager->removeSession(session);
|
||||||
|
emit compressionFailed(tr("Chat agent has no live client"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,23 +84,52 @@ void ChatCompressor::startCompression(const QString &chatFilePath, ChatModel *ch
|
|||||||
m_chatModel = chatModel;
|
m_chatModel = chatModel;
|
||||||
m_originalChatPath = chatFilePath;
|
m_originalChatPath = chatFilePath;
|
||||||
m_accumulatedSummary.clear();
|
m_accumulatedSummary.clear();
|
||||||
|
m_session = session;
|
||||||
|
|
||||||
emit compressionStarted();
|
emit compressionStarted();
|
||||||
|
|
||||||
connectProviderSignals();
|
session->systemPrompt()->setLayer(
|
||||||
|
QStringLiteral("compression"),
|
||||||
|
QStringLiteral(
|
||||||
|
"You are a helpful assistant that creates concise summaries of conversations. "
|
||||||
|
"Your summaries preserve key information, technical details, and the flow of "
|
||||||
|
"discussion."));
|
||||||
|
|
||||||
QJsonObject payload{
|
auto *history = session->history();
|
||||||
{"model", Settings::generalSettings().caModel()}, {"stream", true}};
|
for (const auto &msg : m_chatModel->getChatHistory()) {
|
||||||
|
if (msg.role == ChatModel::ChatRole::Tool || msg.role == ChatModel::ChatRole::FileEdit
|
||||||
|
|| msg.role == ChatModel::ChatRole::Thinking)
|
||||||
|
continue;
|
||||||
|
if (msg.content.trimmed().isEmpty())
|
||||||
|
continue;
|
||||||
|
|
||||||
buildRequestPayload(payload, promptTemplate);
|
Message apiMessage(
|
||||||
|
msg.role == ChatModel::ChatRole::User ? Message::Role::User : Message::Role::Assistant);
|
||||||
|
apiMessage.appendBlock(std::make_unique<LLMQore::TextContent>(msg.content));
|
||||||
|
history->append(std::move(apiMessage));
|
||||||
|
}
|
||||||
|
|
||||||
const QString customEndpoint = Settings::generalSettings().caCustomEndpoint();
|
m_connections.append(connect(
|
||||||
const QString endpoint = !customEndpoint.isEmpty() ? customEndpoint
|
client, &::LLMQore::BaseClient::chunkReceived,
|
||||||
: promptTemplate->endpoint();
|
this, &ChatCompressor::onPartialResponseReceived, Qt::UniqueConnection));
|
||||||
m_provider->client()->setTransferTimeout(
|
m_connections.append(connect(
|
||||||
|
client, &::LLMQore::BaseClient::requestCompleted,
|
||||||
|
this, &ChatCompressor::onFullResponseReceived, Qt::UniqueConnection));
|
||||||
|
m_connections.append(connect(
|
||||||
|
client, &::LLMQore::BaseClient::requestFailed,
|
||||||
|
this, &ChatCompressor::onRequestFailed, Qt::UniqueConnection));
|
||||||
|
|
||||||
|
client->setTransferTimeout(
|
||||||
static_cast<int>(Settings::generalSettings().requestTimeout() * 1000));
|
static_cast<int>(Settings::generalSettings().requestTimeout() * 1000));
|
||||||
m_currentRequestId = m_provider->sendRequest(
|
|
||||||
QUrl(Settings::generalSettings().caUrl()), payload, endpoint);
|
std::vector<std::unique_ptr<LLMQore::ContentBlock>> blocks;
|
||||||
|
blocks.push_back(std::make_unique<LLMQore::TextContent>(buildCompressionPrompt()));
|
||||||
|
|
||||||
|
m_currentRequestId = session->send(std::move(blocks), /*toolsOverride=*/false);
|
||||||
|
if (m_currentRequestId.isEmpty()) {
|
||||||
|
handleCompressionError(tr("Failed to start compression request"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
LOG_MESSAGE(QString("Starting compression request: %1").arg(m_currentRequestId));
|
LOG_MESSAGE(QString("Starting compression request: %1").arg(m_currentRequestId));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,10 +144,6 @@ void ChatCompressor::cancelCompression()
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
LOG_MESSAGE("Cancelling compression request");
|
LOG_MESSAGE("Cancelling compression request");
|
||||||
|
|
||||||
if (m_provider && !m_currentRequestId.isEmpty())
|
|
||||||
m_provider->cancelRequest(m_currentRequestId);
|
|
||||||
|
|
||||||
cleanupState();
|
cleanupState();
|
||||||
emit compressionFailed(tr("Compression cancelled"));
|
emit compressionFailed(tr("Compression cancelled"));
|
||||||
}
|
}
|
||||||
@@ -120,14 +166,18 @@ void ChatCompressor::onFullResponseReceived(const QString &requestId, const QStr
|
|||||||
LOG_MESSAGE(
|
LOG_MESSAGE(
|
||||||
QString("Received summary, length: %1 characters").arg(m_accumulatedSummary.length()));
|
QString("Received summary, length: %1 characters").arg(m_accumulatedSummary.length()));
|
||||||
|
|
||||||
QString compressedPath = createCompressedChatPath(m_originalChatPath);
|
const QString compressedPath = createCompressedChatPath(m_originalChatPath);
|
||||||
if (!createCompressedChatFile(m_originalChatPath, compressedPath, m_accumulatedSummary)) {
|
const QString summary = m_accumulatedSummary;
|
||||||
handleCompressionError(tr("Failed to save compressed chat"));
|
const QString sourcePath = m_originalChatPath;
|
||||||
|
|
||||||
|
cleanupState();
|
||||||
|
|
||||||
|
if (!createCompressedChatFile(sourcePath, compressedPath, summary)) {
|
||||||
|
emit compressionFailed(tr("Failed to save compressed chat"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG_MESSAGE(QString("Compression completed: %1").arg(compressedPath));
|
LOG_MESSAGE(QString("Compression completed: %1").arg(compressedPath));
|
||||||
cleanupState();
|
|
||||||
emit compressionCompleted(compressedPath);
|
emit compressionCompleted(compressedPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,39 +218,6 @@ QString ChatCompressor::buildCompressionPrompt() const
|
|||||||
"Create the summary now:");
|
"Create the summary now:");
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChatCompressor::buildRequestPayload(
|
|
||||||
QJsonObject &payload, PluginLLMCore::PromptTemplate *promptTemplate)
|
|
||||||
{
|
|
||||||
PluginLLMCore::ContextData context;
|
|
||||||
|
|
||||||
context.systemPrompt = QStringLiteral(
|
|
||||||
"You are a helpful assistant that creates concise summaries of conversations. "
|
|
||||||
"Your summaries preserve key information, technical details, and the flow of discussion.");
|
|
||||||
|
|
||||||
QVector<PluginLLMCore::Message> messages;
|
|
||||||
for (const auto &msg : m_chatModel->getChatHistory()) {
|
|
||||||
if (msg.role == ChatModel::ChatRole::Tool
|
|
||||||
|| msg.role == ChatModel::ChatRole::FileEdit
|
|
||||||
|| msg.role == ChatModel::ChatRole::Thinking)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
PluginLLMCore::Message apiMessage;
|
|
||||||
apiMessage.role = (msg.role == ChatModel::ChatRole::User) ? "user" : "assistant";
|
|
||||||
apiMessage.content = msg.content;
|
|
||||||
messages.append(apiMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
PluginLLMCore::Message compressionRequest;
|
|
||||||
compressionRequest.role = "user";
|
|
||||||
compressionRequest.content = buildCompressionPrompt();
|
|
||||||
messages.append(compressionRequest);
|
|
||||||
|
|
||||||
context.history = messages;
|
|
||||||
|
|
||||||
m_provider->prepareRequest(
|
|
||||||
payload, promptTemplate, context, PluginLLMCore::RequestType::Chat, false, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ChatCompressor::createCompressedChatFile(
|
bool ChatCompressor::createCompressedChatFile(
|
||||||
const QString &sourcePath, const QString &destPath, const QString &summary)
|
const QString &sourcePath, const QString &destPath, const QString &summary)
|
||||||
{
|
{
|
||||||
@@ -247,32 +264,6 @@ bool ChatCompressor::createCompressedChatFile(
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChatCompressor::connectProviderSignals()
|
|
||||||
{
|
|
||||||
auto *c = m_provider->client();
|
|
||||||
|
|
||||||
m_connections.append(connect(
|
|
||||||
c,
|
|
||||||
&::LLMQore::BaseClient::chunkReceived,
|
|
||||||
this,
|
|
||||||
&ChatCompressor::onPartialResponseReceived,
|
|
||||||
Qt::UniqueConnection));
|
|
||||||
|
|
||||||
m_connections.append(connect(
|
|
||||||
c,
|
|
||||||
&::LLMQore::BaseClient::requestCompleted,
|
|
||||||
this,
|
|
||||||
&ChatCompressor::onFullResponseReceived,
|
|
||||||
Qt::UniqueConnection));
|
|
||||||
|
|
||||||
m_connections.append(connect(
|
|
||||||
c,
|
|
||||||
&::LLMQore::BaseClient::requestFailed,
|
|
||||||
this,
|
|
||||||
&ChatCompressor::onRequestFailed,
|
|
||||||
Qt::UniqueConnection));
|
|
||||||
}
|
|
||||||
|
|
||||||
void ChatCompressor::disconnectAllSignals()
|
void ChatCompressor::disconnectAllSignals()
|
||||||
{
|
{
|
||||||
for (const auto &connection : std::as_const(m_connections))
|
for (const auto &connection : std::as_const(m_connections))
|
||||||
@@ -284,12 +275,17 @@ void ChatCompressor::cleanupState()
|
|||||||
{
|
{
|
||||||
disconnectAllSignals();
|
disconnectAllSignals();
|
||||||
|
|
||||||
|
Session *session = m_session;
|
||||||
|
|
||||||
m_isCompressing = false;
|
m_isCompressing = false;
|
||||||
m_currentRequestId.clear();
|
m_currentRequestId.clear();
|
||||||
m_originalChatPath.clear();
|
m_originalChatPath.clear();
|
||||||
m_accumulatedSummary.clear();
|
m_accumulatedSummary.clear();
|
||||||
m_chatModel = nullptr;
|
m_chatModel = nullptr;
|
||||||
m_provider = nullptr;
|
m_session = nullptr;
|
||||||
|
|
||||||
|
if (session && m_sessionManager)
|
||||||
|
m_sessionManager->removeSession(session);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace QodeAssist::Chat
|
} // namespace QodeAssist::Chat
|
||||||
|
|||||||
@@ -4,15 +4,15 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <QJsonObject>
|
|
||||||
#include <QList>
|
#include <QList>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
#include <QPointer>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
|
||||||
namespace QodeAssist::PluginLLMCore {
|
namespace QodeAssist {
|
||||||
class Provider;
|
class SessionManager;
|
||||||
class PromptTemplate;
|
class Session;
|
||||||
} // namespace QodeAssist::PluginLLMCore
|
}
|
||||||
|
|
||||||
namespace QodeAssist::Chat {
|
namespace QodeAssist::Chat {
|
||||||
|
|
||||||
@@ -25,6 +25,9 @@ class ChatCompressor : public QObject
|
|||||||
public:
|
public:
|
||||||
explicit ChatCompressor(QObject *parent = nullptr);
|
explicit ChatCompressor(QObject *parent = nullptr);
|
||||||
|
|
||||||
|
void setSessionManager(SessionManager *sessionManager);
|
||||||
|
void setActiveAgent(const QString &agentName);
|
||||||
|
|
||||||
void startCompression(const QString &chatFilePath, ChatModel *chatModel);
|
void startCompression(const QString &chatFilePath, ChatModel *chatModel);
|
||||||
|
|
||||||
bool isCompressing() const;
|
bool isCompressing() const;
|
||||||
@@ -45,17 +48,17 @@ private:
|
|||||||
QString buildCompressionPrompt() const;
|
QString buildCompressionPrompt() const;
|
||||||
bool createCompressedChatFile(
|
bool createCompressedChatFile(
|
||||||
const QString &sourcePath, const QString &destPath, const QString &summary);
|
const QString &sourcePath, const QString &destPath, const QString &summary);
|
||||||
void connectProviderSignals();
|
|
||||||
void disconnectAllSignals();
|
void disconnectAllSignals();
|
||||||
void cleanupState();
|
void cleanupState();
|
||||||
void handleCompressionError(const QString &error);
|
void handleCompressionError(const QString &error);
|
||||||
void buildRequestPayload(QJsonObject &payload, PluginLLMCore::PromptTemplate *promptTemplate);
|
|
||||||
|
|
||||||
bool m_isCompressing = false;
|
bool m_isCompressing = false;
|
||||||
QString m_currentRequestId;
|
QString m_currentRequestId;
|
||||||
QString m_originalChatPath;
|
QString m_originalChatPath;
|
||||||
QString m_accumulatedSummary;
|
QString m_accumulatedSummary;
|
||||||
PluginLLMCore::Provider *m_provider = nullptr;
|
QPointer<SessionManager> m_sessionManager;
|
||||||
|
QString m_activeAgent;
|
||||||
|
QPointer<Session> m_session;
|
||||||
ChatModel *m_chatModel = nullptr;
|
ChatModel *m_chatModel = nullptr;
|
||||||
|
|
||||||
QList<QMetaObject::Connection> m_connections;
|
QList<QMetaObject::Connection> m_connections;
|
||||||
|
|||||||
@@ -1,100 +0,0 @@
|
|||||||
// Copyright (C) 2024-2026 Petr Mironychev
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
|
||||||
|
|
||||||
#include "ChatConfigurationController.hpp"
|
|
||||||
|
|
||||||
#include <utils/aspects.h>
|
|
||||||
|
|
||||||
#include "ConfigurationManager.hpp"
|
|
||||||
#include "GeneralSettings.hpp"
|
|
||||||
|
|
||||||
namespace QodeAssist::Chat {
|
|
||||||
|
|
||||||
ChatConfigurationController::ChatConfigurationController(QObject *parent)
|
|
||||||
: QObject(parent)
|
|
||||||
{
|
|
||||||
auto &settings = Settings::generalSettings();
|
|
||||||
connect(
|
|
||||||
&settings.caProvider,
|
|
||||||
&Utils::BaseAspect::changed,
|
|
||||||
this,
|
|
||||||
&ChatConfigurationController::updateCurrentConfiguration);
|
|
||||||
connect(
|
|
||||||
&settings.caModel,
|
|
||||||
&Utils::BaseAspect::changed,
|
|
||||||
this,
|
|
||||||
&ChatConfigurationController::updateCurrentConfiguration);
|
|
||||||
|
|
||||||
loadAvailableConfigurations();
|
|
||||||
}
|
|
||||||
|
|
||||||
QStringList ChatConfigurationController::availableConfigurations() const
|
|
||||||
{
|
|
||||||
return m_availableConfigurations;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString ChatConfigurationController::currentConfiguration() const
|
|
||||||
{
|
|
||||||
return m_currentConfiguration;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ChatConfigurationController::updateCurrentConfiguration()
|
|
||||||
{
|
|
||||||
auto &settings = Settings::generalSettings();
|
|
||||||
m_currentConfiguration
|
|
||||||
= QString("%1 - %2").arg(settings.caProvider.value(), settings.caModel.value());
|
|
||||||
emit currentConfigurationChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ChatConfigurationController::loadAvailableConfigurations()
|
|
||||||
{
|
|
||||||
auto &manager = Settings::ConfigurationManager::instance();
|
|
||||||
manager.loadConfigurations(Settings::ConfigurationType::Chat);
|
|
||||||
|
|
||||||
QVector<Settings::AIConfiguration> configs = manager.configurations(
|
|
||||||
Settings::ConfigurationType::Chat);
|
|
||||||
|
|
||||||
m_availableConfigurations.clear();
|
|
||||||
m_availableConfigurations.append(QObject::tr("Current Settings"));
|
|
||||||
|
|
||||||
for (const Settings::AIConfiguration &config : configs) {
|
|
||||||
m_availableConfigurations.append(config.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
updateCurrentConfiguration();
|
|
||||||
|
|
||||||
emit availableConfigurationsChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ChatConfigurationController::applyConfiguration(const QString &configName)
|
|
||||||
{
|
|
||||||
if (configName == QObject::tr("Current Settings")) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto &manager = Settings::ConfigurationManager::instance();
|
|
||||||
QVector<Settings::AIConfiguration> configs = manager.configurations(
|
|
||||||
Settings::ConfigurationType::Chat);
|
|
||||||
|
|
||||||
for (const Settings::AIConfiguration &config : configs) {
|
|
||||||
if (config.name == configName) {
|
|
||||||
auto &settings = Settings::generalSettings();
|
|
||||||
|
|
||||||
settings.caProvider.setValue(config.provider);
|
|
||||||
settings.caModel.setValue(config.model);
|
|
||||||
settings.caTemplate.setValue(config.templateName);
|
|
||||||
settings.caUrl.setValue(config.url);
|
|
||||||
settings.caCustomEndpoint.setValue(config.customEndpoint);
|
|
||||||
|
|
||||||
settings.writeSettings();
|
|
||||||
|
|
||||||
m_currentConfiguration = QString("%1 - %2").arg(config.provider, config.model);
|
|
||||||
emit currentConfigurationChanged();
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace QodeAssist::Chat
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
// Copyright (C) 2024-2026 Petr Mironychev
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <QObject>
|
|
||||||
#include <QStringList>
|
|
||||||
|
|
||||||
namespace QodeAssist::Chat {
|
|
||||||
|
|
||||||
class ChatConfigurationController : public QObject
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit ChatConfigurationController(QObject *parent = nullptr);
|
|
||||||
|
|
||||||
QStringList availableConfigurations() const;
|
|
||||||
QString currentConfiguration() const;
|
|
||||||
|
|
||||||
void loadAvailableConfigurations();
|
|
||||||
void applyConfiguration(const QString &configName);
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void availableConfigurationsChanged();
|
|
||||||
void currentConfigurationChanged();
|
|
||||||
|
|
||||||
private:
|
|
||||||
void updateCurrentConfiguration();
|
|
||||||
|
|
||||||
QStringList m_availableConfigurations;
|
|
||||||
QString m_currentConfiguration;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace QodeAssist::Chat
|
|
||||||
@@ -28,9 +28,11 @@
|
|||||||
|
|
||||||
#include "QodeAssistConstants.hpp"
|
#include "QodeAssistConstants.hpp"
|
||||||
|
|
||||||
#include "AgentRoleController.hpp"
|
#include <AgentFactory.hpp>
|
||||||
|
#include <SessionManager.hpp>
|
||||||
|
|
||||||
|
#include "ChatAgentController.hpp"
|
||||||
#include "ChatAssistantSettings.hpp"
|
#include "ChatAssistantSettings.hpp"
|
||||||
#include "ChatConfigurationController.hpp"
|
|
||||||
#include "ChatCompressor.hpp"
|
#include "ChatCompressor.hpp"
|
||||||
#include "ChatHistoryStore.hpp"
|
#include "ChatHistoryStore.hpp"
|
||||||
#include "FileEditController.hpp"
|
#include "FileEditController.hpp"
|
||||||
@@ -38,10 +40,8 @@
|
|||||||
#include "InputTokenCounter.hpp"
|
#include "InputTokenCounter.hpp"
|
||||||
#include "SettingsConstants.hpp"
|
#include "SettingsConstants.hpp"
|
||||||
#include "Logger.hpp"
|
#include "Logger.hpp"
|
||||||
#include "ProvidersManager.hpp"
|
|
||||||
#include "SessionFileRegistry.hpp"
|
#include "SessionFileRegistry.hpp"
|
||||||
#include "context/ContextManager.hpp"
|
#include "context/ContextManager.hpp"
|
||||||
#include "pluginllmcore/RulesLoader.hpp"
|
|
||||||
#include "ProjectSettings.hpp"
|
#include "ProjectSettings.hpp"
|
||||||
#include "SkillsSettings.hpp"
|
#include "SkillsSettings.hpp"
|
||||||
#include "sources/skills/SkillsManager.hpp"
|
#include "sources/skills/SkillsManager.hpp"
|
||||||
@@ -74,13 +74,11 @@ QKeySequence sendMessageKeySequence()
|
|||||||
ChatRootView::ChatRootView(QQuickItem *parent)
|
ChatRootView::ChatRootView(QQuickItem *parent)
|
||||||
: QQuickItem(parent)
|
: QQuickItem(parent)
|
||||||
, m_chatModel(new ChatModel(this))
|
, m_chatModel(new ChatModel(this))
|
||||||
, m_promptProvider(PluginLLMCore::PromptTemplateManager::instance())
|
, m_clientInterface(new ClientInterface(m_chatModel, this))
|
||||||
, m_clientInterface(new ClientInterface(m_chatModel, &m_promptProvider, this))
|
|
||||||
, m_fileManager(new ChatFileManager(this))
|
, m_fileManager(new ChatFileManager(this))
|
||||||
, m_isRequestInProgress(false)
|
, m_isRequestInProgress(false)
|
||||||
, m_chatCompressor(new ChatCompressor(this))
|
, m_chatCompressor(new ChatCompressor(this))
|
||||||
, m_agentRoleController(new AgentRoleController(this))
|
, m_agentController(new ChatAgentController(this))
|
||||||
, m_configurationController(new ChatConfigurationController(this))
|
|
||||||
, m_fileEditController(new FileEditController(m_chatModel, this))
|
, m_fileEditController(new FileEditController(m_chatModel, this))
|
||||||
, m_tokenCounter(
|
, m_tokenCounter(
|
||||||
new InputTokenCounter(m_chatModel, m_clientInterface->contextManager(), this))
|
new InputTokenCounter(m_chatModel, m_clientInterface->contextManager(), this))
|
||||||
@@ -109,22 +107,6 @@ ChatRootView::ChatRootView(QQuickItem *parent)
|
|||||||
},
|
},
|
||||||
Qt::QueuedConnection);
|
Qt::QueuedConnection);
|
||||||
|
|
||||||
auto &settings = Settings::generalSettings();
|
|
||||||
|
|
||||||
connect(
|
|
||||||
&settings.caModel, &Utils::BaseAspect::changed, this, &ChatRootView::currentTemplateChanged);
|
|
||||||
|
|
||||||
connect(
|
|
||||||
m_configurationController,
|
|
||||||
&ChatConfigurationController::availableConfigurationsChanged,
|
|
||||||
this,
|
|
||||||
&ChatRootView::availableConfigurationsChanged);
|
|
||||||
connect(
|
|
||||||
m_configurationController,
|
|
||||||
&ChatConfigurationController::currentConfigurationChanged,
|
|
||||||
this,
|
|
||||||
&ChatRootView::currentConfigurationChanged);
|
|
||||||
|
|
||||||
connect(
|
connect(
|
||||||
m_clientInterface,
|
m_clientInterface,
|
||||||
&ClientInterface::messageReceivedCompletely,
|
&ClientInterface::messageReceivedCompletely,
|
||||||
@@ -171,20 +153,20 @@ ChatRootView::ChatRootView(QQuickItem *parent)
|
|||||||
this,
|
this,
|
||||||
&ChatRootView::inputTokensCountChanged);
|
&ChatRootView::inputTokensCountChanged);
|
||||||
connect(
|
connect(
|
||||||
m_agentRoleController,
|
m_agentController,
|
||||||
&AgentRoleController::availableRolesChanged,
|
&ChatAgentController::availableAgentsChanged,
|
||||||
this,
|
this,
|
||||||
&ChatRootView::availableAgentRolesChanged);
|
&ChatRootView::availableChatAgentsChanged);
|
||||||
connect(
|
connect(
|
||||||
m_agentRoleController,
|
m_agentController,
|
||||||
&AgentRoleController::currentRoleChanged,
|
&ChatAgentController::currentAgentChanged,
|
||||||
this,
|
this,
|
||||||
&ChatRootView::currentAgentRoleChanged);
|
&ChatRootView::currentChatAgentChanged);
|
||||||
connect(
|
connect(
|
||||||
m_agentRoleController,
|
m_agentController,
|
||||||
&AgentRoleController::baseSystemPromptChanged,
|
&ChatAgentController::currentAgentChanged,
|
||||||
this,
|
this,
|
||||||
&ChatRootView::baseSystemPromptChanged);
|
&ChatRootView::isThinkingSupportChanged);
|
||||||
|
|
||||||
auto editors = Core::EditorManager::instance();
|
auto editors = Core::EditorManager::instance();
|
||||||
|
|
||||||
@@ -266,14 +248,6 @@ ChatRootView::ChatRootView(QQuickItem *parent)
|
|||||||
connect(
|
connect(
|
||||||
m_historyStore, &ChatHistoryStore::loadRequested, this, &ChatRootView::loadHistory);
|
m_historyStore, &ChatHistoryStore::loadRequested, this, &ChatRootView::loadHistory);
|
||||||
|
|
||||||
refreshRules();
|
|
||||||
|
|
||||||
connect(
|
|
||||||
ProjectExplorer::ProjectManager::instance(),
|
|
||||||
&ProjectExplorer::ProjectManager::startupProjectChanged,
|
|
||||||
this,
|
|
||||||
&ChatRootView::refreshRules);
|
|
||||||
|
|
||||||
connect(
|
connect(
|
||||||
ProjectExplorer::ProjectManager::instance(),
|
ProjectExplorer::ProjectManager::instance(),
|
||||||
&ProjectExplorer::ProjectManager::projectAdded,
|
&ProjectExplorer::ProjectManager::projectAdded,
|
||||||
@@ -298,12 +272,6 @@ ChatRootView::ChatRootView(QQuickItem *parent)
|
|||||||
this,
|
this,
|
||||||
&ChatRootView::useThinkingChanged);
|
&ChatRootView::useThinkingChanged);
|
||||||
|
|
||||||
connect(
|
|
||||||
&Settings::generalSettings().caProvider,
|
|
||||||
&Utils::BaseAspect::changed,
|
|
||||||
this,
|
|
||||||
&ChatRootView::isThinkingSupportChanged);
|
|
||||||
|
|
||||||
connect(m_fileManager, &ChatFileManager::fileOperationFailed, this, [this](const QString &error) {
|
connect(m_fileManager, &ChatFileManager::fileOperationFailed, this, [this](const QString &error) {
|
||||||
m_lastErrorMessage = error;
|
m_lastErrorMessage = error;
|
||||||
emit lastErrorMessageChanged();
|
emit lastErrorMessageChanged();
|
||||||
@@ -373,6 +341,48 @@ Skills::SkillsManager *ChatRootView::skillsManager() const
|
|||||||
return m_skillsManager;
|
return m_skillsManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AgentFactory *ChatRootView::agentFactory() const
|
||||||
|
{
|
||||||
|
if (!m_agentFactory) {
|
||||||
|
if (auto *engine = qmlEngine(this)) {
|
||||||
|
m_agentFactory = qobject_cast<AgentFactory *>(
|
||||||
|
engine->rootContext()->contextProperty("agentFactory").value<QObject *>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return m_agentFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
SessionManager *ChatRootView::sessionManager() const
|
||||||
|
{
|
||||||
|
if (!m_sessionManager) {
|
||||||
|
if (auto *engine = qmlEngine(this)) {
|
||||||
|
m_sessionManager = qobject_cast<SessionManager *>(
|
||||||
|
engine->rootContext()->contextProperty("sessionManager").value<QObject *>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return m_sessionManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatRootView::loadAvailableChatAgents()
|
||||||
|
{
|
||||||
|
m_agentController->setAgentFactory(agentFactory());
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList ChatRootView::availableChatAgents() const
|
||||||
|
{
|
||||||
|
return m_agentController->availableAgents();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ChatRootView::currentChatAgent() const
|
||||||
|
{
|
||||||
|
return m_agentController->currentAgent();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatRootView::setCurrentChatAgent(const QString &name)
|
||||||
|
{
|
||||||
|
m_agentController->setCurrentAgent(name);
|
||||||
|
}
|
||||||
|
|
||||||
QVariantList ChatRootView::searchSkills(const QString &query) const
|
QVariantList ChatRootView::searchSkills(const QString &query) const
|
||||||
{
|
{
|
||||||
QVariantList results;
|
QVariantList results;
|
||||||
@@ -380,7 +390,7 @@ QVariantList ChatRootView::searchSkills(const QString &query) const
|
|||||||
if (!manager || !Settings::skillsSettings().enableSkills())
|
if (!manager || !Settings::skillsSettings().enableSkills())
|
||||||
return results;
|
return results;
|
||||||
|
|
||||||
auto *project = PluginLLMCore::RulesLoader::getActiveProject();
|
auto *project = ProjectExplorer::ProjectManager::startupProject();
|
||||||
QStringList projectSkillDirs;
|
QStringList projectSkillDirs;
|
||||||
if (project) {
|
if (project) {
|
||||||
Settings::ProjectSettings projectSettings(project);
|
Settings::ProjectSettings projectSettings(project);
|
||||||
@@ -481,7 +491,12 @@ void ChatRootView::dispatchSend(
|
|||||||
|
|
||||||
m_tokenCounter->recordSent();
|
m_tokenCounter->recordSent();
|
||||||
|
|
||||||
|
if (currentChatAgent().isEmpty())
|
||||||
|
loadAvailableChatAgents();
|
||||||
|
|
||||||
m_clientInterface->setSkillsManager(skillsManager());
|
m_clientInterface->setSkillsManager(skillsManager());
|
||||||
|
m_clientInterface->setSessionManager(sessionManager());
|
||||||
|
m_clientInterface->setActiveAgent(currentChatAgent());
|
||||||
m_clientInterface->sendMessage(message, attachments, linkedFiles, useToolsArg, useThinkingArg);
|
m_clientInterface->sendMessage(message, attachments, linkedFiles, useToolsArg, useThinkingArg);
|
||||||
|
|
||||||
m_fileManager->clearIntermediateStorage();
|
m_fileManager->clearIntermediateStorage();
|
||||||
@@ -527,12 +542,6 @@ void ChatRootView::clearMessages()
|
|||||||
clearLinkedFiles();
|
clearLinkedFiles();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString ChatRootView::currentTemplate() const
|
|
||||||
{
|
|
||||||
auto &settings = Settings::generalSettings();
|
|
||||||
return settings.caModel();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ChatRootView::saveHistory(const QString &filePath)
|
void ChatRootView::saveHistory(const QString &filePath)
|
||||||
{
|
{
|
||||||
if (filePath != m_recentFilePath) {
|
if (filePath != m_recentFilePath) {
|
||||||
@@ -821,25 +830,6 @@ void ChatRootView::openChatHistoryFolder()
|
|||||||
m_historyStore->openHistoryFolder();
|
m_historyStore->openHistoryFolder();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChatRootView::openRulesFolder()
|
|
||||||
{
|
|
||||||
auto project = ProjectExplorer::ProjectManager::startupProject();
|
|
||||||
if (!project) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString projectPath = project->projectDirectory().toFSPathString();
|
|
||||||
QString rulesPath = QDir(projectPath).filePath(".qodeassist/rules");
|
|
||||||
|
|
||||||
QDir dir(rulesPath);
|
|
||||||
if (!dir.exists()) {
|
|
||||||
dir.mkpath(".");
|
|
||||||
}
|
|
||||||
|
|
||||||
QUrl url = QUrl::fromLocalFile(dir.absolutePath());
|
|
||||||
QDesktopServices::openUrl(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ChatRootView::openSettings()
|
void ChatRootView::openSettings()
|
||||||
{
|
{
|
||||||
QMetaObject::invokeMethod(
|
QMetaObject::invokeMethod(
|
||||||
@@ -1120,51 +1110,6 @@ QString ChatRootView::lastErrorMessage() const
|
|||||||
return m_lastErrorMessage;
|
return m_lastErrorMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariantList ChatRootView::activeRules() const
|
|
||||||
{
|
|
||||||
return m_activeRules;
|
|
||||||
}
|
|
||||||
|
|
||||||
int ChatRootView::activeRulesCount() const
|
|
||||||
{
|
|
||||||
return m_activeRules.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
QString ChatRootView::getRuleContent(int index)
|
|
||||||
{
|
|
||||||
if (index < 0 || index >= m_activeRules.size())
|
|
||||||
return QString();
|
|
||||||
|
|
||||||
return PluginLLMCore::RulesLoader::loadRuleFileContent(
|
|
||||||
m_activeRules[index].toMap()["filePath"].toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
void ChatRootView::refreshRules()
|
|
||||||
{
|
|
||||||
m_activeRules.clear();
|
|
||||||
|
|
||||||
auto project = PluginLLMCore::RulesLoader::getActiveProject();
|
|
||||||
if (!project) {
|
|
||||||
emit activeRulesChanged();
|
|
||||||
emit activeRulesCountChanged();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto ruleFiles
|
|
||||||
= PluginLLMCore::RulesLoader::getRuleFilesForProject(project, PluginLLMCore::RulesContext::Chat);
|
|
||||||
|
|
||||||
for (const auto &ruleFile : ruleFiles) {
|
|
||||||
QVariantMap ruleMap;
|
|
||||||
ruleMap["filePath"] = ruleFile.filePath;
|
|
||||||
ruleMap["fileName"] = ruleFile.fileName;
|
|
||||||
ruleMap["category"] = ruleFile.category;
|
|
||||||
m_activeRules.append(ruleMap);
|
|
||||||
}
|
|
||||||
|
|
||||||
emit activeRulesChanged();
|
|
||||||
emit activeRulesCountChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ChatRootView::useTools() const
|
bool ChatRootView::useTools() const
|
||||||
{
|
{
|
||||||
return Settings::chatAssistantSettings().enableChatTools();
|
return Settings::chatAssistantSettings().enableChatTools();
|
||||||
@@ -1249,10 +1194,7 @@ QString ChatRootView::lastInfoMessage() const
|
|||||||
|
|
||||||
bool ChatRootView::isThinkingSupport() const
|
bool ChatRootView::isThinkingSupport() const
|
||||||
{
|
{
|
||||||
auto providerName = Settings::generalSettings().caProvider();
|
return m_agentController->currentSupportsThinking();
|
||||||
auto provider = PluginLLMCore::ProvidersManager::instance().getProviderByName(providerName);
|
|
||||||
|
|
||||||
return provider && provider->capabilities().testFlag(PluginLLMCore::ProviderCapability::Thinking);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ChatRootView::hasImageAttachments(const QStringList &attachments) const
|
bool ChatRootView::hasImageAttachments(const QStringList &attachments) const
|
||||||
@@ -1273,66 +1215,6 @@ bool ChatRootView::isImageFile(const QString &filePath) const
|
|||||||
return imageExtensions.contains(fileInfo.suffix().toLower());
|
return imageExtensions.contains(fileInfo.suffix().toLower());
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChatRootView::loadAvailableConfigurations()
|
|
||||||
{
|
|
||||||
m_configurationController->loadAvailableConfigurations();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ChatRootView::applyConfiguration(const QString &configName)
|
|
||||||
{
|
|
||||||
m_configurationController->applyConfiguration(configName);
|
|
||||||
}
|
|
||||||
|
|
||||||
QStringList ChatRootView::availableConfigurations() const
|
|
||||||
{
|
|
||||||
return m_configurationController->availableConfigurations();
|
|
||||||
}
|
|
||||||
|
|
||||||
QString ChatRootView::currentConfiguration() const
|
|
||||||
{
|
|
||||||
return m_configurationController->currentConfiguration();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ChatRootView::loadAvailableAgentRoles()
|
|
||||||
{
|
|
||||||
m_agentRoleController->loadAvailableRoles();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ChatRootView::applyAgentRole(const QString &roleName)
|
|
||||||
{
|
|
||||||
m_agentRoleController->applyRole(roleName);
|
|
||||||
}
|
|
||||||
|
|
||||||
QStringList ChatRootView::availableAgentRoles() const
|
|
||||||
{
|
|
||||||
return m_agentRoleController->availableRoles();
|
|
||||||
}
|
|
||||||
|
|
||||||
QString ChatRootView::currentAgentRole() const
|
|
||||||
{
|
|
||||||
return m_agentRoleController->currentRole();
|
|
||||||
}
|
|
||||||
|
|
||||||
QString ChatRootView::baseSystemPrompt() const
|
|
||||||
{
|
|
||||||
return m_agentRoleController->baseSystemPrompt();
|
|
||||||
}
|
|
||||||
|
|
||||||
QString ChatRootView::currentAgentRoleDescription() const
|
|
||||||
{
|
|
||||||
return m_agentRoleController->currentRoleDescription();
|
|
||||||
}
|
|
||||||
|
|
||||||
QString ChatRootView::currentAgentRoleSystemPrompt() const
|
|
||||||
{
|
|
||||||
return m_agentRoleController->currentRoleSystemPrompt();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ChatRootView::openAgentRolesSettings()
|
|
||||||
{
|
|
||||||
m_agentRoleController->openSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ChatRootView::compressCurrentChat()
|
void ChatRootView::compressCurrentChat()
|
||||||
{
|
{
|
||||||
if (m_chatCompressor->isCompressing()) {
|
if (m_chatCompressor->isCompressing()) {
|
||||||
@@ -1349,6 +1231,10 @@ void ChatRootView::compressCurrentChat()
|
|||||||
|
|
||||||
autosave();
|
autosave();
|
||||||
|
|
||||||
|
if (currentChatAgent().isEmpty())
|
||||||
|
loadAvailableChatAgents();
|
||||||
|
m_chatCompressor->setSessionManager(sessionManager());
|
||||||
|
m_chatCompressor->setActiveAgent(currentChatAgent());
|
||||||
m_chatCompressor->startCompression(m_recentFilePath, m_chatModel);
|
m_chatCompressor->startCompression(m_recentFilePath, m_chatModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,18 +11,21 @@
|
|||||||
#include "ChatFileManager.hpp"
|
#include "ChatFileManager.hpp"
|
||||||
#include "ChatModel.hpp"
|
#include "ChatModel.hpp"
|
||||||
#include "ClientInterface.hpp"
|
#include "ClientInterface.hpp"
|
||||||
#include "pluginllmcore/PromptProviderChat.hpp"
|
|
||||||
#include <coreplugin/editormanager/editormanager.h>
|
#include <coreplugin/editormanager/editormanager.h>
|
||||||
|
|
||||||
namespace QodeAssist::Skills {
|
namespace QodeAssist::Skills {
|
||||||
class SkillsManager;
|
class SkillsManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace QodeAssist {
|
||||||
|
class AgentFactory;
|
||||||
|
class SessionManager;
|
||||||
|
}
|
||||||
|
|
||||||
namespace QodeAssist::Chat {
|
namespace QodeAssist::Chat {
|
||||||
|
|
||||||
class ChatCompressor;
|
class ChatCompressor;
|
||||||
class AgentRoleController;
|
class ChatAgentController;
|
||||||
class ChatConfigurationController;
|
|
||||||
class FileEditController;
|
class FileEditController;
|
||||||
class InputTokenCounter;
|
class InputTokenCounter;
|
||||||
class ChatHistoryStore;
|
class ChatHistoryStore;
|
||||||
@@ -32,7 +35,6 @@ class ChatRootView : public QQuickItem
|
|||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_PROPERTY(QodeAssist::Chat::ChatModel *chatModel READ chatModel NOTIFY chatModelChanged FINAL)
|
Q_PROPERTY(QodeAssist::Chat::ChatModel *chatModel READ chatModel NOTIFY chatModelChanged FINAL)
|
||||||
Q_PROPERTY(QString currentTemplate READ currentTemplate NOTIFY currentTemplateChanged FINAL)
|
|
||||||
Q_PROPERTY(bool isSyncOpenFiles READ isSyncOpenFiles NOTIFY isSyncOpenFilesChanged FINAL)
|
Q_PROPERTY(bool isSyncOpenFiles READ isSyncOpenFiles NOTIFY isSyncOpenFilesChanged FINAL)
|
||||||
Q_PROPERTY(QStringList attachmentFiles READ attachmentFiles NOTIFY attachmentFilesChanged FINAL)
|
Q_PROPERTY(QStringList attachmentFiles READ attachmentFiles NOTIFY attachmentFilesChanged FINAL)
|
||||||
Q_PROPERTY(QStringList linkedFiles READ linkedFiles NOTIFY linkedFilesChanged FINAL)
|
Q_PROPERTY(QStringList linkedFiles READ linkedFiles NOTIFY linkedFilesChanged FINAL)
|
||||||
@@ -46,8 +48,6 @@ class ChatRootView : public QQuickItem
|
|||||||
Q_PROPERTY(bool isRequestInProgress READ isRequestInProgress NOTIFY isRequestInProgressChanged FINAL)
|
Q_PROPERTY(bool isRequestInProgress READ isRequestInProgress NOTIFY isRequestInProgressChanged FINAL)
|
||||||
Q_PROPERTY(QString lastErrorMessage READ lastErrorMessage NOTIFY lastErrorMessageChanged FINAL)
|
Q_PROPERTY(QString lastErrorMessage READ lastErrorMessage NOTIFY lastErrorMessageChanged FINAL)
|
||||||
Q_PROPERTY(QString lastInfoMessage READ lastInfoMessage NOTIFY lastInfoMessageChanged FINAL)
|
Q_PROPERTY(QString lastInfoMessage READ lastInfoMessage NOTIFY lastInfoMessageChanged FINAL)
|
||||||
Q_PROPERTY(QVariantList activeRules READ activeRules NOTIFY activeRulesChanged 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(QString sendShortcutText READ sendShortcutText NOTIFY sendShortcutTextChanged FINAL)
|
||||||
@@ -57,13 +57,8 @@ class ChatRootView : public QQuickItem
|
|||||||
Q_PROPERTY(int currentMessagePendingEdits READ currentMessagePendingEdits NOTIFY currentMessageEditsStatsChanged FINAL)
|
Q_PROPERTY(int currentMessagePendingEdits READ currentMessagePendingEdits NOTIFY currentMessageEditsStatsChanged FINAL)
|
||||||
Q_PROPERTY(int currentMessageRejectedEdits READ currentMessageRejectedEdits NOTIFY currentMessageEditsStatsChanged FINAL)
|
Q_PROPERTY(int currentMessageRejectedEdits READ currentMessageRejectedEdits NOTIFY currentMessageEditsStatsChanged FINAL)
|
||||||
Q_PROPERTY(bool isThinkingSupport READ isThinkingSupport NOTIFY isThinkingSupportChanged FINAL)
|
Q_PROPERTY(bool isThinkingSupport READ isThinkingSupport NOTIFY isThinkingSupportChanged FINAL)
|
||||||
Q_PROPERTY(QStringList availableConfigurations READ availableConfigurations NOTIFY availableConfigurationsChanged FINAL)
|
Q_PROPERTY(QStringList availableChatAgents READ availableChatAgents NOTIFY availableChatAgentsChanged FINAL)
|
||||||
Q_PROPERTY(QString currentConfiguration READ currentConfiguration NOTIFY currentConfigurationChanged FINAL)
|
Q_PROPERTY(QString currentChatAgent READ currentChatAgent WRITE setCurrentChatAgent NOTIFY currentChatAgentChanged FINAL)
|
||||||
Q_PROPERTY(QStringList availableAgentRoles READ availableAgentRoles NOTIFY availableAgentRolesChanged FINAL)
|
|
||||||
Q_PROPERTY(QString currentAgentRole READ currentAgentRole NOTIFY currentAgentRoleChanged FINAL)
|
|
||||||
Q_PROPERTY(QString baseSystemPrompt READ baseSystemPrompt NOTIFY baseSystemPromptChanged FINAL)
|
|
||||||
Q_PROPERTY(QString currentAgentRoleDescription READ currentAgentRoleDescription 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(bool isInEditor READ isInEditor NOTIFY isInEditorChanged FINAL)
|
||||||
Q_PROPERTY(QString chatTitle READ chatTitle NOTIFY chatTitleChanged FINAL)
|
Q_PROPERTY(QString chatTitle READ chatTitle NOTIFY chatTitleChanged FINAL)
|
||||||
@@ -75,7 +70,6 @@ public:
|
|||||||
~ChatRootView() override;
|
~ChatRootView() override;
|
||||||
|
|
||||||
ChatModel *chatModel() const;
|
ChatModel *chatModel() const;
|
||||||
QString currentTemplate() const;
|
|
||||||
|
|
||||||
void saveHistory(const QString &filePath);
|
void saveHistory(const QString &filePath);
|
||||||
void loadHistory(const QString &filePath);
|
void loadHistory(const QString &filePath);
|
||||||
@@ -104,7 +98,6 @@ public:
|
|||||||
QString sendShortcutText() 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 openSettings();
|
Q_INVOKABLE void openSettings();
|
||||||
|
|
||||||
Q_INVOKABLE void openFileInEditor(const QString &filePath);
|
Q_INVOKABLE void openFileInEditor(const QString &filePath);
|
||||||
@@ -139,11 +132,6 @@ public:
|
|||||||
void setRequestProgressStatus(bool state);
|
void setRequestProgressStatus(bool state);
|
||||||
|
|
||||||
QString lastErrorMessage() const;
|
QString lastErrorMessage() const;
|
||||||
|
|
||||||
QVariantList activeRules() const;
|
|
||||||
int activeRulesCount() const;
|
|
||||||
Q_INVOKABLE QString getRuleContent(int index);
|
|
||||||
Q_INVOKABLE void refreshRules();
|
|
||||||
|
|
||||||
Q_INVOKABLE QVariantList searchSkills(const QString &query) const;
|
Q_INVOKABLE QVariantList searchSkills(const QString &query) const;
|
||||||
|
|
||||||
@@ -161,23 +149,14 @@ public:
|
|||||||
Q_INVOKABLE void undoAllFileEditsForCurrentMessage();
|
Q_INVOKABLE void undoAllFileEditsForCurrentMessage();
|
||||||
Q_INVOKABLE void updateCurrentMessageEditsStats();
|
Q_INVOKABLE void updateCurrentMessageEditsStats();
|
||||||
|
|
||||||
Q_INVOKABLE void loadAvailableConfigurations();
|
|
||||||
Q_INVOKABLE void applyConfiguration(const QString &configName);
|
|
||||||
QStringList availableConfigurations() const;
|
|
||||||
QString currentConfiguration() const;
|
|
||||||
|
|
||||||
Q_INVOKABLE void compressCurrentChat();
|
Q_INVOKABLE void compressCurrentChat();
|
||||||
Q_INVOKABLE void cancelCompression();
|
Q_INVOKABLE void cancelCompression();
|
||||||
|
|
||||||
Q_INVOKABLE void loadAvailableAgentRoles();
|
Q_INVOKABLE void loadAvailableChatAgents();
|
||||||
Q_INVOKABLE void applyAgentRole(const QString &roleId);
|
QStringList availableChatAgents() const;
|
||||||
Q_INVOKABLE void openAgentRolesSettings();
|
QString currentChatAgent() const;
|
||||||
QStringList availableAgentRoles() const;
|
void setCurrentChatAgent(const QString &name);
|
||||||
QString currentAgentRole() const;
|
|
||||||
QString baseSystemPrompt() const;
|
|
||||||
QString currentAgentRoleDescription() const;
|
|
||||||
QString currentAgentRoleSystemPrompt() const;
|
|
||||||
|
|
||||||
int currentMessageTotalEdits() const;
|
int currentMessageTotalEdits() const;
|
||||||
int currentMessageAppliedEdits() const;
|
int currentMessageAppliedEdits() const;
|
||||||
int currentMessagePendingEdits() const;
|
int currentMessagePendingEdits() const;
|
||||||
@@ -206,7 +185,6 @@ public slots:
|
|||||||
|
|
||||||
signals:
|
signals:
|
||||||
void chatModelChanged();
|
void chatModelChanged();
|
||||||
void currentTemplateChanged();
|
|
||||||
void attachmentFilesChanged();
|
void attachmentFilesChanged();
|
||||||
void linkedFilesChanged();
|
void linkedFilesChanged();
|
||||||
void inputTokensCountChanged();
|
void inputTokensCountChanged();
|
||||||
@@ -223,20 +201,15 @@ signals:
|
|||||||
void lastErrorMessageChanged();
|
void lastErrorMessageChanged();
|
||||||
void lastInfoMessageChanged();
|
void lastInfoMessageChanged();
|
||||||
void sendShortcutTextChanged();
|
void sendShortcutTextChanged();
|
||||||
void activeRulesChanged();
|
|
||||||
void activeRulesCountChanged();
|
|
||||||
|
|
||||||
void useToolsChanged();
|
void useToolsChanged();
|
||||||
void useThinkingChanged();
|
void useThinkingChanged();
|
||||||
void currentMessageEditsStatsChanged();
|
void currentMessageEditsStatsChanged();
|
||||||
|
|
||||||
void isThinkingSupportChanged();
|
void isThinkingSupportChanged();
|
||||||
void availableConfigurationsChanged();
|
|
||||||
void currentConfigurationChanged();
|
|
||||||
|
|
||||||
void availableAgentRolesChanged();
|
void availableChatAgentsChanged();
|
||||||
void currentAgentRoleChanged();
|
void currentChatAgentChanged();
|
||||||
void baseSystemPromptChanged();
|
|
||||||
|
|
||||||
void isCompressingChanged();
|
void isCompressingChanged();
|
||||||
void compressionCompleted(const QString &compressedChatPath);
|
void compressionCompleted(const QString &compressedChatPath);
|
||||||
@@ -269,12 +242,12 @@ private:
|
|||||||
|
|
||||||
SessionFileRegistry *sessionFileRegistry() const;
|
SessionFileRegistry *sessionFileRegistry() const;
|
||||||
Skills::SkillsManager *skillsManager() const;
|
Skills::SkillsManager *skillsManager() const;
|
||||||
|
AgentFactory *agentFactory() const;
|
||||||
|
SessionManager *sessionManager() const;
|
||||||
|
|
||||||
ChatModel *m_chatModel;
|
ChatModel *m_chatModel;
|
||||||
PluginLLMCore::PromptProviderChat m_promptProvider;
|
|
||||||
ClientInterface *m_clientInterface;
|
ClientInterface *m_clientInterface;
|
||||||
ChatFileManager *m_fileManager;
|
ChatFileManager *m_fileManager;
|
||||||
QString m_currentTemplate;
|
|
||||||
QString m_recentFilePath;
|
QString m_recentFilePath;
|
||||||
QStringList m_attachmentFiles;
|
QStringList m_attachmentFiles;
|
||||||
QStringList m_linkedFiles;
|
QStringList m_linkedFiles;
|
||||||
@@ -294,13 +267,11 @@ private:
|
|||||||
QList<Core::IEditor *> m_currentEditors;
|
QList<Core::IEditor *> m_currentEditors;
|
||||||
bool m_isRequestInProgress;
|
bool m_isRequestInProgress;
|
||||||
QString m_lastErrorMessage;
|
QString m_lastErrorMessage;
|
||||||
QVariantList m_activeRules;
|
|
||||||
|
|
||||||
QString m_lastInfoMessage;
|
QString m_lastInfoMessage;
|
||||||
|
|
||||||
ChatCompressor *m_chatCompressor;
|
ChatCompressor *m_chatCompressor;
|
||||||
AgentRoleController *m_agentRoleController;
|
ChatAgentController *m_agentController;
|
||||||
ChatConfigurationController *m_configurationController;
|
|
||||||
FileEditController *m_fileEditController;
|
FileEditController *m_fileEditController;
|
||||||
InputTokenCounter *m_tokenCounter;
|
InputTokenCounter *m_tokenCounter;
|
||||||
ChatHistoryStore *m_historyStore;
|
ChatHistoryStore *m_historyStore;
|
||||||
@@ -308,6 +279,8 @@ private:
|
|||||||
mutable bool m_sessionFileRegistryResolved = false;
|
mutable bool m_sessionFileRegistryResolved = false;
|
||||||
mutable QPointer<Skills::SkillsManager> m_skillsManager;
|
mutable QPointer<Skills::SkillsManager> m_skillsManager;
|
||||||
mutable bool m_skillsManagerResolved = false;
|
mutable bool m_skillsManagerResolved = false;
|
||||||
|
mutable QPointer<AgentFactory> m_agentFactory;
|
||||||
|
mutable QPointer<SessionManager> m_sessionManager;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace QodeAssist::Chat
|
} // namespace QodeAssist::Chat
|
||||||
|
|||||||
@@ -4,65 +4,75 @@
|
|||||||
|
|
||||||
#include "ClientInterface.hpp"
|
#include "ClientInterface.hpp"
|
||||||
|
|
||||||
#include <LLMQore/BaseClient.hpp>
|
#include <memory>
|
||||||
|
|
||||||
|
#include <LLMQore/BaseClient.hpp>
|
||||||
|
#include <LLMQore/ContentBlocks.hpp>
|
||||||
|
#include <LLMQore/ToolsManager.hpp>
|
||||||
|
|
||||||
|
#include <coreplugin/editormanager/editormanager.h>
|
||||||
|
#include <coreplugin/editormanager/ieditor.h>
|
||||||
|
#include <coreplugin/idocument.h>
|
||||||
#include <projectexplorer/buildconfiguration.h>
|
#include <projectexplorer/buildconfiguration.h>
|
||||||
|
#include <projectexplorer/project.h>
|
||||||
|
#include <projectexplorer/projectmanager.h>
|
||||||
#include <projectexplorer/target.h>
|
#include <projectexplorer/target.h>
|
||||||
#include <texteditor/textdocument.h>
|
#include <texteditor/textdocument.h>
|
||||||
|
#include <texteditor/texteditor.h>
|
||||||
|
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QImageReader>
|
|
||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QMimeDatabase>
|
#include <QMimeDatabase>
|
||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
#include <QUuid>
|
#include <QUuid>
|
||||||
|
|
||||||
#include <coreplugin/editormanager/editormanager.h>
|
#include <ConversationHistory.hpp>
|
||||||
#include <coreplugin/editormanager/ieditor.h>
|
#include <Message.hpp>
|
||||||
#include <coreplugin/idocument.h>
|
#include <Session.hpp>
|
||||||
#include <projectexplorer/project.h>
|
#include <SessionManager.hpp>
|
||||||
#include <projectexplorer/projectexplorer.h>
|
#include <SystemPromptBuilder.hpp>
|
||||||
#include <projectexplorer/projectmanager.h>
|
|
||||||
|
|
||||||
#include <texteditor/textdocument.h>
|
|
||||||
#include <texteditor/texteditor.h>
|
|
||||||
|
|
||||||
#include <LLMQore/ToolsManager.hpp>
|
|
||||||
|
|
||||||
#include "tools/ReadOriginalHistoryTool.hpp"
|
#include "tools/ReadOriginalHistoryTool.hpp"
|
||||||
#include "tools/TodoTool.hpp"
|
#include "tools/TodoTool.hpp"
|
||||||
|
#include "tools/ToolsRegistration.hpp"
|
||||||
|
|
||||||
#include "ChatAssistantSettings.hpp"
|
|
||||||
#include "ChatSerializer.hpp"
|
#include "ChatSerializer.hpp"
|
||||||
#include "GeneralSettings.hpp"
|
#include "GeneralSettings.hpp"
|
||||||
#include "Logger.hpp"
|
#include "Logger.hpp"
|
||||||
#include "ProjectSettings.hpp"
|
#include "ProjectSettings.hpp"
|
||||||
#include "ProvidersManager.hpp"
|
|
||||||
#include "SkillsSettings.hpp"
|
#include "SkillsSettings.hpp"
|
||||||
#include "ToolsSettings.hpp"
|
#include "ToolsSettings.hpp"
|
||||||
#include <RulesLoader.hpp>
|
|
||||||
#include <context/ChangesManager.h>
|
#include <context/ChangesManager.h>
|
||||||
#include <sources/skills/SkillsManager.hpp>
|
#include <sources/skills/SkillsManager.hpp>
|
||||||
|
|
||||||
namespace QodeAssist::Chat {
|
namespace QodeAssist::Chat {
|
||||||
|
|
||||||
ClientInterface::ClientInterface(
|
ClientInterface::ClientInterface(ChatModel *chatModel, QObject *parent)
|
||||||
ChatModel *chatModel, PluginLLMCore::IPromptProvider *promptProvider, QObject *parent)
|
|
||||||
: QObject(parent)
|
: QObject(parent)
|
||||||
, m_promptProvider(promptProvider)
|
|
||||||
, m_chatModel(chatModel)
|
, m_chatModel(chatModel)
|
||||||
, m_contextManager(new Context::ContextManager(this))
|
, m_contextManager(new Context::ContextManager(this))
|
||||||
{}
|
{}
|
||||||
|
|
||||||
|
ClientInterface::~ClientInterface()
|
||||||
|
{
|
||||||
|
cancelRequest();
|
||||||
|
}
|
||||||
|
|
||||||
void ClientInterface::setSkillsManager(Skills::SkillsManager *skillsManager)
|
void ClientInterface::setSkillsManager(Skills::SkillsManager *skillsManager)
|
||||||
{
|
{
|
||||||
m_skillsManager = skillsManager;
|
m_skillsManager = skillsManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
ClientInterface::~ClientInterface()
|
void ClientInterface::setSessionManager(SessionManager *sessionManager)
|
||||||
{
|
{
|
||||||
cancelRequest();
|
m_sessionManager = sessionManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClientInterface::setActiveAgent(const QString &agentName)
|
||||||
|
{
|
||||||
|
m_activeAgent = agentName;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClientInterface::sendMessage(
|
void ClientInterface::sendMessage(
|
||||||
@@ -72,6 +82,8 @@ void ClientInterface::sendMessage(
|
|||||||
bool useTools,
|
bool useTools,
|
||||||
bool useThinking)
|
bool useThinking)
|
||||||
{
|
{
|
||||||
|
Q_UNUSED(useThinking)
|
||||||
|
|
||||||
if (message.trimmed().isEmpty() && attachments.isEmpty()) {
|
if (message.trimmed().isEmpty() && attachments.isEmpty()) {
|
||||||
LOG_MESSAGE("Ignoring empty chat message");
|
LOG_MESSAGE("Ignoring empty chat message");
|
||||||
return;
|
return;
|
||||||
@@ -84,13 +96,11 @@ void ClientInterface::sendMessage(
|
|||||||
|
|
||||||
QList<QString> imageFiles;
|
QList<QString> imageFiles;
|
||||||
QList<QString> textFiles;
|
QList<QString> textFiles;
|
||||||
|
|
||||||
for (const QString &filePath : attachments) {
|
for (const QString &filePath : attachments) {
|
||||||
if (isImageFile(filePath)) {
|
if (isImageFile(filePath))
|
||||||
imageFiles.append(filePath);
|
imageFiles.append(filePath);
|
||||||
} else {
|
else
|
||||||
textFiles.append(filePath);
|
textFiles.append(filePath);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<Context::ContentFile> storedAttachments;
|
QList<Context::ContentFile> storedAttachments;
|
||||||
@@ -116,9 +126,8 @@ void ClientInterface::sendMessage(
|
|||||||
if (!imageFiles.isEmpty() && !m_chatFilePath.isEmpty()) {
|
if (!imageFiles.isEmpty() && !m_chatFilePath.isEmpty()) {
|
||||||
for (const QString &imagePath : imageFiles) {
|
for (const QString &imagePath : imageFiles) {
|
||||||
QString base64Data = encodeImageToBase64(imagePath);
|
QString base64Data = encodeImageToBase64(imagePath);
|
||||||
if (base64Data.isEmpty()) {
|
if (base64Data.isEmpty())
|
||||||
continue;
|
continue;
|
||||||
}
|
|
||||||
|
|
||||||
QString storedPath;
|
QString storedPath;
|
||||||
QFileInfo fileInfo(imagePath);
|
QFileInfo fileInfo(imagePath);
|
||||||
@@ -129,7 +138,6 @@ void ClientInterface::sendMessage(
|
|||||||
imageAttachment.storedPath = storedPath;
|
imageAttachment.storedPath = storedPath;
|
||||||
imageAttachment.mediaType = getMediaTypeForImage(imagePath);
|
imageAttachment.mediaType = getMediaTypeForImage(imagePath);
|
||||||
imageAttachments.append(imageAttachment);
|
imageAttachments.append(imageAttachment);
|
||||||
|
|
||||||
LOG_MESSAGE(QString("Stored image %1 as %2").arg(fileInfo.fileName(), storedPath));
|
LOG_MESSAGE(QString("Stored image %1 as %2").arg(fileInfo.fileName(), storedPath));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -138,318 +146,302 @@ void ClientInterface::sendMessage(
|
|||||||
.arg(imageFiles.size()));
|
.arg(imageFiles.size()));
|
||||||
}
|
}
|
||||||
|
|
||||||
m_chatModel->addMessage(message, ChatModel::ChatRole::User, "", storedAttachments, imageAttachments);
|
if (!m_sessionManager) {
|
||||||
|
const QString error = QStringLiteral("Chat session manager is not available");
|
||||||
auto &chatAssistantSettings = Settings::chatAssistantSettings();
|
LOG_MESSAGE(error);
|
||||||
|
emit errorOccurred(error);
|
||||||
auto providerName = Settings::generalSettings().caProvider();
|
|
||||||
auto provider = PluginLLMCore::ProvidersManager::instance().getProviderByName(providerName);
|
|
||||||
|
|
||||||
if (!provider) {
|
|
||||||
LOG_MESSAGE(QString("No provider found with name: %1").arg(providerName));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto templateName = Settings::generalSettings().caTemplate();
|
// Snapshot prior turns BEFORE the new user message is appended to the model.
|
||||||
auto promptTemplate = m_promptProvider->getTemplateByName(templateName);
|
const QVector<ChatModel::Message> priorHistory = m_chatModel->getChatHistory();
|
||||||
|
|
||||||
if (!promptTemplate) {
|
m_chatModel
|
||||||
LOG_MESSAGE(QString("No template found with name: %1").arg(templateName));
|
->addMessage(message, ChatModel::ChatRole::User, "", storedAttachments, imageAttachments);
|
||||||
|
|
||||||
|
QString sessionError;
|
||||||
|
Session *session = m_sessionManager->createSession(m_activeAgent, &sessionError);
|
||||||
|
if (!session) {
|
||||||
|
const QString error = sessionError.isEmpty()
|
||||||
|
? QStringLiteral("No chat agent selected")
|
||||||
|
: sessionError;
|
||||||
|
LOG_MESSAGE(error);
|
||||||
|
emit errorOccurred(error);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
PluginLLMCore::ContextData context;
|
auto *client = session->client();
|
||||||
|
if (!client) {
|
||||||
const bool isToolsEnabled = useTools;
|
const QString error = QStringLiteral("Chat agent has no live client");
|
||||||
|
LOG_MESSAGE(error);
|
||||||
if (chatAssistantSettings.useSystemPrompt()) {
|
m_sessionManager->removeSession(session);
|
||||||
QString systemPrompt = chatAssistantSettings.systemPrompt();
|
emit errorOccurred(error);
|
||||||
|
return;
|
||||||
const QString lastRoleId = chatAssistantSettings.lastUsedRoleId();
|
|
||||||
if (!lastRoleId.isEmpty()) {
|
|
||||||
const Settings::AgentRole role = Settings::AgentRolesManager::loadRole(lastRoleId);
|
|
||||||
if (!role.id.isEmpty())
|
|
||||||
systemPrompt = systemPrompt + "\n\n" + role.systemPrompt;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto project = PluginLLMCore::RulesLoader::getActiveProject();
|
|
||||||
|
|
||||||
if (project) {
|
|
||||||
systemPrompt += QString("\n# Active project: %1").arg(project->displayName());
|
|
||||||
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());
|
|
||||||
|
|
||||||
if (auto target = project->activeTarget()) {
|
|
||||||
if (auto buildConfig = target->activeBuildConfiguration()) {
|
|
||||||
systemPrompt
|
|
||||||
+= QString(
|
|
||||||
"\n# Build output directory (compiler artifacts only — do NOT "
|
|
||||||
"create or edit source files here): %1")
|
|
||||||
.arg(buildConfig->buildDirectory().toUrlishString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QString projectRules
|
|
||||||
= PluginLLMCore::RulesLoader::loadRulesForProject(project, PluginLLMCore::RulesContext::Chat);
|
|
||||||
|
|
||||||
if (!projectRules.isEmpty()) {
|
|
||||||
systemPrompt += QString("\n# Project Rules\n\n") + projectRules;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
systemPrompt += QString("\n# No active project in IDE");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_skillsManager && Settings::skillsSettings().enableSkills()) {
|
|
||||||
QStringList projectSkillDirs;
|
|
||||||
if (project) {
|
|
||||||
Settings::ProjectSettings projectSettings(project);
|
|
||||||
projectSkillDirs = Settings::SkillsSettings::splitLines(
|
|
||||||
projectSettings.projectSkillDirs());
|
|
||||||
}
|
|
||||||
m_skillsManager->configure(
|
|
||||||
project ? project->projectDirectory().toFSPathString() : QString(),
|
|
||||||
Settings::SkillsSettings::splitPaths(
|
|
||||||
Settings::skillsSettings().globalSkillRoots()),
|
|
||||||
projectSkillDirs);
|
|
||||||
|
|
||||||
const QString alwaysOnSkills = m_skillsManager->alwaysOnBodies();
|
|
||||||
if (!alwaysOnSkills.isEmpty())
|
|
||||||
systemPrompt += QString("\n\n") + alwaysOnSkills;
|
|
||||||
|
|
||||||
const QString skillsCatalog = m_skillsManager->catalogText();
|
|
||||||
if (!skillsCatalog.isEmpty())
|
|
||||||
systemPrompt += QString("\n\n") + skillsCatalog;
|
|
||||||
|
|
||||||
static const QRegularExpression skillCommand(
|
|
||||||
QStringLiteral("(?:^|\\s)/([a-z0-9][a-z0-9-]*)"));
|
|
||||||
QStringList invokedSkillNames;
|
|
||||||
auto skillMatch = skillCommand.globalMatch(message);
|
|
||||||
while (skillMatch.hasNext()) {
|
|
||||||
const QString skillName = skillMatch.next().captured(1);
|
|
||||||
if (invokedSkillNames.contains(skillName))
|
|
||||||
continue;
|
|
||||||
const auto invokedSkill = m_skillsManager->findByName(skillName);
|
|
||||||
if (invokedSkill && !invokedSkill->body.isEmpty()) {
|
|
||||||
invokedSkillNames << skillName;
|
|
||||||
systemPrompt += QString("\n\n# Invoked Skill: %1\n\n%2")
|
|
||||||
.arg(invokedSkill->name, invokedSkill->body);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!linkedFiles.isEmpty()) {
|
|
||||||
systemPrompt = getSystemPromptWithLinkedFiles(systemPrompt, linkedFiles);
|
|
||||||
}
|
|
||||||
context.systemPrompt = systemPrompt;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const bool toolHistory = promptTemplate->supportsToolHistory();
|
Tools::registerQodeAssistTools(client->tools());
|
||||||
|
if (m_skillsManager)
|
||||||
QVector<PluginLLMCore::Message> messages;
|
Tools::registerSkillTool(client->tools(), m_skillsManager);
|
||||||
int toolCallMsgIdx = -1;
|
client->setMaxToolContinuations(Settings::toolsSettings().maxToolContinuations());
|
||||||
for (const auto &msg : m_chatModel->getChatHistory()) {
|
client->setTransferTimeout(
|
||||||
if (msg.role == ChatModel::ChatRole::Tool) {
|
|
||||||
if (!toolHistory || msg.toolName.isEmpty()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (toolCallMsgIdx < 0) {
|
|
||||||
PluginLLMCore::Message assistantCall;
|
|
||||||
assistantCall.role = "assistant";
|
|
||||||
messages.append(assistantCall);
|
|
||||||
toolCallMsgIdx = messages.size() - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
PluginLLMCore::ToolCall call;
|
|
||||||
call.id = msg.id;
|
|
||||||
call.name = msg.toolName;
|
|
||||||
call.arguments = msg.toolArguments;
|
|
||||||
messages[toolCallMsgIdx].toolCalls.append(call);
|
|
||||||
|
|
||||||
PluginLLMCore::Message toolResult;
|
|
||||||
toolResult.role = "tool";
|
|
||||||
toolResult.toolCallId = msg.id;
|
|
||||||
toolResult.toolName = msg.toolName;
|
|
||||||
toolResult.content = msg.toolResult;
|
|
||||||
messages.append(toolResult);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
toolCallMsgIdx = -1;
|
|
||||||
|
|
||||||
if (msg.role == ChatModel::ChatRole::FileEdit) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
PluginLLMCore::Message apiMessage;
|
|
||||||
apiMessage.role = msg.role == ChatModel::ChatRole::User ? "user" : "assistant";
|
|
||||||
apiMessage.content = msg.content;
|
|
||||||
|
|
||||||
if (!msg.attachments.isEmpty() && !m_chatFilePath.isEmpty()) {
|
|
||||||
apiMessage.content += "\n\nAttached files:";
|
|
||||||
for (const auto &attachment : msg.attachments) {
|
|
||||||
QString fileContent = ChatSerializer::loadContentFromStorage(m_chatFilePath, attachment.content);
|
|
||||||
if (!fileContent.isEmpty()) {
|
|
||||||
QString decodedContent = QString::fromUtf8(QByteArray::fromBase64(fileContent.toUtf8()));
|
|
||||||
apiMessage.content += QString("\n\nFile: %1\n```\n%2\n```")
|
|
||||||
.arg(attachment.filename, decodedContent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
apiMessage.isThinking = (msg.role == ChatModel::ChatRole::Thinking);
|
|
||||||
apiMessage.isRedacted = msg.isRedacted;
|
|
||||||
apiMessage.signature = msg.signature;
|
|
||||||
|
|
||||||
if (provider->capabilities().testFlag(PluginLLMCore::ProviderCapability::Image)
|
|
||||||
&& !m_chatFilePath.isEmpty() && !msg.images.isEmpty()) {
|
|
||||||
auto apiImages = loadImagesFromStorage(msg.images);
|
|
||||||
if (!apiImages.isEmpty()) {
|
|
||||||
apiMessage.images = apiImages;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
messages.append(apiMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!imageFiles.isEmpty()
|
|
||||||
&& !provider->capabilities().testFlag(PluginLLMCore::ProviderCapability::Image)) {
|
|
||||||
LOG_MESSAGE(QString("Provider %1 doesn't support images, %2 ignored")
|
|
||||||
.arg(provider->name(), QString::number(imageFiles.size())));
|
|
||||||
}
|
|
||||||
|
|
||||||
context.history = messages;
|
|
||||||
|
|
||||||
QJsonObject payload{
|
|
||||||
{"model", Settings::generalSettings().caModel()}, {"stream", true}};
|
|
||||||
|
|
||||||
provider->prepareRequest(
|
|
||||||
payload,
|
|
||||||
promptTemplate,
|
|
||||||
context,
|
|
||||||
PluginLLMCore::RequestType::Chat,
|
|
||||||
useTools,
|
|
||||||
useThinking);
|
|
||||||
|
|
||||||
provider->client()->setMaxToolContinuations(
|
|
||||||
Settings::toolsSettings().maxToolContinuations());
|
|
||||||
|
|
||||||
provider->client()->setTransferTimeout(
|
|
||||||
static_cast<int>(Settings::generalSettings().requestTimeout() * 1000));
|
static_cast<int>(Settings::generalSettings().requestTimeout() * 1000));
|
||||||
|
|
||||||
connect(
|
const QString chatContext = buildChatContextLayer(message, linkedFiles);
|
||||||
provider->client(),
|
if (!chatContext.isEmpty())
|
||||||
&::LLMQore::BaseClient::chunkReceived,
|
session->systemPrompt()->setLayer(QStringLiteral("chat.context"), chatContext);
|
||||||
this,
|
|
||||||
&ClientInterface::handlePartialResponse,
|
|
||||||
Qt::UniqueConnection);
|
|
||||||
connect(
|
|
||||||
provider->client(),
|
|
||||||
&::LLMQore::BaseClient::requestCompleted,
|
|
||||||
this,
|
|
||||||
&ClientInterface::handleFullResponse,
|
|
||||||
Qt::UniqueConnection);
|
|
||||||
connect(
|
|
||||||
provider->client(),
|
|
||||||
&::LLMQore::BaseClient::requestFinalized,
|
|
||||||
this,
|
|
||||||
&ClientInterface::handleRequestFinalized,
|
|
||||||
Qt::UniqueConnection);
|
|
||||||
connect(
|
|
||||||
provider->client(),
|
|
||||||
&::LLMQore::BaseClient::requestFailed,
|
|
||||||
this,
|
|
||||||
&ClientInterface::handleRequestFailed,
|
|
||||||
Qt::UniqueConnection);
|
|
||||||
connect(
|
|
||||||
provider->client(),
|
|
||||||
&::LLMQore::BaseClient::toolStarted,
|
|
||||||
this,
|
|
||||||
&ClientInterface::handleToolExecutionStarted,
|
|
||||||
Qt::UniqueConnection);
|
|
||||||
connect(
|
|
||||||
provider->client(),
|
|
||||||
&::LLMQore::BaseClient::toolResultReady,
|
|
||||||
this,
|
|
||||||
&ClientInterface::handleToolExecutionCompleted,
|
|
||||||
Qt::UniqueConnection);
|
|
||||||
connect(
|
|
||||||
provider->client(),
|
|
||||||
&::LLMQore::BaseClient::thinkingBlockReceived,
|
|
||||||
this,
|
|
||||||
&ClientInterface::handleThinkingBlockReceived,
|
|
||||||
Qt::UniqueConnection);
|
|
||||||
|
|
||||||
const QString customEndpoint = Settings::generalSettings().caCustomEndpoint();
|
seedHistory(*session->history(), priorHistory);
|
||||||
const QString endpoint = !customEndpoint.isEmpty() ? customEndpoint
|
|
||||||
: promptTemplate->endpoint();
|
|
||||||
auto requestId
|
|
||||||
= provider->sendRequest(QUrl(Settings::generalSettings().caUrl()), payload, endpoint);
|
|
||||||
QJsonObject request{{"id", requestId}};
|
|
||||||
|
|
||||||
m_activeRequests[requestId] = {request, provider, !toolHistory};
|
QString userText = message;
|
||||||
|
if (!storedAttachments.isEmpty() && !m_chatFilePath.isEmpty()) {
|
||||||
|
userText += "\n\nAttached files:";
|
||||||
|
for (const auto &attachment : storedAttachments) {
|
||||||
|
QString fileContent
|
||||||
|
= ChatSerializer::loadContentFromStorage(m_chatFilePath, attachment.content);
|
||||||
|
if (!fileContent.isEmpty()) {
|
||||||
|
QString decoded = QString::fromUtf8(QByteArray::fromBase64(fileContent.toUtf8()));
|
||||||
|
userText += QString("\n\nFile: %1\n```\n%2\n```").arg(attachment.filename, decoded);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
emit requestStarted(requestId);
|
std::vector<std::unique_ptr<LLMQore::ContentBlock>> blocks;
|
||||||
|
blocks.push_back(std::make_unique<LLMQore::TextContent>(userText));
|
||||||
if (provider->capabilities().testFlag(PluginLLMCore::ProviderCapability::Tools)
|
|
||||||
&& provider->toolsManager()) {
|
if (!imageAttachments.isEmpty() && session->supportsImages() && !m_chatFilePath.isEmpty()) {
|
||||||
if (auto *todoTool = qobject_cast<QodeAssist::Tools::TodoTool *>(
|
for (const auto &image : imageAttachments) {
|
||||||
provider->toolsManager()->tool("todo_tool"))) {
|
QString base64
|
||||||
|
= ChatSerializer::loadContentFromStorage(m_chatFilePath, image.storedPath);
|
||||||
|
if (base64.isEmpty())
|
||||||
|
continue;
|
||||||
|
blocks.push_back(std::make_unique<LLMQore::ImageContent>(
|
||||||
|
base64, image.mediaType, LLMQore::ImageContent::ImageSourceType::Base64));
|
||||||
|
}
|
||||||
|
} else if (!imageAttachments.isEmpty() && !session->supportsImages()) {
|
||||||
|
LOG_MESSAGE(QString("Agent '%1' doesn't support images, %2 ignored")
|
||||||
|
.arg(m_activeAgent)
|
||||||
|
.arg(imageAttachments.size()));
|
||||||
|
}
|
||||||
|
|
||||||
|
connect(
|
||||||
|
client, &::LLMQore::BaseClient::chunkReceived,
|
||||||
|
this, &ClientInterface::handlePartialResponse, Qt::UniqueConnection);
|
||||||
|
connect(
|
||||||
|
client, &::LLMQore::BaseClient::requestCompleted,
|
||||||
|
this, &ClientInterface::handleFullResponse, Qt::UniqueConnection);
|
||||||
|
connect(
|
||||||
|
client, &::LLMQore::BaseClient::requestFinalized,
|
||||||
|
this, &ClientInterface::handleRequestFinalized, Qt::UniqueConnection);
|
||||||
|
connect(
|
||||||
|
client, &::LLMQore::BaseClient::requestFailed,
|
||||||
|
this, &ClientInterface::handleRequestFailed, Qt::UniqueConnection);
|
||||||
|
connect(
|
||||||
|
client, &::LLMQore::BaseClient::toolStarted,
|
||||||
|
this, &ClientInterface::handleToolExecutionStarted, Qt::UniqueConnection);
|
||||||
|
connect(
|
||||||
|
client, &::LLMQore::BaseClient::toolResultReady,
|
||||||
|
this, &ClientInterface::handleToolExecutionCompleted, Qt::UniqueConnection);
|
||||||
|
connect(
|
||||||
|
client, &::LLMQore::BaseClient::thinkingBlockReceived,
|
||||||
|
this, &ClientInterface::handleThinkingBlockReceived, Qt::UniqueConnection);
|
||||||
|
|
||||||
|
if (!m_chatFilePath.isEmpty()) {
|
||||||
|
if (auto *todoTool
|
||||||
|
= qobject_cast<QodeAssist::Tools::TodoTool *>(client->tools()->tool("todo_tool"))) {
|
||||||
todoTool->setCurrentSessionId(m_chatFilePath);
|
todoTool->setCurrentSessionId(m_chatFilePath);
|
||||||
}
|
}
|
||||||
if (auto *historyTool = qobject_cast<QodeAssist::Tools::ReadOriginalHistoryTool *>(
|
if (auto *historyTool = qobject_cast<QodeAssist::Tools::ReadOriginalHistoryTool *>(
|
||||||
provider->toolsManager()->tool("read_original_history"))) {
|
client->tools()->tool("read_original_history"))) {
|
||||||
historyTool->setCurrentSessionId(m_chatFilePath);
|
historyTool->setCurrentSessionId(m_chatFilePath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const LLMQore::RequestID requestId = session->send(std::move(blocks), useTools);
|
||||||
|
if (requestId.isEmpty()) {
|
||||||
|
const QString error = QStringLiteral("Failed to start chat request for agent: %1")
|
||||||
|
.arg(m_activeAgent);
|
||||||
|
LOG_MESSAGE(error);
|
||||||
|
m_sessionManager->removeSession(session);
|
||||||
|
emit errorOccurred(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject request{{"id", requestId}};
|
||||||
|
m_activeRequests[requestId] = {request, session, /*dropPreToolText=*/false};
|
||||||
|
|
||||||
|
emit requestStarted(requestId);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClientInterface::seedHistory(
|
||||||
|
ConversationHistory &history, const QVector<ChatModel::Message> &messages) const
|
||||||
|
{
|
||||||
|
int i = 0;
|
||||||
|
while (i < messages.size()) {
|
||||||
|
const ChatModel::Message &msg = messages[i];
|
||||||
|
|
||||||
|
if (msg.role == ChatModel::ChatRole::Tool) {
|
||||||
|
Message assistant(Message::Role::Assistant);
|
||||||
|
Message toolResults(Message::Role::User);
|
||||||
|
while (i < messages.size() && messages[i].role == ChatModel::ChatRole::Tool) {
|
||||||
|
const ChatModel::Message &toolMsg = messages[i];
|
||||||
|
if (!toolMsg.toolName.isEmpty()) {
|
||||||
|
assistant.appendBlock(std::make_unique<LLMQore::ToolUseContent>(
|
||||||
|
toolMsg.id, toolMsg.toolName, toolMsg.toolArguments));
|
||||||
|
toolResults.appendBlock(
|
||||||
|
std::make_unique<LLMQore::ToolResultContent>(toolMsg.id, toolMsg.toolResult));
|
||||||
|
}
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
if (!assistant.blocks().empty()) {
|
||||||
|
history.append(std::move(assistant));
|
||||||
|
history.append(std::move(toolResults));
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
++i;
|
||||||
|
|
||||||
|
if (msg.role == ChatModel::ChatRole::FileEdit
|
||||||
|
|| msg.role == ChatModel::ChatRole::Thinking) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg.role == ChatModel::ChatRole::User) {
|
||||||
|
Message userMessage(Message::Role::User);
|
||||||
|
QString content = msg.content;
|
||||||
|
if (!msg.attachments.isEmpty() && !m_chatFilePath.isEmpty()) {
|
||||||
|
content += "\n\nAttached files:";
|
||||||
|
for (const auto &attachment : msg.attachments) {
|
||||||
|
QString fileContent = ChatSerializer::loadContentFromStorage(
|
||||||
|
m_chatFilePath, attachment.content);
|
||||||
|
if (!fileContent.isEmpty()) {
|
||||||
|
QString decoded
|
||||||
|
= QString::fromUtf8(QByteArray::fromBase64(fileContent.toUtf8()));
|
||||||
|
content
|
||||||
|
+= QString("\n\nFile: %1\n```\n%2\n```").arg(attachment.filename, decoded);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
userMessage.appendBlock(std::make_unique<LLMQore::TextContent>(content));
|
||||||
|
|
||||||
|
if (!msg.images.isEmpty() && !m_chatFilePath.isEmpty()) {
|
||||||
|
for (const auto &image : msg.images) {
|
||||||
|
QString base64 = ChatSerializer::loadContentFromStorage(
|
||||||
|
m_chatFilePath, image.storedPath);
|
||||||
|
if (base64.isEmpty())
|
||||||
|
continue;
|
||||||
|
userMessage.appendBlock(std::make_unique<LLMQore::ImageContent>(
|
||||||
|
base64, image.mediaType, LLMQore::ImageContent::ImageSourceType::Base64));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
history.append(std::move(userMessage));
|
||||||
|
} else { // Assistant
|
||||||
|
if (msg.content.trimmed().isEmpty())
|
||||||
|
continue;
|
||||||
|
Message assistant(Message::Role::Assistant);
|
||||||
|
assistant.appendBlock(std::make_unique<LLMQore::TextContent>(msg.content));
|
||||||
|
history.append(std::move(assistant));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClientInterface::clearMessages()
|
QString ClientInterface::buildChatContextLayer(
|
||||||
|
const QString &message, const QList<QString> &linkedFiles) const
|
||||||
{
|
{
|
||||||
const auto providerName = Settings::generalSettings().caProvider();
|
QString context;
|
||||||
auto *provider = PluginLLMCore::ProvidersManager::instance().getProviderByName(providerName);
|
|
||||||
|
|
||||||
if (provider && !m_chatFilePath.isEmpty()
|
auto *project = ProjectExplorer::ProjectManager::startupProject();
|
||||||
&& provider->capabilities().testFlag(PluginLLMCore::ProviderCapability::Tools)
|
if (project) {
|
||||||
&& provider->toolsManager()) {
|
context += QString("# Active project: %1").arg(project->displayName());
|
||||||
if (auto *todoTool = qobject_cast<QodeAssist::Tools::TodoTool *>(
|
context += QString(
|
||||||
provider->toolsManager()->tool("todo_tool"))) {
|
"\n# Project source root: %1"
|
||||||
todoTool->clearSession(m_chatFilePath);
|
"\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());
|
||||||
|
|
||||||
|
if (auto target = project->activeTarget()) {
|
||||||
|
if (auto buildConfig = target->activeBuildConfiguration()) {
|
||||||
|
context += QString(
|
||||||
|
"\n# Build output directory (compiler artifacts only — do NOT "
|
||||||
|
"create or edit source files here): %1")
|
||||||
|
.arg(buildConfig->buildDirectory().toUrlishString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
context += QString("# No active project in IDE");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_skillsManager && Settings::skillsSettings().enableSkills()) {
|
||||||
|
QStringList projectSkillDirs;
|
||||||
|
if (project) {
|
||||||
|
Settings::ProjectSettings projectSettings(project);
|
||||||
|
projectSkillDirs
|
||||||
|
= Settings::SkillsSettings::splitLines(projectSettings.projectSkillDirs());
|
||||||
|
}
|
||||||
|
m_skillsManager->configure(
|
||||||
|
project ? project->projectDirectory().toFSPathString() : QString(),
|
||||||
|
Settings::SkillsSettings::splitPaths(Settings::skillsSettings().globalSkillRoots()),
|
||||||
|
projectSkillDirs);
|
||||||
|
|
||||||
|
const QString alwaysOnSkills = m_skillsManager->alwaysOnBodies();
|
||||||
|
if (!alwaysOnSkills.isEmpty())
|
||||||
|
context += QString("\n\n") + alwaysOnSkills;
|
||||||
|
|
||||||
|
const QString skillsCatalog = m_skillsManager->catalogText();
|
||||||
|
if (!skillsCatalog.isEmpty())
|
||||||
|
context += QString("\n\n") + skillsCatalog;
|
||||||
|
|
||||||
|
static const QRegularExpression skillCommand(
|
||||||
|
QStringLiteral("(?:^|\\s)/([a-z0-9][a-z0-9-]*)"));
|
||||||
|
QStringList invokedSkillNames;
|
||||||
|
auto skillMatch = skillCommand.globalMatch(message);
|
||||||
|
while (skillMatch.hasNext()) {
|
||||||
|
const QString skillName = skillMatch.next().captured(1);
|
||||||
|
if (invokedSkillNames.contains(skillName))
|
||||||
|
continue;
|
||||||
|
const auto invokedSkill = m_skillsManager->findByName(skillName);
|
||||||
|
if (invokedSkill && !invokedSkill->body.isEmpty()) {
|
||||||
|
invokedSkillNames << skillName;
|
||||||
|
context += QString("\n\n# Invoked Skill: %1\n\n%2")
|
||||||
|
.arg(invokedSkill->name, invokedSkill->body);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!linkedFiles.isEmpty()) {
|
||||||
|
context += "\n\nLinked files for reference:\n";
|
||||||
|
auto contentFiles = m_contextManager->getContentFiles(linkedFiles);
|
||||||
|
for (const auto &file : contentFiles)
|
||||||
|
context += QString("\nFile: %1\nContent:\n%2\n").arg(file.filename, file.content);
|
||||||
|
}
|
||||||
|
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClientInterface::clearMessages()
|
||||||
|
{
|
||||||
m_chatModel->clear();
|
m_chatModel->clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClientInterface::cancelRequest()
|
void ClientInterface::cancelRequest()
|
||||||
{
|
{
|
||||||
QSet<PluginLLMCore::Provider *> providers;
|
const auto requests = m_activeRequests;
|
||||||
for (auto it = m_activeRequests.begin(); it != m_activeRequests.end(); ++it) {
|
|
||||||
if (it.value().provider) {
|
|
||||||
providers.insert(it.value().provider);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto *provider : providers) {
|
|
||||||
disconnect(provider->client(), nullptr, this, nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto it = m_activeRequests.begin(); it != m_activeRequests.end(); ++it) {
|
|
||||||
const RequestContext &ctx = it.value();
|
|
||||||
if (ctx.provider) {
|
|
||||||
ctx.provider->cancelRequest(it.key());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
m_activeRequests.clear();
|
m_activeRequests.clear();
|
||||||
m_accumulatedResponses.clear();
|
m_accumulatedResponses.clear();
|
||||||
m_awaitingContinuation.clear();
|
m_awaitingContinuation.clear();
|
||||||
|
|
||||||
LOG_MESSAGE("All requests cancelled and state cleared");
|
for (auto it = requests.begin(); it != requests.end(); ++it) {
|
||||||
|
Session *session = it.value().session;
|
||||||
|
if (!session)
|
||||||
|
continue;
|
||||||
|
if (auto *client = session->client())
|
||||||
|
disconnect(client, nullptr, this, nullptr);
|
||||||
|
if (m_sessionManager)
|
||||||
|
m_sessionManager->removeSession(session);
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_MESSAGE("All chat requests cancelled and state cleared");
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClientInterface::handleLLMResponse(const QString &response, const QJsonObject &request)
|
void ClientInterface::handleLLMResponse(const QString &response, const QJsonObject &request)
|
||||||
@@ -486,23 +478,6 @@ QString ClientInterface::getCurrentFileContext() const
|
|||||||
return QString("Current file context:\n%1\nFile content:\n%2").arg(fileInfo, content);
|
return QString("Current file context:\n%1\nFile content:\n%2").arg(fileInfo, content);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString ClientInterface::getSystemPromptWithLinkedFiles(
|
|
||||||
const QString &basePrompt, const QList<QString> &linkedFiles) const
|
|
||||||
{
|
|
||||||
QString updatedPrompt = basePrompt;
|
|
||||||
|
|
||||||
if (!linkedFiles.isEmpty()) {
|
|
||||||
updatedPrompt += "\n\nLinked files for reference:\n";
|
|
||||||
|
|
||||||
auto contentFiles = m_contextManager->getContentFiles(linkedFiles);
|
|
||||||
for (const auto &file : contentFiles) {
|
|
||||||
updatedPrompt += QString("\nFile: %1\nContent:\n%2\n").arg(file.filename, file.content);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return updatedPrompt;
|
|
||||||
}
|
|
||||||
|
|
||||||
Context::ContextManager *ClientInterface::contextManager() const
|
Context::ContextManager *ClientInterface::contextManager() const
|
||||||
{
|
{
|
||||||
return m_contextManager;
|
return m_contextManager;
|
||||||
@@ -532,7 +507,8 @@ void ClientInterface::handleFullResponse(const QString &requestId, const QString
|
|||||||
if (it == m_activeRequests.end())
|
if (it == m_activeRequests.end())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const RequestContext &ctx = it.value();
|
const QJsonObject originalRequest = it.value().originalRequest;
|
||||||
|
Session *session = it.value().session;
|
||||||
|
|
||||||
QString finalText = !fullText.isEmpty() ? fullText : m_accumulatedResponses[requestId];
|
QString finalText = !fullText.isEmpty() ? fullText : m_accumulatedResponses[requestId];
|
||||||
|
|
||||||
@@ -546,13 +522,16 @@ void ClientInterface::handleFullResponse(const QString &requestId, const QString
|
|||||||
}
|
}
|
||||||
|
|
||||||
LOG_MESSAGE(
|
LOG_MESSAGE(
|
||||||
"Message completed. Final response for message " + ctx.originalRequest["id"].toString()
|
"Message completed. Final response for message " + originalRequest["id"].toString() + ": "
|
||||||
+ ": " + finalText);
|
+ finalText);
|
||||||
emit messageReceivedCompletely();
|
emit messageReceivedCompletely();
|
||||||
|
|
||||||
m_activeRequests.erase(it);
|
m_activeRequests.erase(it);
|
||||||
m_accumulatedResponses.remove(requestId);
|
m_accumulatedResponses.remove(requestId);
|
||||||
m_awaitingContinuation.remove(requestId);
|
m_awaitingContinuation.remove(requestId);
|
||||||
|
|
||||||
|
if (session && m_sessionManager)
|
||||||
|
m_sessionManager->removeSession(session);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClientInterface::handleRequestFinalized(
|
void ClientInterface::handleRequestFinalized(
|
||||||
@@ -584,12 +563,17 @@ void ClientInterface::handleRequestFailed(const QString &requestId, const QStrin
|
|||||||
if (it == m_activeRequests.end())
|
if (it == m_activeRequests.end())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
Session *session = it.value().session;
|
||||||
|
|
||||||
LOG_MESSAGE(QString("Chat request %1 failed: %2").arg(requestId, error));
|
LOG_MESSAGE(QString("Chat request %1 failed: %2").arg(requestId, error));
|
||||||
emit errorOccurred(error);
|
emit errorOccurred(error);
|
||||||
|
|
||||||
m_activeRequests.erase(it);
|
m_activeRequests.erase(it);
|
||||||
m_accumulatedResponses.remove(requestId);
|
m_accumulatedResponses.remove(requestId);
|
||||||
m_awaitingContinuation.remove(requestId);
|
m_awaitingContinuation.remove(requestId);
|
||||||
|
|
||||||
|
if (session && m_sessionManager)
|
||||||
|
m_sessionManager->removeSession(session);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClientInterface::handleThinkingBlockReceived(
|
void ClientInterface::handleThinkingBlockReceived(
|
||||||
@@ -693,46 +677,8 @@ QString ClientInterface::encodeImageToBase64(const QString &filePath) const
|
|||||||
return imageData.toBase64();
|
return imageData.toBase64();
|
||||||
}
|
}
|
||||||
|
|
||||||
QVector<PluginLLMCore::ImageAttachment> ClientInterface::loadImagesFromStorage(
|
|
||||||
const QList<ChatModel::ImageAttachment> &storedImages) const
|
|
||||||
{
|
|
||||||
QVector<PluginLLMCore::ImageAttachment> apiImages;
|
|
||||||
|
|
||||||
for (const auto &storedImage : storedImages) {
|
|
||||||
QString base64Data
|
|
||||||
= ChatSerializer::loadContentFromStorage(m_chatFilePath, storedImage.storedPath);
|
|
||||||
if (base64Data.isEmpty()) {
|
|
||||||
LOG_MESSAGE(QString("Warning: Failed to load image: %1").arg(storedImage.storedPath));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
PluginLLMCore::ImageAttachment apiImage;
|
|
||||||
apiImage.data = base64Data;
|
|
||||||
apiImage.mediaType = storedImage.mediaType;
|
|
||||||
apiImage.isUrl = false;
|
|
||||||
|
|
||||||
apiImages.append(apiImage);
|
|
||||||
}
|
|
||||||
|
|
||||||
return apiImages;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ClientInterface::setChatFilePath(const QString &filePath)
|
void ClientInterface::setChatFilePath(const QString &filePath)
|
||||||
{
|
{
|
||||||
if (!m_chatFilePath.isEmpty() && m_chatFilePath != filePath) {
|
|
||||||
const auto providerName = Settings::generalSettings().caProvider();
|
|
||||||
auto *provider = PluginLLMCore::ProvidersManager::instance().getProviderByName(providerName);
|
|
||||||
|
|
||||||
if (provider
|
|
||||||
&& provider->capabilities().testFlag(PluginLLMCore::ProviderCapability::Tools)
|
|
||||||
&& provider->toolsManager()) {
|
|
||||||
if (auto *todoTool = qobject_cast<QodeAssist::Tools::TodoTool *>(
|
|
||||||
provider->toolsManager()->tool("todo_tool"))) {
|
|
||||||
todoTool->clearSession(m_chatFilePath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
m_chatFilePath = filePath;
|
m_chatFilePath = filePath;
|
||||||
m_chatModel->setChatFilePath(filePath);
|
m_chatModel->setChatFilePath(filePath);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,16 +5,21 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
#include <QPointer>
|
||||||
#include <QSet>
|
#include <QSet>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QVector>
|
#include <QVector>
|
||||||
|
|
||||||
#include "ChatModel.hpp"
|
#include "ChatModel.hpp"
|
||||||
#include "Provider.hpp"
|
|
||||||
#include "pluginllmcore/IPromptProvider.hpp"
|
|
||||||
#include <LLMQore/BaseClient.hpp>
|
#include <LLMQore/BaseClient.hpp>
|
||||||
#include <context/ContextManager.hpp>
|
#include <context/ContextManager.hpp>
|
||||||
|
|
||||||
|
namespace QodeAssist {
|
||||||
|
class SessionManager;
|
||||||
|
class Session;
|
||||||
|
class ConversationHistory;
|
||||||
|
}
|
||||||
|
|
||||||
namespace QodeAssist::Skills {
|
namespace QodeAssist::Skills {
|
||||||
class SkillsManager;
|
class SkillsManager;
|
||||||
}
|
}
|
||||||
@@ -26,11 +31,12 @@ class ClientInterface : public QObject
|
|||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit ClientInterface(
|
explicit ClientInterface(ChatModel *chatModel, QObject *parent = nullptr);
|
||||||
ChatModel *chatModel, PluginLLMCore::IPromptProvider *promptProvider, QObject *parent = nullptr);
|
|
||||||
~ClientInterface();
|
~ClientInterface();
|
||||||
|
|
||||||
void setSkillsManager(Skills::SkillsManager *skillsManager);
|
void setSkillsManager(Skills::SkillsManager *skillsManager);
|
||||||
|
void setSessionManager(SessionManager *sessionManager);
|
||||||
|
void setActiveAgent(const QString &agentName);
|
||||||
|
|
||||||
void sendMessage(
|
void sendMessage(
|
||||||
const QString &message,
|
const QString &message,
|
||||||
@@ -42,7 +48,7 @@ public:
|
|||||||
void cancelRequest();
|
void cancelRequest();
|
||||||
|
|
||||||
Context::ContextManager *contextManager() const;
|
Context::ContextManager *contextManager() const;
|
||||||
|
|
||||||
void setChatFilePath(const QString &filePath);
|
void setChatFilePath(const QString &filePath);
|
||||||
QString chatFilePath() const;
|
QString chatFilePath() const;
|
||||||
|
|
||||||
@@ -74,24 +80,26 @@ private slots:
|
|||||||
private:
|
private:
|
||||||
void handleLLMResponse(const QString &response, const QJsonObject &request);
|
void handleLLMResponse(const QString &response, const QJsonObject &request);
|
||||||
QString getCurrentFileContext() const;
|
QString getCurrentFileContext() const;
|
||||||
QString getSystemPromptWithLinkedFiles(
|
QString buildChatContextLayer(
|
||||||
const QString &basePrompt, const QList<QString> &linkedFiles) const;
|
const QString &message, const QList<QString> &linkedFiles) const;
|
||||||
|
void seedHistory(
|
||||||
|
ConversationHistory &history, const QVector<ChatModel::Message> &messages) const;
|
||||||
bool isImageFile(const QString &filePath) const;
|
bool isImageFile(const QString &filePath) const;
|
||||||
QString getMediaTypeForImage(const QString &filePath) const;
|
QString getMediaTypeForImage(const QString &filePath) const;
|
||||||
QString encodeImageToBase64(const QString &filePath) const;
|
QString encodeImageToBase64(const QString &filePath) const;
|
||||||
QVector<PluginLLMCore::ImageAttachment> loadImagesFromStorage(const QList<ChatModel::ImageAttachment> &storedImages) const;
|
|
||||||
|
|
||||||
struct RequestContext
|
struct RequestContext
|
||||||
{
|
{
|
||||||
QJsonObject originalRequest;
|
QJsonObject originalRequest;
|
||||||
PluginLLMCore::Provider *provider;
|
QPointer<Session> session;
|
||||||
bool dropPreToolText = false;
|
bool dropPreToolText = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
PluginLLMCore::IPromptProvider *m_promptProvider = nullptr;
|
|
||||||
ChatModel *m_chatModel;
|
ChatModel *m_chatModel;
|
||||||
Context::ContextManager *m_contextManager;
|
Context::ContextManager *m_contextManager;
|
||||||
Skills::SkillsManager *m_skillsManager = nullptr;
|
Skills::SkillsManager *m_skillsManager = nullptr;
|
||||||
|
QPointer<SessionManager> m_sessionManager;
|
||||||
|
QString m_activeAgent;
|
||||||
QString m_chatFilePath;
|
QString m_chatFilePath;
|
||||||
|
|
||||||
QHash<QString, RequestContext> m_activeRequests;
|
QHash<QString, RequestContext> m_activeRequests;
|
||||||
|
|||||||
@@ -6,17 +6,11 @@
|
|||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
#include <LLMQore/ToolsManager.hpp>
|
|
||||||
#include <QJsonArray>
|
|
||||||
#include <QJsonDocument>
|
|
||||||
|
|
||||||
#include <utils/aspects.h>
|
#include <utils/aspects.h>
|
||||||
|
|
||||||
#include "ChatAssistantSettings.hpp"
|
#include "ChatAssistantSettings.hpp"
|
||||||
#include "ChatModel.hpp"
|
#include "ChatModel.hpp"
|
||||||
#include "GeneralSettings.hpp"
|
|
||||||
#include "Logger.hpp"
|
#include "Logger.hpp"
|
||||||
#include "ProvidersManager.hpp"
|
|
||||||
#include "context/ContextManager.hpp"
|
#include "context/ContextManager.hpp"
|
||||||
#include "context/TokenUtils.hpp"
|
#include "context/TokenUtils.hpp"
|
||||||
|
|
||||||
@@ -42,12 +36,6 @@ InputTokenCounter::InputTokenCounter(
|
|||||||
this,
|
this,
|
||||||
&InputTokenCounter::recompute);
|
&InputTokenCounter::recompute);
|
||||||
|
|
||||||
connect(&Settings::generalSettings().caProvider, &Utils::BaseAspect::changed, this, [this]() {
|
|
||||||
rewireToolsChangedConnection();
|
|
||||||
recompute();
|
|
||||||
});
|
|
||||||
|
|
||||||
rewireToolsChangedConnection();
|
|
||||||
recompute();
|
recompute();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,24 +62,6 @@ void InputTokenCounter::setLinkedFiles(const QStringList &linkedFiles)
|
|||||||
recompute();
|
recompute();
|
||||||
}
|
}
|
||||||
|
|
||||||
void InputTokenCounter::rewireToolsChangedConnection()
|
|
||||||
{
|
|
||||||
if (m_toolsChangedConn)
|
|
||||||
QObject::disconnect(m_toolsChangedConn);
|
|
||||||
m_toolsChangedConn = {};
|
|
||||||
|
|
||||||
const auto providerName = Settings::generalSettings().caProvider();
|
|
||||||
auto *provider = PluginLLMCore::ProvidersManager::instance().getProviderByName(providerName);
|
|
||||||
if (!provider)
|
|
||||||
return;
|
|
||||||
auto *tm = provider->toolsManager();
|
|
||||||
if (!tm)
|
|
||||||
return;
|
|
||||||
|
|
||||||
m_toolsChangedConn = connect(
|
|
||||||
tm, &::LLMQore::ToolRegistry::toolsChanged, this, &InputTokenCounter::recompute);
|
|
||||||
}
|
|
||||||
|
|
||||||
void InputTokenCounter::recompute()
|
void InputTokenCounter::recompute()
|
||||||
{
|
{
|
||||||
int inputTokens = m_messageTokens;
|
int inputTokens = m_messageTokens;
|
||||||
@@ -136,21 +106,6 @@ void InputTokenCounter::recompute()
|
|||||||
inputTokens += 4; // + role
|
inputTokens += 4; // + role
|
||||||
}
|
}
|
||||||
|
|
||||||
if (settings.enableChatTools()) {
|
|
||||||
const auto providerName = Settings::generalSettings().caProvider();
|
|
||||||
if (auto *provider = PluginLLMCore::ProvidersManager::instance().getProviderByName(
|
|
||||||
providerName)) {
|
|
||||||
if (auto *tm = provider->toolsManager()) {
|
|
||||||
const QJsonArray toolDefs = tm->getToolsDefinitions();
|
|
||||||
if (!toolDefs.isEmpty()) {
|
|
||||||
const QByteArray serialized
|
|
||||||
= QJsonDocument(toolDefs).toJson(QJsonDocument::Compact);
|
|
||||||
inputTokens += static_cast<int>(serialized.size() / 4);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
m_inputTokens = static_cast<int>(inputTokens * m_calibrationFactor);
|
m_inputTokens = static_cast<int>(inputTokens * m_calibrationFactor);
|
||||||
emit inputTokensChanged();
|
emit inputTokensChanged();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,11 +37,8 @@ signals:
|
|||||||
void inputTokensChanged();
|
void inputTokensChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void rewireToolsChangedConnection();
|
|
||||||
|
|
||||||
ChatModel *m_chatModel;
|
ChatModel *m_chatModel;
|
||||||
Context::ContextManager *m_contextManager;
|
Context::ContextManager *m_contextManager;
|
||||||
QMetaObject::Connection m_toolsChangedConn;
|
|
||||||
|
|
||||||
QStringList m_attachments;
|
QStringList m_attachments;
|
||||||
QStringList m_linkedFiles;
|
QStringList m_linkedFiles;
|
||||||
|
|||||||
@@ -152,29 +152,17 @@ ChatRootView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
settingsButton.onClicked: root.openSettings()
|
settingsButton.onClicked: root.openSettings()
|
||||||
configSelector {
|
agentSelector {
|
||||||
model: root.availableConfigurations
|
model: root.availableChatAgents
|
||||||
displayText: root.currentConfiguration
|
displayText: root.currentChatAgent
|
||||||
onActivated: function(index) {
|
onActivated: function(index) {
|
||||||
if (index > 0) {
|
root.currentChatAgent = root.availableChatAgents[index]
|
||||||
root.applyConfiguration(root.availableConfigurations[index])
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: root.loadAvailableChatAgents()
|
||||||
|
|
||||||
popup.onAboutToShow: {
|
popup.onAboutToShow: {
|
||||||
root.loadAvailableConfigurations()
|
root.loadAvailableChatAgents()
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
roleSelector {
|
|
||||||
model: root.availableAgentRoles
|
|
||||||
displayText: root.currentAgentRole
|
|
||||||
onActivated: function(index) {
|
|
||||||
root.applyAgentRole(root.availableAgentRoles[index])
|
|
||||||
}
|
|
||||||
|
|
||||||
popup.onAboutToShow: {
|
|
||||||
root.loadAvailableAgentRoles()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -839,20 +827,7 @@ ChatRootView {
|
|||||||
x: (parent.width - width) / 2
|
x: (parent.width - width) / 2
|
||||||
y: (parent.height - height) / 2
|
y: (parent.height - height) / 2
|
||||||
|
|
||||||
baseSystemPrompt: root.baseSystemPrompt
|
|
||||||
currentAgentRole: root.currentAgentRole
|
|
||||||
currentAgentRoleDescription: root.currentAgentRoleDescription
|
|
||||||
currentAgentRoleSystemPrompt: root.currentAgentRoleSystemPrompt
|
|
||||||
activeRules: root.activeRules
|
|
||||||
activeRulesCount: root.activeRulesCount
|
|
||||||
|
|
||||||
onOpenSettings: root.openSettings()
|
onOpenSettings: root.openSettings()
|
||||||
onOpenAgentRolesSettings: root.openAgentRolesSettings()
|
|
||||||
onOpenRulesFolder: root.openRulesFolder()
|
|
||||||
onRefreshRules: root.refreshRules()
|
|
||||||
onRuleSelected: function(index) {
|
|
||||||
contextViewer.selectedRuleContent = root.getRuleContent(index)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
|
|||||||
@@ -26,8 +26,7 @@ Rectangle {
|
|||||||
property alias toolsButton: toolsButtonId
|
property alias toolsButton: toolsButtonId
|
||||||
property alias thinkingMode: thinkingModeId
|
property alias thinkingMode: thinkingModeId
|
||||||
property alias settingsButton: settingsButtonId
|
property alias settingsButton: settingsButtonId
|
||||||
property alias configSelector: configSelectorId
|
property alias agentSelector: agentSelectorId
|
||||||
property alias roleSelector: roleSelector
|
|
||||||
property alias relocateTooltip: relocateTooltipId
|
property alias relocateTooltip: relocateTooltipId
|
||||||
|
|
||||||
color: palette.window.hslLightness > 0.5 ?
|
color: palette.window.hslLightness > 0.5 ?
|
||||||
@@ -134,7 +133,7 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
QoAComboBox {
|
QoAComboBox {
|
||||||
id: configSelectorId
|
id: agentSelectorId
|
||||||
|
|
||||||
implicitHeight: 25
|
implicitHeight: 25
|
||||||
|
|
||||||
@@ -142,24 +141,9 @@ Rectangle {
|
|||||||
currentIndex: 0
|
currentIndex: 0
|
||||||
|
|
||||||
QoAToolTip {
|
QoAToolTip {
|
||||||
visible: configSelectorId.hovered
|
visible: agentSelectorId.hovered
|
||||||
delay: 250
|
delay: 250
|
||||||
text: qsTr("Switch saved AI configuration")
|
text: qsTr("Select chat agent (provider, model and role come from the agent)")
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QoAComboBox {
|
|
||||||
id: roleSelector
|
|
||||||
|
|
||||||
implicitHeight: 25
|
|
||||||
|
|
||||||
model: []
|
|
||||||
currentIndex: 0
|
|
||||||
|
|
||||||
QoAToolTip {
|
|
||||||
visible: roleSelector.hovered
|
|
||||||
delay: 250
|
|
||||||
text: qsTr("Switch agent role (different system prompts)")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,236 +0,0 @@
|
|||||||
// 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 "ConfigurationManager.hpp"
|
|
||||||
|
|
||||||
#include <settings/ButtonAspect.hpp>
|
|
||||||
#include <QTimer>
|
|
||||||
|
|
||||||
#include "QodeAssisttr.h"
|
|
||||||
|
|
||||||
namespace QodeAssist {
|
|
||||||
|
|
||||||
ConfigurationManager &ConfigurationManager::instance()
|
|
||||||
{
|
|
||||||
static ConfigurationManager instance;
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ConfigurationManager::init()
|
|
||||||
{
|
|
||||||
setupConnections();
|
|
||||||
updateAllTemplateDescriptions();
|
|
||||||
checkAllTemplate();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ConfigurationManager::updateTemplateDescription(const Utils::StringAspect &templateAspect)
|
|
||||||
{
|
|
||||||
PluginLLMCore::PromptTemplate *templ = m_templateManger.getFimTemplateByName(templateAspect.value());
|
|
||||||
|
|
||||||
if (!templ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (&templateAspect == &m_generalSettings.ccTemplate) {
|
|
||||||
m_generalSettings.ccTemplateDescription.setValue(templ->description());
|
|
||||||
} else if (&templateAspect == &m_generalSettings.caTemplate) {
|
|
||||||
m_generalSettings.caTemplateDescription.setValue(templ->description());
|
|
||||||
} else if (&templateAspect == &m_generalSettings.qrTemplate) {
|
|
||||||
m_generalSettings.qrTemplateDescription.setValue(templ->description());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ConfigurationManager::updateAllTemplateDescriptions()
|
|
||||||
{
|
|
||||||
updateTemplateDescription(m_generalSettings.ccTemplate);
|
|
||||||
updateTemplateDescription(m_generalSettings.caTemplate);
|
|
||||||
updateTemplateDescription(m_generalSettings.qrTemplate);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ConfigurationManager::checkTemplate(const Utils::StringAspect &templateAspect)
|
|
||||||
{
|
|
||||||
PluginLLMCore::PromptTemplate *templ = m_templateManger.getFimTemplateByName(templateAspect.value());
|
|
||||||
|
|
||||||
if (templ->name() == templateAspect.value())
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (&templateAspect == &m_generalSettings.ccTemplate) {
|
|
||||||
m_generalSettings.ccTemplate.setValue(templ->name());
|
|
||||||
} else if (&templateAspect == &m_generalSettings.caTemplate) {
|
|
||||||
m_generalSettings.caTemplate.setValue(templ->name());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ConfigurationManager::checkAllTemplate()
|
|
||||||
{
|
|
||||||
checkTemplate(m_generalSettings.ccTemplate);
|
|
||||||
checkTemplate(m_generalSettings.caTemplate);
|
|
||||||
}
|
|
||||||
|
|
||||||
ConfigurationManager::ConfigurationManager(QObject *parent)
|
|
||||||
: QObject(parent)
|
|
||||||
, m_generalSettings(Settings::generalSettings())
|
|
||||||
, m_providersManager(PluginLLMCore::ProvidersManager::instance())
|
|
||||||
, m_templateManger(PluginLLMCore::PromptTemplateManager::instance())
|
|
||||||
{}
|
|
||||||
|
|
||||||
void ConfigurationManager::setupConnections()
|
|
||||||
{
|
|
||||||
using Config = ConfigurationManager;
|
|
||||||
using Button = ButtonAspect;
|
|
||||||
|
|
||||||
connect(&m_generalSettings.ccSelectProvider, &Button::clicked, this, &Config::selectProvider);
|
|
||||||
connect(&m_generalSettings.caSelectProvider, &Button::clicked, this, &Config::selectProvider);
|
|
||||||
connect(&m_generalSettings.qrSelectProvider, &Button::clicked, this, &Config::selectProvider);
|
|
||||||
connect(&m_generalSettings.ccSelectModel, &Button::clicked, this, &Config::selectModel);
|
|
||||||
connect(&m_generalSettings.caSelectModel, &Button::clicked, this, &Config::selectModel);
|
|
||||||
connect(&m_generalSettings.qrSelectModel, &Button::clicked, this, &Config::selectModel);
|
|
||||||
connect(&m_generalSettings.ccSelectTemplate, &Button::clicked, this, &Config::selectTemplate);
|
|
||||||
connect(&m_generalSettings.caSelectTemplate, &Button::clicked, this, &Config::selectTemplate);
|
|
||||||
connect(&m_generalSettings.qrSelectTemplate, &Button::clicked, this, &Config::selectTemplate);
|
|
||||||
connect(&m_generalSettings.ccSetUrl, &Button::clicked, this, &Config::selectUrl);
|
|
||||||
connect(&m_generalSettings.caSetUrl, &Button::clicked, this, &Config::selectUrl);
|
|
||||||
connect(&m_generalSettings.qrSetUrl, &Button::clicked, this, &Config::selectUrl);
|
|
||||||
|
|
||||||
connect(
|
|
||||||
&m_generalSettings.ccPreset1SelectProvider, &Button::clicked, this, &Config::selectProvider);
|
|
||||||
connect(&m_generalSettings.ccPreset1SetUrl, &Button::clicked, this, &Config::selectUrl);
|
|
||||||
connect(&m_generalSettings.ccPreset1SelectModel, &Button::clicked, this, &Config::selectModel);
|
|
||||||
connect(
|
|
||||||
&m_generalSettings.ccPreset1SelectTemplate, &Button::clicked, this, &Config::selectTemplate);
|
|
||||||
|
|
||||||
connect(&m_generalSettings.ccTemplate, &Utils::StringAspect::changed, this, [this]() {
|
|
||||||
updateTemplateDescription(m_generalSettings.ccTemplate);
|
|
||||||
});
|
|
||||||
|
|
||||||
connect(&m_generalSettings.caTemplate, &Utils::StringAspect::changed, this, [this]() {
|
|
||||||
updateTemplateDescription(m_generalSettings.caTemplate);
|
|
||||||
});
|
|
||||||
|
|
||||||
connect(&m_generalSettings.qrTemplate, &Utils::StringAspect::changed, this, [this]() {
|
|
||||||
updateTemplateDescription(m_generalSettings.qrTemplate);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void ConfigurationManager::selectProvider()
|
|
||||||
{
|
|
||||||
const auto providersList = m_providersManager.providersNames();
|
|
||||||
|
|
||||||
auto *settingsButton = qobject_cast<ButtonAspect *>(sender());
|
|
||||||
if (!settingsButton)
|
|
||||||
return;
|
|
||||||
|
|
||||||
auto &targetSettings = (settingsButton == &m_generalSettings.ccSelectProvider)
|
|
||||||
? m_generalSettings.ccProvider
|
|
||||||
: settingsButton == &m_generalSettings.ccPreset1SelectProvider
|
|
||||||
? m_generalSettings.ccPreset1Provider
|
|
||||||
: settingsButton == &m_generalSettings.qrSelectProvider
|
|
||||||
? m_generalSettings.qrProvider
|
|
||||||
: m_generalSettings.caProvider;
|
|
||||||
|
|
||||||
QTimer::singleShot(0, this, [this, providersList, &targetSettings] {
|
|
||||||
m_generalSettings.showSelectionDialog(
|
|
||||||
providersList, targetSettings, Tr::tr("Select LLM Provider"), Tr::tr("Providers:"));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void ConfigurationManager::selectModel()
|
|
||||||
{
|
|
||||||
auto *settingsButton = qobject_cast<ButtonAspect *>(sender());
|
|
||||||
if (!settingsButton)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const bool isCodeCompletion = (settingsButton == &m_generalSettings.ccSelectModel);
|
|
||||||
const bool isPreset1 = (settingsButton == &m_generalSettings.ccPreset1SelectModel);
|
|
||||||
const bool isQuickRefactor = (settingsButton == &m_generalSettings.qrSelectModel);
|
|
||||||
|
|
||||||
const QString providerName = isCodeCompletion ? m_generalSettings.ccProvider.volatileValue()
|
|
||||||
: isPreset1 ? m_generalSettings.ccPreset1Provider.volatileValue()
|
|
||||||
: isQuickRefactor ? m_generalSettings.qrProvider.volatileValue()
|
|
||||||
: m_generalSettings.caProvider.volatileValue();
|
|
||||||
|
|
||||||
const auto providerUrl = isCodeCompletion ? m_generalSettings.ccUrl.volatileValue()
|
|
||||||
: isPreset1 ? m_generalSettings.ccPreset1Url.volatileValue()
|
|
||||||
: isQuickRefactor ? m_generalSettings.qrUrl.volatileValue()
|
|
||||||
: m_generalSettings.caUrl.volatileValue();
|
|
||||||
|
|
||||||
auto *targetSettings = &(isCodeCompletion ? m_generalSettings.ccModel
|
|
||||||
: isPreset1 ? m_generalSettings.ccPreset1Model
|
|
||||||
: isQuickRefactor ? m_generalSettings.qrModel
|
|
||||||
: m_generalSettings.caModel);
|
|
||||||
|
|
||||||
if (auto provider = m_providersManager.getProviderByName(providerName)) {
|
|
||||||
if (!provider->capabilities().testFlag(PluginLLMCore::ProviderCapability::ModelListing)) {
|
|
||||||
m_generalSettings.showModelsNotSupportedDialog(*targetSettings);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
provider->getInstalledModels(providerUrl)
|
|
||||||
.then(this, [this, targetSettings](const QList<QString> &modelList) {
|
|
||||||
if (modelList.isEmpty()) {
|
|
||||||
m_generalSettings.showModelsNotFoundDialog(*targetSettings);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
m_generalSettings.showSelectionDialog(
|
|
||||||
modelList, *targetSettings, Tr::tr("Select LLM Model"), Tr::tr("Models:"));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ConfigurationManager::selectTemplate()
|
|
||||||
{
|
|
||||||
auto *settingsButton = qobject_cast<ButtonAspect *>(sender());
|
|
||||||
if (!settingsButton)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const bool isCodeCompletion = (settingsButton == &m_generalSettings.ccSelectTemplate);
|
|
||||||
const bool isPreset1 = (settingsButton == &m_generalSettings.ccPreset1SelectTemplate);
|
|
||||||
const bool isQuickRefactor = (settingsButton == &m_generalSettings.qrSelectTemplate);
|
|
||||||
const QString providerName = isCodeCompletion ? m_generalSettings.ccProvider.volatileValue()
|
|
||||||
: isPreset1 ? m_generalSettings.ccPreset1Provider.volatileValue()
|
|
||||||
: isQuickRefactor ? m_generalSettings.qrProvider.volatileValue()
|
|
||||||
: m_generalSettings.caProvider.volatileValue();
|
|
||||||
auto providerID = m_providersManager.getProviderByName(providerName)->providerID();
|
|
||||||
|
|
||||||
const auto templateList = isCodeCompletion || isPreset1
|
|
||||||
? m_templateManger.getFimTemplatesForProvider(providerID)
|
|
||||||
: m_templateManger.getChatTemplatesForProvider(providerID);
|
|
||||||
|
|
||||||
auto &targetSettings = isCodeCompletion ? m_generalSettings.ccTemplate
|
|
||||||
: isPreset1 ? m_generalSettings.ccPreset1Template
|
|
||||||
: isQuickRefactor ? m_generalSettings.qrTemplate
|
|
||||||
: m_generalSettings.caTemplate;
|
|
||||||
|
|
||||||
QTimer::singleShot(0, &m_generalSettings, [this, templateList, &targetSettings]() {
|
|
||||||
m_generalSettings.showSelectionDialog(
|
|
||||||
templateList, targetSettings, Tr::tr("Select Template"), Tr::tr("Templates:"));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void ConfigurationManager::selectUrl()
|
|
||||||
{
|
|
||||||
auto *settingsButton = qobject_cast<ButtonAspect *>(sender());
|
|
||||||
if (!settingsButton)
|
|
||||||
return;
|
|
||||||
|
|
||||||
QStringList urls;
|
|
||||||
for (const auto &name : m_providersManager.providersNames()) {
|
|
||||||
const auto url = m_providersManager.getProviderByName(name)->url();
|
|
||||||
if (!urls.contains(url))
|
|
||||||
urls.append(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto &targetSettings = (settingsButton == &m_generalSettings.ccSetUrl) ? m_generalSettings.ccUrl
|
|
||||||
: settingsButton == &m_generalSettings.ccPreset1SetUrl
|
|
||||||
? m_generalSettings.ccPreset1Url
|
|
||||||
: settingsButton == &m_generalSettings.qrSetUrl
|
|
||||||
? m_generalSettings.qrUrl
|
|
||||||
: m_generalSettings.caUrl;
|
|
||||||
|
|
||||||
QTimer::singleShot(0, &m_generalSettings, [this, urls, &targetSettings]() {
|
|
||||||
m_generalSettings.showUrlSelectionDialog(targetSettings, urls);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace QodeAssist
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
// 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
|
|
||||||
|
|
||||||
#include <QObject>
|
|
||||||
|
|
||||||
#include "pluginllmcore/PromptTemplateManager.hpp"
|
|
||||||
#include "pluginllmcore/ProvidersManager.hpp"
|
|
||||||
#include "settings/GeneralSettings.hpp"
|
|
||||||
|
|
||||||
namespace QodeAssist {
|
|
||||||
|
|
||||||
class ConfigurationManager : public QObject
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
|
||||||
static ConfigurationManager &instance();
|
|
||||||
|
|
||||||
void init();
|
|
||||||
|
|
||||||
void updateTemplateDescription(const Utils::StringAspect &templateAspect);
|
|
||||||
void updateAllTemplateDescriptions();
|
|
||||||
void checkTemplate(const Utils::StringAspect &templateAspect);
|
|
||||||
void checkAllTemplate();
|
|
||||||
|
|
||||||
public slots:
|
|
||||||
void selectProvider();
|
|
||||||
void selectModel();
|
|
||||||
void selectTemplate();
|
|
||||||
void selectUrl();
|
|
||||||
|
|
||||||
private:
|
|
||||||
explicit ConfigurationManager(QObject *parent = nullptr);
|
|
||||||
~ConfigurationManager() = default;
|
|
||||||
ConfigurationManager(const ConfigurationManager &) = delete;
|
|
||||||
ConfigurationManager &operator=(const ConfigurationManager &) = delete;
|
|
||||||
|
|
||||||
Settings::GeneralSettings &m_generalSettings;
|
|
||||||
PluginLLMCore::ProvidersManager &m_providersManager;
|
|
||||||
PluginLLMCore::PromptTemplateManager &m_templateManger;
|
|
||||||
|
|
||||||
void setupConnections();
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace QodeAssist
|
|
||||||
@@ -9,27 +9,39 @@
|
|||||||
#include <QNetworkAccessManager>
|
#include <QNetworkAccessManager>
|
||||||
#include <QNetworkReply>
|
#include <QNetworkReply>
|
||||||
|
|
||||||
|
#include <projectexplorer/project.h>
|
||||||
|
#include <projectexplorer/projectmanager.h>
|
||||||
|
#include <utils/filepath.h>
|
||||||
|
|
||||||
|
#include <AgentConfig.hpp>
|
||||||
|
#include <AgentFactory.hpp>
|
||||||
|
#include <AgentRouter.hpp>
|
||||||
|
#include <ResponseEvent.hpp>
|
||||||
|
#include <Session.hpp>
|
||||||
|
#include <SessionManager.hpp>
|
||||||
|
#include "sources/common/ContextData.hpp"
|
||||||
|
|
||||||
#include "CodeHandler.hpp"
|
#include "CodeHandler.hpp"
|
||||||
#include "context/DocumentContextReader.hpp"
|
#include "context/DocumentContextReader.hpp"
|
||||||
#include "context/Utils.hpp"
|
#include "context/Utils.hpp"
|
||||||
#include "logger/Logger.hpp"
|
#include "logger/Logger.hpp"
|
||||||
#include "settings/CodeCompletionSettings.hpp"
|
#include "settings/CodeCompletionSettings.hpp"
|
||||||
#include "settings/GeneralSettings.hpp"
|
#include "settings/GeneralSettings.hpp"
|
||||||
#include <pluginllmcore/RulesLoader.hpp>
|
#include "sources/settings/PipelinesConfig.hpp"
|
||||||
|
|
||||||
namespace QodeAssist {
|
namespace QodeAssist {
|
||||||
|
|
||||||
LLMClientInterface::LLMClientInterface(
|
LLMClientInterface::LLMClientInterface(
|
||||||
const Settings::GeneralSettings &generalSettings,
|
const Settings::GeneralSettings &generalSettings,
|
||||||
const Settings::CodeCompletionSettings &completeSettings,
|
const Settings::CodeCompletionSettings &completeSettings,
|
||||||
PluginLLMCore::IProviderRegistry &providerRegistry,
|
SessionManager &sessionManager,
|
||||||
PluginLLMCore::IPromptProvider *promptProvider,
|
AgentFactory &agentFactory,
|
||||||
Context::IDocumentReader &documentReader,
|
Context::IDocumentReader &documentReader,
|
||||||
IRequestPerformanceLogger &performanceLogger)
|
IRequestPerformanceLogger &performanceLogger)
|
||||||
: m_generalSettings(generalSettings)
|
: m_generalSettings(generalSettings)
|
||||||
, m_completeSettings(completeSettings)
|
, m_completeSettings(completeSettings)
|
||||||
, m_providerRegistry(providerRegistry)
|
, m_sessionManager(sessionManager)
|
||||||
, m_promptProvider(promptProvider)
|
, m_agentFactory(agentFactory)
|
||||||
, m_documentReader(documentReader)
|
, m_documentReader(documentReader)
|
||||||
, m_performanceLogger(performanceLogger)
|
, m_performanceLogger(performanceLogger)
|
||||||
, m_contextManager(new Context::ContextManager(this))
|
, m_contextManager(new Context::ContextManager(this))
|
||||||
@@ -51,58 +63,64 @@ void LLMClientInterface::startImpl()
|
|||||||
emit started();
|
emit started();
|
||||||
}
|
}
|
||||||
|
|
||||||
void LLMClientInterface::handleFullResponse(const QString &requestId, const QString &fullText)
|
void LLMClientInterface::onSessionEvent(const QString &requestId, const ResponseEvent &event)
|
||||||
{
|
{
|
||||||
auto it = m_activeRequests.find(requestId);
|
auto it = m_activeRequests.find(requestId);
|
||||||
if (it == m_activeRequests.end())
|
if (it == m_activeRequests.end())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const RequestContext &ctx = it.value();
|
if (event.kind() == ResponseEvent::Kind::TextDelta) {
|
||||||
sendCompletionToClient(fullText, ctx.originalRequest, true);
|
if (const auto *delta = event.as<ResponseEvents::TextDelta>())
|
||||||
|
it.value().accumulated += delta->text;
|
||||||
m_activeRequests.erase(it);
|
}
|
||||||
m_performanceLogger.endTimeMeasurement(requestId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void LLMClientInterface::handleRequestFinalized(
|
void LLMClientInterface::onSessionFinished(const QString &requestId)
|
||||||
const ::LLMQore::RequestID &requestId, const ::LLMQore::CompletionInfo &info)
|
|
||||||
{
|
|
||||||
if (!m_activeRequests.contains(requestId) || !info.usage)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const auto &u = *info.usage;
|
|
||||||
LOG_MESSAGE(QString("Completion usage [%1]: prompt=%2 completion=%3 cached=%4 reasoning=%5")
|
|
||||||
.arg(requestId)
|
|
||||||
.arg(u.promptTokens)
|
|
||||||
.arg(u.completionTokens)
|
|
||||||
.arg(u.cachedPromptTokens)
|
|
||||||
.arg(u.reasoningTokens));
|
|
||||||
}
|
|
||||||
|
|
||||||
void LLMClientInterface::handleRequestFailed(const QString &requestId, const QString &error)
|
|
||||||
{
|
{
|
||||||
auto it = m_activeRequests.find(requestId);
|
auto it = m_activeRequests.find(requestId);
|
||||||
if (it == m_activeRequests.end())
|
if (it == m_activeRequests.end())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const RequestContext &ctx = it.value();
|
const QString fullText = it.value().accumulated;
|
||||||
|
const QJsonObject originalRequest = it.value().originalRequest;
|
||||||
|
|
||||||
|
sendCompletionToClient(fullText, originalRequest, true);
|
||||||
|
finishRequest(requestId);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LLMClientInterface::onSessionFailed(const QString &requestId, const QString &error)
|
||||||
|
{
|
||||||
|
auto it = m_activeRequests.find(requestId);
|
||||||
|
if (it == m_activeRequests.end())
|
||||||
|
return;
|
||||||
|
|
||||||
LOG_MESSAGE(QString("Request %1 failed: %2").arg(requestId, error));
|
LOG_MESSAGE(QString("Request %1 failed: %2").arg(requestId, error));
|
||||||
|
|
||||||
// Send LSP error response to client
|
|
||||||
QJsonObject response;
|
QJsonObject response;
|
||||||
response["jsonrpc"] = "2.0";
|
response["jsonrpc"] = "2.0";
|
||||||
response[LanguageServerProtocol::idKey] = ctx.originalRequest["id"];
|
response[LanguageServerProtocol::idKey] = it.value().originalRequest["id"];
|
||||||
|
|
||||||
QJsonObject errorObject;
|
QJsonObject errorObject;
|
||||||
errorObject["code"] = -32603; // Internal error code
|
errorObject["code"] = -32603; // Internal error code
|
||||||
errorObject["message"] = error;
|
errorObject["message"] = error;
|
||||||
response["error"] = errorObject;
|
response["error"] = errorObject;
|
||||||
|
|
||||||
emit messageReceived(LanguageServerProtocol::JsonRpcMessage(response));
|
emit messageReceived(LanguageServerProtocol::JsonRpcMessage(response));
|
||||||
|
finishRequest(requestId);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LLMClientInterface::finishRequest(const QString &requestId)
|
||||||
|
{
|
||||||
|
auto it = m_activeRequests.find(requestId);
|
||||||
|
if (it == m_activeRequests.end())
|
||||||
|
return;
|
||||||
|
|
||||||
|
Session *session = it.value().session;
|
||||||
m_activeRequests.erase(it);
|
m_activeRequests.erase(it);
|
||||||
m_performanceLogger.endTimeMeasurement(requestId);
|
m_performanceLogger.endTimeMeasurement(requestId);
|
||||||
|
|
||||||
|
if (session)
|
||||||
|
m_sessionManager.removeSession(session);
|
||||||
}
|
}
|
||||||
|
|
||||||
void LLMClientInterface::sendData(const QByteArray &data)
|
void LLMClientInterface::sendData(const QByteArray &data)
|
||||||
@@ -135,26 +153,15 @@ void LLMClientInterface::sendData(const QByteArray &data)
|
|||||||
|
|
||||||
void LLMClientInterface::handleCancelRequest()
|
void LLMClientInterface::handleCancelRequest()
|
||||||
{
|
{
|
||||||
QSet<PluginLLMCore::Provider *> providers;
|
const auto requests = m_activeRequests;
|
||||||
for (auto it = m_activeRequests.begin(); it != m_activeRequests.end(); ++it) {
|
|
||||||
if (it.value().provider) {
|
|
||||||
providers.insert(it.value().provider);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto *provider : providers) {
|
|
||||||
disconnect(provider->client(), nullptr, this, nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto it = m_activeRequests.begin(); it != m_activeRequests.end(); ++it) {
|
|
||||||
const RequestContext &ctx = it.value();
|
|
||||||
if (ctx.provider) {
|
|
||||||
ctx.provider->cancelRequest(it.key());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
m_activeRequests.clear();
|
m_activeRequests.clear();
|
||||||
|
|
||||||
|
for (auto it = requests.begin(); it != requests.end(); ++it) {
|
||||||
|
m_performanceLogger.endTimeMeasurement(it.key());
|
||||||
|
if (it.value().session)
|
||||||
|
m_sessionManager.removeSession(it.value().session);
|
||||||
|
}
|
||||||
|
|
||||||
LOG_MESSAGE("All requests cancelled and state cleared");
|
LOG_MESSAGE("All requests cancelled and state cleared");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,133 +244,86 @@ void LLMClientInterface::handleCompletion(const QJsonObject &request)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto updatedContext = prepareContext(request, documentInfo);
|
const QString agentName = pickCompletionAgent(filePath);
|
||||||
|
if (agentName.isEmpty()) {
|
||||||
bool isPreset1Active = m_contextManager->isSpecifyCompletion(documentInfo);
|
QString error = QString("No code completion agent matches: %1").arg(filePath);
|
||||||
|
|
||||||
const auto providerName = !isPreset1Active ? m_generalSettings.ccProvider()
|
|
||||||
: m_generalSettings.ccPreset1Provider();
|
|
||||||
const auto modelName = !isPreset1Active ? m_generalSettings.ccModel()
|
|
||||||
: m_generalSettings.ccPreset1Model();
|
|
||||||
const auto url = !isPreset1Active ? m_generalSettings.ccUrl()
|
|
||||||
: m_generalSettings.ccPreset1Url();
|
|
||||||
|
|
||||||
const auto provider = m_providerRegistry.getProviderByName(providerName);
|
|
||||||
|
|
||||||
if (!provider) {
|
|
||||||
QString error = QString("No provider found with name: %1").arg(providerName);
|
|
||||||
LOG_MESSAGE(error);
|
LOG_MESSAGE(error);
|
||||||
sendErrorResponse(request, error);
|
sendErrorResponse(request, error);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto templateName = !isPreset1Active ? m_generalSettings.ccTemplate()
|
QString sessionError;
|
||||||
: m_generalSettings.ccPreset1Template();
|
Session *session = m_sessionManager.createSession(agentName, &sessionError);
|
||||||
|
if (!session) {
|
||||||
|
LOG_MESSAGE(sessionError);
|
||||||
|
sendErrorResponse(request, sessionError);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
auto promptTemplate = m_promptProvider->getTemplateByName(templateName);
|
Templates::ContextData context = prepareContext(request, documentInfo);
|
||||||
|
|
||||||
if (!promptTemplate) {
|
QString editorContext;
|
||||||
QString error = QString("No template found with name: %1").arg(templateName);
|
if (context.fileContext.has_value())
|
||||||
|
editorContext.append(context.fileContext.value());
|
||||||
|
|
||||||
|
if (m_completeSettings.useOpenFilesContext())
|
||||||
|
editorContext.append(m_contextManager->openedFilesContext({filePath}));
|
||||||
|
|
||||||
|
if (!editorContext.isEmpty())
|
||||||
|
context.systemPrompt = editorContext;
|
||||||
|
|
||||||
|
connect(session, &Session::event, this, [this, session](const ResponseEvent &event) {
|
||||||
|
onSessionEvent(requestIdForSession(session), event);
|
||||||
|
});
|
||||||
|
connect(session, &Session::finished, this, [this, session](const LLMQore::RequestID &, const QString &) {
|
||||||
|
onSessionFinished(requestIdForSession(session));
|
||||||
|
});
|
||||||
|
connect(session, &Session::failed, this, [this, session](const LLMQore::RequestID &, const QString &error) {
|
||||||
|
onSessionFailed(requestIdForSession(session), error);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (auto *client = session->client())
|
||||||
|
client->setTransferTimeout(
|
||||||
|
static_cast<int>(m_generalSettings.requestTimeout() * 1000));
|
||||||
|
|
||||||
|
const LLMQore::RequestID requestId = session->sendCompletion(std::move(context));
|
||||||
|
if (requestId.isEmpty()) {
|
||||||
|
m_sessionManager.removeSession(session);
|
||||||
|
QString error = QString("Failed to start completion request for agent: %1").arg(agentName);
|
||||||
LOG_MESSAGE(error);
|
LOG_MESSAGE(error);
|
||||||
sendErrorResponse(request, error);
|
sendErrorResponse(request, error);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
QJsonObject payload{{"model", modelName}, {"stream", true}};
|
m_activeRequests[requestId] = {request, session, QString()};
|
||||||
|
|
||||||
const auto stopWords = QJsonArray::fromStringList(promptTemplate->stopWords());
|
|
||||||
if (!stopWords.isEmpty())
|
|
||||||
payload["stop"] = stopWords;
|
|
||||||
|
|
||||||
QString systemPrompt;
|
|
||||||
if (m_completeSettings.useSystemPrompt())
|
|
||||||
systemPrompt.append(
|
|
||||||
m_completeSettings.useUserMessageTemplateForCC()
|
|
||||||
&& promptTemplate->type() == PluginLLMCore::TemplateType::Chat
|
|
||||||
? m_completeSettings.systemPromptForNonFimModels()
|
|
||||||
: m_completeSettings.systemPrompt());
|
|
||||||
|
|
||||||
auto project = PluginLLMCore::RulesLoader::getActiveProject();
|
|
||||||
if (project) {
|
|
||||||
QString projectRules
|
|
||||||
= PluginLLMCore::RulesLoader::loadRulesForProject(project, PluginLLMCore::RulesContext::Completions);
|
|
||||||
|
|
||||||
if (!projectRules.isEmpty()) {
|
|
||||||
systemPrompt += "\n\n# Project Rules\n\n" + projectRules;
|
|
||||||
LOG_MESSAGE("Loaded project rules for completion");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (updatedContext.fileContext.has_value())
|
|
||||||
systemPrompt.append(updatedContext.fileContext.value());
|
|
||||||
|
|
||||||
if (m_completeSettings.useOpenFilesContext()) {
|
|
||||||
if (provider->providerID() == PluginLLMCore::ProviderID::LlamaCpp) {
|
|
||||||
for (const auto openedFilePath : m_contextManager->openedFiles({filePath})) {
|
|
||||||
if (!updatedContext.filesMetadata) {
|
|
||||||
updatedContext.filesMetadata = QList<PluginLLMCore::FileMetadata>();
|
|
||||||
}
|
|
||||||
updatedContext.filesMetadata->append({openedFilePath.first, openedFilePath.second});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
systemPrompt.append(m_contextManager->openedFilesContext({filePath}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
updatedContext.systemPrompt = systemPrompt;
|
|
||||||
|
|
||||||
if (promptTemplate->type() == PluginLLMCore::TemplateType::Chat) {
|
|
||||||
QString userMessage;
|
|
||||||
if (m_completeSettings.useUserMessageTemplateForCC()) {
|
|
||||||
userMessage = m_completeSettings.processMessageToFIM(
|
|
||||||
updatedContext.prefix.value_or(""), updatedContext.suffix.value_or(""));
|
|
||||||
} else {
|
|
||||||
userMessage = updatedContext.prefix.value_or("") + updatedContext.suffix.value_or("");
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO refactor add message
|
|
||||||
QVector<PluginLLMCore::Message> messages;
|
|
||||||
messages.append({"user", userMessage});
|
|
||||||
updatedContext.history = messages;
|
|
||||||
}
|
|
||||||
|
|
||||||
provider->prepareRequest(
|
|
||||||
payload,
|
|
||||||
promptTemplate,
|
|
||||||
updatedContext,
|
|
||||||
PluginLLMCore::RequestType::CodeCompletion,
|
|
||||||
false,
|
|
||||||
false);
|
|
||||||
|
|
||||||
connect(
|
|
||||||
provider->client(),
|
|
||||||
&::LLMQore::BaseClient::requestCompleted,
|
|
||||||
this,
|
|
||||||
&LLMClientInterface::handleFullResponse,
|
|
||||||
Qt::UniqueConnection);
|
|
||||||
connect(
|
|
||||||
provider->client(),
|
|
||||||
&::LLMQore::BaseClient::requestFinalized,
|
|
||||||
this,
|
|
||||||
&LLMClientInterface::handleRequestFinalized,
|
|
||||||
Qt::UniqueConnection);
|
|
||||||
connect(
|
|
||||||
provider->client(),
|
|
||||||
&::LLMQore::BaseClient::requestFailed,
|
|
||||||
this,
|
|
||||||
&LLMClientInterface::handleRequestFailed,
|
|
||||||
Qt::UniqueConnection);
|
|
||||||
|
|
||||||
provider->client()->setTransferTimeout(
|
|
||||||
static_cast<int>(m_generalSettings.requestTimeout() * 1000));
|
|
||||||
|
|
||||||
auto requestId
|
|
||||||
= provider->sendRequest(QUrl(url), payload, resolveEndpoint(promptTemplate, isPreset1Active));
|
|
||||||
m_activeRequests[requestId] = {request, provider};
|
|
||||||
m_performanceLogger.startTimeMeasurement(requestId);
|
m_performanceLogger.startTimeMeasurement(requestId);
|
||||||
}
|
}
|
||||||
|
|
||||||
PluginLLMCore::ContextData LLMClientInterface::prepareContext(
|
QString LLMClientInterface::pickCompletionAgent(const QString &filePath) const
|
||||||
|
{
|
||||||
|
const QStringList roster = Settings::PipelinesConfig::load().rosters.codeCompletion;
|
||||||
|
if (roster.isEmpty())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
AgentRouter::Context ctx;
|
||||||
|
ctx.filePath = filePath;
|
||||||
|
if (auto *project = ProjectExplorer::ProjectManager::projectForFile(
|
||||||
|
Utils::FilePath::fromString(filePath)))
|
||||||
|
ctx.projectName = project->displayName();
|
||||||
|
|
||||||
|
return AgentRouter::pickAgent(roster, ctx, m_agentFactory);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString LLMClientInterface::requestIdForSession(Session *session) const
|
||||||
|
{
|
||||||
|
for (auto it = m_activeRequests.cbegin(); it != m_activeRequests.cend(); ++it) {
|
||||||
|
if (it.value().session == session)
|
||||||
|
return it.key();
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
Templates::ContextData LLMClientInterface::prepareContext(
|
||||||
const QJsonObject &request, const Context::DocumentInfo &documentInfo)
|
const QJsonObject &request, const Context::DocumentInfo &documentInfo)
|
||||||
{
|
{
|
||||||
QJsonObject params = request["params"].toObject();
|
QJsonObject params = request["params"].toObject();
|
||||||
@@ -374,15 +334,14 @@ PluginLLMCore::ContextData LLMClientInterface::prepareContext(
|
|||||||
|
|
||||||
Context::DocumentContextReader
|
Context::DocumentContextReader
|
||||||
reader(documentInfo.document, documentInfo.mimeType, documentInfo.filePath);
|
reader(documentInfo.document, documentInfo.mimeType, documentInfo.filePath);
|
||||||
return reader.prepareContext(lineNumber, cursorPosition, m_completeSettings);
|
const PluginLLMCore::ContextData legacy
|
||||||
}
|
= reader.prepareContext(lineNumber, cursorPosition, m_completeSettings);
|
||||||
|
|
||||||
QString LLMClientInterface::resolveEndpoint(
|
Templates::ContextData context;
|
||||||
PluginLLMCore::PromptTemplate *promptTemplate, bool isLanguageSpecify) const
|
context.prefix = legacy.prefix;
|
||||||
{
|
context.suffix = legacy.suffix;
|
||||||
const QString custom = isLanguageSpecify ? m_generalSettings.ccPreset1CustomEndpoint()
|
context.fileContext = legacy.fileContext;
|
||||||
: m_generalSettings.ccCustomEndpoint();
|
return context;
|
||||||
return !custom.isEmpty() ? custom : promptTemplate->endpoint();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Context::ContextManager *LLMClientInterface::contextManager() const
|
Context::ContextManager *LLMClientInterface::contextManager() const
|
||||||
@@ -393,15 +352,6 @@ Context::ContextManager *LLMClientInterface::contextManager() const
|
|||||||
void LLMClientInterface::sendCompletionToClient(
|
void LLMClientInterface::sendCompletionToClient(
|
||||||
const QString &completion, const QJsonObject &request, bool isComplete)
|
const QString &completion, const QJsonObject &request, bool isComplete)
|
||||||
{
|
{
|
||||||
auto filePath = Context::extractFilePathFromRequest(request);
|
|
||||||
auto documentInfo = m_documentReader.readDocument(filePath);
|
|
||||||
bool isPreset1Active = m_contextManager->isSpecifyCompletion(documentInfo);
|
|
||||||
|
|
||||||
auto templateName = !isPreset1Active ? m_generalSettings.ccTemplate()
|
|
||||||
: m_generalSettings.ccPreset1Template();
|
|
||||||
|
|
||||||
auto promptTemplate = m_promptProvider->getTemplateByName(templateName);
|
|
||||||
|
|
||||||
QJsonObject position = request["params"].toObject()["doc"].toObject()["position"].toObject();
|
QJsonObject position = request["params"].toObject()["doc"].toObject()["position"].toObject();
|
||||||
|
|
||||||
QJsonObject response;
|
QJsonObject response;
|
||||||
|
|||||||
@@ -8,12 +8,11 @@
|
|||||||
#include <languageclient/languageclientinterface.h>
|
#include <languageclient/languageclientinterface.h>
|
||||||
#include <texteditor/texteditor.h>
|
#include <texteditor/texteditor.h>
|
||||||
|
|
||||||
|
#include <QPointer>
|
||||||
|
|
||||||
#include <context/ContextManager.hpp>
|
#include <context/ContextManager.hpp>
|
||||||
#include <context/IDocumentReader.hpp>
|
#include <context/IDocumentReader.hpp>
|
||||||
#include <context/ProgrammingLanguage.hpp>
|
#include <context/ProgrammingLanguage.hpp>
|
||||||
#include <pluginllmcore/ContextData.hpp>
|
|
||||||
#include <pluginllmcore/IPromptProvider.hpp>
|
|
||||||
#include <pluginllmcore/IProviderRegistry.hpp>
|
|
||||||
#include <logger/IRequestPerformanceLogger.hpp>
|
#include <logger/IRequestPerformanceLogger.hpp>
|
||||||
#include <settings/CodeCompletionSettings.hpp>
|
#include <settings/CodeCompletionSettings.hpp>
|
||||||
#include <settings/GeneralSettings.hpp>
|
#include <settings/GeneralSettings.hpp>
|
||||||
@@ -23,6 +22,15 @@ class QNetworkAccessManager;
|
|||||||
|
|
||||||
namespace QodeAssist {
|
namespace QodeAssist {
|
||||||
|
|
||||||
|
class SessionManager;
|
||||||
|
class AgentFactory;
|
||||||
|
class Session;
|
||||||
|
class ResponseEvent;
|
||||||
|
|
||||||
|
namespace Templates {
|
||||||
|
struct ContextData;
|
||||||
|
}
|
||||||
|
|
||||||
class LLMClientInterface : public LanguageClient::BaseClientInterface
|
class LLMClientInterface : public LanguageClient::BaseClientInterface
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
@@ -31,8 +39,8 @@ public:
|
|||||||
LLMClientInterface(
|
LLMClientInterface(
|
||||||
const Settings::GeneralSettings &generalSettings,
|
const Settings::GeneralSettings &generalSettings,
|
||||||
const Settings::CodeCompletionSettings &completeSettings,
|
const Settings::CodeCompletionSettings &completeSettings,
|
||||||
PluginLLMCore::IProviderRegistry &providerRegistry,
|
SessionManager &sessionManager,
|
||||||
PluginLLMCore::IPromptProvider *promptProvider,
|
AgentFactory &agentFactory,
|
||||||
Context::IDocumentReader &documentReader,
|
Context::IDocumentReader &documentReader,
|
||||||
IRequestPerformanceLogger &performanceLogger);
|
IRequestPerformanceLogger &performanceLogger);
|
||||||
~LLMClientInterface() override;
|
~LLMClientInterface() override;
|
||||||
@@ -52,12 +60,6 @@ public:
|
|||||||
protected:
|
protected:
|
||||||
void startImpl() override;
|
void startImpl() override;
|
||||||
|
|
||||||
private slots:
|
|
||||||
void handleFullResponse(const QString &requestId, const QString &fullText);
|
|
||||||
void handleRequestFinalized(
|
|
||||||
const ::LLMQore::RequestID &requestId, const ::LLMQore::CompletionInfo &info);
|
|
||||||
void handleRequestFailed(const QString &requestId, const QString &error);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void handleInitialize(const QJsonObject &request);
|
void handleInitialize(const QJsonObject &request);
|
||||||
void handleShutdown(const QJsonObject &request);
|
void handleShutdown(const QJsonObject &request);
|
||||||
@@ -67,22 +69,28 @@ private:
|
|||||||
void handleCancelRequest();
|
void handleCancelRequest();
|
||||||
void sendErrorResponse(const QJsonObject &request, const QString &errorMessage);
|
void sendErrorResponse(const QJsonObject &request, const QString &errorMessage);
|
||||||
|
|
||||||
|
void onSessionEvent(const QString &requestId, const ResponseEvent &event);
|
||||||
|
void onSessionFinished(const QString &requestId);
|
||||||
|
void onSessionFailed(const QString &requestId, const QString &error);
|
||||||
|
void finishRequest(const QString &requestId);
|
||||||
|
QString requestIdForSession(Session *session) const;
|
||||||
|
|
||||||
struct RequestContext
|
struct RequestContext
|
||||||
{
|
{
|
||||||
QJsonObject originalRequest;
|
QJsonObject originalRequest;
|
||||||
PluginLLMCore::Provider *provider;
|
QPointer<Session> session;
|
||||||
|
QString accumulated;
|
||||||
};
|
};
|
||||||
|
|
||||||
PluginLLMCore::ContextData prepareContext(
|
Templates::ContextData prepareContext(
|
||||||
const QJsonObject &request, const Context::DocumentInfo &documentInfo);
|
const QJsonObject &request, const Context::DocumentInfo &documentInfo);
|
||||||
|
|
||||||
QString resolveEndpoint(
|
QString pickCompletionAgent(const QString &filePath) const;
|
||||||
PluginLLMCore::PromptTemplate *promptTemplate, bool isLanguageSpecify) const;
|
|
||||||
|
|
||||||
const Settings::CodeCompletionSettings &m_completeSettings;
|
const Settings::CodeCompletionSettings &m_completeSettings;
|
||||||
const Settings::GeneralSettings &m_generalSettings;
|
const Settings::GeneralSettings &m_generalSettings;
|
||||||
PluginLLMCore::IPromptProvider *m_promptProvider = nullptr;
|
SessionManager &m_sessionManager;
|
||||||
PluginLLMCore::IProviderRegistry &m_providerRegistry;
|
AgentFactory &m_agentFactory;
|
||||||
Context::IDocumentReader &m_documentReader;
|
Context::IDocumentReader &m_documentReader;
|
||||||
IRequestPerformanceLogger &m_performanceLogger;
|
IRequestPerformanceLogger &m_performanceLogger;
|
||||||
QElapsedTimer m_completionTimer;
|
QElapsedTimer m_completionTimer;
|
||||||
|
|||||||
@@ -159,6 +159,16 @@ QodeAssistClient::QodeAssistClient(LLMClientInterface *clientInterface)
|
|||||||
m_refactorWidgetHandler = new RefactorWidgetHandler(this);
|
m_refactorWidgetHandler = new RefactorWidgetHandler(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void QodeAssistClient::setSessionManager(SessionManager *sessionManager)
|
||||||
|
{
|
||||||
|
m_sessionManager = sessionManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
void QodeAssistClient::setAgentFactory(AgentFactory *agentFactory)
|
||||||
|
{
|
||||||
|
m_agentFactory = agentFactory;
|
||||||
|
}
|
||||||
|
|
||||||
QodeAssistClient::~QodeAssistClient()
|
QodeAssistClient::~QodeAssistClient()
|
||||||
{
|
{
|
||||||
cleanupConnections();
|
cleanupConnections();
|
||||||
@@ -319,6 +329,8 @@ void QodeAssistClient::requestQuickRefactor(
|
|||||||
|
|
||||||
if (!m_refactorHandler) {
|
if (!m_refactorHandler) {
|
||||||
m_refactorHandler = new QuickRefactorHandler(this);
|
m_refactorHandler = new QuickRefactorHandler(this);
|
||||||
|
m_refactorHandler->setSessionManager(m_sessionManager);
|
||||||
|
m_refactorHandler->setAgentFactory(m_agentFactory);
|
||||||
connect(
|
connect(
|
||||||
m_refactorHandler,
|
m_refactorHandler,
|
||||||
&QuickRefactorHandler::refactoringCompleted,
|
&QuickRefactorHandler::refactoringCompleted,
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
#include <QPointer>
|
||||||
|
|
||||||
#include "LLMClientInterface.hpp"
|
#include "LLMClientInterface.hpp"
|
||||||
#include "LSPCompletion.hpp"
|
#include "LSPCompletion.hpp"
|
||||||
@@ -21,6 +22,9 @@
|
|||||||
|
|
||||||
namespace QodeAssist {
|
namespace QodeAssist {
|
||||||
|
|
||||||
|
class SessionManager;
|
||||||
|
class AgentFactory;
|
||||||
|
|
||||||
class QodeAssistClient : public LanguageClient::Client
|
class QodeAssistClient : public LanguageClient::Client
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
@@ -28,6 +32,9 @@ public:
|
|||||||
explicit QodeAssistClient(LLMClientInterface *clientInterface);
|
explicit QodeAssistClient(LLMClientInterface *clientInterface);
|
||||||
~QodeAssistClient() override;
|
~QodeAssistClient() override;
|
||||||
|
|
||||||
|
void setSessionManager(SessionManager *sessionManager);
|
||||||
|
void setAgentFactory(AgentFactory *agentFactory);
|
||||||
|
|
||||||
void openDocument(TextEditor::TextDocument *document) override;
|
void openDocument(TextEditor::TextDocument *document) override;
|
||||||
bool canOpenProject(ProjectExplorer::Project *project) override;
|
bool canOpenProject(ProjectExplorer::Project *project) override;
|
||||||
|
|
||||||
@@ -68,6 +75,8 @@ private:
|
|||||||
RefactorSuggestionHoverHandler *m_refactorHoverHandler{nullptr};
|
RefactorSuggestionHoverHandler *m_refactorHoverHandler{nullptr};
|
||||||
RefactorWidgetHandler *m_refactorWidgetHandler{nullptr};
|
RefactorWidgetHandler *m_refactorWidgetHandler{nullptr};
|
||||||
LLMClientInterface *m_llmClient;
|
LLMClientInterface *m_llmClient;
|
||||||
|
SessionManager *m_sessionManager{nullptr};
|
||||||
|
AgentFactory *m_agentFactory{nullptr};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace QodeAssist
|
} // namespace QodeAssist
|
||||||
|
|||||||
@@ -4,24 +4,38 @@
|
|||||||
|
|
||||||
#include "QuickRefactorHandler.hpp"
|
#include "QuickRefactorHandler.hpp"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
#include <LLMQore/BaseClient.hpp>
|
#include <LLMQore/BaseClient.hpp>
|
||||||
|
#include <LLMQore/ContentBlocks.hpp>
|
||||||
|
#include <LLMQore/ToolsManager.hpp>
|
||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QUuid>
|
#include <QUuid>
|
||||||
|
|
||||||
|
#include <projectexplorer/project.h>
|
||||||
|
#include <projectexplorer/projectmanager.h>
|
||||||
|
#include <utils/filepath.h>
|
||||||
|
|
||||||
#include <context/DocumentContextReader.hpp>
|
#include <context/DocumentContextReader.hpp>
|
||||||
#include <pluginllmcore/ResponseCleaner.hpp>
|
|
||||||
#include <context/DocumentReaderQtCreator.hpp>
|
#include <context/DocumentReaderQtCreator.hpp>
|
||||||
#include <context/Utils.hpp>
|
#include <context/Utils.hpp>
|
||||||
#include <pluginllmcore/PromptTemplateManager.hpp>
|
|
||||||
#include <pluginllmcore/ProvidersManager.hpp>
|
|
||||||
#include <pluginllmcore/RulesLoader.hpp>
|
|
||||||
#include <logger/Logger.hpp>
|
#include <logger/Logger.hpp>
|
||||||
#include <settings/ChatAssistantSettings.hpp>
|
#include <pluginllmcore/ResponseCleaner.hpp>
|
||||||
#include <settings/GeneralSettings.hpp>
|
|
||||||
#include <settings/QuickRefactorSettings.hpp>
|
#include <settings/QuickRefactorSettings.hpp>
|
||||||
#include <settings/ToolsSettings.hpp>
|
#include <settings/ToolsSettings.hpp>
|
||||||
|
|
||||||
|
#include "sources/common/ContextData.hpp"
|
||||||
|
|
||||||
|
#include <AgentFactory.hpp>
|
||||||
|
#include <AgentRouter.hpp>
|
||||||
|
#include <Session.hpp>
|
||||||
|
#include <SessionManager.hpp>
|
||||||
|
#include <SystemPromptBuilder.hpp>
|
||||||
|
|
||||||
|
#include "sources/settings/PipelinesConfig.hpp"
|
||||||
|
#include "tools/ToolsRegistration.hpp"
|
||||||
|
|
||||||
namespace QodeAssist {
|
namespace QodeAssist {
|
||||||
|
|
||||||
QuickRefactorHandler::QuickRefactorHandler(QObject *parent)
|
QuickRefactorHandler::QuickRefactorHandler(QObject *parent)
|
||||||
@@ -34,6 +48,16 @@ QuickRefactorHandler::QuickRefactorHandler(QObject *parent)
|
|||||||
|
|
||||||
QuickRefactorHandler::~QuickRefactorHandler() {}
|
QuickRefactorHandler::~QuickRefactorHandler() {}
|
||||||
|
|
||||||
|
void QuickRefactorHandler::setSessionManager(SessionManager *sessionManager)
|
||||||
|
{
|
||||||
|
m_sessionManager = sessionManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
void QuickRefactorHandler::setAgentFactory(AgentFactory *agentFactory)
|
||||||
|
{
|
||||||
|
m_agentFactory = agentFactory;
|
||||||
|
}
|
||||||
|
|
||||||
void QuickRefactorHandler::sendRefactorRequest(
|
void QuickRefactorHandler::sendRefactorRequest(
|
||||||
TextEditor::TextEditorWidget *editor, const QString &instructions)
|
TextEditor::TextEditorWidget *editor, const QString &instructions)
|
||||||
{
|
{
|
||||||
@@ -88,61 +112,70 @@ void QuickRefactorHandler::sendRefactorRequest(
|
|||||||
prepareAndSendRequest(editor, instructions, range);
|
prepareAndSendRequest(editor, instructions, range);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString QuickRefactorHandler::pickRefactorAgent(const QString &filePath) const
|
||||||
|
{
|
||||||
|
const QStringList roster = Settings::PipelinesConfig::load().rosters.quickRefactor;
|
||||||
|
if (roster.isEmpty() || !m_agentFactory)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
AgentRouter::Context ctx;
|
||||||
|
ctx.filePath = filePath;
|
||||||
|
if (auto *project = ProjectExplorer::ProjectManager::projectForFile(
|
||||||
|
Utils::FilePath::fromString(filePath)))
|
||||||
|
ctx.projectName = project->displayName();
|
||||||
|
|
||||||
|
return AgentRouter::pickAgent(roster, ctx, *m_agentFactory);
|
||||||
|
}
|
||||||
|
|
||||||
void QuickRefactorHandler::prepareAndSendRequest(
|
void QuickRefactorHandler::prepareAndSendRequest(
|
||||||
TextEditor::TextEditorWidget *editor,
|
TextEditor::TextEditorWidget *editor,
|
||||||
const QString &instructions,
|
const QString &instructions,
|
||||||
const Utils::Text::Range &range)
|
const Utils::Text::Range &range)
|
||||||
{
|
{
|
||||||
auto &settings = Settings::generalSettings();
|
const auto emitError = [this, editor](const QString &error) {
|
||||||
|
|
||||||
auto &providerRegistry = PluginLLMCore::ProvidersManager::instance();
|
|
||||||
auto &promptManager = PluginLLMCore::PromptTemplateManager::instance();
|
|
||||||
|
|
||||||
const auto providerName = settings.qrProvider();
|
|
||||||
auto provider = providerRegistry.getProviderByName(providerName);
|
|
||||||
|
|
||||||
if (!provider) {
|
|
||||||
QString error = QString("No provider found with name: %1").arg(providerName);
|
|
||||||
LOG_MESSAGE(error);
|
LOG_MESSAGE(error);
|
||||||
RefactorResult result;
|
RefactorResult result;
|
||||||
result.success = false;
|
result.success = false;
|
||||||
result.errorMessage = error;
|
result.errorMessage = error;
|
||||||
result.editor = editor;
|
result.editor = editor;
|
||||||
emit refactoringCompleted(result);
|
emit refactoringCompleted(result);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!m_sessionManager) {
|
||||||
|
emitError(QStringLiteral("Quick refactor session manager is not available"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto templateName = settings.qrTemplate();
|
const QString filePath = editor->textDocument()->filePath().toUrlishString();
|
||||||
auto promptTemplate = promptManager.getChatTemplateByName(templateName);
|
const QString agentName = pickRefactorAgent(filePath);
|
||||||
|
if (agentName.isEmpty()) {
|
||||||
if (!promptTemplate) {
|
emitError(QStringLiteral("No quick refactor agent matches: %1").arg(filePath));
|
||||||
QString error = QString("No template found with name: %1").arg(templateName);
|
|
||||||
LOG_MESSAGE(error);
|
|
||||||
RefactorResult result;
|
|
||||||
result.success = false;
|
|
||||||
result.errorMessage = error;
|
|
||||||
result.editor = editor;
|
|
||||||
emit refactoringCompleted(result);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
QJsonObject payload{
|
QString sessionError;
|
||||||
{"model", Settings::generalSettings().qrModel()}, {"stream", true}};
|
Session *session = m_sessionManager->createSession(agentName, &sessionError);
|
||||||
|
if (!session) {
|
||||||
|
emitError(sessionError.isEmpty() ? QStringLiteral("No quick refactor agent selected")
|
||||||
|
: sessionError);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
PluginLLMCore::ContextData context = prepareContext(editor, range, instructions);
|
auto *client = session->client();
|
||||||
|
if (!client) {
|
||||||
|
m_sessionManager->removeSession(session);
|
||||||
|
emitError(QStringLiteral("Quick refactor agent has no live client"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
bool enableTools = Settings::quickRefactorSettings().useTools();
|
const bool enableTools = Settings::quickRefactorSettings().useTools();
|
||||||
bool enableThinking = Settings::quickRefactorSettings().useThinking();
|
if (enableTools) {
|
||||||
provider->prepareRequest(
|
Tools::registerQodeAssistTools(client->tools());
|
||||||
payload,
|
client->setMaxToolContinuations(Settings::toolsSettings().maxToolContinuations());
|
||||||
promptTemplate,
|
}
|
||||||
context,
|
|
||||||
PluginLLMCore::RequestType::QuickRefactoring,
|
|
||||||
enableTools,
|
|
||||||
enableThinking);
|
|
||||||
|
|
||||||
provider->client()->setMaxToolContinuations(
|
session->systemPrompt()->setLayer(
|
||||||
Settings::toolsSettings().maxToolContinuations());
|
QStringLiteral("refactor"), buildSystemPrompt(editor, range));
|
||||||
|
|
||||||
provider->client()->setTransferTimeout(
|
provider->client()->setTransferTimeout(
|
||||||
static_cast<int>(Settings::generalSettings().requestTimeout() * 1000));
|
static_cast<int>(Settings::generalSettings().requestTimeout() * 1000));
|
||||||
@@ -150,43 +183,38 @@ void QuickRefactorHandler::prepareAndSendRequest(
|
|||||||
m_isRefactoringInProgress = true;
|
m_isRefactoringInProgress = true;
|
||||||
|
|
||||||
connect(
|
connect(
|
||||||
provider->client(),
|
client, &::LLMQore::BaseClient::requestCompleted,
|
||||||
&::LLMQore::BaseClient::requestCompleted,
|
this, &QuickRefactorHandler::handleFullResponse, Qt::UniqueConnection);
|
||||||
this,
|
|
||||||
&QuickRefactorHandler::handleFullResponse,
|
|
||||||
Qt::UniqueConnection);
|
|
||||||
|
|
||||||
connect(
|
connect(
|
||||||
provider->client(),
|
client, &::LLMQore::BaseClient::requestFinalized,
|
||||||
&::LLMQore::BaseClient::requestFinalized,
|
this, &QuickRefactorHandler::handleRequestFinalized, Qt::UniqueConnection);
|
||||||
this,
|
|
||||||
&QuickRefactorHandler::handleRequestFinalized,
|
|
||||||
Qt::UniqueConnection);
|
|
||||||
|
|
||||||
connect(
|
connect(
|
||||||
provider->client(),
|
client, &::LLMQore::BaseClient::requestFailed,
|
||||||
&::LLMQore::BaseClient::requestFailed,
|
this, &QuickRefactorHandler::handleRequestFailed, Qt::UniqueConnection);
|
||||||
this,
|
|
||||||
&QuickRefactorHandler::handleRequestFailed,
|
std::vector<std::unique_ptr<LLMQore::ContentBlock>> blocks;
|
||||||
Qt::UniqueConnection);
|
const QString userMessage = instructions.isEmpty()
|
||||||
|
? QStringLiteral("Refactor the code to improve its quality and maintainability.")
|
||||||
|
: instructions;
|
||||||
|
blocks.push_back(std::make_unique<LLMQore::TextContent>(userMessage));
|
||||||
|
|
||||||
|
const LLMQore::RequestID requestId = session->send(std::move(blocks), enableTools);
|
||||||
|
if (requestId.isEmpty()) {
|
||||||
|
m_isRefactoringInProgress = false;
|
||||||
|
m_sessionManager->removeSession(session);
|
||||||
|
emitError(QStringLiteral("Failed to start quick refactor request for agent: %1")
|
||||||
|
.arg(agentName));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const QString customEndpoint = Settings::generalSettings().qrCustomEndpoint();
|
|
||||||
const QString endpoint = !customEndpoint.isEmpty() ? customEndpoint
|
|
||||||
: promptTemplate->endpoint();
|
|
||||||
auto requestId
|
|
||||||
= provider->sendRequest(QUrl(Settings::generalSettings().qrUrl()), payload, endpoint);
|
|
||||||
m_lastRequestId = requestId;
|
m_lastRequestId = requestId;
|
||||||
QJsonObject request{{"id", requestId}};
|
m_activeRequests[requestId] = {QJsonObject{{"id", requestId}}, session};
|
||||||
|
|
||||||
m_activeRequests[requestId] = {request, provider};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PluginLLMCore::ContextData QuickRefactorHandler::prepareContext(
|
QString QuickRefactorHandler::buildSystemPrompt(
|
||||||
TextEditor::TextEditorWidget *editor,
|
TextEditor::TextEditorWidget *editor, const Utils::Text::Range &range)
|
||||||
const Utils::Text::Range &range,
|
|
||||||
const QString &instructions)
|
|
||||||
{
|
{
|
||||||
PluginLLMCore::ContextData context;
|
Q_UNUSED(range)
|
||||||
|
|
||||||
auto textDocument = editor->textDocument();
|
auto textDocument = editor->textDocument();
|
||||||
Context::DocumentReaderQtCreator documentReader;
|
Context::DocumentReaderQtCreator documentReader;
|
||||||
@@ -194,7 +222,7 @@ PluginLLMCore::ContextData QuickRefactorHandler::prepareContext(
|
|||||||
|
|
||||||
if (!documentInfo.document) {
|
if (!documentInfo.document) {
|
||||||
LOG_MESSAGE("Error: Document is not available");
|
LOG_MESSAGE("Error: Document is not available");
|
||||||
return context;
|
return Settings::quickRefactorSettings().systemPrompt();
|
||||||
}
|
}
|
||||||
|
|
||||||
QTextCursor cursor = editor->textCursor();
|
QTextCursor cursor = editor->textCursor();
|
||||||
@@ -270,17 +298,6 @@ PluginLLMCore::ContextData QuickRefactorHandler::prepareContext(
|
|||||||
|
|
||||||
QString systemPrompt = Settings::quickRefactorSettings().systemPrompt();
|
QString systemPrompt = Settings::quickRefactorSettings().systemPrompt();
|
||||||
|
|
||||||
auto project = PluginLLMCore::RulesLoader::getActiveProject();
|
|
||||||
if (project) {
|
|
||||||
QString projectRules = PluginLLMCore::RulesLoader::loadRulesForProject(
|
|
||||||
project, PluginLLMCore::RulesContext::QuickRefactor);
|
|
||||||
|
|
||||||
if (!projectRules.isEmpty()) {
|
|
||||||
systemPrompt += "\n\n# Project Rules\n\n" + projectRules;
|
|
||||||
LOG_MESSAGE("Loaded project rules for quick refactor");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
systemPrompt += "\n\nFile information:";
|
systemPrompt += "\n\nFile information:";
|
||||||
systemPrompt += "\nLanguage: " + documentInfo.mimeType;
|
systemPrompt += "\nLanguage: " + documentInfo.mimeType;
|
||||||
systemPrompt += "\nFile path: " + documentInfo.filePath;
|
systemPrompt += "\nFile path: " + documentInfo.filePath;
|
||||||
@@ -294,7 +311,7 @@ PluginLLMCore::ContextData QuickRefactorHandler::prepareContext(
|
|||||||
"\n- Your output will completely replace the selected code"
|
"\n- Your output will completely replace the selected code"
|
||||||
: "\n- Generate ONLY the code that should be INSERTED at the <cursor> position"
|
: "\n- Generate ONLY the code that should be INSERTED at the <cursor> position"
|
||||||
"\n- Your output will be inserted at the cursor location";
|
"\n- Your output will be inserted at the cursor location";
|
||||||
|
|
||||||
systemPrompt += "\n\n## Formatting Rules:"
|
systemPrompt += "\n\n## Formatting Rules:"
|
||||||
"\n- Output ONLY the code itself, without ANY explanations or descriptions"
|
"\n- Output ONLY the code itself, without ANY explanations or descriptions"
|
||||||
"\n- Do NOT include markdown code blocks (no ```, no language tags)"
|
"\n- Do NOT include markdown code blocks (no ```, no language tags)"
|
||||||
@@ -302,9 +319,9 @@ PluginLLMCore::ContextData QuickRefactorHandler::prepareContext(
|
|||||||
"\n- Do NOT repeat existing code, be precise with context"
|
"\n- Do NOT repeat existing code, be precise with context"
|
||||||
"\n- Do NOT send in answer <cursor> or </cursor> and other tags"
|
"\n- Do NOT send in answer <cursor> or </cursor> and other tags"
|
||||||
"\n- The output must be ready to insert directly into the editor as-is";
|
"\n- The output must be ready to insert directly into the editor as-is";
|
||||||
|
|
||||||
systemPrompt += "\n\n## Indentation and Whitespace:";
|
systemPrompt += "\n\n## Indentation and Whitespace:";
|
||||||
|
|
||||||
if (cursor.hasSelection()) {
|
if (cursor.hasSelection()) {
|
||||||
QTextBlock startBlock = documentInfo.document->findBlock(cursor.selectionStart());
|
QTextBlock startBlock = documentInfo.document->findBlock(cursor.selectionStart());
|
||||||
int leadingSpaces = 0;
|
int leadingSpaces = 0;
|
||||||
@@ -336,7 +353,7 @@ PluginLLMCore::ContextData QuickRefactorHandler::prepareContext(
|
|||||||
.arg(leadingSpaces);
|
.arg(leadingSpaces);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
systemPrompt += "\n- Use the same indentation style (spaces or tabs) as the surrounding code"
|
systemPrompt += "\n- Use the same indentation style (spaces or tabs) as the surrounding code"
|
||||||
"\n- Maintain consistent indentation for nested blocks"
|
"\n- Maintain consistent indentation for nested blocks"
|
||||||
"\n- Do NOT remove or reduce the base indentation level"
|
"\n- Do NOT remove or reduce the base indentation level"
|
||||||
@@ -349,16 +366,7 @@ PluginLLMCore::ContextData QuickRefactorHandler::prepareContext(
|
|||||||
systemPrompt += "\n\n" + m_contextManager.openedFilesContext({documentInfo.filePath});
|
systemPrompt += "\n\n" + m_contextManager.openedFilesContext({documentInfo.filePath});
|
||||||
}
|
}
|
||||||
|
|
||||||
context.systemPrompt = systemPrompt;
|
return systemPrompt;
|
||||||
|
|
||||||
QVector<PluginLLMCore::Message> messages;
|
|
||||||
messages.append(
|
|
||||||
{"user",
|
|
||||||
instructions.isEmpty() ? "Refactor the code to improve its quality and maintainability."
|
|
||||||
: instructions});
|
|
||||||
context.history = messages;
|
|
||||||
|
|
||||||
return context;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void QuickRefactorHandler::handleLLMResponse(
|
void QuickRefactorHandler::handleLLMResponse(
|
||||||
@@ -398,10 +406,14 @@ void QuickRefactorHandler::cancelRequest()
|
|||||||
|
|
||||||
auto it = m_activeRequests.find(id);
|
auto it = m_activeRequests.find(id);
|
||||||
if (it != m_activeRequests.end()) {
|
if (it != m_activeRequests.end()) {
|
||||||
auto provider = it.value().provider;
|
Session *session = it.value().session;
|
||||||
m_activeRequests.erase(it);
|
m_activeRequests.erase(it);
|
||||||
if (provider)
|
if (session) {
|
||||||
provider->cancelRequest(id);
|
if (auto *client = session->client())
|
||||||
|
disconnect(client, nullptr, this, nullptr);
|
||||||
|
if (m_sessionManager)
|
||||||
|
m_sessionManager->removeSession(session);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RefactorResult result;
|
RefactorResult result;
|
||||||
@@ -412,11 +424,19 @@ void QuickRefactorHandler::cancelRequest()
|
|||||||
|
|
||||||
void QuickRefactorHandler::handleFullResponse(const QString &requestId, const QString &fullText)
|
void QuickRefactorHandler::handleFullResponse(const QString &requestId, const QString &fullText)
|
||||||
{
|
{
|
||||||
if (requestId == m_lastRequestId) {
|
if (requestId != m_lastRequestId)
|
||||||
m_activeRequests.remove(requestId);
|
return;
|
||||||
QJsonObject request{{"id", requestId}};
|
|
||||||
handleLLMResponse(fullText, request, true);
|
auto it = m_activeRequests.find(requestId);
|
||||||
}
|
Session *session = (it != m_activeRequests.end()) ? it.value().session.data() : nullptr;
|
||||||
|
if (it != m_activeRequests.end())
|
||||||
|
m_activeRequests.erase(it);
|
||||||
|
|
||||||
|
QJsonObject request{{"id", requestId}};
|
||||||
|
handleLLMResponse(fullText, request, true);
|
||||||
|
|
||||||
|
if (session && m_sessionManager)
|
||||||
|
m_sessionManager->removeSession(session);
|
||||||
}
|
}
|
||||||
|
|
||||||
void QuickRefactorHandler::handleRequestFinalized(
|
void QuickRefactorHandler::handleRequestFinalized(
|
||||||
@@ -437,15 +457,23 @@ void QuickRefactorHandler::handleRequestFinalized(
|
|||||||
|
|
||||||
void QuickRefactorHandler::handleRequestFailed(const QString &requestId, const QString &error)
|
void QuickRefactorHandler::handleRequestFailed(const QString &requestId, const QString &error)
|
||||||
{
|
{
|
||||||
if (requestId == m_lastRequestId) {
|
if (requestId != m_lastRequestId)
|
||||||
m_activeRequests.remove(requestId);
|
return;
|
||||||
m_isRefactoringInProgress = false;
|
|
||||||
RefactorResult result;
|
auto it = m_activeRequests.find(requestId);
|
||||||
result.success = false;
|
Session *session = (it != m_activeRequests.end()) ? it.value().session.data() : nullptr;
|
||||||
result.errorMessage = error;
|
if (it != m_activeRequests.end())
|
||||||
result.editor = m_currentEditor;
|
m_activeRequests.erase(it);
|
||||||
emit refactoringCompleted(result);
|
|
||||||
}
|
m_isRefactoringInProgress = false;
|
||||||
|
RefactorResult result;
|
||||||
|
result.success = false;
|
||||||
|
result.errorMessage = error;
|
||||||
|
result.editor = m_currentEditor;
|
||||||
|
emit refactoringCompleted(result);
|
||||||
|
|
||||||
|
if (session && m_sessionManager)
|
||||||
|
m_sessionManager->removeSession(session);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace QodeAssist
|
} // namespace QodeAssist
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
#include <QPointer>
|
||||||
|
|
||||||
#include <LLMQore/BaseClient.hpp>
|
#include <LLMQore/BaseClient.hpp>
|
||||||
#include <texteditor/texteditor.h>
|
#include <texteditor/texteditor.h>
|
||||||
@@ -13,11 +14,13 @@
|
|||||||
|
|
||||||
#include <context/ContextManager.hpp>
|
#include <context/ContextManager.hpp>
|
||||||
#include <context/IDocumentReader.hpp>
|
#include <context/IDocumentReader.hpp>
|
||||||
#include <pluginllmcore/ContextData.hpp>
|
|
||||||
#include <pluginllmcore/Provider.hpp>
|
|
||||||
|
|
||||||
namespace QodeAssist {
|
namespace QodeAssist {
|
||||||
|
|
||||||
|
class SessionManager;
|
||||||
|
class Session;
|
||||||
|
class AgentFactory;
|
||||||
|
|
||||||
struct RefactorResult
|
struct RefactorResult
|
||||||
{
|
{
|
||||||
QString newText;
|
QString newText;
|
||||||
@@ -35,6 +38,9 @@ public:
|
|||||||
explicit QuickRefactorHandler(QObject *parent = nullptr);
|
explicit QuickRefactorHandler(QObject *parent = nullptr);
|
||||||
~QuickRefactorHandler() override;
|
~QuickRefactorHandler() override;
|
||||||
|
|
||||||
|
void setSessionManager(SessionManager *sessionManager);
|
||||||
|
void setAgentFactory(AgentFactory *agentFactory);
|
||||||
|
|
||||||
void sendRefactorRequest(TextEditor::TextEditorWidget *editor, const QString &instructions);
|
void sendRefactorRequest(TextEditor::TextEditorWidget *editor, const QString &instructions);
|
||||||
|
|
||||||
void cancelRequest();
|
void cancelRequest();
|
||||||
@@ -56,17 +62,18 @@ private:
|
|||||||
const Utils::Text::Range &range);
|
const Utils::Text::Range &range);
|
||||||
|
|
||||||
void handleLLMResponse(const QString &response, const QJsonObject &request, bool isComplete);
|
void handleLLMResponse(const QString &response, const QJsonObject &request, bool isComplete);
|
||||||
PluginLLMCore::ContextData prepareContext(
|
QString buildSystemPrompt(
|
||||||
TextEditor::TextEditorWidget *editor,
|
TextEditor::TextEditorWidget *editor, const Utils::Text::Range &range);
|
||||||
const Utils::Text::Range &range,
|
QString pickRefactorAgent(const QString &filePath) const;
|
||||||
const QString &instructions);
|
|
||||||
|
|
||||||
struct RequestContext
|
struct RequestContext
|
||||||
{
|
{
|
||||||
QJsonObject originalRequest;
|
QJsonObject originalRequest;
|
||||||
PluginLLMCore::Provider *provider;
|
QPointer<Session> session;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
QPointer<SessionManager> m_sessionManager;
|
||||||
|
QPointer<AgentFactory> m_agentFactory;
|
||||||
QHash<QString, RequestContext> m_activeRequests;
|
QHash<QString, RequestContext> m_activeRequests;
|
||||||
TextEditor::TextEditorWidget *m_currentEditor;
|
TextEditor::TextEditorWidget *m_currentEditor;
|
||||||
Utils::Text::Range m_currentRange;
|
Utils::Text::Range m_currentRange;
|
||||||
|
|||||||
@@ -100,13 +100,9 @@ ProgrammingLanguage ContextManager::getDocumentLanguage(const DocumentInfo &docu
|
|||||||
|
|
||||||
bool ContextManager::isSpecifyCompletion(const DocumentInfo &documentInfo) const
|
bool ContextManager::isSpecifyCompletion(const DocumentInfo &documentInfo) const
|
||||||
{
|
{
|
||||||
const auto &generalSettings = Settings::generalSettings();
|
Q_UNUSED(documentInfo)
|
||||||
|
// Language-specific completion presets were replaced by agent match rules.
|
||||||
Context::ProgrammingLanguage documentLanguage = getDocumentLanguage(documentInfo);
|
return false;
|
||||||
Context::ProgrammingLanguage preset1Language = Context::ProgrammingLanguageUtils::fromString(
|
|
||||||
generalSettings.preset1Language.displayForIndex(generalSettings.preset1Language()));
|
|
||||||
|
|
||||||
return generalSettings.specifyPreset1() && documentLanguage == preset1Language;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<QPair<QString, QString>> ContextManager::openedFiles(const QStringList excludeFiles) const
|
QList<QPair<QString, QString>> ContextManager::openedFiles(const QStringList excludeFiles) const
|
||||||
|
|||||||
258
docs/architecture.md
Normal file
258
docs/architecture.md
Normal file
@@ -0,0 +1,258 @@
|
|||||||
|
# QodeAssist Architecture
|
||||||
|
|
||||||
|
This document describes the runtime architecture of QodeAssist after the
|
||||||
|
migration of all LLM runtime paths onto the agent / `Session` stack
|
||||||
|
("Stack B"). Every runtime LLM path — code completion, chat (send/stream +
|
||||||
|
compression + token counting), and quick refactor — now goes through agents,
|
||||||
|
`Session`, and the `Providers::GenericProvider` layer.
|
||||||
|
|
||||||
|
> Legend: ✅ = on Stack B (active runtime), 🔴 = legacy Stack A (isolated, no
|
||||||
|
> runtime consumers left).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Top level: ownership and dependency injection
|
||||||
|
|
||||||
|
The plugin (`qodeassist.cpp`) owns everything via `new` + parent (no plugin-wide
|
||||||
|
singletons; each feature receives its dependencies explicitly).
|
||||||
|
|
||||||
|
```
|
||||||
|
QodeAssistPlugin
|
||||||
|
Stack B infrastructure:
|
||||||
|
• Providers::registerBuiltinProviders() — registers 13 client_api types
|
||||||
|
• ProviderInstanceFactory — 14 instances from TOML
|
||||||
|
• ProviderSecretsStore
|
||||||
|
• AgentFactory — agents from TOML
|
||||||
|
• SessionManager(agentFactory)
|
||||||
|
• m_engine (QQmlEngine)
|
||||||
|
rootContext: "agentFactory", "sessionManager" — DI for chat (QML)
|
||||||
|
|
||||||
|
Wired into consumers:
|
||||||
|
• QodeAssistClient ← LLMClientInterface(*sessionManager, *agentFactory)
|
||||||
|
← setSessionManager / setAgentFactory (for quick refactor)
|
||||||
|
```
|
||||||
|
|
||||||
|
Chat lives in QML (`ChatRootView` is a `QML_ELEMENT`), so `AgentFactory` and
|
||||||
|
`SessionManager` are exposed as **context properties on the engine's root
|
||||||
|
context** and resolved in `ChatRootView` via
|
||||||
|
`qmlEngine(this)->rootContext()->contextProperty(...)`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Stack B core (agent / Session)
|
||||||
|
|
||||||
|
```
|
||||||
|
AgentFactory.create(name)
|
||||||
|
configByName(name) → AgentConfig (TOML)
|
||||||
|
providerInstance, model, endpoint, role, messageFormat,
|
||||||
|
sampling, enableTools, enableThinking, match{filePatterns,...}
|
||||||
|
buildProviderForAgent:
|
||||||
|
instance = ProviderInstanceFactory.instanceByName(cfg.providerInstance)
|
||||||
|
provider = ProviderFactory::create(instance.clientApi) ◄── keystone
|
||||||
|
provider.setUrl(instance.url)
|
||||||
|
provider.setApiKey(secrets.read(instance.apiKeyRef))
|
||||||
|
▼
|
||||||
|
Agent(config, provider)
|
||||||
|
promptTemplate = JsonPromptTemplate::fromConfig(cfg.messageFormat) (inja)
|
||||||
|
▼
|
||||||
|
SessionManager.createSession(agentName) → Session(agent)
|
||||||
|
├─ ConversationHistory — messages as ContentBlocks
|
||||||
|
├─ SystemPromptBuilder — layers: agent.role + caller layers
|
||||||
|
└─ ResponseRouter(client) — emits ResponseEvent
|
||||||
|
|
||||||
|
Session API:
|
||||||
|
• send(blocks, toolsOverride) — chat/refactor: append user msg + dispatch
|
||||||
|
• sendCompletion(ContextData) — completion: FIM prefix/suffix
|
||||||
|
• client() — agent's LLMQore::BaseClient (direct streaming)
|
||||||
|
• systemPrompt()->setLayer(...) — dynamic context layers
|
||||||
|
• supportsImages() — provider Image capability
|
||||||
|
• history() — for seeding from ChatModel
|
||||||
|
```
|
||||||
|
|
||||||
|
`Session::sendCompletion` and `dispatch` compose `SystemPromptBuilder` layers
|
||||||
|
(`agent.role` + caller-provided) into the request system prompt.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Provider layer — the keystone (implemented during migration)
|
||||||
|
|
||||||
|
The Stack B provider layer previously existed only as an abstract base +
|
||||||
|
empty factory (`registerType` was never called, no concrete providers). This
|
||||||
|
blocked every agent from obtaining a working provider. It is now implemented
|
||||||
|
via a single configuration-driven `GenericProvider`.
|
||||||
|
|
||||||
|
```
|
||||||
|
ProviderFactory (sources/providers, namespace functions)
|
||||||
|
registerType(name, fn) / create(name, parent) / knownNames()
|
||||||
|
▲
|
||||||
|
│ registerBuiltinProviders() — client_api → provider table
|
||||||
|
│
|
||||||
|
GenericProvider : Providers::Provider
|
||||||
|
• owns an LLMQore::BaseClient (created by a ClientFactory)
|
||||||
|
• prepareRequest — inherited from Provider base:
|
||||||
|
delegates to PromptTemplate::buildFullRequest
|
||||||
|
• client() / providerID() / capabilities() / getInstalledModels()
|
||||||
|
```
|
||||||
|
|
||||||
|
### client_api → provider table
|
||||||
|
|
||||||
|
| client_api | LLMQore client | ProviderID | capabilities |
|
||||||
|
|--------------------------------|-------------------------|------------------|-------------------------|
|
||||||
|
| Claude | ClaudeClient | Claude | Tools·Thinking·Image·ModelListing |
|
||||||
|
| Google AI | GoogleAIClient | GoogleAI | Tools·Thinking·Image·ModelListing |
|
||||||
|
| llama.cpp | LlamaCppClient | LlamaCpp | Tools·Thinking·Image·ModelListing |
|
||||||
|
| Mistral AI | MistralClient | MistralAI | Tools·Thinking·Image·ModelListing |
|
||||||
|
| Codestral | MistralClient | MistralAI | Tools·Image |
|
||||||
|
| Ollama (Native) | OllamaClient | Ollama | Tools·Thinking·Image·ModelListing |
|
||||||
|
| Ollama (OpenAI-compatible) | OpenAIClient | OpenAICompatible | Tools·Thinking·Image·ModelListing |
|
||||||
|
| OpenAI (Chat Completions) | OpenAIClient | OpenAI | Tools·Thinking·Image·ModelListing |
|
||||||
|
| OpenAI (Responses API) | OpenAIResponsesClient | OpenAIResponses | Tools·Thinking·Image·ModelListing |
|
||||||
|
| OpenAI Compatible | OpenAIClient | OpenAICompatible | Tools·Image·Thinking |
|
||||||
|
| OpenRouter | OpenAIClient | OpenRouter | Tools·Image·Thinking·ModelListing |
|
||||||
|
| LM Studio (Chat Completions) | OpenAIClient | LMStudio | Tools·Thinking·Image·ModelListing |
|
||||||
|
| LM Studio (Responses API) | OpenAIResponsesClient | OpenAIResponses | Tools·Thinking·Image·ModelListing |
|
||||||
|
|
||||||
|
Request *shape* comes from the agent's prompt template (jinja `messageFormat`),
|
||||||
|
so a single provider class covers every API by varying only the client factory
|
||||||
|
and metadata.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Runtime paths (all on Stack B)
|
||||||
|
|
||||||
|
### 4a. Code completion ✅
|
||||||
|
|
||||||
|
```
|
||||||
|
Qt Creator LSP (getCompletionsCycling)
|
||||||
|
▼
|
||||||
|
LLMClientInterface
|
||||||
|
pickCompletionAgent: AgentRouter.pickAgent(roster.codeCompletion, {file, project})
|
||||||
|
session = sessionManager.createSession(agent)
|
||||||
|
ctx = Templates::ContextData{ prefix, suffix,
|
||||||
|
systemPrompt = fileContext + openFiles }
|
||||||
|
session.sendCompletion(ctx)
|
||||||
|
▼ stream from session.client():
|
||||||
|
requestCompleted → sendCompletionToClient → CodeHandler → LSP
|
||||||
|
system prompt = agent.role; FIM template renders prefix/suffix
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4b. Chat ✅
|
||||||
|
|
||||||
|
```
|
||||||
|
ChatRootView (QML)
|
||||||
|
resolve agentFactory()/sessionManager() = qmlEngine(this)->rootContext()
|
||||||
|
ChatAgentController: agent list (configNames), active agent (persisted),
|
||||||
|
supportsThinking/Tools
|
||||||
|
QML agent picker (TopBar.agentSelector) — replaced provider/model/template combos
|
||||||
|
▼ dispatchSend
|
||||||
|
ClientInterface
|
||||||
|
session = sessionManager.createSession(currentChatAgent)
|
||||||
|
registerQodeAssistTools(session.client().tools()) + registerSkillTool
|
||||||
|
systemPrompt layer "chat.context" = project info + skills + linked files
|
||||||
|
seedHistory(session.history() ← ChatModel: user/assistant/tool-call+result)
|
||||||
|
session.send(userBlocks{text + images}, useTools)
|
||||||
|
▼ stream from session.client() → existing handlers → ChatModel:
|
||||||
|
chunk→addMessage thinking→addThinkingBlock
|
||||||
|
tool→addToolExecutionStatus / updateToolResult
|
||||||
|
finalized→usage completed→messageReceivedCompletely → removeSession
|
||||||
|
|
||||||
|
ChatCompressor → createSession(agent) → seed history → layer "compression" → send(prompt)
|
||||||
|
InputTokenCounter → estimate without provider (calibrated by server usage)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4c. Quick refactor ✅
|
||||||
|
|
||||||
|
```
|
||||||
|
QodeAssistClient.requestQuickRefactor → QuickRefactorHandler (setSessionManager/setAgentFactory)
|
||||||
|
pickRefactorAgent: AgentRouter.pickAgent(roster.quickRefactor, {file, project})
|
||||||
|
session = createSession(agent)
|
||||||
|
if useTools: registerQodeAssistTools(session.client().tools())
|
||||||
|
systemPrompt layer "refactor" = buildSystemPrompt(tagged content +
|
||||||
|
output requirements + indentation rules)
|
||||||
|
session.send(blocks{instructions}, useTools)
|
||||||
|
▼ stream from session.client():
|
||||||
|
requestCompleted → ResponseCleaner → RefactorResult → insert into editor
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Configuration sources
|
||||||
|
|
||||||
|
```
|
||||||
|
~/.config/.../qodeassist/config/
|
||||||
|
providers/*.toml → ProviderInstance { name, client_api, url, api_key_ref }
|
||||||
|
agents/*.toml → AgentConfig { providerInstance, model, endpoint, role,
|
||||||
|
messageFormat, sampling, match, enable* }
|
||||||
|
pipelines rosters → codeCompletion / chatAssistant / chatCompression / quickRefactor
|
||||||
|
consumed by AgentRouter.pickAgent(roster, {filePath, projectName})
|
||||||
|
|
||||||
|
Editor policy (NOT agent config):
|
||||||
|
CodeCompletionSettings — triggers, modelOutputHandler, context extraction,
|
||||||
|
useOpenFilesContext
|
||||||
|
(sampling / prompt-generation fields removed)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Remaining Stack A (runtime does NOT depend on it)
|
||||||
|
|
||||||
|
```
|
||||||
|
🔴 Settings UI: provider/model/template selection pages
|
||||||
|
(ccProvider / caProvider / qrProvider) + ConfigurationManager
|
||||||
|
→ use ProvidersManager
|
||||||
|
🔴 root providers/* (PluginLLMCore::Provider, 14 classes)
|
||||||
|
→ read only chat/quick-refactor sampling settings
|
||||||
|
🔴 pluginllmcore/* (ProvidersManager, PromptTemplateManager, ResponseCleaner,
|
||||||
|
PromptProviderChat/Fim, ContextData)
|
||||||
|
🔴 qodeassist.cpp:144-146 registerProviders() / registerTemplates() (Stack A registration)
|
||||||
|
🔴 qodeassist.cpp:185 MCP skill-tool loop on Stack A providers (effectively dead)
|
||||||
|
🔴 ChatAssistantSettings / QuickRefactorSettings — sampling fields (read only by root providers)
|
||||||
|
|
||||||
|
ResponseCleaner (pluginllmcore) is still used by QuickRefactorHandler as a text
|
||||||
|
utility — orthogonal to the provider stack.
|
||||||
|
```
|
||||||
|
|
||||||
|
### Removed during the migration
|
||||||
|
|
||||||
|
- Rules subsystem (`RulesLoader` + chat "active rules" UI + QuickRefactor rules block)
|
||||||
|
- `ChatConfigurationController`, `AgentRoleController` (chat config/role presets)
|
||||||
|
- `m_promptProvider` (`PromptProviderFim`) in the plugin
|
||||||
|
- `RequestType::CodeCompletion` branch in all 14 root providers
|
||||||
|
- Sampling / prompt-generation fields in `CodeCompletionSettings`
|
||||||
|
- ChatView no longer links `PluginLLMCore`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Dependency summary
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────────── Stack B (active runtime) ────────────────┐
|
||||||
|
LLMClientInterface ─┐ │
|
||||||
|
ClientInterface ────┼─► SessionManager ─► Session ─► Agent ─► GenericProvider ─► LLMQore::*Client
|
||||||
|
QuickRefactorHandler─┘ │ │ │ │
|
||||||
|
ChatCompressor ──────────────┘ │ AgentFactory ProviderFactory
|
||||||
|
AgentRouter (rosters) │ │
|
||||||
|
ProviderInstanceFactory (TOML)
|
||||||
|
└──────────────────────────────────────────────────────────┘
|
||||||
|
|
||||||
|
Stack A (settings UI + ConfigurationManager + MCP loop) — isolated,
|
||||||
|
no runtime consumers remain.
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Open follow-ups (optional)
|
||||||
|
|
||||||
|
1. **Chat picker filtering** — show only `chatAssistant`-roster agents (currently
|
||||||
|
lists all non-hidden agents; the auto-default may land on a FIM agent).
|
||||||
|
Requires wiring ChatView to `PipelinesConfig` (watch for OBJECT-library
|
||||||
|
symbol duplication).
|
||||||
|
2. **MCP tools on agent clients** — MCP skill tools are registered only on Stack A
|
||||||
|
providers; to expose MCP tools to chat agents, register them on the session
|
||||||
|
client alongside `registerQodeAssistTools`.
|
||||||
|
3. **Physical Stack A teardown** — remove the provider/model/template settings UI,
|
||||||
|
`ConfigurationManager`, root `providers/*`, `pluginllmcore/*`, and the
|
||||||
|
registration + MCP loop in `qodeassist.cpp`. Runtime no longer depends on them.
|
||||||
|
4. **Per-message session cost** — chat/refactor create a fresh agent/provider/client
|
||||||
|
(and read secrets) per request; a session pool could reduce latency.
|
||||||
|
```
|
||||||
@@ -10,7 +10,6 @@ add_library(PluginLLMCore STATIC
|
|||||||
PromptTemplate.hpp
|
PromptTemplate.hpp
|
||||||
PromptTemplateManager.hpp PromptTemplateManager.cpp
|
PromptTemplateManager.hpp PromptTemplateManager.cpp
|
||||||
ProviderID.hpp
|
ProviderID.hpp
|
||||||
RulesLoader.hpp RulesLoader.cpp
|
|
||||||
ResponseCleaner.hpp
|
ResponseCleaner.hpp
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,166 +0,0 @@
|
|||||||
// 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 "RulesLoader.hpp"
|
|
||||||
|
|
||||||
#include <QDir>
|
|
||||||
#include <QFile>
|
|
||||||
|
|
||||||
#include <coreplugin/editormanager/editormanager.h>
|
|
||||||
#include <projectexplorer/project.h>
|
|
||||||
#include <projectexplorer/projectmanager.h>
|
|
||||||
|
|
||||||
namespace QodeAssist::PluginLLMCore {
|
|
||||||
|
|
||||||
QString RulesLoader::loadRules(const QString &projectPath, RulesContext context)
|
|
||||||
{
|
|
||||||
if (projectPath.isEmpty()) {
|
|
||||||
return QString();
|
|
||||||
}
|
|
||||||
|
|
||||||
QString combined;
|
|
||||||
QString basePath = projectPath + "/.qodeassist/rules";
|
|
||||||
|
|
||||||
switch (context) {
|
|
||||||
case RulesContext::Completions:
|
|
||||||
combined += loadAllMarkdownFiles(basePath + "/completions");
|
|
||||||
break;
|
|
||||||
case RulesContext::Chat:
|
|
||||||
combined += loadAllMarkdownFiles(basePath + "/common");
|
|
||||||
combined += loadAllMarkdownFiles(basePath + "/chat");
|
|
||||||
break;
|
|
||||||
case RulesContext::QuickRefactor:
|
|
||||||
combined += loadAllMarkdownFiles(basePath + "/common");
|
|
||||||
combined += loadAllMarkdownFiles(basePath + "/quickrefactor");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return combined;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString RulesLoader::loadRulesForProject(ProjectExplorer::Project *project, RulesContext context)
|
|
||||||
{
|
|
||||||
if (!project) {
|
|
||||||
return QString();
|
|
||||||
}
|
|
||||||
|
|
||||||
QString projectPath = getProjectPath(project);
|
|
||||||
return loadRules(projectPath, context);
|
|
||||||
}
|
|
||||||
|
|
||||||
ProjectExplorer::Project *RulesLoader::getActiveProject()
|
|
||||||
{
|
|
||||||
auto currentEditor = Core::EditorManager::currentEditor();
|
|
||||||
if (currentEditor && currentEditor->document()) {
|
|
||||||
Utils::FilePath filePath = currentEditor->document()->filePath();
|
|
||||||
auto project = ProjectExplorer::ProjectManager::projectForFile(filePath);
|
|
||||||
if (project) {
|
|
||||||
return project;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ProjectExplorer::ProjectManager::startupProject();
|
|
||||||
}
|
|
||||||
|
|
||||||
QString RulesLoader::loadAllMarkdownFiles(const QString &dirPath)
|
|
||||||
{
|
|
||||||
QString combined;
|
|
||||||
QDir dir(dirPath);
|
|
||||||
|
|
||||||
if (!dir.exists()) {
|
|
||||||
return QString();
|
|
||||||
}
|
|
||||||
|
|
||||||
QStringList mdFiles = dir.entryList({"*.md"}, QDir::Files, QDir::Name);
|
|
||||||
|
|
||||||
for (const QString &fileName : mdFiles) {
|
|
||||||
QFile file(dir.filePath(fileName));
|
|
||||||
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
|
||||||
combined += file.readAll();
|
|
||||||
combined += "\n\n";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return combined;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString RulesLoader::getProjectPath(ProjectExplorer::Project *project)
|
|
||||||
{
|
|
||||||
if (!project) {
|
|
||||||
return QString();
|
|
||||||
}
|
|
||||||
|
|
||||||
return project->projectDirectory().toUrlishString();
|
|
||||||
}
|
|
||||||
|
|
||||||
QVector<RuleFileInfo> RulesLoader::getRuleFiles(const QString &projectPath, RulesContext context)
|
|
||||||
{
|
|
||||||
if (projectPath.isEmpty()) {
|
|
||||||
return QVector<RuleFileInfo>();
|
|
||||||
}
|
|
||||||
|
|
||||||
QVector<RuleFileInfo> result;
|
|
||||||
QString basePath = projectPath + "/.qodeassist/rules";
|
|
||||||
|
|
||||||
// Always include common rules
|
|
||||||
result.append(collectMarkdownFiles(basePath + "/common", "common"));
|
|
||||||
|
|
||||||
// Add context-specific rules
|
|
||||||
switch (context) {
|
|
||||||
case RulesContext::Completions:
|
|
||||||
result.append(collectMarkdownFiles(basePath + "/completions", "completions"));
|
|
||||||
break;
|
|
||||||
case RulesContext::Chat:
|
|
||||||
result.append(collectMarkdownFiles(basePath + "/chat", "chat"));
|
|
||||||
break;
|
|
||||||
case RulesContext::QuickRefactor:
|
|
||||||
result.append(collectMarkdownFiles(basePath + "/quickrefactor", "quickrefactor"));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
QVector<RuleFileInfo> RulesLoader::getRuleFilesForProject(
|
|
||||||
ProjectExplorer::Project *project, RulesContext context)
|
|
||||||
{
|
|
||||||
if (!project) {
|
|
||||||
return QVector<RuleFileInfo>();
|
|
||||||
}
|
|
||||||
|
|
||||||
QString projectPath = getProjectPath(project);
|
|
||||||
return getRuleFiles(projectPath, context);
|
|
||||||
}
|
|
||||||
|
|
||||||
QString RulesLoader::loadRuleFileContent(const QString &filePath)
|
|
||||||
{
|
|
||||||
QFile file(filePath);
|
|
||||||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
|
||||||
return QString();
|
|
||||||
}
|
|
||||||
|
|
||||||
return file.readAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
QVector<RuleFileInfo> RulesLoader::collectMarkdownFiles(
|
|
||||||
const QString &dirPath, const QString &category)
|
|
||||||
{
|
|
||||||
QVector<RuleFileInfo> result;
|
|
||||||
QDir dir(dirPath);
|
|
||||||
|
|
||||||
if (!dir.exists()) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
QStringList mdFiles = dir.entryList({"*.md"}, QDir::Files, QDir::Name);
|
|
||||||
|
|
||||||
for (const QString &fileName : mdFiles) {
|
|
||||||
QString fullPath = dir.filePath(fileName);
|
|
||||||
result.append({fullPath, fileName, category});
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace QodeAssist::PluginLLMCore
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
// 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
|
|
||||||
|
|
||||||
#include <QString>
|
|
||||||
|
|
||||||
namespace ProjectExplorer {
|
|
||||||
class Project;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace QodeAssist::PluginLLMCore {
|
|
||||||
|
|
||||||
enum class RulesContext { Completions, Chat, QuickRefactor };
|
|
||||||
|
|
||||||
struct RuleFileInfo
|
|
||||||
{
|
|
||||||
QString filePath;
|
|
||||||
QString fileName;
|
|
||||||
QString category; // "common", "chat", "completions", "quickrefactor"
|
|
||||||
};
|
|
||||||
|
|
||||||
class RulesLoader
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
static QString loadRules(const QString &projectPath, RulesContext context);
|
|
||||||
static QString loadRulesForProject(ProjectExplorer::Project *project, RulesContext context);
|
|
||||||
static ProjectExplorer::Project *getActiveProject();
|
|
||||||
|
|
||||||
// New methods for getting rule files info
|
|
||||||
static QVector<RuleFileInfo> getRuleFiles(const QString &projectPath, RulesContext context);
|
|
||||||
static QVector<RuleFileInfo> getRuleFilesForProject(ProjectExplorer::Project *project, RulesContext context);
|
|
||||||
static QString loadRuleFileContent(const QString &filePath);
|
|
||||||
|
|
||||||
private:
|
|
||||||
static QString loadAllMarkdownFiles(const QString &dirPath);
|
|
||||||
static QVector<RuleFileInfo> collectMarkdownFiles(const QString &dirPath, const QString &category);
|
|
||||||
static QString getProjectPath(ProjectExplorer::Project *project);
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace QodeAssist::PluginLLMCore
|
|
||||||
@@ -94,10 +94,7 @@ void ClaudeProvider::prepareRequest(
|
|||||||
request["temperature"] = 1.0;
|
request["temperature"] = 1.0;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (type == PluginLLMCore::RequestType::CodeCompletion) {
|
if (type == PluginLLMCore::RequestType::QuickRefactoring) {
|
||||||
applyModelParams(Settings::codeCompletionSettings());
|
|
||||||
request["temperature"] = Settings::codeCompletionSettings().temperature();
|
|
||||||
} else if (type == PluginLLMCore::RequestType::QuickRefactoring) {
|
|
||||||
const auto &qrSettings = Settings::quickRefactorSettings();
|
const auto &qrSettings = Settings::quickRefactorSettings();
|
||||||
applyModelParams(qrSettings);
|
applyModelParams(qrSettings);
|
||||||
|
|
||||||
|
|||||||
@@ -86,9 +86,7 @@ void DeepSeekProvider::prepareRequest(
|
|||||||
request["presence_penalty"] = settings.presencePenalty();
|
request["presence_penalty"] = settings.presencePenalty();
|
||||||
};
|
};
|
||||||
|
|
||||||
if (type == PluginLLMCore::RequestType::CodeCompletion) {
|
if (type == PluginLLMCore::RequestType::QuickRefactoring) {
|
||||||
applyModelParams(Settings::codeCompletionSettings());
|
|
||||||
} else if (type == PluginLLMCore::RequestType::QuickRefactoring) {
|
|
||||||
applyModelParams(Settings::quickRefactorSettings());
|
applyModelParams(Settings::quickRefactorSettings());
|
||||||
} else {
|
} else {
|
||||||
applyModelParams(Settings::chatAssistantSettings());
|
applyModelParams(Settings::chatAssistantSettings());
|
||||||
|
|||||||
@@ -92,9 +92,7 @@ void GoogleAIProvider::prepareRequest(
|
|||||||
request["generationConfig"] = generationConfig;
|
request["generationConfig"] = generationConfig;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (type == PluginLLMCore::RequestType::CodeCompletion) {
|
if (type == PluginLLMCore::RequestType::QuickRefactoring) {
|
||||||
applyModelParams(Settings::codeCompletionSettings());
|
|
||||||
} else if (type == PluginLLMCore::RequestType::QuickRefactoring) {
|
|
||||||
const auto &qrSettings = Settings::quickRefactorSettings();
|
const auto &qrSettings = Settings::quickRefactorSettings();
|
||||||
|
|
||||||
if (isThinkingEnabled) {
|
if (isThinkingEnabled) {
|
||||||
|
|||||||
@@ -90,9 +90,7 @@ void LMStudioProvider::prepareRequest(
|
|||||||
request["presence_penalty"] = settings.presencePenalty();
|
request["presence_penalty"] = settings.presencePenalty();
|
||||||
};
|
};
|
||||||
|
|
||||||
if (type == PluginLLMCore::RequestType::CodeCompletion) {
|
if (type == PluginLLMCore::RequestType::QuickRefactoring) {
|
||||||
applyModelParams(Settings::codeCompletionSettings());
|
|
||||||
} else if (type == PluginLLMCore::RequestType::QuickRefactoring) {
|
|
||||||
applyModelParams(Settings::quickRefactorSettings());
|
applyModelParams(Settings::quickRefactorSettings());
|
||||||
} else {
|
} else {
|
||||||
applyModelParams(Settings::chatAssistantSettings());
|
applyModelParams(Settings::chatAssistantSettings());
|
||||||
|
|||||||
@@ -82,9 +82,7 @@ void LMStudioResponsesProvider::prepareRequest(
|
|||||||
request["include"] = include;
|
request["include"] = include;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (type == PluginLLMCore::RequestType::CodeCompletion) {
|
if (type == PluginLLMCore::RequestType::QuickRefactoring) {
|
||||||
applyModelParams(Settings::codeCompletionSettings());
|
|
||||||
} else if (type == PluginLLMCore::RequestType::QuickRefactoring) {
|
|
||||||
const auto &qrSettings = Settings::quickRefactorSettings();
|
const auto &qrSettings = Settings::quickRefactorSettings();
|
||||||
applyModelParams(qrSettings);
|
applyModelParams(qrSettings);
|
||||||
|
|
||||||
|
|||||||
@@ -75,9 +75,7 @@ void LlamaCppProvider::prepareRequest(
|
|||||||
request["chat_template_kwargs"] = chatTemplateKwargs;
|
request["chat_template_kwargs"] = chatTemplateKwargs;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (type == PluginLLMCore::RequestType::CodeCompletion) {
|
if (type == PluginLLMCore::RequestType::QuickRefactoring) {
|
||||||
applyModelParams(Settings::codeCompletionSettings());
|
|
||||||
} else if (type == PluginLLMCore::RequestType::QuickRefactoring) {
|
|
||||||
applyModelParams(Settings::quickRefactorSettings());
|
applyModelParams(Settings::quickRefactorSettings());
|
||||||
if (isThinkingEnabled) {
|
if (isThinkingEnabled) {
|
||||||
applyThinkingMode();
|
applyThinkingMode();
|
||||||
|
|||||||
@@ -88,9 +88,7 @@ void MistralAIProvider::prepareRequest(
|
|||||||
request["presence_penalty"] = settings.presencePenalty();
|
request["presence_penalty"] = settings.presencePenalty();
|
||||||
};
|
};
|
||||||
|
|
||||||
if (type == PluginLLMCore::RequestType::CodeCompletion) {
|
if (type == PluginLLMCore::RequestType::QuickRefactoring) {
|
||||||
applyModelParams(Settings::codeCompletionSettings());
|
|
||||||
} else if (type == PluginLLMCore::RequestType::QuickRefactoring) {
|
|
||||||
applyModelParams(Settings::quickRefactorSettings());
|
applyModelParams(Settings::quickRefactorSettings());
|
||||||
} else {
|
} else {
|
||||||
applyModelParams(Settings::chatAssistantSettings());
|
applyModelParams(Settings::chatAssistantSettings());
|
||||||
|
|||||||
@@ -70,9 +70,7 @@ void OllamaCompatProvider::prepareRequest(
|
|||||||
request["presence_penalty"] = settings.presencePenalty();
|
request["presence_penalty"] = settings.presencePenalty();
|
||||||
};
|
};
|
||||||
|
|
||||||
if (type == PluginLLMCore::RequestType::CodeCompletion) {
|
if (type == PluginLLMCore::RequestType::QuickRefactoring) {
|
||||||
applyModelParams(Settings::codeCompletionSettings());
|
|
||||||
} else if (type == PluginLLMCore::RequestType::QuickRefactoring) {
|
|
||||||
applyModelParams(Settings::quickRefactorSettings());
|
applyModelParams(Settings::quickRefactorSettings());
|
||||||
} else {
|
} else {
|
||||||
applyModelParams(Settings::chatAssistantSettings());
|
applyModelParams(Settings::chatAssistantSettings());
|
||||||
|
|||||||
@@ -82,9 +82,7 @@ void OllamaProvider::prepareRequest(
|
|||||||
request["options"] = options;
|
request["options"] = options;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (type == PluginLLMCore::RequestType::CodeCompletion) {
|
if (type == PluginLLMCore::RequestType::QuickRefactoring) {
|
||||||
applySettings(Settings::codeCompletionSettings());
|
|
||||||
} else if (type == PluginLLMCore::RequestType::QuickRefactoring) {
|
|
||||||
const auto &qrSettings = Settings::quickRefactorSettings();
|
const auto &qrSettings = Settings::quickRefactorSettings();
|
||||||
applySettings(qrSettings);
|
applySettings(qrSettings);
|
||||||
|
|
||||||
|
|||||||
@@ -69,9 +69,7 @@ void OpenAICompatProvider::prepareRequest(
|
|||||||
request["presence_penalty"] = settings.presencePenalty();
|
request["presence_penalty"] = settings.presencePenalty();
|
||||||
};
|
};
|
||||||
|
|
||||||
if (type == PluginLLMCore::RequestType::CodeCompletion) {
|
if (type == PluginLLMCore::RequestType::QuickRefactoring) {
|
||||||
applyModelParams(Settings::codeCompletionSettings());
|
|
||||||
} else if (type == PluginLLMCore::RequestType::QuickRefactoring) {
|
|
||||||
applyModelParams(Settings::quickRefactorSettings());
|
applyModelParams(Settings::quickRefactorSettings());
|
||||||
} else {
|
} else {
|
||||||
applyModelParams(Settings::chatAssistantSettings());
|
applyModelParams(Settings::chatAssistantSettings());
|
||||||
|
|||||||
@@ -88,9 +88,7 @@ void OpenAIProvider::prepareRequest(
|
|||||||
request["presence_penalty"] = settings.presencePenalty();
|
request["presence_penalty"] = settings.presencePenalty();
|
||||||
};
|
};
|
||||||
|
|
||||||
if (type == PluginLLMCore::RequestType::CodeCompletion) {
|
if (type == PluginLLMCore::RequestType::QuickRefactoring) {
|
||||||
applyModelParams(Settings::codeCompletionSettings());
|
|
||||||
} else if (type == PluginLLMCore::RequestType::QuickRefactoring) {
|
|
||||||
applyModelParams(Settings::quickRefactorSettings());
|
applyModelParams(Settings::quickRefactorSettings());
|
||||||
} else {
|
} else {
|
||||||
applyModelParams(Settings::chatAssistantSettings());
|
applyModelParams(Settings::chatAssistantSettings());
|
||||||
|
|||||||
@@ -80,9 +80,7 @@ void OpenAIResponsesProvider::prepareRequest(
|
|||||||
request["include"] = include;
|
request["include"] = include;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (type == PluginLLMCore::RequestType::CodeCompletion) {
|
if (type == PluginLLMCore::RequestType::QuickRefactoring) {
|
||||||
applyModelParams(Settings::codeCompletionSettings());
|
|
||||||
} else if (type == PluginLLMCore::RequestType::QuickRefactoring) {
|
|
||||||
const auto &qrSettings = Settings::quickRefactorSettings();
|
const auto &qrSettings = Settings::quickRefactorSettings();
|
||||||
applyModelParams(qrSettings);
|
applyModelParams(qrSettings);
|
||||||
|
|
||||||
|
|||||||
@@ -88,9 +88,7 @@ void QwenProvider::prepareRequest(
|
|||||||
request["presence_penalty"] = settings.presencePenalty();
|
request["presence_penalty"] = settings.presencePenalty();
|
||||||
};
|
};
|
||||||
|
|
||||||
if (type == PluginLLMCore::RequestType::CodeCompletion) {
|
if (type == PluginLLMCore::RequestType::QuickRefactoring) {
|
||||||
applyModelParams(Settings::codeCompletionSettings());
|
|
||||||
} else if (type == PluginLLMCore::RequestType::QuickRefactoring) {
|
|
||||||
applyModelParams(Settings::quickRefactorSettings());
|
applyModelParams(Settings::quickRefactorSettings());
|
||||||
} else {
|
} else {
|
||||||
applyModelParams(Settings::chatAssistantSettings());
|
applyModelParams(Settings::chatAssistantSettings());
|
||||||
|
|||||||
@@ -80,9 +80,7 @@ void QwenResponsesProvider::prepareRequest(
|
|||||||
request["include"] = include;
|
request["include"] = include;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (type == PluginLLMCore::RequestType::CodeCompletion) {
|
if (type == PluginLLMCore::RequestType::QuickRefactoring) {
|
||||||
applyModelParams(Settings::codeCompletionSettings());
|
|
||||||
} else if (type == PluginLLMCore::RequestType::QuickRefactoring) {
|
|
||||||
const auto &qrSettings = Settings::quickRefactorSettings();
|
const auto &qrSettings = Settings::quickRefactorSettings();
|
||||||
applyModelParams(qrSettings);
|
applyModelParams(qrSettings);
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,6 @@
|
|||||||
#include <QTranslator>
|
#include <QTranslator>
|
||||||
|
|
||||||
#include <QInputDialog>
|
#include <QInputDialog>
|
||||||
#include "ConfigurationManager.hpp"
|
|
||||||
#include "QodeAssistClient.hpp"
|
#include "QodeAssistClient.hpp"
|
||||||
#include "UpdateStatusWidget.hpp"
|
#include "UpdateStatusWidget.hpp"
|
||||||
#include "Version.hpp"
|
#include "Version.hpp"
|
||||||
@@ -43,7 +42,6 @@
|
|||||||
#include "chat/ChatOutputPane.h"
|
#include "chat/ChatOutputPane.h"
|
||||||
#include "chat/NavigationPanel.hpp"
|
#include "chat/NavigationPanel.hpp"
|
||||||
#include "context/DocumentReaderQtCreator.hpp"
|
#include "context/DocumentReaderQtCreator.hpp"
|
||||||
#include "pluginllmcore/PromptProviderFim.hpp"
|
|
||||||
#include "pluginllmcore/ProvidersManager.hpp"
|
#include "pluginllmcore/ProvidersManager.hpp"
|
||||||
#include "logger/RequestPerformanceLogger.hpp"
|
#include "logger/RequestPerformanceLogger.hpp"
|
||||||
#include "mcp/McpClientsManager.hpp"
|
#include "mcp/McpClientsManager.hpp"
|
||||||
@@ -56,7 +54,6 @@
|
|||||||
#include "settings/ProjectSettingsPanel.hpp"
|
#include "settings/ProjectSettingsPanel.hpp"
|
||||||
#include "settings/AgentsSettingsPage.hpp"
|
#include "settings/AgentsSettingsPage.hpp"
|
||||||
#include "settings/ProvidersSettingsPage.hpp"
|
#include "settings/ProvidersSettingsPage.hpp"
|
||||||
#include "sources/settings/AgentPipelinesPage.hpp"
|
|
||||||
#include "settings/QuickRefactorSettings.hpp"
|
#include "settings/QuickRefactorSettings.hpp"
|
||||||
#include "settings/SettingsConstants.hpp"
|
#include "settings/SettingsConstants.hpp"
|
||||||
|
|
||||||
@@ -65,6 +62,8 @@
|
|||||||
#include "ProviderSecretsStore.hpp"
|
#include "ProviderSecretsStore.hpp"
|
||||||
|
|
||||||
#include <AgentFactory.hpp>
|
#include <AgentFactory.hpp>
|
||||||
|
#include <GenericProvider.hpp>
|
||||||
|
#include <SessionManager.hpp>
|
||||||
#include "templates/Templates.hpp"
|
#include "templates/Templates.hpp"
|
||||||
#include "widgets/CustomInstructionsManager.hpp"
|
#include "widgets/CustomInstructionsManager.hpp"
|
||||||
#include "widgets/QuickRefactorDialog.hpp"
|
#include "widgets/QuickRefactorDialog.hpp"
|
||||||
@@ -74,6 +73,7 @@
|
|||||||
#include <ChatView/ChatWidget.hpp>
|
#include <ChatView/ChatWidget.hpp>
|
||||||
#include <ChatView/SessionFileRegistry.hpp>
|
#include <ChatView/SessionFileRegistry.hpp>
|
||||||
#include <coreplugin/editormanager/editormanager.h>
|
#include <coreplugin/editormanager/editormanager.h>
|
||||||
|
#include <QQmlContext>
|
||||||
#include <QUuid>
|
#include <QUuid>
|
||||||
#include <coreplugin/actionmanager/actioncontainer.h>
|
#include <coreplugin/actionmanager/actioncontainer.h>
|
||||||
#include <coreplugin/actionmanager/actionmanager.h>
|
#include <coreplugin/actionmanager/actionmanager.h>
|
||||||
@@ -95,7 +95,6 @@ class QodeAssistPlugin final : public ExtensionSystem::IPlugin
|
|||||||
public:
|
public:
|
||||||
QodeAssistPlugin()
|
QodeAssistPlugin()
|
||||||
: m_updater(new PluginUpdater(this))
|
: m_updater(new PluginUpdater(this))
|
||||||
, m_promptProvider(PluginLLMCore::PromptTemplateManager::instance())
|
|
||||||
{}
|
{}
|
||||||
|
|
||||||
~QodeAssistPlugin() final
|
~QodeAssistPlugin() final
|
||||||
@@ -203,8 +202,8 @@ public:
|
|||||||
m_engine, m_sessionFileRegistry, m_skillsManager};
|
m_engine, m_sessionFileRegistry, m_skillsManager};
|
||||||
|
|
||||||
Settings::setupProjectPanel();
|
Settings::setupProjectPanel();
|
||||||
ConfigurationManager::instance().init();
|
|
||||||
|
|
||||||
|
Providers::registerBuiltinProviders();
|
||||||
m_providerInstanceFactory = new Providers::ProviderInstanceFactory(this);
|
m_providerInstanceFactory = new Providers::ProviderInstanceFactory(this);
|
||||||
m_providerSecretsStore = new Providers::ProviderSecretsStore(this);
|
m_providerSecretsStore = new Providers::ProviderSecretsStore(this);
|
||||||
m_providerLauncher = new Providers::ProviderLauncher(this);
|
m_providerLauncher = new Providers::ProviderLauncher(this);
|
||||||
@@ -216,6 +215,9 @@ public:
|
|||||||
m_providersPageNavigator);
|
m_providersPageNavigator);
|
||||||
|
|
||||||
m_agentFactory = new AgentFactory(m_providerInstanceFactory, m_providerSecretsStore, this);
|
m_agentFactory = new AgentFactory(m_providerInstanceFactory, m_providerSecretsStore, this);
|
||||||
|
m_sessionManager = new SessionManager(m_agentFactory, this);
|
||||||
|
m_engine->rootContext()->setContextProperty("agentFactory", m_agentFactory);
|
||||||
|
m_engine->rootContext()->setContextProperty("sessionManager", m_sessionManager);
|
||||||
m_agentsPageNavigator = new Settings::AgentsPageNavigator(this);
|
m_agentsPageNavigator = new Settings::AgentsPageNavigator(this);
|
||||||
m_agentsOptionsPage = Settings::createAgentsSettingsPage(
|
m_agentsOptionsPage = Settings::createAgentsSettingsPage(
|
||||||
m_agentFactory, m_agentsPageNavigator);
|
m_agentFactory, m_agentsPageNavigator);
|
||||||
@@ -342,10 +344,12 @@ public:
|
|||||||
m_qodeAssistClient = new QodeAssistClient(new LLMClientInterface(
|
m_qodeAssistClient = new QodeAssistClient(new LLMClientInterface(
|
||||||
Settings::generalSettings(),
|
Settings::generalSettings(),
|
||||||
Settings::codeCompletionSettings(),
|
Settings::codeCompletionSettings(),
|
||||||
PluginLLMCore::ProvidersManager::instance(),
|
*m_sessionManager,
|
||||||
&m_promptProvider,
|
*m_agentFactory,
|
||||||
m_documentReader,
|
m_documentReader,
|
||||||
m_performanceLogger));
|
m_performanceLogger));
|
||||||
|
m_qodeAssistClient->setSessionManager(m_sessionManager);
|
||||||
|
m_qodeAssistClient->setAgentFactory(m_agentFactory);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool delayedInitialize() final
|
bool delayedInitialize() final
|
||||||
@@ -503,7 +507,6 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
QPointer<QodeAssistClient> m_qodeAssistClient;
|
QPointer<QodeAssistClient> m_qodeAssistClient;
|
||||||
PluginLLMCore::PromptProviderFim m_promptProvider;
|
|
||||||
Context::DocumentReaderQtCreator m_documentReader;
|
Context::DocumentReaderQtCreator m_documentReader;
|
||||||
RequestPerformanceLogger m_performanceLogger;
|
RequestPerformanceLogger m_performanceLogger;
|
||||||
QPointer<Chat::ChatOutputPane> m_chatOutputPane;
|
QPointer<Chat::ChatOutputPane> m_chatOutputPane;
|
||||||
@@ -524,6 +527,7 @@ private:
|
|||||||
QPointer<Settings::ProvidersPageNavigator> m_providersPageNavigator;
|
QPointer<Settings::ProvidersPageNavigator> m_providersPageNavigator;
|
||||||
std::unique_ptr<Core::IOptionsPage> m_providersOptionsPage;
|
std::unique_ptr<Core::IOptionsPage> m_providersOptionsPage;
|
||||||
QPointer<AgentFactory> m_agentFactory;
|
QPointer<AgentFactory> m_agentFactory;
|
||||||
|
QPointer<SessionManager> m_sessionManager;
|
||||||
QPointer<Settings::AgentsPageNavigator> m_agentsPageNavigator;
|
QPointer<Settings::AgentsPageNavigator> m_agentsPageNavigator;
|
||||||
std::unique_ptr<Core::IOptionsPage> m_agentsOptionsPage;
|
std::unique_ptr<Core::IOptionsPage> m_agentsOptionsPage;
|
||||||
QPointer<Settings::AgentPipelinesPageNavigator> m_agentPipelinesPageNavigator;
|
QPointer<Settings::AgentPipelinesPageNavigator> m_agentPipelinesPageNavigator;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
add_library(QodeAssistSettings STATIC
|
add_library(QodeAssistSettings STATIC
|
||||||
GeneralSettings.hpp GeneralSettings.cpp
|
GeneralSettings.hpp GeneralSettings.cpp
|
||||||
ConfigurationManager.hpp ConfigurationManager.cpp
|
|
||||||
SettingsUtils.hpp
|
SettingsUtils.hpp
|
||||||
SettingsConstants.hpp
|
SettingsConstants.hpp
|
||||||
ButtonAspect.hpp
|
ButtonAspect.hpp
|
||||||
@@ -47,5 +46,9 @@ target_link_libraries(QodeAssistSettings
|
|||||||
ProvidersConfig
|
ProvidersConfig
|
||||||
Agents
|
Agents
|
||||||
Skills
|
Skills
|
||||||
|
QodeAssistAgentPipelines
|
||||||
|
)
|
||||||
|
target_include_directories(QodeAssistSettings
|
||||||
|
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}
|
||||||
|
PRIVATE ${CMAKE_SOURCE_DIR}/sources/settings
|
||||||
)
|
)
|
||||||
target_include_directories(QodeAssistSettings PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
|
||||||
|
|||||||
@@ -165,54 +165,6 @@ CodeCompletionSettings::CodeCompletionSettings()
|
|||||||
"for triggering completions. This helps trigger completions based on actual code "
|
"for triggering completions. This helps trigger completions based on actual code "
|
||||||
"characters only."));
|
"characters only."));
|
||||||
|
|
||||||
// General Parameters Settings
|
|
||||||
temperature.setSettingsKey(Constants::CC_TEMPERATURE);
|
|
||||||
temperature.setLabelText(Tr::tr("Temperature:"));
|
|
||||||
temperature.setDefaultValue(0.2);
|
|
||||||
temperature.setRange(0.0, 2.0);
|
|
||||||
temperature.setSingleStep(0.1);
|
|
||||||
|
|
||||||
maxTokens.setSettingsKey(Constants::CC_MAX_TOKENS);
|
|
||||||
maxTokens.setLabelText(Tr::tr("Max Tokens:"));
|
|
||||||
maxTokens.setRange(-1, 900000);
|
|
||||||
maxTokens.setDefaultValue(500);
|
|
||||||
|
|
||||||
// Advanced Parameters
|
|
||||||
useTopP.setSettingsKey(Constants::CC_USE_TOP_P);
|
|
||||||
useTopP.setDefaultValue(false);
|
|
||||||
useTopP.setLabelText(Tr::tr("Top P:"));
|
|
||||||
|
|
||||||
topP.setSettingsKey(Constants::CC_TOP_P);
|
|
||||||
topP.setDefaultValue(0.9);
|
|
||||||
topP.setRange(0.0, 1.0);
|
|
||||||
topP.setSingleStep(0.1);
|
|
||||||
|
|
||||||
useTopK.setSettingsKey(Constants::CC_USE_TOP_K);
|
|
||||||
useTopK.setDefaultValue(false);
|
|
||||||
useTopK.setLabelText(Tr::tr("Top K:"));
|
|
||||||
|
|
||||||
topK.setSettingsKey(Constants::CC_TOP_K);
|
|
||||||
topK.setDefaultValue(50);
|
|
||||||
topK.setRange(1, 1000);
|
|
||||||
|
|
||||||
usePresencePenalty.setSettingsKey(Constants::CC_USE_PRESENCE_PENALTY);
|
|
||||||
usePresencePenalty.setDefaultValue(false);
|
|
||||||
usePresencePenalty.setLabelText(Tr::tr("Presence Penalty:"));
|
|
||||||
|
|
||||||
presencePenalty.setSettingsKey(Constants::CC_PRESENCE_PENALTY);
|
|
||||||
presencePenalty.setDefaultValue(0.0);
|
|
||||||
presencePenalty.setRange(-2.0, 2.0);
|
|
||||||
presencePenalty.setSingleStep(0.1);
|
|
||||||
|
|
||||||
useFrequencyPenalty.setSettingsKey(Constants::CC_USE_FREQUENCY_PENALTY);
|
|
||||||
useFrequencyPenalty.setDefaultValue(false);
|
|
||||||
useFrequencyPenalty.setLabelText(Tr::tr("Frequency Penalty:"));
|
|
||||||
|
|
||||||
frequencyPenalty.setSettingsKey(Constants::CC_FREQUENCY_PENALTY);
|
|
||||||
frequencyPenalty.setDefaultValue(0.0);
|
|
||||||
frequencyPenalty.setRange(-2.0, 2.0);
|
|
||||||
frequencyPenalty.setSingleStep(0.1);
|
|
||||||
|
|
||||||
// Context Settings
|
// Context Settings
|
||||||
readFullFile.setSettingsKey(Constants::CC_READ_FULL_FILE);
|
readFullFile.setSettingsKey(Constants::CC_READ_FULL_FILE);
|
||||||
readFullFile.setLabelText(Tr::tr("Read Full File"));
|
readFullFile.setLabelText(Tr::tr("Read Full File"));
|
||||||
@@ -240,43 +192,6 @@ CodeCompletionSettings::CodeCompletionSettings()
|
|||||||
"You are an expert C++, Qt, and QML code completion assistant. Your task is to provide "
|
"You are an expert C++, Qt, and QML code completion assistant. Your task is to provide "
|
||||||
"precise and contextually appropriate code completions.\n\n");
|
"precise and contextually appropriate code completions.\n\n");
|
||||||
|
|
||||||
useUserMessageTemplateForCC.setSettingsKey(Constants::CC_USE_USER_TEMPLATE);
|
|
||||||
useUserMessageTemplateForCC.setDefaultValue(true);
|
|
||||||
useUserMessageTemplateForCC.setLabelText(
|
|
||||||
Tr::tr("Use special system prompt and user message for non FIM models"));
|
|
||||||
|
|
||||||
systemPromptForNonFimModels.setSettingsKey(Constants::CC_SYSTEM_PROMPT_FOR_NON_FIM);
|
|
||||||
systemPromptForNonFimModels.setDisplayStyle(Utils::StringAspect::TextEditDisplay);
|
|
||||||
systemPromptForNonFimModels.setLabelText(Tr::tr("System prompt for non FIM models:"));
|
|
||||||
systemPromptForNonFimModels.setDefaultValue(
|
|
||||||
"You are an expert C++, Qt, and QML code completion assistant. Your task is to provide "
|
|
||||||
"precise and contextually appropriate code completions.\n\n"
|
|
||||||
"Core Requirements:\n"
|
|
||||||
"1. Continue code exactly from the cursor position, ensuring it properly connects with any "
|
|
||||||
"existing code after the cursor\n"
|
|
||||||
"2. Never repeat existing code before or after the cursor\n"
|
|
||||||
"Specific Guidelines:\n"
|
|
||||||
"- For function calls: Complete parameters with appropriate types and names\n"
|
|
||||||
"- For class members: Respect access modifiers and class conventions\n"
|
|
||||||
"- Respect existing indentation and formatting\n"
|
|
||||||
"- Consider scope and visibility of referenced symbols\n"
|
|
||||||
"- Ensure seamless integration with code both before and after the cursor\n\n"
|
|
||||||
"Context Format:\n"
|
|
||||||
"<code_context>\n"
|
|
||||||
"{{code before cursor}}<cursor>{{code after cursor}}\n"
|
|
||||||
"</code_context>\n\n"
|
|
||||||
"Response Format:\n"
|
|
||||||
"- No explanations or comments\n"
|
|
||||||
"- Only include new characters needed to create valid code\n"
|
|
||||||
"- Should be codeblock with language\n");
|
|
||||||
|
|
||||||
userMessageTemplateForCC.setSettingsKey(Constants::CC_USER_TEMPLATE);
|
|
||||||
userMessageTemplateForCC.setDisplayStyle(Utils::StringAspect::TextEditDisplay);
|
|
||||||
userMessageTemplateForCC.setLabelText(Tr::tr("User message for non FIM models:"));
|
|
||||||
userMessageTemplateForCC.setDefaultValue(
|
|
||||||
"Here is the code context with insertion points:\n"
|
|
||||||
"<code_context>\n${prefix}<cursor>${suffix}\n</code_context>\n\n");
|
|
||||||
|
|
||||||
customLanguages.setSettingsKey(Constants::CC_CUSTOM_LANGUAGES);
|
customLanguages.setSettingsKey(Constants::CC_CUSTOM_LANGUAGES);
|
||||||
customLanguages.setLabelText(
|
customLanguages.setLabelText(
|
||||||
Tr::tr("Additional Programming Languages for handling: Example: rust,//,rust rs,rs"));
|
Tr::tr("Additional Programming Languages for handling: Example: rust,//,rust rs,rs"));
|
||||||
@@ -311,39 +226,6 @@ CodeCompletionSettings::CodeCompletionSettings()
|
|||||||
maxChangesCacheSize.setRange(2, 1000);
|
maxChangesCacheSize.setRange(2, 1000);
|
||||||
maxChangesCacheSize.setDefaultValue(10);
|
maxChangesCacheSize.setDefaultValue(10);
|
||||||
|
|
||||||
// Ollama Settings
|
|
||||||
ollamaLivetime.setSettingsKey(Constants::CC_OLLAMA_LIVETIME);
|
|
||||||
ollamaLivetime.setToolTip(
|
|
||||||
Tr::tr("Time to suspend Ollama after completion request (in minutes), "
|
|
||||||
"Only Ollama, -1 to disable"));
|
|
||||||
ollamaLivetime.setLabelText("Livetime:");
|
|
||||||
ollamaLivetime.setDefaultValue("5m");
|
|
||||||
ollamaLivetime.setDisplayStyle(Utils::StringAspect::LineEditDisplay);
|
|
||||||
|
|
||||||
contextWindow.setSettingsKey(Constants::CC_OLLAMA_CONTEXT_WINDOW);
|
|
||||||
contextWindow.setLabelText(Tr::tr("Context Window:"));
|
|
||||||
contextWindow.setRange(-1, 10000);
|
|
||||||
contextWindow.setDefaultValue(2048);
|
|
||||||
|
|
||||||
// OpenAI Responses API Settings
|
|
||||||
openAIResponsesReasoningEffort.setSettingsKey(Constants::CC_OPENAI_RESPONSES_REASONING_EFFORT);
|
|
||||||
openAIResponsesReasoningEffort.setLabelText(Tr::tr("Reasoning effort:"));
|
|
||||||
openAIResponsesReasoningEffort.setDisplayStyle(Utils::SelectionAspect::DisplayStyle::ComboBox);
|
|
||||||
openAIResponsesReasoningEffort.addOption("None");
|
|
||||||
openAIResponsesReasoningEffort.addOption("Minimal");
|
|
||||||
openAIResponsesReasoningEffort.addOption("Low");
|
|
||||||
openAIResponsesReasoningEffort.addOption("Medium");
|
|
||||||
openAIResponsesReasoningEffort.addOption("High");
|
|
||||||
openAIResponsesReasoningEffort.setDefaultValue("Medium");
|
|
||||||
openAIResponsesReasoningEffort.setToolTip(
|
|
||||||
Tr::tr("Constrains effort on reasoning for OpenAI gpt-5 and o-series models:\n\n"
|
|
||||||
"None: No reasoning (gpt-5.1 only)\n"
|
|
||||||
"Minimal: Minimal reasoning effort (o-series only)\n"
|
|
||||||
"Low: Low reasoning effort\n"
|
|
||||||
"Medium: Balanced reasoning (default for most models)\n"
|
|
||||||
"High: Maximum reasoning effort (gpt-5-pro only supports this)\n\n"
|
|
||||||
"Note: Reducing effort = faster responses + fewer tokens"));
|
|
||||||
|
|
||||||
resetToDefaults.m_buttonText = Tr::tr("Reset Page to Defaults");
|
resetToDefaults.m_buttonText = Tr::tr("Reset Page to Defaults");
|
||||||
|
|
||||||
readSettings();
|
readSettings();
|
||||||
@@ -357,23 +239,6 @@ CodeCompletionSettings::CodeCompletionSettings()
|
|||||||
setLayouter([this]() {
|
setLayouter([this]() {
|
||||||
using namespace Layouting;
|
using namespace Layouting;
|
||||||
|
|
||||||
auto genGrid = Grid{};
|
|
||||||
genGrid.addRow({Row{temperature}});
|
|
||||||
genGrid.addRow({Row{maxTokens}});
|
|
||||||
|
|
||||||
auto advancedGrid = Grid{};
|
|
||||||
advancedGrid.addRow({useTopP, topP});
|
|
||||||
advancedGrid.addRow({useTopK, topK});
|
|
||||||
advancedGrid.addRow({usePresencePenalty, presencePenalty});
|
|
||||||
advancedGrid.addRow({useFrequencyPenalty, frequencyPenalty});
|
|
||||||
|
|
||||||
auto ollamaGrid = Grid{};
|
|
||||||
ollamaGrid.addRow({ollamaLivetime});
|
|
||||||
ollamaGrid.addRow({contextWindow});
|
|
||||||
|
|
||||||
auto openAIResponsesGrid = Grid{};
|
|
||||||
openAIResponsesGrid.addRow({openAIResponsesReasoningEffort});
|
|
||||||
|
|
||||||
auto contextGrid = Grid{};
|
auto contextGrid = Grid{};
|
||||||
contextGrid.addRow({Row{readFullFile}});
|
contextGrid.addRow({Row{readFullFile}});
|
||||||
contextGrid.addRow({Row{readFileParts, readStringsBeforeCursor, readStringsAfterCursor}});
|
contextGrid.addRow({Row{readFileParts, readStringsBeforeCursor, readStringsAfterCursor}});
|
||||||
@@ -382,14 +247,7 @@ CodeCompletionSettings::CodeCompletionSettings()
|
|||||||
Row{contextGrid, Stretch{1}},
|
Row{contextGrid, Stretch{1}},
|
||||||
Row{useSystemPrompt, Stretch{1}},
|
Row{useSystemPrompt, Stretch{1}},
|
||||||
Group{title(Tr::tr("Prompts for FIM models")), Column{systemPrompt}},
|
Group{title(Tr::tr("Prompts for FIM models")), Column{systemPrompt}},
|
||||||
Group{
|
customLanguages,
|
||||||
title(Tr::tr("Prompts for Non FIM models")),
|
|
||||||
Column{
|
|
||||||
Row{useUserMessageTemplateForCC, Stretch{1}},
|
|
||||||
systemPromptForNonFimModels,
|
|
||||||
userMessageTemplateForCC,
|
|
||||||
customLanguages,
|
|
||||||
}},
|
|
||||||
Row{useProjectChangesCache, maxChangesCacheSize, Stretch{1}}};
|
Row{useProjectChangesCache, maxChangesCacheSize, Stretch{1}}};
|
||||||
|
|
||||||
auto generalSettings = Column{
|
auto generalSettings = Column{
|
||||||
@@ -418,19 +276,7 @@ CodeCompletionSettings::CodeCompletionSettings()
|
|||||||
Space{8},
|
Space{8},
|
||||||
Group{title(Tr::tr("Automatic Trigger Mode")), autoTriggerSettings}}},
|
Group{title(Tr::tr("Automatic Trigger Mode")), autoTriggerSettings}}},
|
||||||
Space{8},
|
Space{8},
|
||||||
Group{title(Tr::tr("General Parameters")),
|
|
||||||
Column{
|
|
||||||
Row{genGrid, Stretch{1}},
|
|
||||||
}},
|
|
||||||
Space{8},
|
|
||||||
Group{title(Tr::tr("Advanced Parameters")),
|
|
||||||
Column{Row{advancedGrid, Stretch{1}}}},
|
|
||||||
Space{8},
|
|
||||||
Group{title(Tr::tr("Context Settings")), contextItem},
|
Group{title(Tr::tr("Context Settings")), contextItem},
|
||||||
Space{8},
|
|
||||||
Group{title(Tr::tr("OpenAI Responses API")), Column{Row{openAIResponsesGrid, Stretch{1}}}},
|
|
||||||
Space{8},
|
|
||||||
Group{title(Tr::tr("Ollama Settings")), Column{Row{ollamaGrid, Stretch{1}}}},
|
|
||||||
Stretch{1}};
|
Stretch{1}};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -470,16 +316,6 @@ void CodeCompletionSettings::resetSettingsToDefaults()
|
|||||||
if (reply == QMessageBox::Yes) {
|
if (reply == QMessageBox::Yes) {
|
||||||
resetAspect(autoCompletion);
|
resetAspect(autoCompletion);
|
||||||
resetAspect(multiLineCompletion);
|
resetAspect(multiLineCompletion);
|
||||||
resetAspect(temperature);
|
|
||||||
resetAspect(maxTokens);
|
|
||||||
resetAspect(useTopP);
|
|
||||||
resetAspect(topP);
|
|
||||||
resetAspect(useTopK);
|
|
||||||
resetAspect(topK);
|
|
||||||
resetAspect(usePresencePenalty);
|
|
||||||
resetAspect(presencePenalty);
|
|
||||||
resetAspect(useFrequencyPenalty);
|
|
||||||
resetAspect(frequencyPenalty);
|
|
||||||
resetAspect(readFullFile);
|
resetAspect(readFullFile);
|
||||||
resetAspect(readFileParts);
|
resetAspect(readFileParts);
|
||||||
resetAspect(readStringsBeforeCursor);
|
resetAspect(readStringsBeforeCursor);
|
||||||
@@ -488,12 +324,6 @@ void CodeCompletionSettings::resetSettingsToDefaults()
|
|||||||
resetAspect(systemPrompt);
|
resetAspect(systemPrompt);
|
||||||
resetAspect(useProjectChangesCache);
|
resetAspect(useProjectChangesCache);
|
||||||
resetAspect(maxChangesCacheSize);
|
resetAspect(maxChangesCacheSize);
|
||||||
resetAspect(ollamaLivetime);
|
|
||||||
resetAspect(contextWindow);
|
|
||||||
resetAspect(openAIResponsesReasoningEffort);
|
|
||||||
resetAspect(useUserMessageTemplateForCC);
|
|
||||||
resetAspect(userMessageTemplateForCC);
|
|
||||||
resetAspect(systemPromptForNonFimModels);
|
|
||||||
resetAspect(customLanguages);
|
resetAspect(customLanguages);
|
||||||
resetAspect(showProgressWidget);
|
resetAspect(showProgressWidget);
|
||||||
resetAspect(useOpenFilesContext);
|
resetAspect(useOpenFilesContext);
|
||||||
@@ -527,14 +357,6 @@ void CodeCompletionSettings::migrateCompletionMode()
|
|||||||
writeSettings();
|
writeSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString CodeCompletionSettings::processMessageToFIM(const QString &prefix, const QString &suffix) const
|
|
||||||
{
|
|
||||||
QString result = userMessageTemplateForCC();
|
|
||||||
result.replace("${prefix}", prefix);
|
|
||||||
result.replace("${suffix}", suffix);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
class CodeCompletionSettingsPage : public Core::IOptionsPage
|
class CodeCompletionSettingsPage : public Core::IOptionsPage
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|||||||
@@ -41,23 +41,6 @@ public:
|
|||||||
Utils::BoolAspect abortAssistOnRequest{this};
|
Utils::BoolAspect abortAssistOnRequest{this};
|
||||||
Utils::BoolAspect useOpenFilesContext{this};
|
Utils::BoolAspect useOpenFilesContext{this};
|
||||||
|
|
||||||
// General Parameters Settings
|
|
||||||
Utils::DoubleAspect temperature{this};
|
|
||||||
Utils::IntegerAspect maxTokens{this};
|
|
||||||
|
|
||||||
// Advanced Parameters
|
|
||||||
Utils::BoolAspect useTopP{this};
|
|
||||||
Utils::DoubleAspect topP{this};
|
|
||||||
|
|
||||||
Utils::BoolAspect useTopK{this};
|
|
||||||
Utils::IntegerAspect topK{this};
|
|
||||||
|
|
||||||
Utils::BoolAspect usePresencePenalty{this};
|
|
||||||
Utils::DoubleAspect presencePenalty{this};
|
|
||||||
|
|
||||||
Utils::BoolAspect useFrequencyPenalty{this};
|
|
||||||
Utils::DoubleAspect frequencyPenalty{this};
|
|
||||||
|
|
||||||
// Context Settings
|
// Context Settings
|
||||||
Utils::BoolAspect readFullFile{this};
|
Utils::BoolAspect readFullFile{this};
|
||||||
Utils::BoolAspect readFileParts{this};
|
Utils::BoolAspect readFileParts{this};
|
||||||
@@ -65,21 +48,9 @@ public:
|
|||||||
Utils::IntegerAspect readStringsAfterCursor{this};
|
Utils::IntegerAspect readStringsAfterCursor{this};
|
||||||
Utils::BoolAspect useSystemPrompt{this};
|
Utils::BoolAspect useSystemPrompt{this};
|
||||||
Utils::StringAspect systemPrompt{this};
|
Utils::StringAspect systemPrompt{this};
|
||||||
Utils::BoolAspect useUserMessageTemplateForCC{this};
|
|
||||||
Utils::StringAspect systemPromptForNonFimModels{this};
|
|
||||||
Utils::StringAspect userMessageTemplateForCC{this};
|
|
||||||
Utils::BoolAspect useProjectChangesCache{this};
|
Utils::BoolAspect useProjectChangesCache{this};
|
||||||
Utils::IntegerAspect maxChangesCacheSize{this};
|
Utils::IntegerAspect maxChangesCacheSize{this};
|
||||||
|
|
||||||
// Ollama Settings
|
|
||||||
Utils::StringAspect ollamaLivetime{this};
|
|
||||||
Utils::IntegerAspect contextWindow{this};
|
|
||||||
|
|
||||||
// OpenAI Responses API Settings
|
|
||||||
Utils::SelectionAspect openAIResponsesReasoningEffort{this};
|
|
||||||
|
|
||||||
QString processMessageToFIM(const QString &prefix, const QString &suffix) const;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void setupConnections();
|
void setupConnections();
|
||||||
void resetSettingsToDefaults();
|
void resetSettingsToDefaults();
|
||||||
|
|||||||
@@ -1,373 +0,0 @@
|
|||||||
// Copyright (C) 2024-2026 Petr Mironychev
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
|
||||||
|
|
||||||
#include "ConfigurationManager.hpp"
|
|
||||||
|
|
||||||
#include <QDir>
|
|
||||||
#include <QFile>
|
|
||||||
#include <QJsonArray>
|
|
||||||
#include <QJsonDocument>
|
|
||||||
#include <QJsonObject>
|
|
||||||
#include <QRegularExpression>
|
|
||||||
#include <QUuid>
|
|
||||||
|
|
||||||
#include <coreplugin/icore.h>
|
|
||||||
|
|
||||||
#include "Logger.hpp"
|
|
||||||
#include "ProviderNameMigration.hpp"
|
|
||||||
|
|
||||||
namespace QodeAssist::Settings {
|
|
||||||
|
|
||||||
ConfigurationManager::ConfigurationManager(QObject *parent)
|
|
||||||
: QObject(parent)
|
|
||||||
{}
|
|
||||||
|
|
||||||
ConfigurationManager &ConfigurationManager::instance()
|
|
||||||
{
|
|
||||||
static ConfigurationManager instance;
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
QVector<AIConfiguration> ConfigurationManager::getPredefinedConfigurations(
|
|
||||||
ConfigurationType type)
|
|
||||||
{
|
|
||||||
QVector<AIConfiguration> presets;
|
|
||||||
|
|
||||||
AIConfiguration claudeOpus;
|
|
||||||
claudeOpus.id = "preset_claude_opus";
|
|
||||||
claudeOpus.name = "Claude Opus 4.7";
|
|
||||||
claudeOpus.provider = "Claude";
|
|
||||||
claudeOpus.model = "claude-opus-4-7";
|
|
||||||
claudeOpus.url = "https://api.anthropic.com";
|
|
||||||
claudeOpus.customEndpoint = "";
|
|
||||||
claudeOpus.templateName = "Claude";
|
|
||||||
claudeOpus.type = type;
|
|
||||||
claudeOpus.isPredefined = true;
|
|
||||||
|
|
||||||
AIConfiguration claudeSonnet;
|
|
||||||
claudeSonnet.id = "preset_claude_sonnet";
|
|
||||||
claudeSonnet.name = "Claude Sonnet 4.6";
|
|
||||||
claudeSonnet.provider = "Claude";
|
|
||||||
claudeSonnet.model = "claude-sonnet-4-6";
|
|
||||||
claudeSonnet.url = "https://api.anthropic.com";
|
|
||||||
claudeSonnet.customEndpoint = "";
|
|
||||||
claudeSonnet.templateName = "Claude";
|
|
||||||
claudeSonnet.type = type;
|
|
||||||
claudeSonnet.isPredefined = true;
|
|
||||||
|
|
||||||
AIConfiguration claudeHaiku;
|
|
||||||
claudeHaiku.id = "preset_claude_haiku";
|
|
||||||
claudeHaiku.name = "Claude Haiku 4.5";
|
|
||||||
claudeHaiku.provider = "Claude";
|
|
||||||
claudeHaiku.model = "claude-haiku-4-5-20251001";
|
|
||||||
claudeHaiku.url = "https://api.anthropic.com";
|
|
||||||
claudeHaiku.customEndpoint = "";
|
|
||||||
claudeHaiku.templateName = "Claude";
|
|
||||||
claudeHaiku.type = type;
|
|
||||||
claudeHaiku.isPredefined = true;
|
|
||||||
|
|
||||||
AIConfiguration codestral;
|
|
||||||
codestral.id = "preset_codestral";
|
|
||||||
codestral.name = "Codestral";
|
|
||||||
codestral.provider = "Codestral";
|
|
||||||
codestral.model = "codestral-latest";
|
|
||||||
codestral.url = "https://codestral.mistral.ai";
|
|
||||||
codestral.customEndpoint = "";
|
|
||||||
codestral.templateName = type == ConfigurationType::CodeCompletion ? "Mistral AI FIM" : "Mistral AI Chat";
|
|
||||||
codestral.type = type;
|
|
||||||
codestral.isPredefined = true;
|
|
||||||
|
|
||||||
AIConfiguration mistral;
|
|
||||||
mistral.id = "preset_mistral";
|
|
||||||
mistral.name = "Mistral";
|
|
||||||
mistral.provider = "Mistral AI";
|
|
||||||
mistral.model = type == ConfigurationType::CodeCompletion ? "codestral-latest" : "mistral-large-latest";
|
|
||||||
mistral.url = "https://api.mistral.ai";
|
|
||||||
mistral.customEndpoint = "";
|
|
||||||
mistral.templateName = type == ConfigurationType::CodeCompletion ? "Mistral AI FIM" : "Mistral AI Chat";
|
|
||||||
mistral.type = type;
|
|
||||||
mistral.isPredefined = true;
|
|
||||||
|
|
||||||
AIConfiguration geminiFlash;
|
|
||||||
geminiFlash.id = "preset_gemini_flash";
|
|
||||||
geminiFlash.name = "Gemini 2.5 Flash";
|
|
||||||
geminiFlash.provider = "Google AI";
|
|
||||||
geminiFlash.model = "gemini-2.5-flash";
|
|
||||||
geminiFlash.url = "https://generativelanguage.googleapis.com/v1beta";
|
|
||||||
geminiFlash.customEndpoint = "";
|
|
||||||
geminiFlash.templateName = "Google AI";
|
|
||||||
geminiFlash.type = type;
|
|
||||||
geminiFlash.isPredefined = true;
|
|
||||||
|
|
||||||
AIConfiguration qwenPlus;
|
|
||||||
qwenPlus.id = "preset_qwen_plus";
|
|
||||||
qwenPlus.name = "Qwen3.6 Plus";
|
|
||||||
qwenPlus.provider = "Qwen (OpenAI Response)";
|
|
||||||
qwenPlus.model = "qwen3.6-plus";
|
|
||||||
qwenPlus.url = "https://dashscope-intl.aliyuncs.com/compatible-mode/v1";
|
|
||||||
qwenPlus.customEndpoint = "";
|
|
||||||
qwenPlus.templateName = "OpenAI Responses";
|
|
||||||
qwenPlus.type = type;
|
|
||||||
qwenPlus.isPredefined = true;
|
|
||||||
|
|
||||||
AIConfiguration qwenMax;
|
|
||||||
qwenMax.id = "preset_qwen_max";
|
|
||||||
qwenMax.name = "Qwen3.7 Max";
|
|
||||||
qwenMax.provider = "Qwen (OpenAI Response)";
|
|
||||||
qwenMax.model = "qwen3.7-max";
|
|
||||||
qwenMax.url = "https://dashscope-intl.aliyuncs.com/compatible-mode/v1";
|
|
||||||
qwenMax.customEndpoint = "";
|
|
||||||
qwenMax.templateName = "OpenAI Responses";
|
|
||||||
qwenMax.type = type;
|
|
||||||
qwenMax.isPredefined = true;
|
|
||||||
|
|
||||||
AIConfiguration deepSeekFlash;
|
|
||||||
deepSeekFlash.id = "preset_deepseek_flash";
|
|
||||||
deepSeekFlash.name = "DeepSeek V4 Flash";
|
|
||||||
deepSeekFlash.provider = "DeepSeek";
|
|
||||||
deepSeekFlash.model = "deepseek-v4-flash";
|
|
||||||
deepSeekFlash.url = "https://api.deepseek.com";
|
|
||||||
deepSeekFlash.customEndpoint = "";
|
|
||||||
deepSeekFlash.templateName = "OpenAI Compatible";
|
|
||||||
deepSeekFlash.type = type;
|
|
||||||
deepSeekFlash.isPredefined = true;
|
|
||||||
|
|
||||||
AIConfiguration deepSeekPro;
|
|
||||||
deepSeekPro.id = "preset_deepseek_pro";
|
|
||||||
deepSeekPro.name = "DeepSeek V4 Pro";
|
|
||||||
deepSeekPro.provider = "DeepSeek";
|
|
||||||
deepSeekPro.model = "deepseek-v4-pro";
|
|
||||||
deepSeekPro.url = "https://api.deepseek.com";
|
|
||||||
deepSeekPro.customEndpoint = "";
|
|
||||||
deepSeekPro.templateName = "OpenAI Compatible";
|
|
||||||
deepSeekPro.type = type;
|
|
||||||
deepSeekPro.isPredefined = true;
|
|
||||||
|
|
||||||
AIConfiguration gpt;
|
|
||||||
gpt.id = "preset_gpt";
|
|
||||||
gpt.name = "gpt-5.5";
|
|
||||||
gpt.provider = "OpenAI (Responses API)";
|
|
||||||
gpt.model = "gpt-5.5";
|
|
||||||
gpt.url = "https://api.openai.com/v1";
|
|
||||||
gpt.customEndpoint = "";
|
|
||||||
gpt.templateName = "OpenAI Responses";
|
|
||||||
gpt.type = type;
|
|
||||||
gpt.isPredefined = true;
|
|
||||||
|
|
||||||
presets.append(claudeSonnet);
|
|
||||||
presets.append(claudeHaiku);
|
|
||||||
presets.append(claudeOpus);
|
|
||||||
presets.append(gpt);
|
|
||||||
presets.append(codestral);
|
|
||||||
presets.append(mistral);
|
|
||||||
presets.append(geminiFlash);
|
|
||||||
presets.append(qwenPlus);
|
|
||||||
presets.append(qwenMax);
|
|
||||||
presets.append(deepSeekFlash);
|
|
||||||
presets.append(deepSeekPro);
|
|
||||||
|
|
||||||
return presets;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString ConfigurationManager::configurationTypeToString(ConfigurationType type) const
|
|
||||||
{
|
|
||||||
switch (type) {
|
|
||||||
case ConfigurationType::CodeCompletion:
|
|
||||||
return "code_completion";
|
|
||||||
case ConfigurationType::Chat:
|
|
||||||
return "chat";
|
|
||||||
case ConfigurationType::QuickRefactor:
|
|
||||||
return "quick_refactor";
|
|
||||||
}
|
|
||||||
return "unknown";
|
|
||||||
}
|
|
||||||
|
|
||||||
QString ConfigurationManager::getConfigurationDirectory(ConfigurationType type) const
|
|
||||||
{
|
|
||||||
QString path = QString("%1/qodeassist/configurations/%2")
|
|
||||||
.arg(Core::ICore::userResourcePath().toFSPathString(),
|
|
||||||
configurationTypeToString(type));
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ConfigurationManager::ensureDirectoryExists(ConfigurationType type) const
|
|
||||||
{
|
|
||||||
QDir dir(getConfigurationDirectory(type));
|
|
||||||
if (!dir.exists()) {
|
|
||||||
return dir.mkpath(".");
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ConfigurationManager::loadConfigurations(ConfigurationType type)
|
|
||||||
{
|
|
||||||
QVector<AIConfiguration> *configs = nullptr;
|
|
||||||
switch (type) {
|
|
||||||
case ConfigurationType::CodeCompletion:
|
|
||||||
configs = &m_ccConfigurations;
|
|
||||||
break;
|
|
||||||
case ConfigurationType::Chat:
|
|
||||||
configs = &m_caConfigurations;
|
|
||||||
break;
|
|
||||||
case ConfigurationType::QuickRefactor:
|
|
||||||
configs = &m_qrConfigurations;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!configs) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
configs->clear();
|
|
||||||
|
|
||||||
QVector<AIConfiguration> predefinedConfigs = getPredefinedConfigurations(type);
|
|
||||||
configs->append(predefinedConfigs);
|
|
||||||
|
|
||||||
if (!ensureDirectoryExists(type)) {
|
|
||||||
LOG_MESSAGE("Failed to create configuration directory");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
QDir dir(getConfigurationDirectory(type));
|
|
||||||
QStringList filters;
|
|
||||||
filters << "*.json";
|
|
||||||
QFileInfoList files = dir.entryInfoList(filters, QDir::Files);
|
|
||||||
|
|
||||||
for (const QFileInfo &fileInfo : files) {
|
|
||||||
QFile file(fileInfo.absoluteFilePath());
|
|
||||||
if (!file.open(QIODevice::ReadOnly)) {
|
|
||||||
LOG_MESSAGE(QString("Failed to open configuration file: %1").arg(fileInfo.fileName()));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
QJsonDocument doc = QJsonDocument::fromJson(file.readAll());
|
|
||||||
file.close();
|
|
||||||
|
|
||||||
if (!doc.isObject()) {
|
|
||||||
LOG_MESSAGE(QString("Invalid configuration file: %1").arg(fileInfo.fileName()));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
QJsonObject obj = doc.object();
|
|
||||||
AIConfiguration config;
|
|
||||||
config.id = obj["id"].toString();
|
|
||||||
config.name = obj["name"].toString();
|
|
||||||
config.provider = migrateProviderName(obj["provider"].toString());
|
|
||||||
config.model = obj["model"].toString();
|
|
||||||
config.templateName = obj["template"].toString();
|
|
||||||
config.url = obj["url"].toString();
|
|
||||||
config.customEndpoint = obj["customEndpoint"].toString();
|
|
||||||
config.type = type;
|
|
||||||
config.formatVersion = obj.value("formatVersion").toInt(1);
|
|
||||||
|
|
||||||
config.isPredefined = false;
|
|
||||||
|
|
||||||
if (config.id.isEmpty() || config.name.isEmpty()) {
|
|
||||||
LOG_MESSAGE(QString("Invalid configuration data in file: %1").arg(fileInfo.fileName()));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
configs->append(config);
|
|
||||||
}
|
|
||||||
|
|
||||||
emit configurationsChanged(type);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ConfigurationManager::saveConfiguration(const AIConfiguration &config)
|
|
||||||
{
|
|
||||||
if (!ensureDirectoryExists(config.type)) {
|
|
||||||
LOG_MESSAGE("Failed to create configuration directory");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
QJsonObject obj;
|
|
||||||
obj["formatVersion"] = config.formatVersion;
|
|
||||||
obj["id"] = config.id;
|
|
||||||
obj["name"] = config.name;
|
|
||||||
obj["provider"] = config.provider;
|
|
||||||
obj["model"] = config.model;
|
|
||||||
obj["template"] = config.templateName;
|
|
||||||
obj["url"] = config.url;
|
|
||||||
obj["customEndpoint"] = config.customEndpoint;
|
|
||||||
|
|
||||||
QString sanitizedName = config.name;
|
|
||||||
sanitizedName.replace(" ", "_");
|
|
||||||
sanitizedName.replace(QRegularExpression("[^a-zA-Z0-9_-]"), "");
|
|
||||||
|
|
||||||
QString fileName = QString("%1/%2_%3.json")
|
|
||||||
.arg(getConfigurationDirectory(config.type), sanitizedName, config.id);
|
|
||||||
|
|
||||||
QFile file(fileName);
|
|
||||||
if (!file.open(QIODevice::WriteOnly)) {
|
|
||||||
LOG_MESSAGE(QString("Failed to create configuration file: %1").arg(fileName));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
QJsonDocument doc(obj);
|
|
||||||
file.write(doc.toJson(QJsonDocument::Indented));
|
|
||||||
file.close();
|
|
||||||
|
|
||||||
loadConfigurations(config.type);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ConfigurationManager::deleteConfiguration(const QString &id, ConfigurationType type)
|
|
||||||
{
|
|
||||||
AIConfiguration config = getConfigurationById(id, type);
|
|
||||||
if (config.isPredefined) {
|
|
||||||
LOG_MESSAGE(QString("Cannot delete predefined configuration: %1").arg(id));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
QDir dir(getConfigurationDirectory(type));
|
|
||||||
QStringList filters;
|
|
||||||
filters << QString("*_%1.json").arg(id);
|
|
||||||
QFileInfoList files = dir.entryInfoList(filters, QDir::Files);
|
|
||||||
|
|
||||||
if (files.isEmpty()) {
|
|
||||||
LOG_MESSAGE(QString("Configuration file not found for id: %1").arg(id));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const QFileInfo &fileInfo : files) {
|
|
||||||
QFile file(fileInfo.absoluteFilePath());
|
|
||||||
if (!file.remove()) {
|
|
||||||
LOG_MESSAGE(QString("Failed to delete configuration file: %1")
|
|
||||||
.arg(fileInfo.absoluteFilePath()));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
loadConfigurations(type);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
QVector<AIConfiguration> ConfigurationManager::configurations(ConfigurationType type) const
|
|
||||||
{
|
|
||||||
switch (type) {
|
|
||||||
case ConfigurationType::CodeCompletion:
|
|
||||||
return m_ccConfigurations;
|
|
||||||
case ConfigurationType::Chat:
|
|
||||||
return m_caConfigurations;
|
|
||||||
case ConfigurationType::QuickRefactor:
|
|
||||||
return m_qrConfigurations;
|
|
||||||
}
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
AIConfiguration ConfigurationManager::getConfigurationById(const QString &id,
|
|
||||||
ConfigurationType type) const
|
|
||||||
{
|
|
||||||
const QVector<AIConfiguration> &configs = configurations(type);
|
|
||||||
for (const AIConfiguration &config : configs) {
|
|
||||||
if (config.id == id) {
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return AIConfiguration();
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace QodeAssist::Settings
|
|
||||||
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
// Copyright (C) 2024-2026 Petr Mironychev
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <QObject>
|
|
||||||
#include <QString>
|
|
||||||
#include <QVector>
|
|
||||||
|
|
||||||
namespace QodeAssist::Settings {
|
|
||||||
|
|
||||||
enum class ConfigurationType { CodeCompletion, Chat, QuickRefactor };
|
|
||||||
|
|
||||||
inline constexpr int CONFIGURATION_FORMAT_VERSION = 1;
|
|
||||||
|
|
||||||
struct AIConfiguration
|
|
||||||
{
|
|
||||||
QString id;
|
|
||||||
QString name;
|
|
||||||
QString provider;
|
|
||||||
QString model;
|
|
||||||
QString templateName;
|
|
||||||
QString url;
|
|
||||||
// Empty = use the template's endpoint; non-empty = override path.
|
|
||||||
QString customEndpoint;
|
|
||||||
ConfigurationType type;
|
|
||||||
int formatVersion = CONFIGURATION_FORMAT_VERSION;
|
|
||||||
bool isPredefined = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
class ConfigurationManager : public QObject
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
|
||||||
static ConfigurationManager &instance();
|
|
||||||
|
|
||||||
bool loadConfigurations(ConfigurationType type);
|
|
||||||
bool saveConfiguration(const AIConfiguration &config);
|
|
||||||
bool deleteConfiguration(const QString &id, ConfigurationType type);
|
|
||||||
|
|
||||||
QVector<AIConfiguration> configurations(ConfigurationType type) const;
|
|
||||||
AIConfiguration getConfigurationById(const QString &id, ConfigurationType type) const;
|
|
||||||
|
|
||||||
QString getConfigurationDirectory(ConfigurationType type) const;
|
|
||||||
|
|
||||||
static QVector<AIConfiguration> getPredefinedConfigurations(ConfigurationType type);
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void configurationsChanged(ConfigurationType type);
|
|
||||||
|
|
||||||
private:
|
|
||||||
explicit ConfigurationManager(QObject *parent = nullptr);
|
|
||||||
~ConfigurationManager() override = default;
|
|
||||||
|
|
||||||
bool ensureDirectoryExists(ConfigurationType type) const;
|
|
||||||
QString configurationTypeToString(ConfigurationType type) const;
|
|
||||||
|
|
||||||
QVector<AIConfiguration> m_ccConfigurations;
|
|
||||||
QVector<AIConfiguration> m_caConfigurations;
|
|
||||||
QVector<AIConfiguration> m_qrConfigurations;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace QodeAssist::Settings
|
|
||||||
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -4,19 +4,23 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
#include <utils/aspects.h>
|
#include <utils/aspects.h>
|
||||||
#include <QPointer>
|
|
||||||
|
|
||||||
#include "ButtonAspect.hpp"
|
#include "ButtonAspect.hpp"
|
||||||
#include "ConfigurationManager.hpp"
|
|
||||||
|
|
||||||
namespace Utils {
|
namespace Core {
|
||||||
class DetailsWidget;
|
class IOptionsPage;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace QodeAssist::PluginLLMCore {
|
namespace QodeAssist {
|
||||||
class Provider;
|
class AgentFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace QodeAssist::Settings {
|
namespace QodeAssist::Settings {
|
||||||
|
|
||||||
class GeneralSettings : public Utils::AspectContainer
|
class GeneralSettings : public Utils::AspectContainer
|
||||||
@@ -33,139 +37,9 @@ public:
|
|||||||
ButtonAspect checkUpdate{this};
|
ButtonAspect checkUpdate{this};
|
||||||
ButtonAspect resetToDefaults{this};
|
ButtonAspect resetToDefaults{this};
|
||||||
|
|
||||||
// code completion setttings
|
|
||||||
Utils::SelectionAspect ccPresetConfig{this};
|
|
||||||
ButtonAspect ccConfigureApiKey{this};
|
|
||||||
|
|
||||||
Utils::StringAspect ccProvider{this};
|
|
||||||
ButtonAspect ccSelectProvider{this};
|
|
||||||
|
|
||||||
Utils::StringAspect ccModel{this};
|
|
||||||
ButtonAspect ccSelectModel{this};
|
|
||||||
|
|
||||||
Utils::StringAspect ccTemplate{this};
|
|
||||||
ButtonAspect ccSelectTemplate{this};
|
|
||||||
|
|
||||||
Utils::StringAspect ccUrl{this};
|
|
||||||
ButtonAspect ccSetUrl{this};
|
|
||||||
|
|
||||||
Utils::StringAspect ccCustomEndpoint{this};
|
|
||||||
|
|
||||||
Utils::StringAspect ccStatus{this};
|
|
||||||
ButtonAspect ccTest{this};
|
|
||||||
|
|
||||||
Utils::StringAspect ccTemplateDescription{this};
|
|
||||||
|
|
||||||
ButtonAspect ccSaveConfig{this};
|
|
||||||
ButtonAspect ccLoadConfig{this};
|
|
||||||
ButtonAspect ccOpenConfigFolder{this};
|
|
||||||
|
|
||||||
// TODO create dynamic presets system
|
|
||||||
// preset1 for code completion settings
|
|
||||||
Utils::BoolAspect specifyPreset1{this};
|
|
||||||
Utils::SelectionAspect preset1Language{this};
|
|
||||||
|
|
||||||
Utils::StringAspect ccPreset1Provider{this};
|
|
||||||
ButtonAspect ccPreset1SelectProvider{this};
|
|
||||||
|
|
||||||
Utils::StringAspect ccPreset1Url{this};
|
|
||||||
ButtonAspect ccPreset1SetUrl{this};
|
|
||||||
|
|
||||||
Utils::StringAspect ccPreset1CustomEndpoint{this};
|
|
||||||
|
|
||||||
Utils::StringAspect ccPreset1Model{this};
|
|
||||||
ButtonAspect ccPreset1SelectModel{this};
|
|
||||||
|
|
||||||
Utils::StringAspect ccPreset1Template{this};
|
|
||||||
ButtonAspect ccPreset1SelectTemplate{this};
|
|
||||||
|
|
||||||
// chat assistant settings
|
|
||||||
Utils::SelectionAspect caPresetConfig{this};
|
|
||||||
ButtonAspect caConfigureApiKey{this};
|
|
||||||
|
|
||||||
Utils::StringAspect caProvider{this};
|
|
||||||
ButtonAspect caSelectProvider{this};
|
|
||||||
|
|
||||||
Utils::StringAspect caModel{this};
|
|
||||||
ButtonAspect caSelectModel{this};
|
|
||||||
|
|
||||||
Utils::StringAspect caTemplate{this};
|
|
||||||
ButtonAspect caSelectTemplate{this};
|
|
||||||
|
|
||||||
Utils::StringAspect caUrl{this};
|
|
||||||
ButtonAspect caSetUrl{this};
|
|
||||||
|
|
||||||
Utils::StringAspect caCustomEndpoint{this};
|
|
||||||
|
|
||||||
Utils::StringAspect caStatus{this};
|
|
||||||
ButtonAspect caTest{this};
|
|
||||||
|
|
||||||
Utils::StringAspect caTemplateDescription{this};
|
|
||||||
|
|
||||||
ButtonAspect caSaveConfig{this};
|
|
||||||
ButtonAspect caLoadConfig{this};
|
|
||||||
ButtonAspect caOpenConfigFolder{this};
|
|
||||||
|
|
||||||
// quick refactor settings
|
|
||||||
Utils::SelectionAspect qrPresetConfig{this};
|
|
||||||
ButtonAspect qrConfigureApiKey{this};
|
|
||||||
|
|
||||||
Utils::StringAspect qrProvider{this};
|
|
||||||
ButtonAspect qrSelectProvider{this};
|
|
||||||
|
|
||||||
Utils::StringAspect qrModel{this};
|
|
||||||
ButtonAspect qrSelectModel{this};
|
|
||||||
|
|
||||||
Utils::StringAspect qrTemplate{this};
|
|
||||||
ButtonAspect qrSelectTemplate{this};
|
|
||||||
|
|
||||||
Utils::StringAspect qrUrl{this};
|
|
||||||
ButtonAspect qrSetUrl{this};
|
|
||||||
|
|
||||||
Utils::StringAspect qrCustomEndpoint{this};
|
|
||||||
|
|
||||||
Utils::StringAspect qrStatus{this};
|
|
||||||
ButtonAspect qrTest{this};
|
|
||||||
|
|
||||||
Utils::StringAspect qrTemplateDescription{this};
|
|
||||||
|
|
||||||
ButtonAspect qrSaveConfig{this};
|
|
||||||
ButtonAspect qrLoadConfig{this};
|
|
||||||
ButtonAspect qrOpenConfigFolder{this};
|
|
||||||
|
|
||||||
ButtonAspect ccShowTemplateInfo{this};
|
|
||||||
ButtonAspect caShowTemplateInfo{this};
|
|
||||||
ButtonAspect qrShowTemplateInfo{this};
|
|
||||||
|
|
||||||
void showSelectionDialog(
|
|
||||||
const QStringList &data,
|
|
||||||
Utils::StringAspect &aspect,
|
|
||||||
const QString &title = {},
|
|
||||||
const QString &text = {});
|
|
||||||
|
|
||||||
void showModelsNotFoundDialog(Utils::StringAspect &aspect);
|
|
||||||
|
|
||||||
void showModelsNotSupportedDialog(Utils::StringAspect &aspect);
|
|
||||||
|
|
||||||
void showUrlSelectionDialog(Utils::StringAspect &aspect, const QStringList &predefinedUrls);
|
|
||||||
|
|
||||||
void showTemplateInfoDialog(const Utils::StringAspect &descriptionAspect, const QString &templateName);
|
|
||||||
|
|
||||||
void updatePreset1Visiblity(bool state);
|
|
||||||
|
|
||||||
void onSaveConfiguration(const QString &prefix);
|
|
||||||
void onLoadConfiguration(const QString &prefix);
|
|
||||||
|
|
||||||
void loadPresetConfigurations(Utils::SelectionAspect &aspect, ConfigurationType type);
|
|
||||||
void applyPresetConfiguration(int index, ConfigurationType type);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void setupConnections();
|
void setupConnections();
|
||||||
void resetPageToDefaults();
|
void resetPageToDefaults();
|
||||||
|
|
||||||
QVector<AIConfiguration> m_ccPresets;
|
|
||||||
QVector<AIConfiguration> m_caPresets;
|
|
||||||
QVector<AIConfiguration> m_qrPresets;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
GeneralSettings &generalSettings();
|
GeneralSettings &generalSettings();
|
||||||
@@ -173,4 +47,22 @@ GeneralSettings &generalSettings();
|
|||||||
void showSettings(const Utils::Id page);
|
void showSettings(const Utils::Id page);
|
||||||
void showSettings(const Utils::Id page, Utils::Id item);
|
void showSettings(const Utils::Id page, Utils::Id item);
|
||||||
|
|
||||||
|
class AgentsPageNavigator;
|
||||||
|
|
||||||
|
class AgentPipelinesPageNavigator : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
Q_DISABLE_COPY_MOVE(AgentPipelinesPageNavigator)
|
||||||
|
public:
|
||||||
|
explicit AgentPipelinesPageNavigator(QObject *parent = nullptr);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void editAgentRequested(const QString &agentName);
|
||||||
|
};
|
||||||
|
|
||||||
|
std::unique_ptr<Core::IOptionsPage> createAgentPipelinesSettingsPage(
|
||||||
|
AgentFactory *agentFactory,
|
||||||
|
AgentPipelinesPageNavigator *navigator,
|
||||||
|
AgentsPageNavigator *agentsNavigator);
|
||||||
|
|
||||||
} // namespace QodeAssist::Settings
|
} // namespace QodeAssist::Settings
|
||||||
|
|||||||
@@ -179,31 +179,11 @@ const char CC_READ_STRINGS_BEFORE_CURSOR[] = "QodeAssist.ccReadStringsBeforeCurs
|
|||||||
const char CC_READ_STRINGS_AFTER_CURSOR[] = "QodeAssist.ccReadStringsAfterCursor";
|
const char CC_READ_STRINGS_AFTER_CURSOR[] = "QodeAssist.ccReadStringsAfterCursor";
|
||||||
const char CC_USE_SYSTEM_PROMPT[] = "QodeAssist.ccUseSystemPrompt";
|
const char CC_USE_SYSTEM_PROMPT[] = "QodeAssist.ccUseSystemPrompt";
|
||||||
const char CC_SYSTEM_PROMPT[] = "QodeAssist.ccSystemPrompt";
|
const char CC_SYSTEM_PROMPT[] = "QodeAssist.ccSystemPrompt";
|
||||||
const char CC_SYSTEM_PROMPT_FOR_NON_FIM[] = "QodeAssist.ccSystemPromptForNonFim";
|
|
||||||
const char CC_USE_USER_TEMPLATE[] = "QodeAssist.ccUseUserTemplate";
|
|
||||||
const char CC_USER_TEMPLATE[] = "QodeAssist.ccUserTemplate";
|
|
||||||
const char CC_USE_PROJECT_CHANGES_CACHE[] = "QodeAssist.ccUseProjectChangesCache";
|
const char CC_USE_PROJECT_CHANGES_CACHE[] = "QodeAssist.ccUseProjectChangesCache";
|
||||||
const char CC_MAX_CHANGES_CACHE_SIZE[] = "QodeAssist.ccMaxChangesCacheSize";
|
const char CC_MAX_CHANGES_CACHE_SIZE[] = "QodeAssist.ccMaxChangesCacheSize";
|
||||||
const char CA_USE_SYSTEM_PROMPT[] = "QodeAssist.useChatSystemPrompt";
|
const char CA_USE_SYSTEM_PROMPT[] = "QodeAssist.useChatSystemPrompt";
|
||||||
const char CA_SYSTEM_PROMPT[] = "QodeAssist.chatSystemPrompt";
|
const char CA_SYSTEM_PROMPT[] = "QodeAssist.chatSystemPrompt";
|
||||||
|
|
||||||
// preset prompt settings
|
|
||||||
const char CC_TEMPERATURE[] = "QodeAssist.ccTemperature";
|
|
||||||
const char CC_MAX_TOKENS[] = "QodeAssist.ccMaxTokens";
|
|
||||||
const char CC_USE_TOP_P[] = "QodeAssist.ccUseTopP";
|
|
||||||
const char CC_TOP_P[] = "QodeAssist.ccTopP";
|
|
||||||
const char CC_USE_TOP_K[] = "QodeAssist.ccUseTopK";
|
|
||||||
const char CC_TOP_K[] = "QodeAssist.ccTopK";
|
|
||||||
const char CC_USE_PRESENCE_PENALTY[] = "QodeAssist.ccUsePresencePenalty";
|
|
||||||
const char CC_PRESENCE_PENALTY[] = "QodeAssist.ccPresencePenalty";
|
|
||||||
const char CC_USE_FREQUENCY_PENALTY[] = "QodeAssist.fimUseFrequencyPenalty";
|
|
||||||
const char CC_FREQUENCY_PENALTY[] = "QodeAssist.fimFrequencyPenalty";
|
|
||||||
const char CC_OLLAMA_LIVETIME[] = "QodeAssist.fimOllamaLivetime";
|
|
||||||
const char CC_OLLAMA_CONTEXT_WINDOW[] = "QodeAssist.ccOllamaContextWindow";
|
|
||||||
|
|
||||||
// OpenAI Responses API Settings
|
|
||||||
const char CC_OPENAI_RESPONSES_REASONING_EFFORT[] = "QodeAssist.ccOpenAIResponsesReasoningEffort";
|
|
||||||
|
|
||||||
const char CA_TEMPERATURE[] = "QodeAssist.chatTemperature";
|
const char CA_TEMPERATURE[] = "QodeAssist.chatTemperature";
|
||||||
const char CA_MAX_TOKENS[] = "QodeAssist.chatMaxTokens";
|
const char CA_MAX_TOKENS[] = "QodeAssist.chatMaxTokens";
|
||||||
const char CA_USE_TOP_P[] = "QodeAssist.chatUseTopP";
|
const char CA_USE_TOP_P[] = "QodeAssist.chatUseTopP";
|
||||||
|
|||||||
@@ -5,5 +5,6 @@ add_subdirectory(common)
|
|||||||
add_subdirectory(providers)
|
add_subdirectory(providers)
|
||||||
add_subdirectory(templates)
|
add_subdirectory(templates)
|
||||||
add_subdirectory(agents)
|
add_subdirectory(agents)
|
||||||
|
add_subdirectory(Session)
|
||||||
add_subdirectory(providersConfig)
|
add_subdirectory(providersConfig)
|
||||||
add_subdirectory(settings)
|
add_subdirectory(settings)
|
||||||
|
|||||||
@@ -106,6 +106,19 @@ bool Session::isInFlight() const noexcept
|
|||||||
return !m_inFlight.isEmpty();
|
return !m_inFlight.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LLMQore::BaseClient *Session::client() const noexcept
|
||||||
|
{
|
||||||
|
auto *provider = m_agent ? m_agent->provider() : nullptr;
|
||||||
|
return provider ? provider->client() : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Session::supportsImages() const noexcept
|
||||||
|
{
|
||||||
|
auto *provider = m_agent ? m_agent->provider() : nullptr;
|
||||||
|
return provider
|
||||||
|
&& provider->capabilities().testFlag(Providers::ProviderCapability::Image);
|
||||||
|
}
|
||||||
|
|
||||||
void Session::setContentLoader(ContentLoader loader)
|
void Session::setContentLoader(ContentLoader loader)
|
||||||
{
|
{
|
||||||
m_contentLoader = std::move(loader);
|
m_contentLoader = std::move(loader);
|
||||||
@@ -186,6 +199,13 @@ LLMQore::RequestID Session::sendCompletion(Templates::ContextData ctx)
|
|||||||
auto *tmpl = m_agent->promptTemplate();
|
auto *tmpl = m_agent->promptTemplate();
|
||||||
const auto &cfg = m_agent->config();
|
const auto &cfg = m_agent->config();
|
||||||
|
|
||||||
|
const QString rolePrompt = m_systemPrompt ? m_systemPrompt->compose() : QString();
|
||||||
|
if (!rolePrompt.isEmpty()) {
|
||||||
|
ctx.systemPrompt = (ctx.systemPrompt && !ctx.systemPrompt->isEmpty())
|
||||||
|
? rolePrompt + QStringLiteral("\n\n") + *ctx.systemPrompt
|
||||||
|
: rolePrompt;
|
||||||
|
}
|
||||||
|
|
||||||
QJsonObject payload{{QStringLiteral("model"), cfg.model}};
|
QJsonObject payload{{QStringLiteral("model"), cfg.model}};
|
||||||
if (!provider->prepareRequest(payload, tmpl, ctx, /*tools=*/false, /*thinking=*/false))
|
if (!provider->prepareRequest(payload, tmpl, ctx, /*tools=*/false, /*thinking=*/false))
|
||||||
return {};
|
return {};
|
||||||
|
|||||||
@@ -55,6 +55,9 @@ public:
|
|||||||
ConversationHistory *history() const noexcept { return m_history; }
|
ConversationHistory *history() const noexcept { return m_history; }
|
||||||
SystemPromptBuilder *systemPrompt() const noexcept { return m_systemPrompt; }
|
SystemPromptBuilder *systemPrompt() const noexcept { return m_systemPrompt; }
|
||||||
|
|
||||||
|
LLMQore::BaseClient *client() const noexcept;
|
||||||
|
bool supportsImages() const noexcept;
|
||||||
|
|
||||||
void setContextBindings(Templates::ContextRenderer::Bindings bindings);
|
void setContextBindings(Templates::ContextRenderer::Bindings bindings);
|
||||||
|
|
||||||
QString renderAgentContext() const;
|
QString renderAgentContext() const;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ add_library(Providers STATIC
|
|||||||
ProviderID.hpp
|
ProviderID.hpp
|
||||||
Provider.hpp Provider.cpp
|
Provider.hpp Provider.cpp
|
||||||
ProviderFactory.hpp ProviderFactory.cpp
|
ProviderFactory.hpp ProviderFactory.cpp
|
||||||
|
GenericProvider.hpp GenericProvider.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(Providers
|
target_link_libraries(Providers
|
||||||
|
|||||||
113
sources/providers/GenericProvider.cpp
Normal file
113
sources/providers/GenericProvider.cpp
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
// Copyright (C) 2024-2026 Petr Mironychev
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#include "GenericProvider.hpp"
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include <LLMQore/BaseClient.hpp>
|
||||||
|
#include <LLMQore/ClaudeClient.hpp>
|
||||||
|
#include <LLMQore/GoogleAIClient.hpp>
|
||||||
|
#include <LLMQore/LlamaCppClient.hpp>
|
||||||
|
#include <LLMQore/MistralClient.hpp>
|
||||||
|
#include <LLMQore/OllamaClient.hpp>
|
||||||
|
#include <LLMQore/OpenAIClient.hpp>
|
||||||
|
#include <LLMQore/OpenAIResponsesClient.hpp>
|
||||||
|
|
||||||
|
#include "ProviderFactory.hpp"
|
||||||
|
|
||||||
|
namespace QodeAssist::Providers {
|
||||||
|
|
||||||
|
GenericProvider::GenericProvider(
|
||||||
|
QString name,
|
||||||
|
ProviderID id,
|
||||||
|
ProviderCapabilities capabilities,
|
||||||
|
const ClientFactory &clientFactory,
|
||||||
|
QObject *parent)
|
||||||
|
: Provider(parent)
|
||||||
|
, m_name(std::move(name))
|
||||||
|
, m_id(id)
|
||||||
|
, m_capabilities(capabilities)
|
||||||
|
, m_client(clientFactory(this))
|
||||||
|
{}
|
||||||
|
|
||||||
|
QString GenericProvider::name() const
|
||||||
|
{
|
||||||
|
return m_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
ProviderID GenericProvider::providerID() const
|
||||||
|
{
|
||||||
|
return m_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
ProviderCapabilities GenericProvider::capabilities() const
|
||||||
|
{
|
||||||
|
return m_capabilities;
|
||||||
|
}
|
||||||
|
|
||||||
|
::LLMQore::BaseClient *GenericProvider::client() const
|
||||||
|
{
|
||||||
|
return m_client;
|
||||||
|
}
|
||||||
|
|
||||||
|
QFuture<QList<QString>> GenericProvider::getInstalledModels(const QString &url)
|
||||||
|
{
|
||||||
|
m_client->setUrl(url);
|
||||||
|
m_client->setApiKey(apiKey());
|
||||||
|
return m_client->listModels();
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
using Cap = ProviderCapability;
|
||||||
|
|
||||||
|
template<typename ClientT>
|
||||||
|
GenericProvider::ClientFactory makeFactory()
|
||||||
|
{
|
||||||
|
return [](QObject *parent) -> ::LLMQore::BaseClient * {
|
||||||
|
return new ClientT(QString(), QString(), QString(), parent);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void registerBuiltinProviders()
|
||||||
|
{
|
||||||
|
const auto reg = [](const QString &api,
|
||||||
|
ProviderID id,
|
||||||
|
ProviderCapabilities caps,
|
||||||
|
GenericProvider::ClientFactory factory) {
|
||||||
|
ProviderFactory::registerType(api, [=](QObject *parent) -> Provider * {
|
||||||
|
return new GenericProvider(api, id, caps, factory, parent);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const ProviderCapabilities full
|
||||||
|
= Cap::Tools | Cap::Thinking | Cap::Image | Cap::ModelListing;
|
||||||
|
|
||||||
|
reg("Claude", ProviderID::Claude, full, makeFactory<::LLMQore::ClaudeClient>());
|
||||||
|
reg("Google AI", ProviderID::GoogleAI, full, makeFactory<::LLMQore::GoogleAIClient>());
|
||||||
|
reg("llama.cpp", ProviderID::LlamaCpp, full, makeFactory<::LLMQore::LlamaCppClient>());
|
||||||
|
reg("LM Studio (Chat Completions)", ProviderID::LMStudio, full,
|
||||||
|
makeFactory<::LLMQore::OpenAIClient>());
|
||||||
|
reg("LM Studio (Responses API)", ProviderID::OpenAIResponses, full,
|
||||||
|
makeFactory<::LLMQore::OpenAIResponsesClient>());
|
||||||
|
reg("Mistral AI", ProviderID::MistralAI, full, makeFactory<::LLMQore::MistralClient>());
|
||||||
|
reg("Codestral", ProviderID::MistralAI, Cap::Tools | Cap::Image,
|
||||||
|
makeFactory<::LLMQore::MistralClient>());
|
||||||
|
reg("Ollama (Native)", ProviderID::Ollama, full, makeFactory<::LLMQore::OllamaClient>());
|
||||||
|
reg("Ollama (OpenAI-compatible)", ProviderID::OpenAICompatible, full,
|
||||||
|
makeFactory<::LLMQore::OpenAIClient>());
|
||||||
|
reg("OpenAI (Chat Completions)", ProviderID::OpenAI, full,
|
||||||
|
makeFactory<::LLMQore::OpenAIClient>());
|
||||||
|
reg("OpenAI (Responses API)", ProviderID::OpenAIResponses, full,
|
||||||
|
makeFactory<::LLMQore::OpenAIResponsesClient>());
|
||||||
|
reg("OpenAI Compatible", ProviderID::OpenAICompatible,
|
||||||
|
Cap::Tools | Cap::Image | Cap::Thinking, makeFactory<::LLMQore::OpenAIClient>());
|
||||||
|
reg("OpenRouter", ProviderID::OpenRouter,
|
||||||
|
Cap::Tools | Cap::Image | Cap::Thinking | Cap::ModelListing,
|
||||||
|
makeFactory<::LLMQore::OpenAIClient>());
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace QodeAssist::Providers
|
||||||
50
sources/providers/GenericProvider.hpp
Normal file
50
sources/providers/GenericProvider.hpp
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
// Copyright (C) 2024-2026 Petr Mironychev
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
#include "Provider.hpp"
|
||||||
|
|
||||||
|
namespace LLMQore {
|
||||||
|
class BaseClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace QodeAssist::Providers {
|
||||||
|
|
||||||
|
// A configuration-driven provider: it owns an LLMQore client and exposes a
|
||||||
|
// fixed identity/capability set. Concrete behaviour (request shape) comes from
|
||||||
|
// the agent's prompt template via Provider::prepareRequest, so a single class
|
||||||
|
// covers every client_api by varying the client factory + metadata.
|
||||||
|
class GenericProvider : public Provider
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
using ClientFactory = std::function<::LLMQore::BaseClient *(QObject *)>;
|
||||||
|
|
||||||
|
GenericProvider(
|
||||||
|
QString name,
|
||||||
|
ProviderID id,
|
||||||
|
ProviderCapabilities capabilities,
|
||||||
|
const ClientFactory &clientFactory,
|
||||||
|
QObject *parent = nullptr);
|
||||||
|
|
||||||
|
QString name() const override;
|
||||||
|
QFuture<QList<QString>> getInstalledModels(const QString &url) override;
|
||||||
|
ProviderID providerID() const override;
|
||||||
|
ProviderCapabilities capabilities() const override;
|
||||||
|
::LLMQore::BaseClient *client() const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
QString m_name;
|
||||||
|
ProviderID m_id;
|
||||||
|
ProviderCapabilities m_capabilities;
|
||||||
|
::LLMQore::BaseClient *m_client;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Registers every built-in client_api into ProviderFactory. Must be called once
|
||||||
|
// at plugin startup before any agent/session is created.
|
||||||
|
void registerBuiltinProviders();
|
||||||
|
|
||||||
|
} // namespace QodeAssist::Providers
|
||||||
@@ -1,274 +0,0 @@
|
|||||||
// Copyright (C) 2024-2026 Petr Mironychev
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
|
||||||
|
|
||||||
#include "AgentPipelinesPage.hpp"
|
|
||||||
|
|
||||||
#include <coreplugin/dialogs/ioptionspage.h>
|
|
||||||
#include <coreplugin/icore.h>
|
|
||||||
|
|
||||||
#include <QColor>
|
|
||||||
#include <QFont>
|
|
||||||
#include <QFrame>
|
|
||||||
#include <QHBoxLayout>
|
|
||||||
#include <QLabel>
|
|
||||||
#include <QMessageBox>
|
|
||||||
#include <QPointer>
|
|
||||||
#include <QPushButton>
|
|
||||||
#include <QScrollArea>
|
|
||||||
#include <QTimer>
|
|
||||||
#include <QVBoxLayout>
|
|
||||||
|
|
||||||
#include "../../Version.hpp"
|
|
||||||
#include "AgentRosterWidget.hpp"
|
|
||||||
#include "AgentsSettingsPage.hpp"
|
|
||||||
#include "Logger.hpp"
|
|
||||||
#include "PipelinesConfig.hpp"
|
|
||||||
#include "SettingsConstants.hpp"
|
|
||||||
#include "SettingsTr.hpp"
|
|
||||||
|
|
||||||
#include <AgentFactory.hpp>
|
|
||||||
|
|
||||||
namespace QodeAssist::Settings {
|
|
||||||
|
|
||||||
AgentPipelinesPageNavigator::AgentPipelinesPageNavigator(QObject *parent)
|
|
||||||
: QObject(parent)
|
|
||||||
{}
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
constexpr int kSaveDebounceMs = 300;
|
|
||||||
|
|
||||||
struct SlotMeta
|
|
||||||
{
|
|
||||||
const char *title;
|
|
||||||
const char *hint;
|
|
||||||
};
|
|
||||||
|
|
||||||
const SlotMeta kSlotMeta[] = {
|
|
||||||
{TrConstants::CODE_COMPLETION, TrConstants::SLOT_HINT_CODE_COMPLETION},
|
|
||||||
{TrConstants::CHAT_ASSISTANT, TrConstants::SLOT_HINT_CHAT_ASSISTANT},
|
|
||||||
{TrConstants::CHAT_COMPRESSION, TrConstants::SLOT_HINT_CHAT_COMPRESSION},
|
|
||||||
{TrConstants::QUICK_REFACTOR, TrConstants::SLOT_HINT_QUICK_REFACTOR},
|
|
||||||
};
|
|
||||||
|
|
||||||
class AgentPipelinesPageWidget : public Core::IOptionsPageWidget
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
Q_DISABLE_COPY_MOVE(AgentPipelinesPageWidget)
|
|
||||||
public:
|
|
||||||
AgentPipelinesPageWidget(
|
|
||||||
const QPointer<AgentFactory> &agentFactory,
|
|
||||||
const QPointer<AgentPipelinesPageNavigator> &navigator,
|
|
||||||
const QPointer<AgentsPageNavigator> &agentsNavigator)
|
|
||||||
: m_agentFactory(agentFactory)
|
|
||||||
, m_navigator(navigator)
|
|
||||||
, m_agentsNavigator(agentsNavigator)
|
|
||||||
{
|
|
||||||
m_titleLabel = new QLabel(Tr::tr(TrConstants::AGENT_PIPELINES), this);
|
|
||||||
QFont tf = m_titleLabel->font();
|
|
||||||
tf.setBold(true);
|
|
||||||
tf.setPixelSize(13);
|
|
||||||
m_titleLabel->setFont(tf);
|
|
||||||
|
|
||||||
m_resetBtn = new QPushButton(Tr::tr(TrConstants::RESET_TO_DEFAULTS), this);
|
|
||||||
|
|
||||||
auto *headerRow = new QHBoxLayout;
|
|
||||||
headerRow->setContentsMargins(0, 0, 0, 0);
|
|
||||||
headerRow->setSpacing(8);
|
|
||||||
headerRow->addWidget(m_titleLabel);
|
|
||||||
headerRow->addStretch(1);
|
|
||||||
headerRow->addWidget(m_resetBtn);
|
|
||||||
|
|
||||||
auto *headerSep = new QFrame(this);
|
|
||||||
headerSep->setFrameShape(QFrame::HLine);
|
|
||||||
headerSep->setFrameShadow(QFrame::Sunken);
|
|
||||||
|
|
||||||
m_rosters[0] = new AgentRosterWidget(this);
|
|
||||||
m_rosters[1] = new AgentRosterWidget(this);
|
|
||||||
m_rosters[2] = new AgentRosterWidget(this);
|
|
||||||
m_rosters[3] = new AgentRosterWidget(this);
|
|
||||||
|
|
||||||
for (int i = 0; i < kRosterCount; ++i)
|
|
||||||
m_rosters[i]->setSlot(Tr::tr(kSlotMeta[i].title), Tr::tr(kSlotMeta[i].hint), {});
|
|
||||||
|
|
||||||
auto *content = new QWidget(this);
|
|
||||||
auto *contentLay = new QVBoxLayout(content);
|
|
||||||
contentLay->setContentsMargins(0, 0, 0, 0);
|
|
||||||
contentLay->setSpacing(12);
|
|
||||||
for (int i = 0; i < kRosterCount; ++i)
|
|
||||||
contentLay->addWidget(m_rosters[i]);
|
|
||||||
contentLay->addStretch(1);
|
|
||||||
|
|
||||||
auto *scroll = new QScrollArea(this);
|
|
||||||
scroll->setWidgetResizable(true);
|
|
||||||
scroll->setFrameShape(QFrame::NoFrame);
|
|
||||||
scroll->setWidget(content);
|
|
||||||
|
|
||||||
auto *root = new QVBoxLayout(this);
|
|
||||||
root->setContentsMargins(8, 8, 8, 8);
|
|
||||||
root->setSpacing(6);
|
|
||||||
root->addLayout(headerRow);
|
|
||||||
root->addWidget(headerSep);
|
|
||||||
root->addWidget(scroll, 1);
|
|
||||||
|
|
||||||
m_saveDebounce = new QTimer(this);
|
|
||||||
m_saveDebounce->setSingleShot(true);
|
|
||||||
m_saveDebounce->setInterval(kSaveDebounceMs);
|
|
||||||
connect(m_saveDebounce, &QTimer::timeout, this, [this]() { persistRosters(); });
|
|
||||||
|
|
||||||
loadFromSettings();
|
|
||||||
|
|
||||||
connect(m_resetBtn, &QPushButton::clicked, this, &AgentPipelinesPageWidget::onReset);
|
|
||||||
|
|
||||||
for (int i = 0; i < kRosterCount; ++i) {
|
|
||||||
connect(m_rosters[i], &AgentRosterWidget::editAgentRequested, this,
|
|
||||||
&AgentPipelinesPageWidget::onEditAgent);
|
|
||||||
connect(m_rosters[i], &AgentRosterWidget::rosterChanged, this,
|
|
||||||
[this](const QStringList &) { m_saveDebounce->start(); });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
~AgentPipelinesPageWidget() override
|
|
||||||
{
|
|
||||||
if (m_saveDebounce && m_saveDebounce->isActive()) {
|
|
||||||
m_saveDebounce->stop();
|
|
||||||
persistRosters();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void apply() final
|
|
||||||
{
|
|
||||||
if (m_saveDebounce && m_saveDebounce->isActive())
|
|
||||||
m_saveDebounce->stop();
|
|
||||||
persistRosters();
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
static constexpr int kRosterCount = 4;
|
|
||||||
|
|
||||||
void persistRosters()
|
|
||||||
{
|
|
||||||
PipelineRosters rosters;
|
|
||||||
rosters.codeCompletion = m_rosters[0]->roster();
|
|
||||||
rosters.chatAssistant = m_rosters[1]->roster();
|
|
||||||
rosters.chatCompression = m_rosters[2]->roster();
|
|
||||||
rosters.quickRefactor = m_rosters[3]->roster();
|
|
||||||
QString err;
|
|
||||||
if (!PipelinesConfig::save(rosters, &err)) {
|
|
||||||
LOG_MESSAGE(QStringLiteral("[Pipelines] save failed (%1): %2")
|
|
||||||
.arg(PipelinesConfig::filePath(), err));
|
|
||||||
if (!m_saveErrorShown) {
|
|
||||||
m_saveErrorShown = true;
|
|
||||||
QMessageBox::warning(
|
|
||||||
Core::ICore::dialogParent(),
|
|
||||||
Tr::tr(TrConstants::AGENT_PIPELINES),
|
|
||||||
tr("Failed to save pipelines.toml:\n%1\n\n"
|
|
||||||
"Further save failures will only be logged.")
|
|
||||||
.arg(err));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
m_saveErrorShown = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void onReset()
|
|
||||||
{
|
|
||||||
const auto reply = QMessageBox::question(
|
|
||||||
Core::ICore::dialogParent(),
|
|
||||||
Tr::tr(TrConstants::RESET_SETTINGS),
|
|
||||||
Tr::tr(TrConstants::CONFIRMATION),
|
|
||||||
QMessageBox::Yes | QMessageBox::No);
|
|
||||||
|
|
||||||
if (reply != QMessageBox::Yes)
|
|
||||||
return;
|
|
||||||
|
|
||||||
QString err;
|
|
||||||
if (!PipelinesConfig::save(PipelineRosters::defaults(), &err))
|
|
||||||
LOG_MESSAGE(QStringLiteral("[Pipelines] failed to reset rosters: %1").arg(err));
|
|
||||||
|
|
||||||
m_saveErrorShown = false;
|
|
||||||
loadFromSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
void onEditAgent(const QString &name)
|
|
||||||
{
|
|
||||||
if (m_agentsNavigator)
|
|
||||||
m_agentsNavigator->requestSelectAgent(name);
|
|
||||||
if (m_navigator)
|
|
||||||
emit m_navigator->editAgentRequested(name);
|
|
||||||
|
|
||||||
#if QODEASSIST_QT_CREATOR_VERSION >= QT_VERSION_CHECK(18, 0, 83)
|
|
||||||
Core::ICore::showSettings(Constants::QODE_ASSIST_AGENTS_SETTINGS_PAGE_ID);
|
|
||||||
#else
|
|
||||||
Core::ICore::showOptionsDialog(Constants::QODE_ASSIST_AGENTS_SETTINGS_PAGE_ID);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void loadFromSettings()
|
|
||||||
{
|
|
||||||
const PipelinesLoadResult lr = PipelinesConfig::load();
|
|
||||||
if (lr.status == PipelinesLoadStatus::ParseError
|
|
||||||
|| lr.status == PipelinesLoadStatus::SchemaError) {
|
|
||||||
QMessageBox::warning(
|
|
||||||
Core::ICore::dialogParent(),
|
|
||||||
Tr::tr(TrConstants::AGENT_PIPELINES),
|
|
||||||
tr("pipelines.toml has issues — using defaults for affected entries:\n%1\n\n"
|
|
||||||
"Click OK to continue. Changes you make here will overwrite the file.")
|
|
||||||
.arg(lr.message));
|
|
||||||
}
|
|
||||||
|
|
||||||
AgentFactory *factory = m_agentFactory.data();
|
|
||||||
m_rosters[0]->setRoster(lr.rosters.codeCompletion, factory);
|
|
||||||
m_rosters[1]->setRoster(lr.rosters.chatAssistant, factory);
|
|
||||||
m_rosters[2]->setRoster(lr.rosters.chatCompression, factory);
|
|
||||||
m_rosters[3]->setRoster(lr.rosters.quickRefactor, factory);
|
|
||||||
}
|
|
||||||
|
|
||||||
QPointer<AgentFactory> m_agentFactory;
|
|
||||||
QPointer<AgentPipelinesPageNavigator> m_navigator;
|
|
||||||
QPointer<AgentsPageNavigator> m_agentsNavigator;
|
|
||||||
|
|
||||||
QLabel *m_titleLabel = nullptr;
|
|
||||||
QPushButton *m_resetBtn = nullptr;
|
|
||||||
|
|
||||||
AgentRosterWidget *m_rosters[kRosterCount] = {};
|
|
||||||
|
|
||||||
QTimer *m_saveDebounce = nullptr;
|
|
||||||
bool m_saveErrorShown = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
class AgentPipelinesOptionsPage final : public Core::IOptionsPage
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
AgentPipelinesOptionsPage(
|
|
||||||
AgentFactory *agentFactory,
|
|
||||||
AgentPipelinesPageNavigator *navigator,
|
|
||||||
AgentsPageNavigator *agentsNavigator)
|
|
||||||
{
|
|
||||||
setId(Constants::QODE_ASSIST_AGENT_PIPELINES_PAGE_ID);
|
|
||||||
setDisplayName(Tr::tr(TrConstants::AGENT_PIPELINES));
|
|
||||||
setCategory(Constants::QODE_ASSIST_GENERAL_OPTIONS_CATEGORY);
|
|
||||||
const QPointer<AgentFactory> factoryPtr(agentFactory);
|
|
||||||
const QPointer<AgentPipelinesPageNavigator> navPtr(navigator);
|
|
||||||
const QPointer<AgentsPageNavigator> agentsNavPtr(agentsNavigator);
|
|
||||||
setWidgetCreator([factoryPtr, navPtr, agentsNavPtr] {
|
|
||||||
return new AgentPipelinesPageWidget(factoryPtr, navPtr, agentsNavPtr);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
std::unique_ptr<Core::IOptionsPage> createAgentPipelinesSettingsPage(
|
|
||||||
AgentFactory *agentFactory,
|
|
||||||
AgentPipelinesPageNavigator *navigator,
|
|
||||||
AgentsPageNavigator *agentsNavigator)
|
|
||||||
{
|
|
||||||
return std::make_unique<AgentPipelinesOptionsPage>(
|
|
||||||
agentFactory, navigator, agentsNavigator);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace QodeAssist::Settings
|
|
||||||
|
|
||||||
#include "AgentPipelinesPage.moc"
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
// Copyright (C) 2024-2026 Petr Mironychev
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
#include <QObject>
|
|
||||||
#include <QString>
|
|
||||||
|
|
||||||
namespace Core {
|
|
||||||
class IOptionsPage;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace QodeAssist {
|
|
||||||
class AgentFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace QodeAssist::Settings {
|
|
||||||
|
|
||||||
class AgentsPageNavigator;
|
|
||||||
|
|
||||||
class AgentPipelinesPageNavigator : public QObject
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
Q_DISABLE_COPY_MOVE(AgentPipelinesPageNavigator)
|
|
||||||
public:
|
|
||||||
explicit AgentPipelinesPageNavigator(QObject *parent = nullptr);
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void editAgentRequested(const QString &agentName);
|
|
||||||
};
|
|
||||||
|
|
||||||
std::unique_ptr<Core::IOptionsPage> createAgentPipelinesSettingsPage(
|
|
||||||
AgentFactory *agentFactory,
|
|
||||||
AgentPipelinesPageNavigator *navigator,
|
|
||||||
AgentsPageNavigator *agentsNavigator);
|
|
||||||
|
|
||||||
} // namespace QodeAssist::Settings
|
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
add_library(QodeAssistAgentPipelines OBJECT
|
add_library(QodeAssistAgentPipelines STATIC
|
||||||
AgentPipelinesPage.hpp AgentPipelinesPage.cpp
|
|
||||||
PipelinesConfig.hpp PipelinesConfig.cpp
|
PipelinesConfig.hpp PipelinesConfig.cpp
|
||||||
AgentRosterWidget.hpp AgentRosterWidget.cpp
|
AgentRosterWidget.hpp AgentRosterWidget.cpp
|
||||||
AgentSlotWidget.hpp AgentSlotWidget.cpp
|
AgentSlotWidget.hpp AgentSlotWidget.cpp
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
add_executable(QodeAssistTest
|
add_executable(QodeAssistTest
|
||||||
../CodeHandler.cpp
|
../CodeHandler.cpp
|
||||||
../LLMClientInterface.cpp
|
|
||||||
../LLMSuggestion.cpp
|
../LLMSuggestion.cpp
|
||||||
CodeHandlerTest.cpp
|
CodeHandlerTest.cpp
|
||||||
ClaudeCacheControlTest.cpp
|
ClaudeCacheControlTest.cpp
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
#include "CustomInstructionsManager.hpp"
|
#include "CustomInstructionsManager.hpp"
|
||||||
#include "QodeAssisttr.h"
|
#include "QodeAssisttr.h"
|
||||||
|
|
||||||
#include "settings/ConfigurationManager.hpp"
|
|
||||||
#include "settings/GeneralSettings.hpp"
|
#include "settings/GeneralSettings.hpp"
|
||||||
#include "settings/QuickRefactorSettings.hpp"
|
#include "settings/QuickRefactorSettings.hpp"
|
||||||
#include "settings/SettingsConstants.hpp"
|
#include "settings/SettingsConstants.hpp"
|
||||||
@@ -113,11 +112,6 @@ void QuickRefactorDialog::setupUi()
|
|||||||
actionsLayout->addWidget(m_alternativeButton);
|
actionsLayout->addWidget(m_alternativeButton);
|
||||||
actionsLayout->addStretch();
|
actionsLayout->addStretch();
|
||||||
|
|
||||||
m_configComboBox = new QComboBox(this);
|
|
||||||
m_configComboBox->setMinimumWidth(200);
|
|
||||||
m_configComboBox->setToolTip(Tr::tr("Switch AI configuration"));
|
|
||||||
actionsLayout->addWidget(m_configComboBox);
|
|
||||||
|
|
||||||
Utils::Theme *theme = Utils::creatorTheme();
|
Utils::Theme *theme = Utils::creatorTheme();
|
||||||
QColor iconColor = theme ? theme->color(Utils::Theme::TextColorNormal) : QColor(Qt::white);
|
QColor iconColor = theme ? theme->color(Utils::Theme::TextColorNormal) : QColor(Qt::white);
|
||||||
|
|
||||||
@@ -244,13 +238,6 @@ void QuickRefactorDialog::setupUi()
|
|||||||
&QuickRefactorDialog::onOpenInstructionsFolder);
|
&QuickRefactorDialog::onOpenInstructionsFolder);
|
||||||
|
|
||||||
loadCustomCommands();
|
loadCustomCommands();
|
||||||
loadAvailableConfigurations();
|
|
||||||
|
|
||||||
connect(
|
|
||||||
m_configComboBox,
|
|
||||||
QOverload<int>::of(&QComboBox::currentIndexChanged),
|
|
||||||
this,
|
|
||||||
&QuickRefactorDialog::onConfigurationChanged);
|
|
||||||
|
|
||||||
QDialogButtonBox *buttonBox
|
QDialogButtonBox *buttonBox
|
||||||
= new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
|
= new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
|
||||||
@@ -577,60 +564,6 @@ void QuickRefactorDialog::onOpenSettings()
|
|||||||
Settings::showSettings(Constants::QODE_ASSIST_QUICK_REFACTOR_SETTINGS_PAGE_ID);
|
Settings::showSettings(Constants::QODE_ASSIST_QUICK_REFACTOR_SETTINGS_PAGE_ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString QuickRefactorDialog::selectedConfiguration() const
|
|
||||||
{
|
|
||||||
return m_selectedConfiguration;
|
|
||||||
}
|
|
||||||
|
|
||||||
void QuickRefactorDialog::loadAvailableConfigurations()
|
|
||||||
{
|
|
||||||
auto &manager = Settings::ConfigurationManager::instance();
|
|
||||||
manager.loadConfigurations(Settings::ConfigurationType::QuickRefactor);
|
|
||||||
|
|
||||||
QVector<Settings::AIConfiguration> configs = manager.configurations(
|
|
||||||
Settings::ConfigurationType::QuickRefactor);
|
|
||||||
|
|
||||||
m_configComboBox->clear();
|
|
||||||
m_configComboBox->addItem(Tr::tr("Current"), QString());
|
|
||||||
|
|
||||||
for (const Settings::AIConfiguration &config : configs) {
|
|
||||||
m_configComboBox->addItem(config.name, config.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto &settings = Settings::generalSettings();
|
|
||||||
QString currentProvider = settings.qrProvider.value();
|
|
||||||
QString currentModel = settings.qrModel.value();
|
|
||||||
QString currentConfigText = QString("%1/%2").arg(currentProvider, currentModel);
|
|
||||||
m_configComboBox->setItemText(0, Tr::tr("Current (%1)").arg(currentConfigText));
|
|
||||||
}
|
|
||||||
|
|
||||||
void QuickRefactorDialog::onConfigurationChanged(int index)
|
|
||||||
{
|
|
||||||
if (index == 0) {
|
|
||||||
m_selectedConfiguration.clear();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString configId = m_configComboBox->itemData(index).toString();
|
|
||||||
m_selectedConfiguration = m_configComboBox->itemText(index);
|
|
||||||
|
|
||||||
auto &manager = Settings::ConfigurationManager::instance();
|
|
||||||
Settings::AIConfiguration config
|
|
||||||
= manager.getConfigurationById(configId, Settings::ConfigurationType::QuickRefactor);
|
|
||||||
|
|
||||||
if (!config.id.isEmpty()) {
|
|
||||||
auto &settings = Settings::generalSettings();
|
|
||||||
|
|
||||||
settings.qrProvider.setValue(config.provider);
|
|
||||||
settings.qrModel.setValue(config.model);
|
|
||||||
settings.qrTemplate.setValue(config.templateName);
|
|
||||||
settings.qrUrl.setValue(config.url);
|
|
||||||
settings.qrCustomEndpoint.setValue(config.customEndpoint);
|
|
||||||
|
|
||||||
settings.writeSettings();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void QuickRefactorDialog::validateAndAccept()
|
void QuickRefactorDialog::validateAndAccept()
|
||||||
{
|
{
|
||||||
QString instruction = m_instructionEdit->toPlainText().trimmed();
|
QString instruction = m_instructionEdit->toPlainText().trimmed();
|
||||||
|
|||||||
@@ -33,8 +33,6 @@ public:
|
|||||||
|
|
||||||
Action selectedAction() const;
|
Action selectedAction() const;
|
||||||
|
|
||||||
QString selectedConfiguration() const;
|
|
||||||
|
|
||||||
bool eventFilter(QObject *watched, QEvent *event) override;
|
bool eventFilter(QObject *watched, QEvent *event) override;
|
||||||
void keyPressEvent(QKeyEvent *event) override;
|
void keyPressEvent(QKeyEvent *event) override;
|
||||||
|
|
||||||
@@ -50,8 +48,6 @@ private slots:
|
|||||||
void onOpenInstructionsFolder();
|
void onOpenInstructionsFolder();
|
||||||
void onOpenSettings();
|
void onOpenSettings();
|
||||||
void loadCustomCommands();
|
void loadCustomCommands();
|
||||||
void loadAvailableConfigurations();
|
|
||||||
void onConfigurationChanged(int index);
|
|
||||||
void validateAndAccept();
|
void validateAndAccept();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -71,12 +67,10 @@ private:
|
|||||||
QToolButton *m_toolsButton;
|
QToolButton *m_toolsButton;
|
||||||
QToolButton *m_thinkingButton;
|
QToolButton *m_thinkingButton;
|
||||||
QComboBox *m_commandsComboBox;
|
QComboBox *m_commandsComboBox;
|
||||||
QComboBox *m_configComboBox;
|
|
||||||
|
|
||||||
Action m_selectedAction = Action::Custom;
|
Action m_selectedAction = Action::Custom;
|
||||||
QString m_lastInstructions;
|
QString m_lastInstructions;
|
||||||
QString m_selectedConfiguration;
|
|
||||||
|
|
||||||
QIcon m_toolsIconOn;
|
QIcon m_toolsIconOn;
|
||||||
QIcon m_toolsIconOff;
|
QIcon m_toolsIconOff;
|
||||||
QIcon m_thinkingIconOn;
|
QIcon m_thinkingIconOn;
|
||||||
|
|||||||
Reference in New Issue
Block a user