Compare commits

...

5 Commits

Author SHA1 Message Date
18fb2b530f chore: Upgrade plugin to 0.7.1 2025-10-14 02:35:52 +02:00
f0d2e42680 feat: Add support QtCreator 17.0.2 (#239)
feat: Add support QtC 17.0.2
2025-10-14 02:19:14 +02:00
ff0f994ec6 feat: Add project-specific rules support 2025-10-14 01:53:44 +02:00
45df27e749 feat: Add tool for reading issues tab 2025-10-13 18:33:17 +02:00
02863003a9 doc: Added tools info to README.md 2025-10-12 13:29:47 +02:00
14 changed files with 566 additions and 7 deletions

View File

@ -47,7 +47,7 @@ jobs:
qt_config:
- {
qt_version: "6.9.2",
qt_creator_version: "17.0.1"
qt_creator_version: "17.0.2"
}
- {
qt_version: "6.8.3",

3
.gitignore vendored
View File

@ -73,4 +73,5 @@ CMakeLists.txt.user*
*.dll
*.exe
/build
/build
/.qodeassist

View File

@ -119,6 +119,7 @@ add_qtc_plugin(QodeAssist
tools/ListProjectFilesTool.hpp tools/ListProjectFilesTool.cpp
tools/ToolsManager.hpp tools/ToolsManager.cpp
tools/SearchInProjectTool.hpp tools/SearchInProjectTool.cpp
tools/GetIssuesListTool.hpp tools/GetIssuesListTool.cpp
providers/ClaudeMessage.hpp providers/ClaudeMessage.cpp
providers/OpenAIMessage.hpp providers/OpenAIMessage.cpp
providers/OllamaMessage.hpp providers/OllamaMessage.cpp

View File

@ -28,6 +28,9 @@
#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/editormanager/ieditor.h>
#include <coreplugin/idocument.h>
#include <projectexplorer/project.h>
#include <projectexplorer/projectexplorer.h>
#include <projectexplorer/projectmanager.h>
#include <texteditor/textdocument.h>
#include <texteditor/texteditor.h>
@ -37,6 +40,7 @@
#include "Logger.hpp"
#include "ProvidersManager.hpp"
#include "RequestConfig.hpp"
#include <RulesLoader.hpp>
namespace QodeAssist::Chat {
@ -81,6 +85,17 @@ void ClientInterface::sendMessage(
if (chatAssistantSettings.useSystemPrompt()) {
QString systemPrompt = chatAssistantSettings.systemPrompt();
auto project = LLMCore::RulesLoader::getActiveProject();
if (project) {
QString projectRules
= LLMCore::RulesLoader::loadRulesForProject(project, LLMCore::RulesContext::Chat);
if (!projectRules.isEmpty()) {
systemPrompt += "\n\n# Project Rules\n\n" + projectRules;
}
}
if (!linkedFiles.isEmpty()) {
systemPrompt = getSystemPromptWithLinkedFiles(systemPrompt, linkedFiles);
}

View File

@ -30,6 +30,7 @@
#include "settings/CodeCompletionSettings.hpp"
#include "settings/GeneralSettings.hpp"
#include <llmcore/RequestConfig.hpp>
#include <llmcore/RulesLoader.hpp>
namespace QodeAssist {
@ -242,6 +243,18 @@ void LLMClientInterface::handleCompletion(const QJsonObject &request)
&& promptTemplate->type() == LLMCore::TemplateType::Chat
? m_completeSettings.systemPromptForNonFimModels()
: m_completeSettings.systemPrompt());
auto project = LLMCore::RulesLoader::getActiveProject();
if (project) {
QString projectRules
= LLMCore::RulesLoader::loadRulesForProject(project, LLMCore::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());

View File

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

View File

@ -29,6 +29,7 @@
#include <llmcore/PromptTemplateManager.hpp>
#include <llmcore/ProvidersManager.hpp>
#include <llmcore/RequestConfig.hpp>
#include <llmcore/RulesLoader.hpp>
#include <logger/Logger.hpp>
#include <settings/ChatAssistantSettings.hpp>
#include <settings/GeneralSettings.hpp>
@ -206,6 +207,18 @@ LLMCore::ContextData QuickRefactorHandler::prepareContext(
}
QString systemPrompt = Settings::codeCompletionSettings().quickRefactorSystemPrompt();
auto project = LLMCore::RulesLoader::getActiveProject();
if (project) {
QString projectRules = LLMCore::RulesLoader::loadRulesForProject(
project, LLMCore::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 += "\nLanguage: " + documentInfo.mimeType;
systemPrompt += "\nFile path: " + documentInfo.filePath;

View File

@ -3,7 +3,7 @@
![GitHub Downloads (all assets, all releases)](https://img.shields.io/github/downloads/Palm1r/QodeAssist/total?color=41%2C173%2C71)
![GitHub Tag](https://img.shields.io/github/v/tag/Palm1r/QodeAssist)
![Static Badge](https://img.shields.io/badge/QtCreator-16.0.2-brightgreen)
![Static Badge](https://img.shields.io/badge/QtCreator-17.0.0-brightgreen)
![Static Badge](https://img.shields.io/badge/QtCreator-17.0.1-brightgreen)
[![](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 is an AI-powered coding assistant plugin for Qt Creator. It provides intelligent code completion and suggestions for C++ and QML, leveraging large language models through local providers like Ollama. Enhance your coding productivity with context-aware AI assistance directly in your Qt development environment.
@ -55,9 +55,10 @@
- LM Studio
- Mistral AI
- Google AI
- OpenAI-compatible providers(eg. llama.cpp, https://openrouter.ai)
- OpenAI-compatible providers (eg. llama.cpp, https://openrouter.ai)
- Extensive library of model-specific templates
- Easy configuration and model selection
- Support tools/function calling (enabled by default)
Join our Discord Community: Have questions or want to discuss QodeAssist? Join our [Discord server](https://discord.gg/BGMkUsXUgf) to connect with other users and get support!
@ -96,6 +97,11 @@ Join our Discord Community: Have questions or want to discuss QodeAssist? Join o
<img width="600" alt="OpenedDocumentsSync" src="https://github.com/user-attachments/assets/08efda2f-dc4d-44c3-927c-e6a975090d2f">
</details>
<details>
<summary>Example how tools works: (click to expand)</summary>
<img width="600" alt="ToolsDemo" src="https://github.com/user-attachments/assets/cf6273ad-d5c8-47fc-81e6-23d929547f6c">
</details>
## Install plugin to QtCreator
1. Install Latest Qt Creator
2. Download the QodeAssist plugin for your Qt Creator
@ -186,6 +192,7 @@ ollama run qwen2.5-coder:32b
- The URL is set to http://localhost:11434
- Your installed model appears in the model selection
- The prompt template is Ollama Auto FIM or Ollama Auto Chat for chat assistance. You can specify template if it is not work correct
- Disable using tools if your model doesn't support tooling
4. Click Apply if you made any changes
You're all set! QodeAssist is now ready to use in Qt Creator.
@ -201,6 +208,7 @@ You're all set! QodeAssist is now ready to use in Qt Creator.
- Set the llama.cpp URL (e.g. http://localhost:8080)
- Fill in model name
- Choose template for model(e.g. llama.cpp FIM for any model with FIM support)
- Disable using tools if your model doesn't support tooling
<details>
<summary>Example of llama.cpp settings: (click to expand)</summary>
<img width="829" alt="llama.cpp Settings" src="https://github.com/user-attachments/assets/8c75602c-60f3-49ed-a7a9-d3c972061ea2" />
@ -210,6 +218,34 @@ You're all set! QodeAssist is now ready to use in Qt Creator.
The plugin comes with default system prompts optimized for chat and instruct models, as these currently provide better results for code assistance. If you prefer using FIM (Fill-in-Middle) models, you can easily customize the system prompt in the settings.
## Project Rules Configuration
QodeAssist supports project-specific rules to customize AI behavior for your codebase. Create a `.qodeassist/rules/` directory in your project root.
### Quick Start
```bash
mkdir -p .qodeassist/rules/{common,completion,chat,quickrefactor}
```
```
.qodeassist/
└── rules/
├── common/ # Applied to all contexts
├── completion/ # Code completion only
├── chat/ # Chat assistant only
└── quickrefactor/ # Quick refactor only
```
All .md files in each directory are automatically loaded and added to the system prompt.
Example
Create .qodeassist/rules/common/general.md:
```markdown
# Project Guidelines
- Use snake_case for private members
- Prefix interfaces with 'I'
- Always document public APIs
- Prefer Qt containers over STL
```
## File Context Feature
QodeAssist provides two powerful ways to include source code files in your chat conversations: Attachments and Linked Files. Each serves a distinct purpose and helps provide better context for the AI assistant.

View File

@ -15,9 +15,9 @@ add_library(LLMCore STATIC
HttpClient.hpp HttpClient.cpp
DataBuffers.hpp
SSEBuffer.hpp SSEBuffer.cpp
BaseTool.hpp
BaseTool.cpp
BaseTool.hpp BaseTool.cpp
ContentBlocks.hpp
RulesLoader.hpp RulesLoader.cpp
)
target_link_libraries(LLMCore

112
llmcore/RulesLoader.cpp Normal file
View File

@ -0,0 +1,112 @@
/*
* Copyright (C) 2025 Petr Mironychev
*
* This file is part of QodeAssist.
*
* QodeAssist is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* QodeAssist is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
*/
#include "RulesLoader.hpp"
#include <QDir>
#include <QFile>
#include <coreplugin/editormanager/editormanager.h>
#include <projectexplorer/project.h>
#include <projectexplorer/projectmanager.h>
namespace QodeAssist::LLMCore {
QString RulesLoader::loadRules(const QString &projectPath, RulesContext context)
{
if (projectPath.isEmpty()) {
return QString();
}
QString combined;
QString basePath = projectPath + "/.qodeassist/rules";
combined += loadAllMarkdownFiles(basePath + "/common");
switch (context) {
case RulesContext::Completions:
combined += loadAllMarkdownFiles(basePath + "/completions");
break;
case RulesContext::Chat:
combined += loadAllMarkdownFiles(basePath + "/chat");
break;
case RulesContext::QuickRefactor:
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();
}
} // namespace QodeAssist::LLMCore

44
llmcore/RulesLoader.hpp Normal file
View File

@ -0,0 +1,44 @@
/*
* Copyright (C) 2025 Petr Mironychev
*
* This file is part of QodeAssist.
*
* QodeAssist is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* QodeAssist is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <QString>
namespace ProjectExplorer {
class Project;
}
namespace QodeAssist::LLMCore {
enum class RulesContext { Completions, Chat, QuickRefactor };
class RulesLoader
{
public:
static QString loadRules(const QString &projectPath, RulesContext context);
static QString loadRulesForProject(ProjectExplorer::Project *project, RulesContext context);
static ProjectExplorer::Project *getActiveProject();
private:
static QString loadAllMarkdownFiles(const QString &dirPath);
static QString getProjectPath(ProjectExplorer::Project *project);
};
} // namespace QodeAssist::LLMCore

258
tools/GetIssuesListTool.cpp Normal file
View File

@ -0,0 +1,258 @@
/*
* Copyright (C) 2025 Petr Mironychev
*
* This file is part of QodeAssist.
*
* QodeAssist is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* QodeAssist is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
*/
#include "GetIssuesListTool.hpp"
#include <logger/Logger.hpp>
#include <projectexplorer/taskhub.h>
#include <QJsonArray>
#include <QJsonObject>
#include <QMutexLocker>
#include <QtConcurrent>
namespace QodeAssist::Tools {
IssuesTracker &IssuesTracker::instance()
{
static IssuesTracker tracker;
return tracker;
}
IssuesTracker::IssuesTracker(QObject *parent)
: QObject(parent)
{
LOG_MESSAGE("IssuesTracker: Initializing tracker");
auto &hub = ProjectExplorer::taskHub();
connect(&hub, &ProjectExplorer::TaskHub::taskAdded, this, &IssuesTracker::onTaskAdded);
connect(&hub, &ProjectExplorer::TaskHub::taskRemoved, this, &IssuesTracker::onTaskRemoved);
connect(&hub, &ProjectExplorer::TaskHub::tasksCleared, this, &IssuesTracker::onTasksCleared);
LOG_MESSAGE("IssuesTracker: Connected to TaskHub signals");
}
QList<ProjectExplorer::Task> IssuesTracker::getTasks() const
{
QMutexLocker locker(&m_mutex);
LOG_MESSAGE(QString("IssuesTracker: getTasks() called, current count: %1").arg(m_tasks.size()));
return m_tasks;
}
void IssuesTracker::onTaskAdded(const ProjectExplorer::Task &task)
{
QMutexLocker locker(&m_mutex);
m_tasks.append(task);
QString typeStr;
switch (task.type) {
case ProjectExplorer::Task::Error:
typeStr = "ERROR";
break;
case ProjectExplorer::Task::Warning:
typeStr = "WARNING";
break;
default:
typeStr = "INFO";
break;
}
LOG_MESSAGE(QString("IssuesTracker: Task added [%1] %2 at %3:%4 (total: %5)")
.arg(typeStr)
.arg(task.description())
.arg(task.file.toUrlishString())
.arg(task.line)
.arg(m_tasks.size()));
}
void IssuesTracker::onTaskRemoved(const ProjectExplorer::Task &task)
{
QMutexLocker locker(&m_mutex);
m_tasks.removeOne(task);
LOG_MESSAGE(QString("IssuesTracker: Task removed: %1 (total: %2)")
.arg(task.description())
.arg(m_tasks.size()));
}
void IssuesTracker::onTasksCleared(Utils::Id categoryId)
{
QMutexLocker locker(&m_mutex);
if (categoryId.isValid()) {
int beforeCount = m_tasks.size();
m_tasks.erase(
std::remove_if(
m_tasks.begin(),
m_tasks.end(),
[categoryId](const ProjectExplorer::Task &task) {
return task.category == categoryId;
}),
m_tasks.end());
int removedCount = beforeCount - m_tasks.size();
LOG_MESSAGE(
QString("IssuesTracker: Tasks cleared for category %1, removed %2 tasks (total: %3)")
.arg(categoryId.toString())
.arg(removedCount)
.arg(m_tasks.size()));
} else {
int clearedCount = m_tasks.size();
m_tasks.clear();
LOG_MESSAGE(QString("IssuesTracker: All tasks cleared, removed %1 tasks").arg(clearedCount));
}
}
GetIssuesListTool::GetIssuesListTool(QObject *parent)
: BaseTool(parent)
{
LOG_MESSAGE("GetIssuesListTool: Initializing tool");
IssuesTracker::instance();
}
QString GetIssuesListTool::name() const
{
return "get_issues_list";
}
QString GetIssuesListTool::stringName() const
{
return "Getting issues list from Qt Creator";
}
QString GetIssuesListTool::description() const
{
return "Get list of errors, warnings and other issues from Qt Creator's Issues panel. "
"Returns information about compilation errors, static analysis warnings, and other "
"diagnostic messages.";
}
QJsonObject GetIssuesListTool::getDefinition(LLMCore::ToolSchemaFormat format) const
{
QJsonObject definition;
definition["type"] = "object";
QJsonObject properties;
properties["severity"] = QJsonObject{
{"type", "string"},
{"description", "Filter by severity: 'error', 'warning', or 'all'"},
{"enum", QJsonArray{"error", "warning", "all"}}};
definition["properties"] = properties;
definition["required"] = QJsonArray();
switch (format) {
case LLMCore::ToolSchemaFormat::OpenAI:
return customizeForOpenAI(definition);
case LLMCore::ToolSchemaFormat::Claude:
return customizeForClaude(definition);
case LLMCore::ToolSchemaFormat::Ollama:
return customizeForOllama(definition);
case LLMCore::ToolSchemaFormat::Google:
return customizeForGoogle(definition);
}
return definition;
}
LLMCore::ToolPermissions GetIssuesListTool::requiredPermissions() const
{
return LLMCore::ToolPermission::FileSystemRead;
}
QFuture<QString> GetIssuesListTool::executeAsync(const QJsonObject &input)
{
return QtConcurrent::run([input]() -> QString {
LOG_MESSAGE("GetIssuesListTool: Starting execution");
QString severityFilter = input.value("severity").toString("all");
LOG_MESSAGE(QString("GetIssuesListTool: Severity filter: %1").arg(severityFilter));
const auto tasks = IssuesTracker::instance().getTasks();
if (tasks.isEmpty()) {
LOG_MESSAGE("GetIssuesListTool: No issues found");
return "No issues found in Qt Creator Issues panel.";
}
LOG_MESSAGE(QString("GetIssuesListTool: Processing %1 tasks").arg(tasks.size()));
QStringList results;
results.append(QString("Total issues in panel: %1\n").arg(tasks.size()));
int errorCount = 0;
int warningCount = 0;
int processedCount = 0;
for (const ProjectExplorer::Task &task : tasks) {
if (severityFilter == "error" && task.type != ProjectExplorer::Task::Error)
continue;
if (severityFilter == "warning" && task.type != ProjectExplorer::Task::Warning)
continue;
QString typeStr;
switch (task.type) {
case ProjectExplorer::Task::Error:
typeStr = "ERROR";
errorCount++;
break;
case ProjectExplorer::Task::Warning:
typeStr = "WARNING";
warningCount++;
break;
default:
typeStr = "INFO";
break;
}
QString issueText = QString("[%1] %2").arg(typeStr, task.description());
if (!task.file.isEmpty()) {
issueText += QString("\n File: %1").arg(task.file.toUrlishString());
if (task.line > 0) {
issueText += QString(":%1").arg(task.line);
if (task.column > 0) {
issueText += QString(":%1").arg(task.column);
}
}
}
if (!task.category.toString().isEmpty()) {
issueText += QString("\n Category: %1").arg(task.category.toString());
}
results.append(issueText);
processedCount++;
}
QString summary = QString("\nSummary: %1 errors, %2 warnings (processed %3 tasks)")
.arg(errorCount)
.arg(warningCount)
.arg(processedCount);
results.prepend(summary);
LOG_MESSAGE(QString("GetIssuesListTool: Execution completed - %1 errors, %2 warnings")
.arg(errorCount)
.arg(warningCount));
return results.join("\n\n");
});
}
} // namespace QodeAssist::Tools

View File

@ -0,0 +1,64 @@
/*
* Copyright (C) 2025 Petr Mironychev
*
* This file is part of QodeAssist.
*
* QodeAssist is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* QodeAssist is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <llmcore/BaseTool.hpp>
#include <projectexplorer/task.h>
#include <QList>
#include <QMutex>
namespace QodeAssist::Tools {
class IssuesTracker : public QObject
{
Q_OBJECT
public:
static IssuesTracker &instance();
QList<ProjectExplorer::Task> getTasks() const;
private:
explicit IssuesTracker(QObject *parent = nullptr);
~IssuesTracker() override = default;
void onTaskAdded(const ProjectExplorer::Task &task);
void onTaskRemoved(const ProjectExplorer::Task &task);
void onTasksCleared(Utils::Id categoryId);
QList<ProjectExplorer::Task> m_tasks;
mutable QMutex m_mutex;
};
class GetIssuesListTool : public LLMCore::BaseTool
{
Q_OBJECT
public:
explicit GetIssuesListTool(QObject *parent = nullptr);
QString name() const override;
QString stringName() const override;
QString description() const override;
QJsonObject getDefinition(LLMCore::ToolSchemaFormat format) const override;
LLMCore::ToolPermissions requiredPermissions() const override;
QFuture<QString> executeAsync(const QJsonObject &input = QJsonObject()) override;
};
} // namespace QodeAssist::Tools

View File

@ -24,6 +24,7 @@
#include <QJsonArray>
#include <QJsonObject>
#include "GetIssuesListTool.hpp"
#include "ListProjectFilesTool.hpp"
#include "ReadProjectFileByNameTool.hpp"
#include "ReadVisibleFilesTool.hpp"
@ -43,6 +44,7 @@ void ToolsFactory::registerTools()
registerTool(new ReadProjectFileByNameTool(this));
registerTool(new ListProjectFilesTool(this));
registerTool(new SearchInProjectTool(this));
registerTool(new GetIssuesListTool(this));
LOG_MESSAGE(QString("Registered %1 tools").arg(m_tools.size()));
}