Compare commits

..

10 Commits

Author SHA1 Message Date
Petr Mironychev
dcf5796ad7 refactor: Move Qwen provider to separate classes 2026-05-29 12:49:32 +02:00
Petr Mironychev
033c0e8652 feat: Add DeepSeek provider 2026-05-29 12:41:18 +02:00
Petr Mironychev
ea67ba0e2a feat: Add Qwen provider 2026-05-29 11:33:06 +02:00
Petr Mironychev
0cf915c4a5 feat: Update dialog with update 2026-05-29 10:35:52 +02:00
Petr Mironychev
99caa853d5 chore: Update plugin to 0.9.18 version 2026-05-29 09:45:34 +02:00
Petr Mironychev
278624d412 fix: Prevent toolbar button clicks being eaten by focus guard 2026-05-29 09:44:44 +02:00
Petr Mironychev
f8adf4d264 fix: Clean request id by clear() 2026-05-28 17:58:33 +02:00
Petr Mironychev
bfcd8dc1fb fix: Prevent crash on cancelling quick refactor via progress widget 2026-05-28 16:00:18 +02:00
Petr Mironychev
33321b2499 Update README with extension registry example
Added an example of the extension registry with an image.
2026-05-28 14:40:28 +02:00
Petr Mironychev
362533a5c0 doc: Add installation from registry 2026-05-28 14:38:28 +02:00
27 changed files with 728 additions and 43 deletions

View File

@@ -116,6 +116,9 @@ add_qtc_plugin(QodeAssist
providers/LlamaCppProvider.hpp providers/LlamaCppProvider.cpp
providers/CodestralProvider.hpp providers/CodestralProvider.cpp
providers/OpenAIResponsesProvider.hpp providers/OpenAIResponsesProvider.cpp
providers/QwenProvider.hpp providers/QwenProvider.cpp
providers/QwenResponsesProvider.hpp providers/QwenResponsesProvider.cpp
providers/DeepSeekProvider.hpp providers/DeepSeekProvider.cpp
QodeAssist.qrc
LSPCompletion.hpp
LLMSuggestion.hpp LLMSuggestion.cpp

View File

@@ -1,7 +1,7 @@
{
"Id" : "qodeassist",
"Name" : "QodeAssist",
"Version" : "0.9.17",
"Version" : "0.9.18",
"CompatVersion" : "${IDE_VERSION}",
"Vendor" : "Petr Mironychev",
"VendorId" : "petrmironychev",

View File

@@ -385,25 +385,25 @@ void QuickRefactorHandler::handleLLMResponse(
void QuickRefactorHandler::cancelRequest()
{
if (m_isRefactoringInProgress) {
auto id = m_lastRequestId;
if (!m_isRefactoringInProgress)
return;
for (auto it = m_activeRequests.begin(); it != m_activeRequests.end(); ++it) {
if (it.key() == id) {
const RequestContext &ctx = it.value();
ctx.provider->cancelRequest(id);
m_activeRequests.erase(it);
break;
}
}
const auto id = m_lastRequestId;
m_isRefactoringInProgress = false;
m_lastRequestId.clear();
m_isRefactoringInProgress = false;
RefactorResult result;
result.success = false;
result.errorMessage = "Refactoring request was cancelled";
emit refactoringCompleted(result);
auto it = m_activeRequests.find(id);
if (it != m_activeRequests.end()) {
auto provider = it.value().provider;
m_activeRequests.erase(it);
if (provider)
provider->cancelRequest(id);
}
RefactorResult result;
result.success = false;
result.errorMessage = "Refactoring request was cancelled";
emit refactoringCompleted(result);
}
void QuickRefactorHandler::handleFullResponse(const QString &requestId, const QString &fullText)

View File

@@ -6,7 +6,7 @@
[![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)
[![Discord](https://dcbadge.limes.pink/api/server/BGMkUsXUgf?style=flat)](https://discord.gg/BGMkUsXUgf)
![qodeassist-icon](https://github.com/user-attachments/assets/dc336712-83cb-440d-8761-8d0a31de898d) **QodeAssist** brings a full AI coding workflow to Qt Creator for C++ and QML — smart code completion, multi-panel chat, inline quick refactoring, and project-aware tool calling. It works with local runtimes (Ollama, llama.cpp, LM Studio) and cloud providers (Claude, OpenAI, Google AI, Mistral), can run as an **MCP server** so other clients reuse its project context, and can also act as an **MCP client** to consume tools from external MCP servers (authenticated MCP servers are not supported yet).
![qodeassist-icon](https://github.com/user-attachments/assets/dc336712-83cb-440d-8761-8d0a31de898d) **QodeAssist** brings a full AI coding workflow to Qt Creator for C++ and QML — smart code completion, multi-panel chat, inline quick refactoring, and project-aware tool calling. It works with local runtimes (Ollama, llama.cpp, LM Studio) and cloud providers (Claude, OpenAI, Google AI, Mistral, Qwen, DeepSeek), can run as an **MCP server** so other clients reuse its project context, and can also act as an **MCP client** to consume tools from external MCP servers (authenticated MCP servers are not supported yet).
⚠️ **Important Notice About Paid Providers**
> When using paid providers like Claude, OpenRouter or OpenAI-compatible services:
@@ -39,7 +39,8 @@ QodeAssist enhances Qt Creator with AI-powered coding assistance:
- **MCP Server** — expose QodeAssist's project-aware tools to external MCP clients (Claude Code, VS Code, Claude Desktop via bridge)
- **MCP Client Hub** — connect QodeAssist to external MCP servers and use their tools in Chat and Quick Refactor (authenticated MCP servers are not supported yet)
- **File Context** — attach, link, or auto-sync open editor files for richer prompts
- **Many Providers** — Ollama, llama.cpp, LM Studio (Chat + Responses), Claude, OpenAI (Chat + Responses), Google AI, Mistral, Codestral, OpenRouter, any OpenAI-compatible endpoint
- **Many Providers** — Ollama, llama.cpp, LM Studio (Chat + Responses), Claude, OpenAI (Chat + Responses), Google AI, Mistral, Codestral, OpenRouter, Qwen (OpenAI + Responses), DeepSeek, any OpenAI-compatible endpoint
- **Reasoning / Thinking** — streamed chain-of-thought is shown for reasoning models across Claude, Google, OpenAI Responses, and any OpenAI-compatible endpoint that returns `reasoning_content` (DeepSeek, Qwen QwQ/Qwen3-Thinking, LM Studio, OpenRouter, …)
- **Customizable** — per-project rules (`.qodeassist/rules/`), agent roles, reusable refactor templates, full prompt-template control
**Join our [Discord Community](https://discord.gg/BGMkUsXUgf)** to get support and connect with other users!
@@ -91,7 +92,27 @@ QodeAssist enhances Qt Creator with AI-powered coding assistance:
## Install plugin to QtCreator
### Method 1: Using QodeAssistUpdater (Beta)
### Method 1: Using the Extension Registry (Recommended)
You can install and update QodeAssist directly from within Qt Creator by adding the QodeAssist registry as an external extension repository.
1. Open the Extensions page (`Qt Creator → Extensions`) and switch to the **Browser** tab
2. Enable **Use External Repository**
3. Next to **Repository URLs**, click **Add** and paste the registry archive URL matching your Qt Creator version:
- **Latest (QtC 19)**: `https://github.com/Palm1r/extension-registry/archive/refs/heads/qodeassist.tar.gz`
- **QtC 19**: `https://github.com/Palm1r/extension-registry/archive/refs/heads/qodeassist-qtc19.tar.gz`
- **QtC 18**: `https://github.com/Palm1r/extension-registry/archive/refs/heads/qodeassist-qtc18.tar.gz`
<details>
<summary>Example of extension registry: (click to expand)</summary>
<img width="600" alt="RegistryExample" src="https://github.com/user-attachments/assets/8ab8cf10-72e7-4961-8c5a-21d530378a05">
</details>
4. Click **Apply** — QodeAssist will appear in the extensions list, where you can **Install** it
5. Updates can be installed from the same screen when a new version is published
> **Note:** This is an external repository not maintained by The Qt Company. By adding it you accept responsibility for managing the associated risks, as stated in the Extensions page.
### Method 2: Using QodeAssistUpdater (Beta)
QodeAssistUpdater is a command-line utility that automates plugin installation and updates with automatic Qt Creator version detection and checksum verification.
@@ -119,7 +140,7 @@ Download pre-built binary from [QodeAssistUpdater releases](https://github.com/P
For more information, visit the [QodeAssistUpdater repository](https://github.com/Palm1r/QodeAssistUpdater).
### Method 2: Manual Installation
### Method 3: Manual Installation
1. Install Latest Qt Creator
2. Download the QodeAssist plugin for your Qt Creator
@@ -149,6 +170,8 @@ The Quick Setup feature provides one-click configuration for popular cloud AI mo
- **OpenAI** (gpt-5.2-codex)
- **Mistral AI** (Codestral 2501)
- **Google AI** (Gemini 2.5 Flash)
- **Qwen** (Qwen3.6 Plus, Qwen3.7 Max)
- **DeepSeek** (DeepSeek V4 Flash, DeepSeek V4 Pro)
3. **Configure API Key** - Click "Configure API Key" button and enter your API key in Provider Settings
All settings (provider, model, template, URL) are configured automatically. Just add your API key and you're ready to go!
@@ -169,6 +192,8 @@ For advanced users or local models, choose your preferred provider and follow th
- **[OpenAI](docs/openai-configuration.md)** — Chat Completions and Responses API
- **[Mistral AI](docs/mistral-configuration.md)** / **Codestral**
- **[Google AI](docs/google-ai-configuration.md)** — Gemini
- **Qwen (Alibaba)** — DashScope OpenAI-compatible Chat and Responses endpoints
- **DeepSeek** — `deepseek-chat` and `deepseek-reasoner` (reasoning shown as thinking)
- **OpenAI-compatible** — OpenRouter and any custom endpoint
### Recommended Models for Best Experience
@@ -233,7 +258,7 @@ Configure in: `Tools → Options → QodeAssist → Code Completion → General
- **[Chat Summarization](docs/chat-summarization.md)** - Compress long conversations into AI-generated summaries
- **[File Context](docs/file-context.md)** - Attach or link files for better context
- Automatic syncing with open editor files (optional)
- Extended thinking mode (Claude, other providers in plan) - Enable deeper reasoning for complex tasks
- Extended thinking / reasoning mode - shows streamed chain-of-thought for reasoning models (Claude, Google, OpenAI Responses, and OpenAI-compatible endpoints returning `reasoning_content` such as DeepSeek, Qwen, LM Studio, OpenRouter)
### Quick Refactoring
- Inline code refactoring directly in the editor with AI assistance

View File

@@ -7,6 +7,7 @@ import QtQuick.Controls.Basic
Button {
id: control
focusPolicy: Qt.NoFocus
padding: 4
icon.width: 16

View File

@@ -14,6 +14,8 @@ enum class ProviderID {
MistralAI,
OpenRouter,
GoogleAI,
LlamaCpp
LlamaCpp,
Qwen,
DeepSeek
};
}

View File

@@ -0,0 +1,110 @@
// Copyright (C) 2024-2026 Petr Mironychev
// SPDX-License-Identifier: GPL-3.0-or-later
#include "DeepSeekProvider.hpp"
#include <LLMQore/ToolsManager.hpp>
#include "logger/Logger.hpp"
#include "settings/ChatAssistantSettings.hpp"
#include "settings/CodeCompletionSettings.hpp"
#include "settings/GeneralSettings.hpp"
#include "settings/ProviderSettings.hpp"
#include "settings/QuickRefactorSettings.hpp"
#include "tools/ToolsRegistration.hpp"
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
namespace QodeAssist::Providers {
DeepSeekProvider::DeepSeekProvider(QObject *parent)
: PluginLLMCore::Provider(parent)
, m_client(new ::LLMQore::OpenAIClient(QString(), QString(), QString(), this))
{
Tools::registerQodeAssistTools(m_client->tools());
}
QString DeepSeekProvider::name() const
{
return "DeepSeek";
}
QString DeepSeekProvider::apiKey() const
{
return Settings::providerSettings().deepSeekApiKey();
}
QString DeepSeekProvider::url() const
{
return "https://api.deepseek.com";
}
QFuture<QList<QString>> DeepSeekProvider::getInstalledModels(const QString &url)
{
m_client->setUrl(url);
m_client->setApiKey(apiKey());
return m_client->listModels();
}
PluginLLMCore::ProviderID DeepSeekProvider::providerID() const
{
return PluginLLMCore::ProviderID::DeepSeek;
}
PluginLLMCore::ProviderCapabilities DeepSeekProvider::capabilities() const
{
return PluginLLMCore::ProviderCapability::Tools
| PluginLLMCore::ProviderCapability::Thinking
| PluginLLMCore::ProviderCapability::ModelListing;
}
void DeepSeekProvider::prepareRequest(
QJsonObject &request,
PluginLLMCore::PromptTemplate *prompt,
PluginLLMCore::ContextData context,
PluginLLMCore::RequestType type,
bool isToolsEnabled,
bool isThinkingEnabled)
{
if (!prompt->isSupportProvider(providerID())) {
LOG_MESSAGE(QString("Template %1 doesn't support %2 provider").arg(name(), prompt->name()));
}
prompt->prepareRequest(request, context);
auto applyModelParams = [&request](const auto &settings) {
request["max_tokens"] = settings.maxTokens();
request["temperature"] = settings.temperature();
if (settings.useTopP())
request["top_p"] = settings.topP();
if (settings.useFrequencyPenalty())
request["frequency_penalty"] = settings.frequencyPenalty();
if (settings.usePresencePenalty())
request["presence_penalty"] = settings.presencePenalty();
};
if (type == PluginLLMCore::RequestType::CodeCompletion) {
applyModelParams(Settings::codeCompletionSettings());
} else if (type == PluginLLMCore::RequestType::QuickRefactoring) {
applyModelParams(Settings::quickRefactorSettings());
} else {
applyModelParams(Settings::chatAssistantSettings());
}
if (isToolsEnabled) {
auto toolsDefinitions = m_client->tools()->getToolsDefinitions();
if (!toolsDefinitions.isEmpty()) {
request["tools"] = toolsDefinitions;
LOG_MESSAGE(QString("Added %1 tools to DeepSeek request").arg(toolsDefinitions.size()));
}
}
}
::LLMQore::BaseClient *DeepSeekProvider::client() const
{
return m_client;
}
} // namespace QodeAssist::Providers

View File

@@ -0,0 +1,37 @@
// Copyright (C) 2024-2026 Petr Mironychev
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <LLMQore/OpenAIClient.hpp>
#include <pluginllmcore/Provider.hpp>
namespace QodeAssist::Providers {
class DeepSeekProvider : public PluginLLMCore::Provider
{
Q_OBJECT
public:
explicit DeepSeekProvider(QObject *parent = nullptr);
QString name() const override;
QString url() const override;
void prepareRequest(
QJsonObject &request,
PluginLLMCore::PromptTemplate *prompt,
PluginLLMCore::ContextData context,
PluginLLMCore::RequestType type,
bool isToolsEnabled,
bool isThinkingEnabled) override;
QFuture<QList<QString>> getInstalledModels(const QString &url) override;
PluginLLMCore::ProviderID providerID() const override;
PluginLLMCore::ProviderCapabilities capabilities() const override;
::LLMQore::BaseClient *client() const override;
QString apiKey() const override;
private:
::LLMQore::OpenAIClient *m_client;
};
} // namespace QodeAssist::Providers

View File

@@ -57,7 +57,8 @@ PluginLLMCore::ProviderID LMStudioProvider::providerID() const
PluginLLMCore::ProviderCapabilities LMStudioProvider::capabilities() const
{
return PluginLLMCore::ProviderCapability::Tools | PluginLLMCore::ProviderCapability::Image
| PluginLLMCore::ProviderCapability::ModelListing;
| PluginLLMCore::ProviderCapability::ModelListing
| PluginLLMCore::ProviderCapability::Thinking;
}
void LMStudioProvider::prepareRequest(

View File

@@ -55,7 +55,8 @@ PluginLLMCore::ProviderID MistralAIProvider::providerID() const
PluginLLMCore::ProviderCapabilities MistralAIProvider::capabilities() const
{
return PluginLLMCore::ProviderCapability::Tools | PluginLLMCore::ProviderCapability::Image
| PluginLLMCore::ProviderCapability::ModelListing;
| PluginLLMCore::ProviderCapability::ModelListing
| PluginLLMCore::ProviderCapability::Thinking;
}
void MistralAIProvider::prepareRequest(

View File

@@ -113,7 +113,8 @@ PluginLLMCore::ProviderID OllamaCompatProvider::providerID() const
PluginLLMCore::ProviderCapabilities OllamaCompatProvider::capabilities() const
{
return PluginLLMCore::ProviderCapability::Tools | PluginLLMCore::ProviderCapability::Image
| PluginLLMCore::ProviderCapability::ModelListing;
| PluginLLMCore::ProviderCapability::ModelListing
| PluginLLMCore::ProviderCapability::Thinking;
}
::LLMQore::BaseClient *OllamaCompatProvider::client() const

View File

@@ -98,7 +98,8 @@ PluginLLMCore::ProviderID OpenAICompatProvider::providerID() const
PluginLLMCore::ProviderCapabilities OpenAICompatProvider::capabilities() const
{
return PluginLLMCore::ProviderCapability::Tools | PluginLLMCore::ProviderCapability::Image;
return PluginLLMCore::ProviderCapability::Tools | PluginLLMCore::ProviderCapability::Image
| PluginLLMCore::ProviderCapability::Thinking;
}
::LLMQore::BaseClient *OpenAICompatProvider::client() const

View File

@@ -129,7 +129,8 @@ PluginLLMCore::ProviderID OpenAIProvider::providerID() const
PluginLLMCore::ProviderCapabilities OpenAIProvider::capabilities() const
{
return PluginLLMCore::ProviderCapability::Tools | PluginLLMCore::ProviderCapability::Image
| PluginLLMCore::ProviderCapability::ModelListing;
| PluginLLMCore::ProviderCapability::ModelListing
| PluginLLMCore::ProviderCapability::Thinking;
}
::LLMQore::BaseClient *OpenAIProvider::client() const

View File

@@ -5,6 +5,7 @@
#include "pluginllmcore/ProvidersManager.hpp"
#include "providers/ClaudeProvider.hpp"
#include "providers/DeepSeekProvider.hpp"
#include "providers/CodestralProvider.hpp"
#include "providers/GoogleAIProvider.hpp"
#include "providers/LMStudioProvider.hpp"
@@ -17,6 +18,8 @@
#include "providers/OpenAIProvider.hpp"
#include "providers/OpenAIResponsesProvider.hpp"
#include "providers/OpenRouterAIProvider.hpp"
#include "providers/QwenProvider.hpp"
#include "providers/QwenResponsesProvider.hpp"
namespace QodeAssist::Providers {
@@ -36,6 +39,9 @@ inline void registerProviders()
providerManager.registerProvider<GoogleAIProvider>();
providerManager.registerProvider<LlamaCppProvider>();
providerManager.registerProvider<CodestralProvider>();
providerManager.registerProvider<QwenProvider>();
providerManager.registerProvider<QwenResponsesProvider>();
providerManager.registerProvider<DeepSeekProvider>();
}
} // namespace QodeAssist::Providers

112
providers/QwenProvider.cpp Normal file
View File

@@ -0,0 +1,112 @@
// Copyright (C) 2024-2026 Petr Mironychev
// SPDX-License-Identifier: GPL-3.0-or-later
#include "QwenProvider.hpp"
#include <LLMQore/ToolsManager.hpp>
#include "logger/Logger.hpp"
#include "settings/ChatAssistantSettings.hpp"
#include "settings/CodeCompletionSettings.hpp"
#include "settings/GeneralSettings.hpp"
#include "settings/ProviderSettings.hpp"
#include "settings/QuickRefactorSettings.hpp"
#include "tools/ToolsRegistration.hpp"
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
namespace QodeAssist::Providers {
QwenProvider::QwenProvider(QObject *parent)
: PluginLLMCore::Provider(parent)
, m_client(new ::LLMQore::OpenAIClient(QString(), QString(), QString(), this))
{
Tools::registerQodeAssistTools(m_client->tools());
}
QString QwenProvider::name() const
{
return "Qwen (OpenAI)";
}
QString QwenProvider::apiKey() const
{
return Settings::providerSettings().qwenApiKey();
}
QString QwenProvider::url() const
{
return "https://dashscope-intl.aliyuncs.com/compatible-mode/v1";
}
QFuture<QList<QString>> QwenProvider::getInstalledModels(const QString &url)
{
m_client->setUrl(url);
m_client->setApiKey(apiKey());
return m_client->listModels();
}
PluginLLMCore::ProviderID QwenProvider::providerID() const
{
return PluginLLMCore::ProviderID::Qwen;
}
PluginLLMCore::ProviderCapabilities QwenProvider::capabilities() const
{
return PluginLLMCore::ProviderCapability::Tools | PluginLLMCore::ProviderCapability::Image
| PluginLLMCore::ProviderCapability::Thinking
| PluginLLMCore::ProviderCapability::ModelListing;
}
void QwenProvider::prepareRequest(
QJsonObject &request,
PluginLLMCore::PromptTemplate *prompt,
PluginLLMCore::ContextData context,
PluginLLMCore::RequestType type,
bool isToolsEnabled,
bool isThinkingEnabled)
{
if (!prompt->isSupportProvider(providerID())) {
LOG_MESSAGE(QString("Template %1 doesn't support %2 provider").arg(name(), prompt->name()));
}
prompt->prepareRequest(request, context);
auto applyModelParams = [&request](const auto &settings) {
request["max_tokens"] = settings.maxTokens();
request["temperature"] = settings.temperature();
if (settings.useTopP())
request["top_p"] = settings.topP();
if (settings.useTopK())
request["top_k"] = settings.topK();
if (settings.useFrequencyPenalty())
request["frequency_penalty"] = settings.frequencyPenalty();
if (settings.usePresencePenalty())
request["presence_penalty"] = settings.presencePenalty();
};
if (type == PluginLLMCore::RequestType::CodeCompletion) {
applyModelParams(Settings::codeCompletionSettings());
} else if (type == PluginLLMCore::RequestType::QuickRefactoring) {
applyModelParams(Settings::quickRefactorSettings());
} else {
applyModelParams(Settings::chatAssistantSettings());
}
if (isToolsEnabled) {
auto toolsDefinitions = m_client->tools()->getToolsDefinitions();
if (!toolsDefinitions.isEmpty()) {
request["tools"] = toolsDefinitions;
LOG_MESSAGE(QString("Added %1 tools to Qwen request").arg(toolsDefinitions.size()));
}
}
}
::LLMQore::BaseClient *QwenProvider::client() const
{
return m_client;
}
} // namespace QodeAssist::Providers

View File

@@ -0,0 +1,37 @@
// Copyright (C) 2024-2026 Petr Mironychev
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <LLMQore/OpenAIClient.hpp>
#include <pluginllmcore/Provider.hpp>
namespace QodeAssist::Providers {
class QwenProvider : public PluginLLMCore::Provider
{
Q_OBJECT
public:
explicit QwenProvider(QObject *parent = nullptr);
QString name() const override;
QString url() const override;
void prepareRequest(
QJsonObject &request,
PluginLLMCore::PromptTemplate *prompt,
PluginLLMCore::ContextData context,
PluginLLMCore::RequestType type,
bool isToolsEnabled,
bool isThinkingEnabled) override;
QFuture<QList<QString>> getInstalledModels(const QString &url) override;
PluginLLMCore::ProviderID providerID() const override;
PluginLLMCore::ProviderCapabilities capabilities() const override;
::LLMQore::BaseClient *client() const override;
QString apiKey() const override;
private:
::LLMQore::OpenAIClient *m_client;
};
} // namespace QodeAssist::Providers

View File

@@ -0,0 +1,135 @@
// Copyright (C) 2024-2026 Petr Mironychev
// SPDX-License-Identifier: GPL-3.0-or-later
#include "QwenResponsesProvider.hpp"
#include <LLMQore/ToolsManager.hpp>
#include "logger/Logger.hpp"
#include "settings/ChatAssistantSettings.hpp"
#include "settings/CodeCompletionSettings.hpp"
#include "settings/GeneralSettings.hpp"
#include "settings/ProviderSettings.hpp"
#include "settings/QuickRefactorSettings.hpp"
#include "tools/ToolsRegistration.hpp"
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
namespace QodeAssist::Providers {
QwenResponsesProvider::QwenResponsesProvider(QObject *parent)
: PluginLLMCore::Provider(parent)
, m_client(new ::LLMQore::OpenAIResponsesClient(QString(), QString(), QString(), this))
{
Tools::registerQodeAssistTools(m_client->tools());
}
QString QwenResponsesProvider::name() const
{
return "Qwen (OpenAI Response)";
}
QString QwenResponsesProvider::apiKey() const
{
return Settings::providerSettings().qwenApiKey();
}
QString QwenResponsesProvider::url() const
{
return "https://dashscope-intl.aliyuncs.com/compatible-mode/v1";
}
void QwenResponsesProvider::prepareRequest(
QJsonObject &request,
PluginLLMCore::PromptTemplate *prompt,
PluginLLMCore::ContextData context,
PluginLLMCore::RequestType type,
bool isToolsEnabled,
bool isThinkingEnabled)
{
if (!prompt->isSupportProvider(providerID())) {
LOG_MESSAGE(QString("Template %1 doesn't support %2 provider").arg(name(), prompt->name()));
}
prompt->prepareRequest(request, context);
auto applyModelParams = [&request](const auto &settings) {
request["max_output_tokens"] = settings.maxTokens();
if (settings.useTopP()) {
request["top_p"] = settings.topP();
}
};
auto applyThinkingMode = [&request](const auto &settings) {
QString effortStr = settings.openAIResponsesReasoningEffort.stringValue().toLower();
if (effortStr.isEmpty()) {
effortStr = "medium";
}
QJsonObject reasoning;
reasoning["effort"] = effortStr;
request["reasoning"] = reasoning;
request["max_output_tokens"] = settings.thinkingMaxTokens();
request["store"] = true;
QJsonArray include;
include.append("reasoning.encrypted_content");
request["include"] = include;
};
if (type == PluginLLMCore::RequestType::CodeCompletion) {
applyModelParams(Settings::codeCompletionSettings());
} else if (type == PluginLLMCore::RequestType::QuickRefactoring) {
const auto &qrSettings = Settings::quickRefactorSettings();
applyModelParams(qrSettings);
if (isThinkingEnabled) {
applyThinkingMode(qrSettings);
}
} else {
const auto &chatSettings = Settings::chatAssistantSettings();
applyModelParams(chatSettings);
if (isThinkingEnabled) {
applyThinkingMode(chatSettings);
}
}
if (isToolsEnabled) {
const auto toolsDefinitions = m_client->tools()->getToolsDefinitions();
if (!toolsDefinitions.isEmpty()) {
request["tools"] = toolsDefinitions;
LOG_MESSAGE(
QString("Added %1 tools to Qwen Responses request").arg(toolsDefinitions.size()));
}
}
request["stream"] = true;
}
QFuture<QList<QString>> QwenResponsesProvider::getInstalledModels(const QString &url)
{
m_client->setUrl(url);
m_client->setApiKey(apiKey());
return m_client->listModels();
}
PluginLLMCore::ProviderID QwenResponsesProvider::providerID() const
{
return PluginLLMCore::ProviderID::OpenAIResponses;
}
PluginLLMCore::ProviderCapabilities QwenResponsesProvider::capabilities() const
{
return PluginLLMCore::ProviderCapability::Tools | PluginLLMCore::ProviderCapability::Thinking
| PluginLLMCore::ProviderCapability::Image | PluginLLMCore::ProviderCapability::ModelListing;
}
::LLMQore::BaseClient *QwenResponsesProvider::client() const
{
return m_client;
}
} // namespace QodeAssist::Providers

View File

@@ -0,0 +1,37 @@
// Copyright (C) 2024-2026 Petr Mironychev
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <LLMQore/OpenAIResponsesClient.hpp>
#include <pluginllmcore/Provider.hpp>
namespace QodeAssist::Providers {
class QwenResponsesProvider : public PluginLLMCore::Provider
{
Q_OBJECT
public:
explicit QwenResponsesProvider(QObject *parent = nullptr);
QString name() const override;
QString url() const override;
void prepareRequest(
QJsonObject &request,
PluginLLMCore::PromptTemplate *prompt,
PluginLLMCore::ContextData context,
PluginLLMCore::RequestType type,
bool isToolsEnabled,
bool isThinkingEnabled) override;
QFuture<QList<QString>> getInstalledModels(const QString &url) override;
PluginLLMCore::ProviderID providerID() const override;
PluginLLMCore::ProviderCapabilities capabilities() const override;
::LLMQore::BaseClient *client() const override;
QString apiKey() const override;
private:
::LLMQore::OpenAIResponsesClient *m_client;
};
} // namespace QodeAssist::Providers

View File

@@ -99,6 +99,50 @@ QVector<AIConfiguration> ConfigurationManager::getPredefinedConfigurations(
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";
@@ -117,6 +161,10 @@ QVector<AIConfiguration> ConfigurationManager::getPredefinedConfigurations(
presets.append(codestral);
presets.append(mistral);
presets.append(geminiFlash);
presets.append(qwenPlus);
presets.append(qwenMax);
presets.append(deepSeekFlash);
presets.append(deepSeekPro);
return presets;
}

View File

@@ -332,7 +332,23 @@ GeneralSettings::GeneralSettings()
Row{qrPresetConfig, qrConfigureApiKey, Stretch{1}},
qrGrid}};
auto *supportLabel = new QLabel(Tr::tr("Support the development of QodeAssist:"));
auto *supportLinks = new QLabel(
"<a href='https://ko-fi.com/qodeassist' style='color: #0066cc;'>Support on Ko-fi ☕</a>"
" &nbsp;|&nbsp; "
"<a href='https://github.com/Palm1r/"
"QodeAssist?tab=readme-ov-file#support-the-development-of-qodeassist' "
"style='color: #0066cc;'>Support page on GitHub</a>"
" &nbsp;|&nbsp; "
"<a href='https://www.paypal.com/paypalme/palm1r' style='color: #0066cc;'>Support via "
"PayPal 💳</a>");
supportLinks->setOpenExternalLinks(true);
supportLinks->setTextFormat(Qt::RichText);
auto rootLayout = Column{
Row{supportLabel, supportLinks, Stretch{1}},
Space{8},
Row{enableQodeAssist, Stretch{1}, Row{checkUpdate, resetToDefaults}},
Row{enableLogging, Stretch{1}},
Row{enableCheckUpdate, Stretch{1}},

View File

@@ -123,6 +123,24 @@ ProviderSettings::ProviderSettings()
llamaCppApiKey.setDefaultValue("");
llamaCppApiKey.setAutoApply(true);
// Qwen (Alibaba) Settings
qwenApiKey.setSettingsKey(Constants::QWEN_API_KEY);
qwenApiKey.setLabelText(Tr::tr("Qwen API Key:"));
qwenApiKey.setDisplayStyle(Utils::StringAspect::LineEditDisplay);
qwenApiKey.setPlaceHolderText(Tr::tr("Enter your API key here"));
qwenApiKey.setHistoryCompleter(Constants::QWEN_API_KEY_HISTORY);
qwenApiKey.setDefaultValue("");
qwenApiKey.setAutoApply(true);
// DeepSeek Settings
deepSeekApiKey.setSettingsKey(Constants::DEEPSEEK_API_KEY);
deepSeekApiKey.setLabelText(Tr::tr("DeepSeek API Key:"));
deepSeekApiKey.setDisplayStyle(Utils::StringAspect::LineEditDisplay);
deepSeekApiKey.setPlaceHolderText(Tr::tr("Enter your API key here"));
deepSeekApiKey.setHistoryCompleter(Constants::DEEPSEEK_API_KEY_HISTORY);
deepSeekApiKey.setDefaultValue("");
deepSeekApiKey.setAutoApply(true);
resetToDefaults.m_buttonText = Tr::tr("Reset Page to Defaults");
readSettings();
@@ -152,6 +170,10 @@ ProviderSettings::ProviderSettings()
Group{title(Tr::tr("Ollama Settings")), Column{ollamaBasicAuthApiKey}},
Space{8},
Group{title(Tr::tr("llama.cpp Settings")), Column{llamaCppApiKey}},
Space{8},
Group{title(Tr::tr("Qwen (Alibaba) Settings")), Column{qwenApiKey}},
Space{8},
Group{title(Tr::tr("DeepSeek Settings")), Column{deepSeekApiKey}},
Stretch{1}};
});
}
@@ -189,6 +211,10 @@ void ProviderSettings::setupConnections()
connect(&llamaCppApiKey, &ButtonAspect::changed, this, [this]() {
llamaCppApiKey.writeSettings();
});
connect(&qwenApiKey, &ButtonAspect::changed, this, [this]() { qwenApiKey.writeSettings(); });
connect(&deepSeekApiKey, &ButtonAspect::changed, this, [this]() {
deepSeekApiKey.writeSettings();
});
}
void ProviderSettings::resetSettingsToDefaults()
@@ -211,6 +237,8 @@ void ProviderSettings::resetSettingsToDefaults()
resetAspect(googleAiApiKey);
resetAspect(ollamaBasicAuthApiKey);
resetAspect(llamaCppApiKey);
resetAspect(qwenApiKey);
resetAspect(deepSeekApiKey);
writeSettings();
}
}

View File

@@ -28,6 +28,8 @@ public:
Utils::StringAspect googleAiApiKey{this};
Utils::StringAspect ollamaBasicAuthApiKey{this};
Utils::StringAspect llamaCppApiKey{this};
Utils::StringAspect qwenApiKey{this};
Utils::StringAspect deepSeekApiKey{this};
private:
void setupConnections();

View File

@@ -163,6 +163,10 @@ const char OLLAMA_BASIC_AUTH_API_KEY[] = "QodeAssist.ollamaBasicAuthApiKey";
const char OLLAMA_BASIC_AUTH_API_KEY_HISTORY[] = "QodeAssist.ollamaBasicAuthApiKeyHistory";
const char LLAMA_CPP_API_KEY[] = "QodeAssist.llamaCppApiKey";
const char LLAMA_CPP_API_KEY_HISTORY[] = "QodeAssist.llamaCppApiKeyHistory";
const char QWEN_API_KEY[] = "QodeAssist.qwenApiKey";
const char QWEN_API_KEY_HISTORY[] = "QodeAssist.qwenApiKeyHistory";
const char DEEPSEEK_API_KEY[] = "QodeAssist.deepSeekApiKey";
const char DEEPSEEK_API_KEY_HISTORY[] = "QodeAssist.deepSeekApiKeyHistory";
const char CLAUDE_ENABLE_PROMPT_CACHING[] = "QodeAssist.claudeEnablePromptCaching";
const char CLAUDE_USE_EXTENDED_CACHE_TTL[] = "QodeAssist.claudeUseExtendedCacheTTL";

View File

@@ -6,7 +6,11 @@
#include <coreplugin/icore.h>
#include <extensionsystem/pluginmanager.h>
#include <extensionsystem/pluginspec.h>
#include <QClipboard>
#include <QDesktopServices>
#include <QFrame>
#include <QGroupBox>
#include <QGuiApplication>
#include <QHBoxLayout>
#include <QVBoxLayout>
@@ -17,8 +21,7 @@ UpdateDialog::UpdateDialog(QWidget *parent)
, m_updater(new PluginUpdater(this))
{
setWindowTitle(tr("QodeAssist Update"));
setMinimumWidth(400);
setMinimumHeight(300);
setFixedSize(700, 720);
m_layout = new QVBoxLayout(this);
m_layout->setSpacing(12);
@@ -48,24 +51,94 @@ UpdateDialog::UpdateDialog(QWidget *parent)
githubSupportLink->setAlignment(Qt::AlignCenter);
m_layout->addWidget(githubSupportLink);
auto *paypalLink = new QLabel(
"<a href='https://www.paypal.com/paypalme/palm1r' style='color: #0066cc;'>Support via PayPal "
"💳</a>",
this);
paypalLink->setOpenExternalLinks(true);
paypalLink->setTextFormat(Qt::RichText);
paypalLink->setAlignment(Qt::AlignCenter);
m_layout->addWidget(paypalLink);
m_layout->addSpacing(20);
auto *updaterInfoLabel = new QLabel(
tr("QodeAssistUpdater - convenient tool for plugin installation and updates"),
this);
updaterInfoLabel->setAlignment(Qt::AlignCenter);
updaterInfoLabel->setWordWrap(true);
m_layout->addWidget(updaterInfoLabel);
auto *registryGroup = new QGroupBox(tr("Install via Extension Registry (recommended)"), this);
auto *registryLayout = new QVBoxLayout(registryGroup);
registryLayout->setSpacing(10);
m_buttonOpenUpdaterRelease = new QPushButton(tr("Download QodeAssistUpdater"), this);
auto *registryInfoLabel = new QLabel(
tr("In Qt Creator open Extensions → Browser tab, enable \"Use External Repository\", "
"add one of the URLs below and click Apply to install QodeAssist. Updates are then "
"installed from the same screen."),
registryGroup);
registryInfoLabel->setWordWrap(true);
registryLayout->addWidget(registryInfoLabel);
const auto addRegistryRow = [&](const QString &description, const QString &url) {
auto *card = new QFrame(registryGroup);
card->setFrameShape(QFrame::StyledPanel);
auto *cardLayout = new QHBoxLayout(card);
cardLayout->setContentsMargins(10, 8, 10, 8);
cardLayout->setSpacing(10);
auto *textLayout = new QVBoxLayout;
textLayout->setSpacing(2);
auto *desc = new QLabel(description, card);
desc->setStyleSheet("font-weight: bold;");
textLayout->addWidget(desc);
auto *link = new QLabel(
QString("<a href='%1' style='color: #0066cc;'>%1</a>").arg(url), card);
link->setOpenExternalLinks(true);
link->setTextFormat(Qt::RichText);
link->setTextInteractionFlags(Qt::TextBrowserInteraction);
link->setWordWrap(true);
textLayout->addWidget(link);
cardLayout->addLayout(textLayout, 1);
auto *copyButton = new QPushButton(tr("Copy"), card);
copyButton->setMaximumWidth(70);
connect(copyButton, &QPushButton::clicked, this, [url]() {
QGuiApplication::clipboard()->setText(url);
});
cardLayout->addWidget(copyButton, 0, Qt::AlignVCenter);
registryLayout->addWidget(card);
};
addRegistryRow(
tr("Latest (for the newest Qt Creator, always up to date)"),
"https://github.com/Palm1r/extension-registry/archive/refs/heads/qodeassist.tar.gz");
addRegistryRow(
tr("Only for Qt Creator %1").arg(QODEASSIST_QT_CREATOR_VERSION_MAJOR),
QString("https://github.com/Palm1r/extension-registry/archive/refs/heads/"
"qodeassist-qtc%1.tar.gz")
.arg(QODEASSIST_QT_CREATOR_VERSION_MAJOR));
m_layout->addWidget(registryGroup);
auto *updaterGroup = new QGroupBox(tr("Alternative: QodeAssistUpdater"), this);
auto *updaterLayout = new QVBoxLayout(updaterGroup);
updaterLayout->setSpacing(10);
auto *updaterInfoLabel = new QLabel(
tr("A standalone tool for installing and updating the plugin."), updaterGroup);
updaterInfoLabel->setWordWrap(true);
updaterLayout->addWidget(updaterInfoLabel);
m_buttonOpenUpdaterRelease = new QPushButton(tr("Download QodeAssistUpdater"), updaterGroup);
m_buttonOpenUpdaterRelease->setMaximumWidth(250);
auto *updaterButtonLayout = new QHBoxLayout;
updaterButtonLayout->addStretch();
updaterButtonLayout->addWidget(m_buttonOpenUpdaterRelease);
updaterButtonLayout->addStretch();
m_layout->addLayout(updaterButtonLayout);
updaterLayout->addLayout(updaterButtonLayout);
m_layout->addSpacing(20);
m_layout->addWidget(updaterGroup);
m_layout->addSpacing(10);
m_titleLabel = new QLabel(tr("A new version of QodeAssist is available!"), this);
m_titleLabel->setStyleSheet("font-weight: bold; font-size: 14px;");

View File

@@ -81,6 +81,8 @@ public:
case PluginLLMCore::ProviderID::OpenRouter:
case PluginLLMCore::ProviderID::LMStudio:
case PluginLLMCore::ProviderID::LlamaCpp:
case PluginLLMCore::ProviderID::Qwen:
case PluginLLMCore::ProviderID::DeepSeek:
return true;
default:
return false;

View File

@@ -142,10 +142,12 @@ void ProgressWidget::leaveEvent(QEvent *event)
void ProgressWidget::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton && m_isHovered) {
event->accept();
auto callback = m_cancelCallback;
emit cancelRequested();
if (m_cancelCallback) {
m_cancelCallback();
}
if (callback)
callback();
return;
}
QWidget::mousePressEvent(event);
}