mirror of
https://github.com/Palm1r/QodeAssist.git
synced 2026-07-24 20:20:59 -04:00
feat: add support acp in common chat (#369)
This commit is contained in:
@@ -1,211 +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 "AgentRole.hpp"
|
||||
|
||||
#include <coreplugin/icore.h>
|
||||
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
|
||||
namespace QodeAssist::Settings {
|
||||
|
||||
QString AgentRolesManager::getConfigurationDirectory()
|
||||
{
|
||||
QString path = QString("%1/qodeassist/agent_roles")
|
||||
.arg(Core::ICore::userResourcePath().toFSPathString());
|
||||
QDir().mkpath(path);
|
||||
return path;
|
||||
}
|
||||
|
||||
QList<AgentRole> AgentRolesManager::loadAllRoles()
|
||||
{
|
||||
QList<AgentRole> roles;
|
||||
QString configDir = getConfigurationDirectory();
|
||||
QDir dir(configDir);
|
||||
|
||||
ensureDefaultRoles();
|
||||
|
||||
const QStringList jsonFiles = dir.entryList({"*.json"}, QDir::Files);
|
||||
for (const QString &fileName : jsonFiles) {
|
||||
AgentRole role = loadRoleFromFile(dir.absoluteFilePath(fileName));
|
||||
if (!role.id.isEmpty()) {
|
||||
roles.append(role);
|
||||
}
|
||||
}
|
||||
|
||||
return roles;
|
||||
}
|
||||
|
||||
AgentRole AgentRolesManager::loadRole(const QString &roleId)
|
||||
{
|
||||
if (roleId.isEmpty())
|
||||
return {};
|
||||
|
||||
QString filePath = QDir(getConfigurationDirectory()).absoluteFilePath(roleId + ".json");
|
||||
if (!QFile::exists(filePath))
|
||||
return {};
|
||||
|
||||
return loadRoleFromFile(filePath);
|
||||
}
|
||||
|
||||
AgentRole AgentRolesManager::loadRoleFromFile(const QString &filePath)
|
||||
{
|
||||
QFile file(filePath);
|
||||
if (!file.open(QIODevice::ReadOnly))
|
||||
return {};
|
||||
|
||||
QJsonDocument doc = QJsonDocument::fromJson(file.readAll());
|
||||
if (doc.isNull() || !doc.isObject())
|
||||
return {};
|
||||
|
||||
return AgentRole::fromJson(doc.object());
|
||||
}
|
||||
|
||||
bool AgentRolesManager::saveRole(const AgentRole &role)
|
||||
{
|
||||
if (role.id.isEmpty())
|
||||
return false;
|
||||
|
||||
QString filePath = QDir(getConfigurationDirectory()).absoluteFilePath(role.id + ".json");
|
||||
QFile file(filePath);
|
||||
|
||||
if (!file.open(QIODevice::WriteOnly))
|
||||
return false;
|
||||
|
||||
QJsonDocument doc(role.toJson());
|
||||
file.write(doc.toJson(QJsonDocument::Indented));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AgentRolesManager::deleteRole(const QString &roleId)
|
||||
{
|
||||
if (roleId.isEmpty())
|
||||
return false;
|
||||
|
||||
QString filePath = QDir(getConfigurationDirectory()).absoluteFilePath(roleId + ".json");
|
||||
return QFile::remove(filePath);
|
||||
}
|
||||
|
||||
bool AgentRolesManager::roleExists(const QString &roleId)
|
||||
{
|
||||
if (roleId.isEmpty())
|
||||
return false;
|
||||
|
||||
QString filePath = QDir(getConfigurationDirectory()).absoluteFilePath(roleId + ".json");
|
||||
return QFile::exists(filePath);
|
||||
}
|
||||
|
||||
void AgentRolesManager::ensureDefaultRoles()
|
||||
{
|
||||
QDir dir(getConfigurationDirectory());
|
||||
|
||||
if (!dir.exists("developer.json"))
|
||||
saveRole(getDefaultDeveloperRole());
|
||||
|
||||
if (!dir.exists("reviewer.json"))
|
||||
saveRole(getDefaultReviewerRole());
|
||||
|
||||
if (!dir.exists("researcher.json"))
|
||||
saveRole(getDefaultResearcherRole());
|
||||
}
|
||||
|
||||
AgentRole AgentRolesManager::getDefaultDeveloperRole()
|
||||
{
|
||||
return AgentRole{
|
||||
"developer",
|
||||
"Developer",
|
||||
"Experienced Qt/C++ developer for implementation tasks",
|
||||
"You are an experienced Qt/C++ developer working on a Qt Creator plugin.\n\n"
|
||||
"Your workflow:\n"
|
||||
"1. **Analyze** - understand the problem and what needs to be done\n"
|
||||
"2. **Propose solution** - explain your approach in 2-3 sentences\n"
|
||||
"3. **Wait for approval** - don't write code until the solution is confirmed\n"
|
||||
"4. **Implement** - write clean, minimal code that solves the task\n\n"
|
||||
"When analyzing:\n"
|
||||
"- Ask clarifying questions if requirements are unclear\n"
|
||||
"- Check existing code for similar patterns\n"
|
||||
"- Consider edge cases and potential issues\n\n"
|
||||
"When proposing:\n"
|
||||
"- Explain what you'll change and why\n"
|
||||
"- Mention files you'll modify\n"
|
||||
"- Note any architectural implications\n\n"
|
||||
"When implementing:\n"
|
||||
"- Use C++20, Qt6, follow existing codebase style\n"
|
||||
"- Write only what's needed (MVP approach)\n"
|
||||
"- Include file paths and necessary changes\n"
|
||||
"- Handle errors properly\n"
|
||||
"- Make sure it compiles\n\n"
|
||||
"Keep it practical:\n"
|
||||
"- Short explanations, let code speak\n"
|
||||
"- No over-engineering or unnecessary refactoring\n"
|
||||
"- No TODOs, debug code, or unfinished work\n"
|
||||
"- Point out non-obvious things\n\n"
|
||||
"You're a pragmatic team member who thinks before coding.",
|
||||
true};
|
||||
}
|
||||
|
||||
AgentRole AgentRolesManager::getDefaultReviewerRole()
|
||||
{
|
||||
return AgentRole{
|
||||
"reviewer",
|
||||
"Code Reviewer",
|
||||
"Expert C++/QML code reviewer for quality assurance",
|
||||
"You are an expert C++/QML code reviewer specializing in C++20 and Qt6.\n\n"
|
||||
"What you check:\n"
|
||||
"- Bugs, memory leaks, undefined behavior\n"
|
||||
"- C++20 compliance and Qt6 patterns\n"
|
||||
"- RAII, move semantics, smart pointers\n"
|
||||
"- Qt parent-child ownership and signal/slot correctness\n"
|
||||
"- Thread safety and Qt concurrent usage\n"
|
||||
"- const-correctness and Qt container usage\n"
|
||||
"- Performance bottlenecks\n"
|
||||
"- Production readiness: error handling, no debug leftovers\n\n"
|
||||
"What you do:\n"
|
||||
"- Point out problems with clear explanations\n"
|
||||
"- Suggest specific fixes with code examples\n"
|
||||
"- Remove unnecessary comments, keep essential docs only\n"
|
||||
"- Flag anything that's not production-ready\n"
|
||||
"- Recommend optimizations when you spot them\n\n"
|
||||
"Focus on: correctness, performance, maintainability, Qt idioms.\n\n"
|
||||
"Be direct and specific. Show, don't just tell.",
|
||||
true};
|
||||
}
|
||||
|
||||
AgentRole AgentRolesManager::getDefaultResearcherRole()
|
||||
{
|
||||
return AgentRole{
|
||||
"researcher",
|
||||
"Researcher",
|
||||
"Research-oriented developer for exploring solutions",
|
||||
"You are a research-oriented Qt/C++ developer who investigates problems and explores "
|
||||
"solutions.\n\n"
|
||||
"Your job is to think, not to code:\n"
|
||||
"- Deep dive into the problem before suggesting anything\n"
|
||||
"- Research Qt docs, patterns, and best practices\n"
|
||||
"- Find multiple ways to solve it\n"
|
||||
"- Compare trade-offs: performance, complexity, maintainability\n"
|
||||
"- Look for relevant Qt APIs and modules\n"
|
||||
"- Think about architectural consequences\n\n"
|
||||
"How you work:\n"
|
||||
"1. **Problem Analysis** - what exactly needs solving\n"
|
||||
"2. **Research Findings** - what you learned about this problem space\n"
|
||||
"3. **Solution Options** - present 2-3 approaches with honest pros/cons\n"
|
||||
"4. **Recommendation** - which one fits best and why\n"
|
||||
"5. **Next Steps** - what to consider before implementing\n\n"
|
||||
"What you provide:\n"
|
||||
"- Clear comparison of different approaches\n"
|
||||
"- Code snippets as examples (not ready-to-use patches)\n"
|
||||
"- Links to docs, examples, similar implementations\n"
|
||||
"- Questions to clarify requirements\n"
|
||||
"- Warning about potential problems\n\n"
|
||||
"You DO NOT write implementation code. You explore options and let the developer choose.\n\n"
|
||||
"Think like a consultant: research thoroughly, present clearly, stay objective.",
|
||||
true};
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Settings
|
||||
@@ -1,67 +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 <QJsonObject>
|
||||
#include <QList>
|
||||
#include <QString>
|
||||
|
||||
namespace QodeAssist::Settings {
|
||||
|
||||
struct AgentRole
|
||||
{
|
||||
QString id;
|
||||
QString name;
|
||||
QString description;
|
||||
QString systemPrompt;
|
||||
bool isBuiltin = false;
|
||||
|
||||
QJsonObject toJson() const
|
||||
{
|
||||
return QJsonObject{
|
||||
{"id", id},
|
||||
{"name", name},
|
||||
{"description", description},
|
||||
{"systemPrompt", systemPrompt},
|
||||
{"isBuiltin", isBuiltin}};
|
||||
}
|
||||
|
||||
static AgentRole fromJson(const QJsonObject &json)
|
||||
{
|
||||
return AgentRole{
|
||||
json["id"].toString(),
|
||||
json["name"].toString(),
|
||||
json["description"].toString(),
|
||||
json["systemPrompt"].toString(),
|
||||
json["isBuiltin"].toBool(false)};
|
||||
}
|
||||
|
||||
bool operator==(const AgentRole &other) const { return id == other.id; }
|
||||
};
|
||||
|
||||
class AgentRolesManager
|
||||
{
|
||||
public:
|
||||
static QString getConfigurationDirectory();
|
||||
static QList<AgentRole> loadAllRoles();
|
||||
static AgentRole loadRole(const QString &roleId);
|
||||
static AgentRole loadRoleFromFile(const QString &filePath);
|
||||
static bool saveRole(const AgentRole &role);
|
||||
static bool deleteRole(const QString &roleId);
|
||||
static bool roleExists(const QString &roleId);
|
||||
static void ensureDefaultRoles();
|
||||
|
||||
static AgentRole getNoRole()
|
||||
{
|
||||
return AgentRole{"", "No Role", "Use base system prompt without role specialization", "", false};
|
||||
}
|
||||
|
||||
private:
|
||||
static AgentRole getDefaultDeveloperRole();
|
||||
static AgentRole getDefaultReviewerRole();
|
||||
static AgentRole getDefaultResearcherRole();
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Settings
|
||||
@@ -1,110 +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 "AgentRoleDialog.hpp"
|
||||
|
||||
#include <QDialogButtonBox>
|
||||
#include <QFormLayout>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QPushButton>
|
||||
#include <QTextEdit>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
namespace QodeAssist::Settings {
|
||||
|
||||
AgentRoleDialog::AgentRoleDialog(Action action, QWidget *parent)
|
||||
: QDialog{parent}
|
||||
, m_action{action}
|
||||
{
|
||||
auto getTitle = [](Action action) {
|
||||
switch(action)
|
||||
{
|
||||
case Action::Add:
|
||||
return tr("Add Agent Role");
|
||||
case Action::Duplicate:
|
||||
return tr("Duplicate Agent Role");
|
||||
case Action::Edit:
|
||||
return tr("Edit Agent Role");
|
||||
}
|
||||
};
|
||||
|
||||
setWindowTitle(getTitle(action));
|
||||
setupUI();
|
||||
}
|
||||
|
||||
void AgentRoleDialog::setupUI()
|
||||
{
|
||||
auto *mainLayout = new QVBoxLayout(this);
|
||||
auto *formLayout = new QFormLayout();
|
||||
|
||||
m_nameEdit = new QLineEdit(this);
|
||||
m_nameEdit->setPlaceholderText(tr("e.g., Developer, Code Reviewer"));
|
||||
formLayout->addRow(tr("Name:"), m_nameEdit);
|
||||
|
||||
m_idEdit = new QLineEdit(this);
|
||||
m_idEdit->setPlaceholderText(tr("e.g., developer, code_reviewer"));
|
||||
formLayout->addRow(tr("ID:"), m_idEdit);
|
||||
|
||||
m_descriptionEdit = new QTextEdit(this);
|
||||
m_descriptionEdit->setPlaceholderText(tr("Brief description of this role..."));
|
||||
m_descriptionEdit->setMaximumHeight(80);
|
||||
formLayout->addRow(tr("Description:"), m_descriptionEdit);
|
||||
|
||||
mainLayout->addLayout(formLayout);
|
||||
|
||||
auto *promptLabel = new QLabel(tr("System Prompt:"), this);
|
||||
mainLayout->addWidget(promptLabel);
|
||||
|
||||
m_systemPromptEdit = new QTextEdit(this);
|
||||
m_systemPromptEdit->setPlaceholderText(
|
||||
tr("You are an expert in...\n\nYour role is to:\n- Task 1\n- Task 2\n- Task 3"));
|
||||
mainLayout->addWidget(m_systemPromptEdit);
|
||||
|
||||
m_buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
|
||||
mainLayout->addWidget(m_buttonBox);
|
||||
|
||||
connect(m_buttonBox, &QDialogButtonBox::accepted, this, &AgentRoleDialog::accept);
|
||||
connect(m_buttonBox, &QDialogButtonBox::rejected, this, &AgentRoleDialog::reject);
|
||||
connect(m_nameEdit, &QLineEdit::textChanged, this, &AgentRoleDialog::validateInput);
|
||||
connect(m_idEdit, &QLineEdit::textChanged, this, &AgentRoleDialog::validateInput);
|
||||
connect(m_systemPromptEdit, &QTextEdit::textChanged, this, &AgentRoleDialog::validateInput);
|
||||
|
||||
if (m_action == Action::Edit) {
|
||||
m_idEdit->setEnabled(false);
|
||||
m_idEdit->setToolTip(tr("ID cannot be changed for existing roles"));
|
||||
}
|
||||
|
||||
setMinimumSize(600, 500);
|
||||
validateInput();
|
||||
}
|
||||
|
||||
void AgentRoleDialog::validateInput()
|
||||
{
|
||||
bool valid = !m_nameEdit->text().trimmed().isEmpty()
|
||||
&& !m_idEdit->text().trimmed().isEmpty()
|
||||
&& !m_systemPromptEdit->toPlainText().trimmed().isEmpty();
|
||||
|
||||
m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(valid);
|
||||
}
|
||||
|
||||
AgentRole AgentRoleDialog::getRole() const
|
||||
{
|
||||
return AgentRole{
|
||||
m_idEdit->text().trimmed(),
|
||||
m_nameEdit->text().trimmed(),
|
||||
m_descriptionEdit->toPlainText().trimmed(),
|
||||
m_systemPromptEdit->toPlainText().trimmed(),
|
||||
false};
|
||||
}
|
||||
|
||||
void AgentRoleDialog::setRole(const AgentRole &role)
|
||||
{
|
||||
m_idEdit->setText(role.id);
|
||||
m_nameEdit->setText(role.name);
|
||||
m_descriptionEdit->setPlainText(role.description);
|
||||
m_systemPromptEdit->setPlainText(role.systemPrompt);
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Settings
|
||||
@@ -1,50 +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 <QDialog>
|
||||
|
||||
#include "AgentRole.hpp"
|
||||
|
||||
class QLineEdit;
|
||||
class QTextEdit;
|
||||
class QDialogButtonBox;
|
||||
|
||||
namespace QodeAssist::Settings {
|
||||
|
||||
class AgentRoleDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum class Action {
|
||||
Add,
|
||||
Duplicate,
|
||||
Edit,
|
||||
};
|
||||
|
||||
explicit AgentRoleDialog(Action action, QWidget *parent = nullptr);
|
||||
explicit AgentRoleDialog(const AgentRole &role, Action action, QWidget *parent = nullptr)
|
||||
: AgentRoleDialog{action, parent}
|
||||
{
|
||||
setRole(role);
|
||||
}
|
||||
|
||||
AgentRole getRole() const;
|
||||
void setRole(const AgentRole &role);
|
||||
|
||||
private:
|
||||
void setupUI();
|
||||
void validateInput();
|
||||
|
||||
QLineEdit *m_nameEdit = nullptr;
|
||||
QLineEdit *m_idEdit = nullptr;
|
||||
QTextEdit *m_descriptionEdit = nullptr;
|
||||
QTextEdit *m_systemPromptEdit = nullptr;
|
||||
QDialogButtonBox *m_buttonBox = nullptr;
|
||||
Action m_action;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Settings
|
||||
@@ -1,242 +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 "AgentRolesWidget.hpp"
|
||||
|
||||
#include "AgentRole.hpp"
|
||||
#include "AgentRoleDialog.hpp"
|
||||
#include "SettingsTr.hpp"
|
||||
|
||||
#include <QDesktopServices>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QListWidget>
|
||||
#include <QMessageBox>
|
||||
#include <QPushButton>
|
||||
#include <QUrl>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
namespace QodeAssist::Settings {
|
||||
|
||||
void AgentRolesWidget::setupUI()
|
||||
{
|
||||
auto *mainLayout = new QVBoxLayout(this);
|
||||
|
||||
auto *headerLayout = new QHBoxLayout();
|
||||
|
||||
auto *infoLabel = new QLabel(
|
||||
Tr::tr("Agent roles define different system prompts for specific tasks."), this);
|
||||
infoLabel->setWordWrap(true);
|
||||
headerLayout->addWidget(infoLabel, 1);
|
||||
|
||||
auto *openFolderButton = new QPushButton(Tr::tr("Open Roles Folder..."), this);
|
||||
connect(openFolderButton, &QPushButton::clicked, this, &AgentRolesWidget::onOpenRolesFolder);
|
||||
headerLayout->addWidget(openFolderButton);
|
||||
|
||||
mainLayout->addLayout(headerLayout);
|
||||
|
||||
auto *contentLayout = new QHBoxLayout();
|
||||
|
||||
m_rolesList = new QListWidget(this);
|
||||
m_rolesList->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
connect(m_rolesList, &QListWidget::itemSelectionChanged, this, &AgentRolesWidget::updateButtons);
|
||||
connect(m_rolesList, &QListWidget::itemDoubleClicked, this, &AgentRolesWidget::onEditRole);
|
||||
contentLayout->addWidget(m_rolesList, 1);
|
||||
|
||||
auto *buttonsLayout = new QVBoxLayout();
|
||||
|
||||
m_addButton = new QPushButton(Tr::tr("Add..."), this);
|
||||
connect(m_addButton, &QPushButton::clicked, this, &AgentRolesWidget::onAddRole);
|
||||
buttonsLayout->addWidget(m_addButton);
|
||||
|
||||
m_editButton = new QPushButton(Tr::tr("Edit..."), this);
|
||||
connect(m_editButton, &QPushButton::clicked, this, &AgentRolesWidget::onEditRole);
|
||||
buttonsLayout->addWidget(m_editButton);
|
||||
|
||||
m_duplicateButton = new QPushButton(Tr::tr("Duplicate..."), this);
|
||||
connect(m_duplicateButton, &QPushButton::clicked, this, &AgentRolesWidget::onDuplicateRole);
|
||||
buttonsLayout->addWidget(m_duplicateButton);
|
||||
|
||||
m_deleteButton = new QPushButton(Tr::tr("Delete"), this);
|
||||
connect(m_deleteButton, &QPushButton::clicked, this, &AgentRolesWidget::onDeleteRole);
|
||||
buttonsLayout->addWidget(m_deleteButton);
|
||||
|
||||
buttonsLayout->addStretch();
|
||||
|
||||
contentLayout->addLayout(buttonsLayout);
|
||||
mainLayout->addLayout(contentLayout);
|
||||
|
||||
updateButtons();
|
||||
}
|
||||
|
||||
void AgentRolesWidget::loadRoles()
|
||||
{
|
||||
m_rolesList->clear();
|
||||
|
||||
const QList<AgentRole> roles = AgentRolesManager::loadAllRoles();
|
||||
for (const AgentRole &role : roles) {
|
||||
auto *item = new QListWidgetItem(role.name, m_rolesList);
|
||||
item->setData(Qt::UserRole, role.id);
|
||||
|
||||
QString tooltip = role.description;
|
||||
if (role.isBuiltin) {
|
||||
tooltip += "\n\n" + Tr::tr("(Built-in role)");
|
||||
item->setForeground(Qt::darkGray);
|
||||
}
|
||||
item->setToolTip(tooltip);
|
||||
}
|
||||
}
|
||||
|
||||
void AgentRolesWidget::updateButtons()
|
||||
{
|
||||
QListWidgetItem *selectedItem = m_rolesList->currentItem();
|
||||
bool hasSelection = selectedItem != nullptr;
|
||||
bool isBuiltin = false;
|
||||
|
||||
if (hasSelection) {
|
||||
QString roleId = selectedItem->data(Qt::UserRole).toString();
|
||||
AgentRole role = AgentRolesManager::loadRole(roleId);
|
||||
isBuiltin = role.isBuiltin;
|
||||
}
|
||||
|
||||
m_editButton->setEnabled(hasSelection);
|
||||
m_duplicateButton->setEnabled(hasSelection);
|
||||
m_deleteButton->setEnabled(hasSelection && !isBuiltin);
|
||||
}
|
||||
|
||||
void AgentRolesWidget::onAddRole()
|
||||
{
|
||||
AgentRoleDialog dialog{AgentRoleDialog::Action::Add, this};
|
||||
if (dialog.exec() != QDialog::Accepted)
|
||||
return;
|
||||
|
||||
AgentRole newRole = dialog.getRole();
|
||||
|
||||
if (AgentRolesManager::roleExists(newRole.id)) {
|
||||
QMessageBox::warning(
|
||||
this,
|
||||
Tr::tr("Role Already Exists"),
|
||||
Tr::tr("A role with ID '%1' already exists. Please use a different ID.")
|
||||
.arg(newRole.id));
|
||||
return;
|
||||
}
|
||||
|
||||
if (AgentRolesManager::saveRole(newRole)) {
|
||||
loadRoles();
|
||||
} else {
|
||||
QMessageBox::critical(
|
||||
this, Tr::tr("Error"), Tr::tr("Failed to save role '%1'.").arg(newRole.name));
|
||||
}
|
||||
}
|
||||
|
||||
void AgentRolesWidget::onEditRole()
|
||||
{
|
||||
QListWidgetItem *selectedItem = m_rolesList->currentItem();
|
||||
if (!selectedItem)
|
||||
return;
|
||||
|
||||
QString roleId = selectedItem->data(Qt::UserRole).toString();
|
||||
AgentRole role = AgentRolesManager::loadRole(roleId);
|
||||
|
||||
if (role.isBuiltin) {
|
||||
QMessageBox::information(
|
||||
this,
|
||||
Tr::tr("Cannot Edit Built-in Role"),
|
||||
Tr::tr(
|
||||
"Built-in roles cannot be edited. You can duplicate this role and modify the copy."));
|
||||
return;
|
||||
}
|
||||
|
||||
AgentRoleDialog dialog{role, AgentRoleDialog::Action::Edit, this};
|
||||
if (dialog.exec() != QDialog::Accepted)
|
||||
return;
|
||||
|
||||
AgentRole updatedRole = dialog.getRole();
|
||||
|
||||
if (AgentRolesManager::saveRole(updatedRole)) {
|
||||
loadRoles();
|
||||
} else {
|
||||
QMessageBox::critical(
|
||||
this, Tr::tr("Error"), Tr::tr("Failed to update role '%1'.").arg(updatedRole.name));
|
||||
}
|
||||
}
|
||||
|
||||
void AgentRolesWidget::onDuplicateRole()
|
||||
{
|
||||
QListWidgetItem *selectedItem = m_rolesList->currentItem();
|
||||
if (!selectedItem)
|
||||
return;
|
||||
|
||||
QString roleId = selectedItem->data(Qt::UserRole).toString();
|
||||
AgentRole role = AgentRolesManager::loadRole(roleId);
|
||||
|
||||
role.name += " (Copy)";
|
||||
role.id += "_copy";
|
||||
role.isBuiltin = false;
|
||||
|
||||
int counter = 1;
|
||||
QString baseId = role.id;
|
||||
while (AgentRolesManager::roleExists(role.id)) {
|
||||
role.id = baseId + QString::number(counter++);
|
||||
}
|
||||
|
||||
AgentRoleDialog dialog{role, AgentRoleDialog::Action::Duplicate, this};
|
||||
if (dialog.exec() != QDialog::Accepted)
|
||||
return;
|
||||
|
||||
AgentRole newRole = dialog.getRole();
|
||||
|
||||
if (AgentRolesManager::roleExists(newRole.id)) {
|
||||
QMessageBox::warning(
|
||||
this,
|
||||
Tr::tr("Role Already Exists"),
|
||||
Tr::tr("A role with ID '%1' already exists. Please use a different ID.")
|
||||
.arg(newRole.id));
|
||||
return;
|
||||
}
|
||||
|
||||
if (AgentRolesManager::saveRole(newRole)) {
|
||||
loadRoles();
|
||||
} else {
|
||||
QMessageBox::critical(this, Tr::tr("Error"), Tr::tr("Failed to duplicate role."));
|
||||
}
|
||||
}
|
||||
|
||||
void AgentRolesWidget::onDeleteRole()
|
||||
{
|
||||
QListWidgetItem *selectedItem = m_rolesList->currentItem();
|
||||
if (!selectedItem)
|
||||
return;
|
||||
|
||||
QString roleId = selectedItem->data(Qt::UserRole).toString();
|
||||
AgentRole role = AgentRolesManager::loadRole(roleId);
|
||||
|
||||
if (role.isBuiltin) {
|
||||
QMessageBox::information(
|
||||
this, Tr::tr("Cannot Delete Built-in Role"), Tr::tr("Built-in roles cannot be deleted."));
|
||||
return;
|
||||
}
|
||||
|
||||
QMessageBox::StandardButton reply = QMessageBox::question(
|
||||
this,
|
||||
Tr::tr("Delete Role"),
|
||||
Tr::tr("Are you sure you want to delete the role '%1'?").arg(role.name),
|
||||
QMessageBox::Yes | QMessageBox::No);
|
||||
|
||||
if (reply == QMessageBox::Yes) {
|
||||
if (AgentRolesManager::deleteRole(roleId)) {
|
||||
loadRoles();
|
||||
} else {
|
||||
QMessageBox::critical(
|
||||
this, Tr::tr("Error"), Tr::tr("Failed to delete role '%1'.").arg(role.name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AgentRolesWidget::onOpenRolesFolder()
|
||||
{
|
||||
QDesktopServices::openUrl(QUrl::fromLocalFile(AgentRolesManager::getConfigurationDirectory()));
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Settings
|
||||
@@ -1,43 +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 <coreplugin/dialogs/ioptionspage.h>
|
||||
|
||||
class QListWidget;
|
||||
class QPushButton;
|
||||
|
||||
namespace QodeAssist::Settings {
|
||||
|
||||
class AgentRolesWidget : public Core::IOptionsPageWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit AgentRolesWidget()
|
||||
{
|
||||
setupUI();
|
||||
loadRoles();
|
||||
}
|
||||
|
||||
private:
|
||||
void setupUI();
|
||||
void loadRoles();
|
||||
void updateButtons();
|
||||
|
||||
void onAddRole();
|
||||
void onEditRole();
|
||||
void onDuplicateRole();
|
||||
void onDeleteRole();
|
||||
void onOpenRolesFolder();
|
||||
|
||||
QListWidget *m_rolesList = nullptr;
|
||||
QPushButton *m_addButton = nullptr;
|
||||
QPushButton *m_editButton = nullptr;
|
||||
QPushButton *m_duplicateButton = nullptr;
|
||||
QPushButton *m_deleteButton = nullptr;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Settings
|
||||
56
sources/settings/AgentsSettings.cpp
Normal file
56
sources/settings/AgentsSettings.cpp
Normal file
@@ -0,0 +1,56 @@
|
||||
// Copyright (C) 2026 Petr Mironychev
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||
|
||||
#include "AgentsSettings.hpp"
|
||||
|
||||
#include <QDir>
|
||||
|
||||
#include "SettingsConstants.hpp"
|
||||
#include "SettingsTr.hpp"
|
||||
|
||||
namespace QodeAssist::Settings {
|
||||
|
||||
AgentsSettings &agentsSettings()
|
||||
{
|
||||
static AgentsSettings settings;
|
||||
return settings;
|
||||
}
|
||||
|
||||
AgentsSettings::AgentsSettings()
|
||||
{
|
||||
setAutoApply(false);
|
||||
|
||||
setDisplayName(Tr::tr("Agents"));
|
||||
|
||||
agentExtraPaths.setSettingsKey(Constants::AGENT_EXTRA_PATHS);
|
||||
agentExtraPaths.setLabelText(Tr::tr("Extra PATH for launching agents:"));
|
||||
agentExtraPaths.setDisplayStyle(Utils::StringAspect::LineEditDisplay);
|
||||
agentExtraPaths.setToolTip(
|
||||
Tr::tr(
|
||||
"Directories to prepend to PATH when launching ACP agents, and to search for "
|
||||
"the agent's executable. Useful when Qt Creator is started from the dock and "
|
||||
"doesn't see Homebrew, nvm, uv, etc. Separate multiple entries with '%1'.")
|
||||
.arg(QDir::listSeparator()));
|
||||
#ifdef Q_OS_MACOS
|
||||
agentExtraPaths.setDefaultValue(QStringLiteral("/opt/homebrew/bin:/usr/local/bin"));
|
||||
#else
|
||||
agentExtraPaths.setDefaultValue(QString{});
|
||||
#endif
|
||||
|
||||
agentForwardedVariables.setSettingsKey(Constants::AGENT_FORWARDED_VARIABLES);
|
||||
agentForwardedVariables.setLabelText(Tr::tr("Forward these variables to agents:"));
|
||||
agentForwardedVariables.setDisplayStyle(Utils::StringAspect::LineEditDisplay);
|
||||
agentForwardedVariables.setToolTip(
|
||||
Tr::tr(
|
||||
"Names of environment variables (not their values) passed on to ACP agents, "
|
||||
"separated by spaces or commas. A variable already present in Qt Creator's own "
|
||||
"environment is used as is; on macOS and Linux the rest are read once from a login "
|
||||
"shell, so a token exported in your shell profile reaches agents even when Qt "
|
||||
"Creator was started from the dock."));
|
||||
agentForwardedVariables.setDefaultValue(QStringLiteral("CLAUDE_CODE_OAUTH_TOKEN"));
|
||||
|
||||
readSettings();
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Settings
|
||||
22
sources/settings/AgentsSettings.hpp
Normal file
22
sources/settings/AgentsSettings.hpp
Normal file
@@ -0,0 +1,22 @@
|
||||
// Copyright (C) 2026 Petr Mironychev
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <utils/aspects.h>
|
||||
|
||||
namespace QodeAssist::Settings {
|
||||
|
||||
class AgentsSettings : public Utils::AspectContainer
|
||||
{
|
||||
public:
|
||||
AgentsSettings();
|
||||
|
||||
Utils::StringAspect agentExtraPaths{this};
|
||||
Utils::StringAspect agentForwardedVariables{this};
|
||||
};
|
||||
|
||||
AgentsSettings &agentsSettings();
|
||||
|
||||
} // namespace QodeAssist::Settings
|
||||
348
sources/settings/AgentsWidget.cpp
Normal file
348
sources/settings/AgentsWidget.cpp
Normal file
@@ -0,0 +1,348 @@
|
||||
// Copyright (C) 2026 Petr Mironychev
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||
|
||||
#include "AgentsWidget.hpp"
|
||||
|
||||
#include <QDesktopServices>
|
||||
#include <QDir>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QListWidget>
|
||||
#include <QPushButton>
|
||||
#include <QTextBrowser>
|
||||
#include <QUrl>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include <coreplugin/dialogs/ioptionspage.h>
|
||||
#include <projectexplorer/project.h>
|
||||
#include <projectexplorer/projectmanager.h>
|
||||
|
||||
#include "AgentsSettings.hpp"
|
||||
#include "SettingsConstants.hpp"
|
||||
#include "SettingsTr.hpp"
|
||||
#include "acp/AgentCatalogStore.hpp"
|
||||
#include "acp/AgentTester.hpp"
|
||||
|
||||
namespace QodeAssist::Settings {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int agentIdRole = Qt::UserRole;
|
||||
|
||||
QString distributionSummary(const Acp::AgentDefinition &agent)
|
||||
{
|
||||
const QString kind = Acp::agentDistributionName(agent.distribution.kind);
|
||||
switch (agent.distribution.kind) {
|
||||
case Acp::AgentDistributionKind::Npx:
|
||||
case Acp::AgentDistributionKind::Uvx:
|
||||
return QStringLiteral("%1 (%2)").arg(kind, agent.distribution.package);
|
||||
case Acp::AgentDistributionKind::Command:
|
||||
return QStringLiteral("%1 (%2)").arg(kind, agent.distribution.command);
|
||||
case Acp::AgentDistributionKind::Binary:
|
||||
case Acp::AgentDistributionKind::Unknown:
|
||||
return kind;
|
||||
}
|
||||
return kind;
|
||||
}
|
||||
|
||||
QString linkRow(const QString &label, const QString &url)
|
||||
{
|
||||
if (url.isEmpty())
|
||||
return {};
|
||||
return QStringLiteral("<b>%1</b> <a href=\"%2\">%2</a><br>")
|
||||
.arg(label.toHtmlEscaped(), url.toHtmlEscaped());
|
||||
}
|
||||
|
||||
QString unavailableGuidance(const Acp::AgentDefinition &agent)
|
||||
{
|
||||
if (agent.distribution.kind == Acp::AgentDistributionKind::Binary) {
|
||||
QString text = Tr::tr(
|
||||
"This agent ships as a platform binary. QodeAssist does not download binaries yet: "
|
||||
"install it manually, then add a JSON file to the agents folder with a "
|
||||
"\"command\" distribution pointing at the executable.");
|
||||
|
||||
if (const Acp::AgentBinaryTarget *target = Acp::binaryTargetForCurrentPlatform(agent)) {
|
||||
text += QStringLiteral("<br><br>")
|
||||
+ Tr::tr("Download for %1:").arg(target->platform.toHtmlEscaped())
|
||||
+ QStringLiteral(" <a href=\"%1\">%1</a>").arg(target->archive.toHtmlEscaped());
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
return Tr::tr("This agent has no distribution QodeAssist can launch.");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
AgentsWidget::AgentsWidget(Acp::AgentCatalogStore *store)
|
||||
: m_store(store)
|
||||
, m_tester(new Acp::AgentTester(this))
|
||||
{
|
||||
setupUI();
|
||||
|
||||
connect(m_store, &Acp::AgentCatalogStore::catalogChanged, this, &AgentsWidget::populateList);
|
||||
connect(
|
||||
m_store,
|
||||
&Acp::AgentCatalogStore::refreshFinished,
|
||||
this,
|
||||
[this](bool ok, const QString &error) {
|
||||
showStatus(
|
||||
ok ? Tr::tr("Agent list refreshed from the registry.")
|
||||
: Tr::tr("Registry refresh failed: %1").arg(error));
|
||||
updateButtons();
|
||||
});
|
||||
|
||||
connect(m_tester, &Acp::AgentTester::finished, this, [this](bool ok, const QString &report) {
|
||||
showStatus(ok ? Tr::tr("Agent responded.") : Tr::tr("Agent test failed."));
|
||||
m_details->append(
|
||||
QStringLiteral("<hr><b>%1</b><pre>%2</pre>")
|
||||
.arg(ok ? Tr::tr("Test result") : Tr::tr("Test failed"), report.toHtmlEscaped()));
|
||||
updateButtons();
|
||||
});
|
||||
|
||||
populateList();
|
||||
}
|
||||
|
||||
void AgentsWidget::apply()
|
||||
{
|
||||
auto &settings = agentsSettings();
|
||||
settings.agentExtraPaths.setValue(m_extraPathsEdit->text());
|
||||
settings.agentForwardedVariables.setValue(m_forwardedVariablesEdit->text());
|
||||
settings.writeSettings();
|
||||
}
|
||||
|
||||
QLineEdit *AgentsWidget::addSettingRow(QVBoxLayout *layout, Utils::StringAspect &aspect)
|
||||
{
|
||||
auto *row = new QHBoxLayout();
|
||||
auto *label = new QLabel(aspect.labelText(), this);
|
||||
label->setToolTip(aspect.toolTip());
|
||||
|
||||
auto *edit = new QLineEdit(aspect.volatileValue(), this);
|
||||
edit->setToolTip(aspect.toolTip());
|
||||
connect(edit, &QLineEdit::textChanged, &aspect, [&aspect](const QString &text) {
|
||||
aspect.setVolatileValue(text);
|
||||
});
|
||||
|
||||
row->addWidget(label);
|
||||
row->addWidget(edit, 1);
|
||||
layout->addLayout(row);
|
||||
|
||||
return edit;
|
||||
}
|
||||
|
||||
void AgentsWidget::setupUI()
|
||||
{
|
||||
auto *mainLayout = new QVBoxLayout(this);
|
||||
|
||||
auto *headerLayout = new QHBoxLayout();
|
||||
auto *info = new QLabel(
|
||||
Tr::tr(
|
||||
"ACP agents from your own JSON files, the agent registry, and the bundled "
|
||||
"snapshot. Definitions with the same id override each other in that order."),
|
||||
this);
|
||||
info->setWordWrap(true);
|
||||
headerLayout->addWidget(info, 1);
|
||||
|
||||
auto *openFolderButton = new QPushButton(Tr::tr("Open Agents Folder..."), this);
|
||||
connect(openFolderButton, &QPushButton::clicked, this, &AgentsWidget::onOpenAgentsFolder);
|
||||
headerLayout->addWidget(openFolderButton);
|
||||
mainLayout->addLayout(headerLayout);
|
||||
|
||||
m_extraPathsEdit = addSettingRow(mainLayout, agentsSettings().agentExtraPaths);
|
||||
m_forwardedVariablesEdit = addSettingRow(mainLayout, agentsSettings().agentForwardedVariables);
|
||||
|
||||
auto *contentLayout = new QHBoxLayout();
|
||||
|
||||
m_agentsList = new QListWidget(this);
|
||||
m_agentsList->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
connect(m_agentsList, &QListWidget::itemSelectionChanged, this, &AgentsWidget::showSelectedAgent);
|
||||
contentLayout->addWidget(m_agentsList, 1);
|
||||
|
||||
auto *buttonsLayout = new QVBoxLayout();
|
||||
|
||||
m_testButton = new QPushButton(Tr::tr("Test"), this);
|
||||
m_testButton->setToolTip(
|
||||
Tr::tr("Start the agent, run the ACP handshake and report its capabilities."));
|
||||
connect(m_testButton, &QPushButton::clicked, this, &AgentsWidget::onTest);
|
||||
buttonsLayout->addWidget(m_testButton);
|
||||
|
||||
m_reloadButton = new QPushButton(Tr::tr("Reload"), this);
|
||||
m_reloadButton->setToolTip(Tr::tr("Re-read the agent JSON files and the cached registry."));
|
||||
connect(m_reloadButton, &QPushButton::clicked, this, [this]() {
|
||||
m_store->reload();
|
||||
showStatus(Tr::tr("Agent list reloaded from disk."));
|
||||
});
|
||||
buttonsLayout->addWidget(m_reloadButton);
|
||||
|
||||
m_refreshButton = new QPushButton(Tr::tr("Refresh from Registry"), this);
|
||||
m_refreshButton->setToolTip(Tr::tr("Download the agent registry and update the local cache."));
|
||||
connect(m_refreshButton, &QPushButton::clicked, this, &AgentsWidget::onRefresh);
|
||||
buttonsLayout->addWidget(m_refreshButton);
|
||||
|
||||
buttonsLayout->addStretch();
|
||||
contentLayout->addLayout(buttonsLayout);
|
||||
mainLayout->addLayout(contentLayout, 1);
|
||||
|
||||
m_details = new QTextBrowser(this);
|
||||
m_details->setOpenExternalLinks(true);
|
||||
m_details->setMinimumHeight(160);
|
||||
mainLayout->addWidget(m_details, 1);
|
||||
|
||||
m_status = new QLabel(this);
|
||||
m_status->setWordWrap(true);
|
||||
mainLayout->addWidget(m_status);
|
||||
}
|
||||
|
||||
void AgentsWidget::populateList()
|
||||
{
|
||||
const QString selectedId = m_agentsList->currentItem()
|
||||
? m_agentsList->currentItem()->data(agentIdRole).toString()
|
||||
: QString();
|
||||
|
||||
m_agentsList->clear();
|
||||
|
||||
const QList<Acp::AgentDefinition> agents = m_store->catalog().agents();
|
||||
for (const Acp::AgentDefinition &agent : agents) {
|
||||
const QString label = agent.version.isEmpty()
|
||||
? agent.name
|
||||
: QStringLiteral("%1 %2").arg(agent.name, agent.version);
|
||||
auto *item = new QListWidgetItem(label, m_agentsList);
|
||||
item->setData(agentIdRole, agent.id);
|
||||
|
||||
if (agent.isLaunchable()) {
|
||||
item->setToolTip(
|
||||
QStringLiteral("%1 — %2")
|
||||
.arg(distributionSummary(agent), Acp::agentSourceName(agent.source)));
|
||||
} else {
|
||||
item->setForeground(palette().brush(QPalette::Disabled, QPalette::Text));
|
||||
item->setToolTip(unavailableGuidance(agent));
|
||||
}
|
||||
|
||||
if (agent.id == selectedId)
|
||||
m_agentsList->setCurrentItem(item);
|
||||
}
|
||||
|
||||
showStatus(
|
||||
m_store->hasCachedRegistry()
|
||||
? QString()
|
||||
: Tr::tr(
|
||||
"Showing the bundled agent snapshot — press Refresh from Registry for the "
|
||||
"published list."));
|
||||
showSelectedAgent();
|
||||
}
|
||||
|
||||
void AgentsWidget::showStatus(const QString &message)
|
||||
{
|
||||
const QStringList warnings = m_store->warnings();
|
||||
if (warnings.isEmpty()) {
|
||||
m_status->setText(message);
|
||||
return;
|
||||
}
|
||||
|
||||
const QString ignored = Tr::tr("Ignored entries: %1").arg(warnings.join(QStringLiteral("; ")));
|
||||
m_status->setText(message.isEmpty() ? ignored : QStringLiteral("%1 %2").arg(message, ignored));
|
||||
}
|
||||
|
||||
void AgentsWidget::showSelectedAgent()
|
||||
{
|
||||
updateButtons();
|
||||
|
||||
const auto agent = selectedAgent();
|
||||
if (!agent) {
|
||||
m_details->clear();
|
||||
return;
|
||||
}
|
||||
|
||||
QString html = QStringLiteral("<h3>%1</h3>").arg(agent->name.toHtmlEscaped());
|
||||
if (!agent->description.isEmpty())
|
||||
html += QStringLiteral("<p>%1</p>").arg(agent->description.toHtmlEscaped());
|
||||
|
||||
html += QStringLiteral("<p>");
|
||||
html += QStringLiteral("<b>%1</b> %2<br>").arg(Tr::tr("Id:"), agent->id.toHtmlEscaped());
|
||||
html += QStringLiteral("<b>%1</b> %2<br>")
|
||||
.arg(Tr::tr("Distribution:"), distributionSummary(*agent).toHtmlEscaped());
|
||||
html += QStringLiteral("<b>%1</b> %2<br>")
|
||||
.arg(Tr::tr("Defined by:"), agent->origin.toHtmlEscaped());
|
||||
if (!agent->license.isEmpty())
|
||||
html += QStringLiteral("<b>%1</b> %2<br>")
|
||||
.arg(Tr::tr("License:"), agent->license.toHtmlEscaped());
|
||||
if (!agent->authors.isEmpty())
|
||||
html += QStringLiteral("<b>%1</b> %2<br>")
|
||||
.arg(
|
||||
Tr::tr("Authors:"),
|
||||
agent->authors.join(QStringLiteral(", ")).toHtmlEscaped());
|
||||
html += linkRow(Tr::tr("Repository:"), agent->repository);
|
||||
html += linkRow(Tr::tr("Website:"), agent->website);
|
||||
html += QStringLiteral("</p>");
|
||||
|
||||
if (!agent->isLaunchable())
|
||||
html += QStringLiteral("<p>%1</p>").arg(unavailableGuidance(*agent));
|
||||
|
||||
m_details->setHtml(html);
|
||||
}
|
||||
|
||||
void AgentsWidget::updateButtons()
|
||||
{
|
||||
const auto agent = selectedAgent();
|
||||
const bool testing = m_tester->isRunning();
|
||||
m_testButton->setText(testing ? Tr::tr("Cancel Test") : Tr::tr("Test"));
|
||||
m_testButton->setEnabled(testing || (agent && agent->isLaunchable()));
|
||||
m_refreshButton->setEnabled(!m_store->isRefreshing());
|
||||
m_refreshButton->setText(
|
||||
m_store->isRefreshing() ? Tr::tr("Refreshing...") : Tr::tr("Refresh from Registry"));
|
||||
}
|
||||
|
||||
void AgentsWidget::onTest()
|
||||
{
|
||||
if (m_tester->isRunning()) {
|
||||
m_tester->cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
const auto agent = selectedAgent();
|
||||
if (!agent)
|
||||
return;
|
||||
|
||||
showStatus(Tr::tr("Starting %1...").arg(agent->name));
|
||||
m_tester->start(*agent, testWorkingDirectory());
|
||||
updateButtons();
|
||||
}
|
||||
|
||||
void AgentsWidget::onRefresh()
|
||||
{
|
||||
showStatus(Tr::tr("Downloading the agent registry..."));
|
||||
m_store->refreshFromRegistry();
|
||||
updateButtons();
|
||||
}
|
||||
|
||||
void AgentsWidget::onOpenAgentsFolder()
|
||||
{
|
||||
QDesktopServices::openUrl(QUrl::fromLocalFile(Acp::AgentCatalogStore::userAgentsDirectory()));
|
||||
}
|
||||
|
||||
std::optional<Acp::AgentDefinition> AgentsWidget::selectedAgent() const
|
||||
{
|
||||
const QListWidgetItem *item = m_agentsList->currentItem();
|
||||
if (!item)
|
||||
return std::nullopt;
|
||||
return m_store->catalog().agent(item->data(agentIdRole).toString());
|
||||
}
|
||||
|
||||
QString AgentsWidget::testWorkingDirectory()
|
||||
{
|
||||
if (const ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject())
|
||||
return project->projectDirectory().path();
|
||||
return QDir::homePath();
|
||||
}
|
||||
|
||||
AgentsSettingsPage::AgentsSettingsPage(Acp::AgentCatalogStore *store)
|
||||
{
|
||||
setId(Constants::QODE_ASSIST_AGENTS_SETTINGS_PAGE_ID);
|
||||
setDisplayName(Tr::tr("Agents"));
|
||||
setCategory(Constants::QODE_ASSIST_GENERAL_OPTIONS_CATEGORY);
|
||||
setWidgetCreator([store]() { return new AgentsWidget(store); });
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Settings
|
||||
74
sources/settings/AgentsWidget.hpp
Normal file
74
sources/settings/AgentsWidget.hpp
Normal file
@@ -0,0 +1,74 @@
|
||||
// Copyright (C) 2026 Petr Mironychev
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include <coreplugin/dialogs/ioptionspage.h>
|
||||
#include <utils/aspects.h>
|
||||
|
||||
#include "acp/AgentDefinition.hpp"
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QLabel;
|
||||
class QLineEdit;
|
||||
class QListWidget;
|
||||
class QPushButton;
|
||||
class QTextBrowser;
|
||||
class QVBoxLayout;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
namespace QodeAssist::Acp {
|
||||
class AgentCatalogStore;
|
||||
class AgentTester;
|
||||
} // namespace QodeAssist::Acp
|
||||
|
||||
namespace QodeAssist::Settings {
|
||||
|
||||
class AgentsWidget : public Core::IOptionsPageWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit AgentsWidget(Acp::AgentCatalogStore *store);
|
||||
|
||||
void apply() override;
|
||||
|
||||
private:
|
||||
void setupUI();
|
||||
void populateList();
|
||||
void updateButtons();
|
||||
void showSelectedAgent();
|
||||
void showStatus(const QString &message);
|
||||
|
||||
void onTest();
|
||||
void onRefresh();
|
||||
void onOpenAgentsFolder();
|
||||
|
||||
std::optional<Acp::AgentDefinition> selectedAgent() const;
|
||||
static QString testWorkingDirectory();
|
||||
|
||||
Acp::AgentCatalogStore *m_store = nullptr;
|
||||
Acp::AgentTester *m_tester = nullptr;
|
||||
|
||||
QLineEdit *addSettingRow(QVBoxLayout *layout, Utils::StringAspect &aspect);
|
||||
|
||||
QLineEdit *m_extraPathsEdit = nullptr;
|
||||
QLineEdit *m_forwardedVariablesEdit = nullptr;
|
||||
QListWidget *m_agentsList = nullptr;
|
||||
QTextBrowser *m_details = nullptr;
|
||||
QLabel *m_status = nullptr;
|
||||
QPushButton *m_testButton = nullptr;
|
||||
QPushButton *m_reloadButton = nullptr;
|
||||
QPushButton *m_refreshButton = nullptr;
|
||||
};
|
||||
|
||||
class AgentsSettingsPage : public Core::IOptionsPage
|
||||
{
|
||||
public:
|
||||
explicit AgentsSettingsPage(Acp::AgentCatalogStore *store);
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Settings
|
||||
@@ -18,9 +18,6 @@ add_library(QodeAssistSettings STATIC
|
||||
ProviderNameMigration.hpp
|
||||
PluginUpdater.hpp PluginUpdater.cpp
|
||||
UpdateDialog.hpp UpdateDialog.cpp
|
||||
AgentRole.hpp AgentRole.cpp
|
||||
AgentRoleDialog.hpp AgentRoleDialog.cpp
|
||||
AgentRolesWidget.hpp AgentRolesWidget.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(QodeAssistSettings
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
#include "SettingsConstants.hpp"
|
||||
#include "SettingsTr.hpp"
|
||||
#include "SettingsUtils.hpp"
|
||||
#include "AgentRolesWidget.hpp"
|
||||
|
||||
namespace QodeAssist::Settings {
|
||||
|
||||
@@ -30,10 +29,6 @@ ChatAssistantSettings::ChatAssistantSettings()
|
||||
|
||||
setDisplayName(Tr::tr("Chat Assistant"));
|
||||
|
||||
linkOpenFiles.setSettingsKey(Constants::CA_LINK_OPEN_FILES);
|
||||
linkOpenFiles.setLabelText(Tr::tr("Sync open files with assistant by default"));
|
||||
linkOpenFiles.setDefaultValue(false);
|
||||
|
||||
autosave.setSettingsKey(Constants::CA_AUTOSAVE);
|
||||
autosave.setDefaultValue(true);
|
||||
autosave.setLabelText(Tr::tr("Enable autosave when message received"));
|
||||
@@ -252,9 +247,6 @@ ChatAssistantSettings::ChatAssistantSettings()
|
||||
chatRenderer.setDefaultValue("rhi");
|
||||
#endif
|
||||
|
||||
lastUsedRoleId.setSettingsKey(Constants::CA_LAST_USED_ROLE);
|
||||
lastUsedRoleId.setDefaultValue("");
|
||||
|
||||
resetToDefaults.m_buttonText = TrConstants::RESET_TO_DEFAULTS;
|
||||
|
||||
readSettings();
|
||||
@@ -297,7 +289,6 @@ ChatAssistantSettings::ChatAssistantSettings()
|
||||
Group{
|
||||
title(Tr::tr("Chat Settings")),
|
||||
Column{
|
||||
linkOpenFiles,
|
||||
autosave,
|
||||
Row{autoCompress, autoCompressThreshold, Stretch{1}}}},
|
||||
Space{8},
|
||||
@@ -371,7 +362,6 @@ void ChatAssistantSettings::resetSettingsToDefaults()
|
||||
resetAspect(thinkingBudgetTokens);
|
||||
resetAspect(thinkingMaxTokens);
|
||||
resetAspect(openAIResponsesReasoningEffort);
|
||||
resetAspect(linkOpenFiles);
|
||||
resetAspect(enableChatTools);
|
||||
resetAspect(textFontFamily);
|
||||
resetAspect(codeFontFamily);
|
||||
@@ -397,18 +387,4 @@ public:
|
||||
|
||||
const ChatAssistantSettingsPage chatAssistantSettingsPage;
|
||||
|
||||
class AgentRolesSettingsPage : public Core::IOptionsPage
|
||||
{
|
||||
public:
|
||||
AgentRolesSettingsPage()
|
||||
{
|
||||
setId("QodeAssist.AgentRoles");
|
||||
setDisplayName(Tr::tr("Agent Roles"));
|
||||
setCategory(Constants::QODE_ASSIST_GENERAL_OPTIONS_CATEGORY);
|
||||
setWidgetCreator([]() { return new AgentRolesWidget(); });
|
||||
}
|
||||
};
|
||||
|
||||
const AgentRolesSettingsPage agentRolesSettingsPage;
|
||||
|
||||
} // namespace QodeAssist::Settings
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
|
||||
#include <utils/aspects.h>
|
||||
|
||||
#include "AgentRole.hpp"
|
||||
#include "ButtonAspect.hpp"
|
||||
|
||||
namespace QodeAssist::Settings {
|
||||
@@ -19,7 +18,6 @@ public:
|
||||
ButtonAspect resetToDefaults{this};
|
||||
|
||||
// Chat settings
|
||||
Utils::BoolAspect linkOpenFiles{this};
|
||||
Utils::BoolAspect autosave{this};
|
||||
Utils::BoolAspect enableChatInBottomToolBar{this};
|
||||
Utils::BoolAspect enableChatInNavigationPanel{this};
|
||||
@@ -69,8 +67,6 @@ public:
|
||||
|
||||
Utils::SelectionAspect chatRenderer{this};
|
||||
|
||||
Utils::StringAspect lastUsedRoleId{this};
|
||||
|
||||
private:
|
||||
void setupConnections();
|
||||
void resetSettingsToDefaults();
|
||||
|
||||
@@ -303,14 +303,6 @@ CodeCompletionSettings::CodeCompletionSettings()
|
||||
useOpenFilesContext.setLabelText(Tr::tr("Include context from open files"));
|
||||
useOpenFilesContext.setDefaultValue(false);
|
||||
|
||||
useProjectChangesCache.setSettingsKey(Constants::CC_USE_PROJECT_CHANGES_CACHE);
|
||||
useProjectChangesCache.setDefaultValue(true);
|
||||
useProjectChangesCache.setLabelText(Tr::tr("Max Changes Cache Size:"));
|
||||
|
||||
maxChangesCacheSize.setSettingsKey(Constants::CC_MAX_CHANGES_CACHE_SIZE);
|
||||
maxChangesCacheSize.setRange(2, 1000);
|
||||
maxChangesCacheSize.setDefaultValue(10);
|
||||
|
||||
// Ollama Settings
|
||||
ollamaLivetime.setSettingsKey(Constants::CC_OLLAMA_LIVETIME);
|
||||
ollamaLivetime.setToolTip(
|
||||
@@ -389,8 +381,7 @@ CodeCompletionSettings::CodeCompletionSettings()
|
||||
systemPromptForNonFimModels,
|
||||
userMessageTemplateForCC,
|
||||
customLanguages,
|
||||
}},
|
||||
Row{useProjectChangesCache, maxChangesCacheSize, Stretch{1}}};
|
||||
}}};
|
||||
|
||||
auto generalSettings = Column{
|
||||
autoCompletion,
|
||||
@@ -486,8 +477,6 @@ void CodeCompletionSettings::resetSettingsToDefaults()
|
||||
resetAspect(readStringsAfterCursor);
|
||||
resetAspect(useSystemPrompt);
|
||||
resetAspect(systemPrompt);
|
||||
resetAspect(useProjectChangesCache);
|
||||
resetAspect(maxChangesCacheSize);
|
||||
resetAspect(ollamaLivetime);
|
||||
resetAspect(contextWindow);
|
||||
resetAspect(openAIResponsesReasoningEffort);
|
||||
|
||||
@@ -68,8 +68,6 @@ public:
|
||||
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};
|
||||
|
||||
@@ -82,7 +82,6 @@ const char CC_MODEL_OUTPUT_HANDLER[] = "QodeAssist.ccModelOutputHandler";
|
||||
const char CA_AUTO_APPLY_FILE_EDITS[] = "QodeAssist.caAutoApplyFileEdits";
|
||||
const char CA_AUTO_COMPRESS[] = "QodeAssist.caAutoCompress";
|
||||
const char CA_AUTO_COMPRESS_THRESHOLD[] = "QodeAssist.caAutoCompressThreshold";
|
||||
const char CA_LINK_OPEN_FILES[] = "QodeAssist.caLinkOpenFiles";
|
||||
const char CA_AUTOSAVE[] = "QodeAssist.caAutosave";
|
||||
const char CC_CUSTOM_LANGUAGES[] = "QodeAssist.ccCustomLanguages";
|
||||
|
||||
@@ -122,6 +121,10 @@ const char MCP_SERVER_PORT[] = "QodeAssist.mcpServerPort";
|
||||
const char MCP_ENABLE_CLIENTS[] = "QodeAssist.mcpEnableClients";
|
||||
const char MCP_CLIENT_EXTRA_PATHS[] = "QodeAssist.mcpClientExtraPaths";
|
||||
|
||||
// ACP agent settings
|
||||
const char AGENT_EXTRA_PATHS[] = "QodeAssist.agentExtraPaths";
|
||||
const char AGENT_FORWARDED_VARIABLES[] = "QodeAssist.agentForwardedVariables";
|
||||
|
||||
const char QODE_ASSIST_GENERAL_OPTIONS_ID[] = "QodeAssist.GeneralOptions";
|
||||
const char QODE_ASSIST_GENERAL_SETTINGS_PAGE_ID[] = "QodeAssist.1GeneralSettingsPageId";
|
||||
const char QODE_ASSIST_CODE_COMPLETION_SETTINGS_PAGE_ID[]
|
||||
@@ -133,6 +136,7 @@ const char QODE_ASSIST_QUICK_REFACTOR_SETTINGS_PAGE_ID[]
|
||||
const char QODE_ASSIST_TOOLS_SETTINGS_PAGE_ID[] = "QodeAssist.5ToolsSettingsPageId";
|
||||
const char QODE_ASSIST_MCP_SETTINGS_PAGE_ID[] = "QodeAssist.6McpSettingsPageId";
|
||||
const char QODE_ASSIST_SKILLS_SETTINGS_PAGE_ID[] = "QodeAssist.8SkillsSettingsPageId";
|
||||
const char QODE_ASSIST_AGENTS_SETTINGS_PAGE_ID[] = "QodeAssist.9AgentsSettingsPageId";
|
||||
|
||||
const char QODE_ASSIST_GENERAL_OPTIONS_CATEGORY[] = "QodeAssist.Category";
|
||||
const char QODE_ASSIST_GENERAL_OPTIONS_DISPLAY_CATEGORY[] = "QodeAssist";
|
||||
@@ -176,8 +180,6 @@ 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";
|
||||
|
||||
@@ -224,7 +226,6 @@ const char CA_CODE_FONT_SIZE[] = "QodeAssist.caCodeFontSize";
|
||||
const char CA_TEXT_FORMAT[] = "QodeAssist.caTextFormat";
|
||||
const char CA_CHAT_RENDERER[] = "QodeAssist.caChatRenderer";
|
||||
|
||||
const char CA_LAST_USED_ROLE[] = "QodeAssist.caLastUsedRole";
|
||||
|
||||
// quick refactor preset prompt settings
|
||||
const char QR_TEMPERATURE[] = "QodeAssist.qrTemperature";
|
||||
|
||||
Reference in New Issue
Block a user