refactor: Move to agent-session architecture

This commit is contained in:
Petr Mironychev
2026-06-01 11:47:52 +02:00
parent 02c11ee5a0
commit 6220308a93
66 changed files with 1764 additions and 4039 deletions

View File

@@ -1,6 +1,5 @@
add_library(QodeAssistSettings STATIC
GeneralSettings.hpp GeneralSettings.cpp
ConfigurationManager.hpp ConfigurationManager.cpp
SettingsUtils.hpp
SettingsConstants.hpp
ButtonAspect.hpp
@@ -47,5 +46,9 @@ target_link_libraries(QodeAssistSettings
ProvidersConfig
Agents
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})

View File

@@ -165,54 +165,6 @@ CodeCompletionSettings::CodeCompletionSettings()
"for triggering completions. This helps trigger completions based on actual code "
"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
readFullFile.setSettingsKey(Constants::CC_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 "
"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.setLabelText(
Tr::tr("Additional Programming Languages for handling: Example: rust,//,rust rs,rs"));
@@ -311,39 +226,6 @@ CodeCompletionSettings::CodeCompletionSettings()
maxChangesCacheSize.setRange(2, 1000);
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");
readSettings();
@@ -357,23 +239,6 @@ CodeCompletionSettings::CodeCompletionSettings()
setLayouter([this]() {
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{};
contextGrid.addRow({Row{readFullFile}});
contextGrid.addRow({Row{readFileParts, readStringsBeforeCursor, readStringsAfterCursor}});
@@ -382,14 +247,7 @@ CodeCompletionSettings::CodeCompletionSettings()
Row{contextGrid, Stretch{1}},
Row{useSystemPrompt, Stretch{1}},
Group{title(Tr::tr("Prompts for FIM models")), Column{systemPrompt}},
Group{
title(Tr::tr("Prompts for Non FIM models")),
Column{
Row{useUserMessageTemplateForCC, Stretch{1}},
systemPromptForNonFimModels,
userMessageTemplateForCC,
customLanguages,
}},
customLanguages,
Row{useProjectChangesCache, maxChangesCacheSize, Stretch{1}}};
auto generalSettings = Column{
@@ -418,19 +276,7 @@ CodeCompletionSettings::CodeCompletionSettings()
Space{8},
Group{title(Tr::tr("Automatic Trigger Mode")), autoTriggerSettings}}},
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},
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}};
});
}
@@ -470,16 +316,6 @@ void CodeCompletionSettings::resetSettingsToDefaults()
if (reply == QMessageBox::Yes) {
resetAspect(autoCompletion);
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(readFileParts);
resetAspect(readStringsBeforeCursor);
@@ -488,12 +324,6 @@ void CodeCompletionSettings::resetSettingsToDefaults()
resetAspect(systemPrompt);
resetAspect(useProjectChangesCache);
resetAspect(maxChangesCacheSize);
resetAspect(ollamaLivetime);
resetAspect(contextWindow);
resetAspect(openAIResponsesReasoningEffort);
resetAspect(useUserMessageTemplateForCC);
resetAspect(userMessageTemplateForCC);
resetAspect(systemPromptForNonFimModels);
resetAspect(customLanguages);
resetAspect(showProgressWidget);
resetAspect(useOpenFilesContext);
@@ -527,14 +357,6 @@ void CodeCompletionSettings::migrateCompletionMode()
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
{
public:

View File

@@ -41,23 +41,6 @@ public:
Utils::BoolAspect abortAssistOnRequest{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
Utils::BoolAspect readFullFile{this};
Utils::BoolAspect readFileParts{this};
@@ -65,21 +48,9 @@ public:
Utils::IntegerAspect readStringsAfterCursor{this};
Utils::BoolAspect useSystemPrompt{this};
Utils::StringAspect systemPrompt{this};
Utils::BoolAspect useUserMessageTemplateForCC{this};
Utils::StringAspect systemPromptForNonFimModels{this};
Utils::StringAspect userMessageTemplateForCC{this};
Utils::BoolAspect useProjectChangesCache{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:
void setupConnections();
void resetSettingsToDefaults();

View File

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

View File

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

View File

@@ -4,19 +4,23 @@
#pragma once
#include <memory>
#include <QObject>
#include <QString>
#include <utils/aspects.h>
#include <QPointer>
#include "ButtonAspect.hpp"
#include "ConfigurationManager.hpp"
namespace Utils {
class DetailsWidget;
namespace Core {
class IOptionsPage;
}
namespace QodeAssist::PluginLLMCore {
class Provider;
namespace QodeAssist {
class AgentFactory;
}
namespace QodeAssist::Settings {
class GeneralSettings : public Utils::AspectContainer
@@ -33,139 +37,9 @@ public:
ButtonAspect checkUpdate{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:
void setupConnections();
void resetPageToDefaults();
QVector<AIConfiguration> m_ccPresets;
QVector<AIConfiguration> m_caPresets;
QVector<AIConfiguration> m_qrPresets;
};
GeneralSettings &generalSettings();
@@ -173,4 +47,22 @@ GeneralSettings &generalSettings();
void showSettings(const Utils::Id page);
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

View File

@@ -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_USE_SYSTEM_PROMPT[] = "QodeAssist.ccUseSystemPrompt";
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_MAX_CHANGES_CACHE_SIZE[] = "QodeAssist.ccMaxChangesCacheSize";
const char CA_USE_SYSTEM_PROMPT[] = "QodeAssist.useChatSystemPrompt";
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_MAX_TOKENS[] = "QodeAssist.chatMaxTokens";
const char CA_USE_TOP_P[] = "QodeAssist.chatUseTopP";