diff --git a/README.md b/README.md index 82698a9..0fcb04b 100644 --- a/README.md +++ b/README.md @@ -125,16 +125,30 @@ For more information, visit the [QodeAssistUpdater repository](https://github.co ## Configuration -QodeAssist supports multiple LLM providers. Choose your preferred provider and follow the configuration guide: +### Quick Setup (Recommended for Beginners) -### Supported Providers +The Quick Setup feature provides one-click configuration for popular cloud AI models. Get started in 3 easy steps: + +1. **Open QodeAssist Settings** +2. **Select a Preset** - Choose from the Quick Setup dropdown: + - **Anthropic Claude** (Sonnet 4.5, Haiku 4.5, Opus 4.5) + - **OpenAI** (gpt-5.2-codex) + - **Mistral AI** (Codestral 2501) + - **Google AI** (Gemini 2.5 Flash) +3. **Configure API Key** - Click "Configure API Key" button and enter your API key in Provider Settings + +All settings (provider, model, template, URL) are configured automatically. Just add your API key and you're ready to go! + +### Manual Provider Configuration + +For advanced users or local models, choose your preferred provider and follow the detailed configuration guide: - **[Ollama](docs/ollama-configuration.md)** - Local LLM provider - **[llama.cpp](docs/llamacpp-configuration.md)** - Local LLM server -- **[Anthropic Claude](docs/claude-configuration.md)** - Сloud provider -- **[OpenAI](docs/openai-configuration.md)** - Сloud provider (includes Responses API support) -- **[Mistral AI](docs/mistral-configuration.md)** - Сloud provider -- **[Google AI](docs/google-ai-configuration.md)** - Сloud provider +- **[Anthropic Claude](docs/claude-configuration.md)** - Cloud provider (manual setup) +- **[OpenAI](docs/openai-configuration.md)** - Cloud provider (includes Responses API support) +- **[Mistral AI](docs/mistral-configuration.md)** - Cloud provider +- **[Google AI](docs/google-ai-configuration.md)** - Cloud provider - **LM Studio** - Local LLM provider - **OpenAI-compatible** - Custom providers (OpenRouter, etc.) diff --git a/settings/ConfigurationManager.cpp b/settings/ConfigurationManager.cpp index 32fb69c..0e1a976 100644 --- a/settings/ConfigurationManager.cpp +++ b/settings/ConfigurationManager.cpp @@ -43,6 +43,93 @@ ConfigurationManager &ConfigurationManager::instance() return instance; } +QVector ConfigurationManager::getPredefinedConfigurations( + ConfigurationType type) +{ + QVector presets; + + AIConfiguration claudeOpus; + claudeOpus.id = "preset_claude_opus"; + claudeOpus.name = "Claude Opus 4.5"; + claudeOpus.provider = "Claude"; + claudeOpus.model = "claude-opus-4-5-20251101"; + claudeOpus.url = "https://api.anthropic.com"; + claudeOpus.endpointMode = "Auto"; + claudeOpus.customEndpoint = ""; + claudeOpus.templateName = "Claude"; + claudeOpus.type = type; + claudeOpus.isPredefined = true; + + AIConfiguration claudeSonnet; + claudeSonnet.id = "preset_claude_sonnet"; + claudeSonnet.name = "Claude Sonnet 4.5"; + claudeSonnet.provider = "Claude"; + claudeSonnet.model = "claude-sonnet-4-5-20250929"; + claudeSonnet.url = "https://api.anthropic.com"; + claudeSonnet.endpointMode = "Auto"; + 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.endpointMode = "Auto"; + claudeHaiku.customEndpoint = ""; + claudeHaiku.templateName = "Claude"; + claudeHaiku.type = type; + claudeHaiku.isPredefined = true; + + AIConfiguration mistralCodestral; + mistralCodestral.id = "preset_mistral_codestral"; + mistralCodestral.name = "Mistral Codestral"; + mistralCodestral.provider = "Mistral AI"; + mistralCodestral.model = "codestral-2501"; + mistralCodestral.url = "https://api.mistral.ai"; + mistralCodestral.endpointMode = "Auto"; + mistralCodestral.customEndpoint = ""; + mistralCodestral.templateName = type == ConfigurationType::CodeCompletion ? "Mistral AI FIM" : "Mistral AI Chat"; + mistralCodestral.type = type; + mistralCodestral.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.endpointMode = "Auto"; + geminiFlash.customEndpoint = ""; + geminiFlash.templateName = "Google AI"; + geminiFlash.type = type; + geminiFlash.isPredefined = true; + + AIConfiguration gpt52codex; + gpt52codex.id = "preset_gpt52codex"; + gpt52codex.name = "gpt-5.2-codex"; + gpt52codex.provider = "OpenAI Responses"; + gpt52codex.model = "gpt-5.2-codex"; + gpt52codex.url = "https://api.openai.com"; + gpt52codex.endpointMode = "Auto"; + gpt52codex.customEndpoint = ""; + gpt52codex.templateName = "OpenAI Responses"; + gpt52codex.type = type; + gpt52codex.isPredefined = true; + + presets.append(claudeSonnet); + presets.append(claudeHaiku); + presets.append(claudeOpus); + presets.append(gpt52codex); + presets.append(mistralCodestral); + presets.append(geminiFlash); + + return presets; +} + QString ConfigurationManager::configurationTypeToString(ConfigurationType type) const { switch (type) { @@ -94,6 +181,9 @@ bool ConfigurationManager::loadConfigurations(ConfigurationType type) configs->clear(); + QVector predefinedConfigs = getPredefinedConfigurations(type); + configs->append(predefinedConfigs); + if (!ensureDirectoryExists(type)) { LOG_MESSAGE("Failed to create configuration directory"); return false; @@ -131,6 +221,7 @@ bool ConfigurationManager::loadConfigurations(ConfigurationType type) 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())); @@ -185,6 +276,12 @@ bool ConfigurationManager::saveConfiguration(const AIConfiguration &config) 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); diff --git a/settings/ConfigurationManager.hpp b/settings/ConfigurationManager.hpp index 6ba0d64..8fbc163 100644 --- a/settings/ConfigurationManager.hpp +++ b/settings/ConfigurationManager.hpp @@ -41,6 +41,7 @@ struct AIConfiguration QString customEndpoint; ConfigurationType type; int formatVersion = CONFIGURATION_FORMAT_VERSION; + bool isPredefined = false; }; class ConfigurationManager : public QObject @@ -58,6 +59,8 @@ public: AIConfiguration getConfigurationById(const QString &id, ConfigurationType type) const; QString getConfigurationDirectory(ConfigurationType type) const; + + static QVector getPredefinedConfigurations(ConfigurationType type); signals: void configurationsChanged(ConfigurationType type); diff --git a/settings/GeneralSettings.cpp b/settings/GeneralSettings.cpp index 4507c2b..6ae9e56 100644 --- a/settings/GeneralSettings.cpp +++ b/settings/GeneralSettings.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -88,6 +89,27 @@ GeneralSettings::GeneralSettings() resetToDefaults.m_buttonText = TrConstants::RESET_TO_DEFAULTS; checkUpdate.m_buttonText = TrConstants::CHECK_UPDATE; + + ccPresetConfig.setDisplayStyle(Utils::SelectionAspect::DisplayStyle::ComboBox); + ccPresetConfig.setLabelText(Tr::tr("Quick Setup")); + loadPresetConfigurations(ccPresetConfig, ConfigurationType::CodeCompletion); + + ccConfigureApiKey.m_buttonText = Tr::tr("Configure API Key"); + ccConfigureApiKey.m_tooltip = Tr::tr("Open Provider Settings to configure API keys"); + + caPresetConfig.setDisplayStyle(Utils::SelectionAspect::DisplayStyle::ComboBox); + caPresetConfig.setLabelText(Tr::tr("Quick Setup")); + loadPresetConfigurations(caPresetConfig, ConfigurationType::Chat); + + caConfigureApiKey.m_buttonText = Tr::tr("Configure API Key"); + caConfigureApiKey.m_tooltip = Tr::tr("Open Provider Settings to configure API keys"); + + qrPresetConfig.setDisplayStyle(Utils::SelectionAspect::DisplayStyle::ComboBox); + qrPresetConfig.setLabelText(Tr::tr("Quick Setup")); + loadPresetConfigurations(qrPresetConfig, ConfigurationType::QuickRefactor); + + qrConfigureApiKey.m_buttonText = Tr::tr("Configure API Key"); + qrConfigureApiKey.m_tooltip = Tr::tr("Open Provider Settings to configure API keys"); initStringAspect(ccProvider, Constants::CC_PROVIDER, TrConstants::PROVIDER, "Ollama"); ccProvider.setReadOnly(true); @@ -127,6 +149,7 @@ GeneralSettings::GeneralSettings() ccSaveConfig.m_buttonText = TrConstants::SAVE_CONFIG; ccLoadConfig.m_buttonText = TrConstants::LOAD_CONFIG; + ccLoadConfig.m_tooltip = Tr::tr("Load configuration (includes predefined cloud models)"); ccOpenConfigFolder.m_buttonText = TrConstants::OPEN_CONFIG_FOLDER; ccOpenConfigFolder.m_icon = Utils::Icons::OPENFILE.icon(); ccOpenConfigFolder.m_isCompact = true; @@ -218,6 +241,7 @@ GeneralSettings::GeneralSettings() caSaveConfig.m_buttonText = TrConstants::SAVE_CONFIG; caLoadConfig.m_buttonText = TrConstants::LOAD_CONFIG; + caLoadConfig.m_tooltip = Tr::tr("Load configuration (includes predefined cloud models)"); caOpenConfigFolder.m_buttonText = TrConstants::OPEN_CONFIG_FOLDER; caOpenConfigFolder.m_icon = Utils::Icons::OPENFILE.icon(); caOpenConfigFolder.m_isCompact = true; @@ -262,6 +286,7 @@ GeneralSettings::GeneralSettings() qrSaveConfig.m_buttonText = TrConstants::SAVE_CONFIG; qrLoadConfig.m_buttonText = TrConstants::LOAD_CONFIG; + qrLoadConfig.m_tooltip = Tr::tr("Load configuration (includes predefined cloud models)"); qrOpenConfigFolder.m_buttonText = TrConstants::OPEN_CONFIG_FOLDER; qrOpenConfigFolder.m_icon = Utils::Icons::OPENFILE.icon(); qrOpenConfigFolder.m_isCompact = true; @@ -325,17 +350,24 @@ GeneralSettings::GeneralSettings() title(TrConstants::CODE_COMPLETION), Column{ Row{ccSaveConfig, ccLoadConfig, ccOpenConfigFolder, Stretch{1}}, + Row{ccPresetConfig, ccConfigureApiKey, Stretch{1}}, ccGrid, Row{specifyPreset1, preset1Language, Stretch{1}}, ccPreset1Grid}}; auto caGroup = Group{ title(TrConstants::CHAT_ASSISTANT), - Column{Row{caSaveConfig, caLoadConfig, caOpenConfigFolder, Stretch{1}}, caGrid}}; + Column{ + Row{caSaveConfig, caLoadConfig, caOpenConfigFolder, Stretch{1}}, + Row{caPresetConfig, caConfigureApiKey, Stretch{1}}, + caGrid}}; auto qrGroup = Group{ title(TrConstants::QUICK_REFACTOR), - Column{Row{qrSaveConfig, qrLoadConfig, qrOpenConfigFolder, Stretch{1}}, qrGrid}}; + Column{ + Row{qrSaveConfig, qrLoadConfig, qrOpenConfigFolder, Stretch{1}}, + Row{qrPresetConfig, qrConfigureApiKey, Stretch{1}}, + qrGrid}}; auto rootLayout = Column{ Row{enableQodeAssist, Stretch{1}, Row{checkUpdate, resetToDefaults}}, @@ -570,6 +602,33 @@ void GeneralSettings::setupConnections() connect(&checkUpdate, &ButtonAspect::clicked, this, [this]() { QodeAssist::UpdateDialog::checkForUpdatesAndShow(Core::ICore::dialogParent()); }); + + connect(&ccPresetConfig, &Utils::SelectionAspect::volatileValueChanged, this, [this]() { + applyPresetConfiguration(ccPresetConfig.volatileValue(), ConfigurationType::CodeCompletion); + ccPresetConfig.setValue(0); + }); + + connect(&ccConfigureApiKey, &ButtonAspect::clicked, this, []() { + Core::ICore::showOptionsDialog(Constants::QODE_ASSIST_PROVIDER_SETTINGS_PAGE_ID); + }); + + connect(&caPresetConfig, &Utils::SelectionAspect::volatileValueChanged, this, [this]() { + applyPresetConfiguration(caPresetConfig.volatileValue(), ConfigurationType::Chat); + caPresetConfig.setValue(0); + }); + + connect(&caConfigureApiKey, &ButtonAspect::clicked, this, []() { + Core::ICore::showOptionsDialog(Constants::QODE_ASSIST_PROVIDER_SETTINGS_PAGE_ID); + }); + + connect(&qrPresetConfig, &Utils::SelectionAspect::volatileValueChanged, this, [this]() { + applyPresetConfiguration(qrPresetConfig.volatileValue(), ConfigurationType::QuickRefactor); + qrPresetConfig.setValue(0); + }); + + connect(&qrConfigureApiKey, &ButtonAspect::clicked, this, []() { + Core::ICore::showOptionsDialog(Constants::QODE_ASSIST_PROVIDER_SETTINGS_PAGE_ID); + }); connect(&specifyPreset1, &Utils::BoolAspect::volatileValueChanged, this, [this]() { updatePreset1Visiblity(specifyPreset1.volatileValue()); @@ -776,11 +835,33 @@ void GeneralSettings::onLoadConfiguration(const QString &prefix) SettingsDialog dialog(TrConstants::LOAD_CONFIGURATION); dialog.addLabel(TrConstants::SELECT_CONFIGURATION); + + int predefinedCount = 0; + for (const AIConfiguration &config : configs) { + if (config.isPredefined) { + predefinedCount++; + } + } + + if (predefinedCount > 0) { + auto *hintLabel = dialog.addLabel( + Tr::tr("[Preset] configurations are predefined cloud models ready to use.")); + QFont hintFont = hintLabel->font(); + hintFont.setItalic(true); + hintFont.setPointSize(hintFont.pointSize() - 1); + hintLabel->setFont(hintFont); + hintLabel->setStyleSheet("color: gray;"); + } + dialog.addSpacing(); QStringList configNames; for (const AIConfiguration &config : configs) { - configNames.append(config.name); + QString displayName = config.name; + if (config.isPredefined) { + displayName = QString("[Preset] %1").arg(config.name); + } + configNames.append(displayName); } auto configList = dialog.addComboBox(configNames, QString()); @@ -790,9 +871,31 @@ void GeneralSettings::onLoadConfiguration(const QString &prefix) auto *okButton = new QPushButton(TrConstants::OK); auto *cancelButton = new QPushButton(TrConstants::CANCEL); + auto updateDeleteButtonState = [&]() { + int currentIndex = configList->currentIndex(); + if (currentIndex >= 0 && currentIndex < configs.size()) { + deleteButton->setEnabled(!configs[currentIndex].isPredefined); + } + }; + + connect(configList, + QOverload::of(&QComboBox::currentIndexChanged), + updateDeleteButtonState); + + updateDeleteButtonState(); + connect(deleteButton, &QPushButton::clicked, &dialog, [&]() { int currentIndex = configList->currentIndex(); if (currentIndex >= 0 && currentIndex < configs.size()) { + const AIConfiguration &configToDelete = configs[currentIndex]; + if (configToDelete.isPredefined) { + QMessageBox::information( + &dialog, + TrConstants::DELETE_CONFIGURATION, + Tr::tr("Predefined configurations cannot be deleted.")); + return; + } + QMessageBox::StandardButton reply = QMessageBox::question( &dialog, TrConstants::DELETE_CONFIGURATION, @@ -800,7 +903,6 @@ void GeneralSettings::onLoadConfiguration(const QString &prefix) QMessageBox::Yes | QMessageBox::No); if (reply == QMessageBox::Yes) { - const AIConfiguration &configToDelete = configs[currentIndex]; if (manager.deleteConfiguration(configToDelete.id, type)) { dialog.accept(); onLoadConfiguration(prefix); @@ -860,6 +962,73 @@ void GeneralSettings::onLoadConfiguration(const QString &prefix) dialog.exec(); } +void GeneralSettings::loadPresetConfigurations(Utils::SelectionAspect &aspect, + ConfigurationType type) +{ + QVector presets = ConfigurationManager::getPredefinedConfigurations(type); + + if (type == ConfigurationType::CodeCompletion) { + m_ccPresets = presets; + } else if (type == ConfigurationType::Chat) { + m_caPresets = presets; + } else if (type == ConfigurationType::QuickRefactor) { + m_qrPresets = presets; + } + + aspect.addOption(Tr::tr("-- Select Preset --")); + for (const AIConfiguration &config : presets) { + aspect.addOption(config.name); + } + aspect.setDefaultValue(0); +} + +void GeneralSettings::applyPresetConfiguration(int index, ConfigurationType type) +{ + if (index <= 0) { + return; + } + + QVector *presets = nullptr; + if (type == ConfigurationType::CodeCompletion) { + presets = &m_ccPresets; + } else if (type == ConfigurationType::Chat) { + presets = &m_caPresets; + } else if (type == ConfigurationType::QuickRefactor) { + presets = &m_qrPresets; + } + + if (!presets || index - 1 >= presets->size()) { + return; + } + + const AIConfiguration &config = presets->at(index - 1); + + if (type == ConfigurationType::CodeCompletion) { + ccProvider.setValue(config.provider); + ccModel.setValue(config.model); + ccTemplate.setValue(config.templateName); + ccUrl.setValue(config.url); + ccEndpointMode.setValue(ccEndpointMode.indexForDisplay(config.endpointMode)); + ccCustomEndpoint.setValue(config.customEndpoint); + } else if (type == ConfigurationType::Chat) { + caProvider.setValue(config.provider); + caModel.setValue(config.model); + caTemplate.setValue(config.templateName); + caUrl.setValue(config.url); + caEndpointMode.setValue(caEndpointMode.indexForDisplay(config.endpointMode)); + caCustomEndpoint.setValue(config.customEndpoint); + } else if (type == ConfigurationType::QuickRefactor) { + qrProvider.setValue(config.provider); + qrModel.setValue(config.model); + qrTemplate.setValue(config.templateName); + qrUrl.setValue(config.url); + qrEndpointMode.setValue(qrEndpointMode.indexForDisplay(config.endpointMode)); + qrCustomEndpoint.setValue(config.customEndpoint); + } + + writeSettings(); +} + class GeneralSettingsPage : public Core::IOptionsPage { public: diff --git a/settings/GeneralSettings.hpp b/settings/GeneralSettings.hpp index 07601ec..16bff35 100644 --- a/settings/GeneralSettings.hpp +++ b/settings/GeneralSettings.hpp @@ -23,6 +23,7 @@ #include #include "ButtonAspect.hpp" +#include "ConfigurationManager.hpp" namespace Utils { class DetailsWidget; @@ -46,6 +47,9 @@ public: ButtonAspect resetToDefaults{this}; // code completion setttings + Utils::SelectionAspect ccPresetConfig{this}; + ButtonAspect ccConfigureApiKey{this}; + Utils::StringAspect ccProvider{this}; ButtonAspect ccSelectProvider{this}; @@ -91,6 +95,9 @@ public: ButtonAspect ccPreset1SelectTemplate{this}; // chat assistant settings + Utils::SelectionAspect caPresetConfig{this}; + ButtonAspect caConfigureApiKey{this}; + Utils::StringAspect caProvider{this}; ButtonAspect caSelectProvider{this}; @@ -116,6 +123,9 @@ public: ButtonAspect caOpenConfigFolder{this}; // quick refactor settings + Utils::SelectionAspect qrPresetConfig{this}; + ButtonAspect qrConfigureApiKey{this}; + Utils::StringAspect qrProvider{this}; ButtonAspect qrSelectProvider{this}; @@ -162,10 +172,17 @@ public: 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 m_ccPresets; + QVector m_caPresets; + QVector m_qrPresets; }; GeneralSettings &generalSettings();