fix: Disable shown agent without selecting

This commit is contained in:
Petr Mironychev
2026-06-30 13:45:48 +02:00
parent 714b1367b7
commit c070d65366
7 changed files with 111 additions and 32 deletions

View File

@@ -92,7 +92,10 @@ AgentDetailPane::AgentDetailPane(QWidget *parent)
m_path->setPalette(pp); m_path->setPalette(pp);
m_openBtn = new QPushButton(tr("Open in editor"), this); m_openBtn = new QPushButton(tr("Open in editor"), this);
m_dupBtn = new QPushButton(tr("Duplicate"), this); m_dupBtn = new QPushButton(tr("Customize a copy"), this);
m_dupBtn->setToolTip(
tr("Create an editable user agent that inherits from this one — "
"override only the fields you want."));
m_deleteBtn = new QPushButton(tr("Delete"), this); m_deleteBtn = new QPushButton(tr("Delete"), this);
connect(m_openBtn, &QPushButton::clicked, this, [this] { connect(m_openBtn, &QPushButton::clicked, this, [this] {
if (m_current) if (m_current)
@@ -107,7 +110,8 @@ AgentDetailPane::AgentDetailPane(QWidget *parent)
emit deleteRequested(*m_current); emit deleteRequested(*m_current);
}); });
auto *actions = new QHBoxLayout; m_actionsHolder = new QWidget(this);
auto *actions = new QHBoxLayout(m_actionsHolder);
actions->setContentsMargins(0, 0, 0, 0); actions->setContentsMargins(0, 0, 0, 0);
actions->setSpacing(6); actions->setSpacing(6);
actions->addWidget(m_openBtn); actions->addWidget(m_openBtn);
@@ -130,11 +134,11 @@ AgentDetailPane::AgentDetailPane(QWidget *parent)
headerRow->setContentsMargins(0, 0, 0, 0); headerRow->setContentsMargins(0, 0, 0, 0);
headerRow->setSpacing(8); headerRow->setSpacing(8);
headerRow->addLayout(headerLeft, 1); headerRow->addLayout(headerLeft, 1);
headerRow->addLayout(actions); headerRow->addWidget(m_actionsHolder);
auto *headerSep = new QFrame(this); m_headerSep = new QFrame(this);
headerSep->setFrameShape(QFrame::HLine); m_headerSep->setFrameShape(QFrame::HLine);
headerSep->setFrameShadow(QFrame::Sunken); m_headerSep->setFrameShadow(QFrame::Sunken);
m_description = new QLabel(this); m_description = new QLabel(this);
m_description->setWordWrap(true); m_description->setWordWrap(true);
@@ -319,18 +323,41 @@ AgentDetailPane::AgentDetailPane(QWidget *parent)
m_rawToggle->setText(on ? tr("▾ Hide raw TOML") : tr("▸ Show raw TOML")); m_rawToggle->setText(on ? tr("▾ Hide raw TOML") : tr("▸ Show raw TOML"));
}); });
m_baseRawToggle = new QToolButton(this);
m_baseRawToggle->setText(tr("▸ Show base agent TOML"));
m_baseRawToggle->setCursor(Qt::PointingHandCursor);
m_baseRawToggle->setAutoRaise(true);
m_baseRawToggle->setCheckable(true);
m_baseRawToml = new QPlainTextEdit(this);
m_baseRawToml->setReadOnly(true);
m_baseRawToml->setFont(monospaceFont(11));
m_baseRawToml->setMinimumHeight(140);
m_baseRawToml->setVisible(false);
connect(m_baseRawToggle, &QToolButton::toggled, this, [this](bool on) {
m_baseRawToml->setVisible(on);
m_baseRawToggle->setText(on ? tr("▾ Hide base agent TOML") : tr("▸ Show base agent TOML"));
});
m_body = new QWidget(this);
auto *bodyLayout = new QVBoxLayout(m_body);
bodyLayout->setContentsMargins(0, 0, 0, 0);
bodyLayout->setSpacing(10);
bodyLayout->addWidget(identity);
bodyLayout->addWidget(connection);
bodyLayout->addWidget(capabilities);
bodyLayout->addWidget(match);
bodyLayout->addWidget(m_rawToggle, 0, Qt::AlignLeft);
bodyLayout->addWidget(m_rawToml);
bodyLayout->addWidget(m_baseRawToggle, 0, Qt::AlignLeft);
bodyLayout->addWidget(m_baseRawToml);
auto *root = new QVBoxLayout(this); auto *root = new QVBoxLayout(this);
root->setContentsMargins(12, 12, 12, 12); root->setContentsMargins(12, 12, 12, 12);
root->setSpacing(10); root->setSpacing(10);
root->addLayout(headerRow); root->addLayout(headerRow);
root->addWidget(headerSep); root->addWidget(m_headerSep);
root->addWidget(m_description); root->addWidget(m_description);
root->addWidget(identity); root->addWidget(m_body);
root->addWidget(connection);
root->addWidget(capabilities);
root->addWidget(match);
root->addWidget(m_rawToggle, 0, Qt::AlignLeft);
root->addWidget(m_rawToml);
root->addStretch(1); root->addStretch(1);
clear(); clear();
@@ -494,6 +521,8 @@ void AgentDetailPane::setAgent(const AgentConfig &cfg)
m_current = &m_currentStorage; m_current = &m_currentStorage;
const bool user = cfg.isUserSource(); const bool user = cfg.isUserSource();
setDetailsVisible(true);
m_name->setText(cfg.name); m_name->setText(cfg.name);
m_path->setText(cfg.sourcePath); m_path->setText(cfg.sourcePath);
m_description->setText( m_description->setText(
@@ -576,23 +605,17 @@ void AgentDetailPane::setAgent(const AgentConfig &cfg)
m_filePatternsValue->setText(cfg.match.filePatterns.join(QStringLiteral(", "))); m_filePatternsValue->setText(cfg.match.filePatterns.join(QStringLiteral(", ")));
m_filePatternsValue->setPlaceholderText(tr("(matches every file)")); m_filePatternsValue->setPlaceholderText(tr("(matches every file)"));
const FileReadResult raw = readFileTextCapped(cfg.sourcePath, kRawTomlMaxBytes); fillRawToml(m_rawToml, cfg.sourcePath);
switch (raw.status) {
case FileReadStatus::Ok: const QString basePath
m_rawToml->setPlainText(raw.content); = m_agentFactory ? m_agentFactory->sourcePathForName(cfg.extendsName) : QString();
break; const bool hasBase = !cfg.extendsName.isEmpty() && !basePath.isEmpty();
case FileReadStatus::Truncated: m_baseRawToggle->setVisible(hasBase);
m_rawToml->setPlainText( m_baseRawToml->setVisible(hasBase && m_baseRawToggle->isChecked());
raw.content + QStringLiteral("\n\n") if (hasBase)
+ tr("(truncated at %1 bytes)").arg(kRawTomlMaxBytes)); fillRawToml(m_baseRawToml, basePath);
break; else
case FileReadStatus::Empty: m_baseRawToml->clear();
m_rawToml->setPlainText(tr("(source file is empty)"));
break;
case FileReadStatus::OpenFailed:
m_rawToml->setPlainText(tr("(source file unavailable: %1)").arg(raw.error));
break;
}
m_openBtn->setEnabled(user); m_openBtn->setEnabled(user);
m_openBtn->setToolTip( m_openBtn->setToolTip(
@@ -609,6 +632,7 @@ void AgentDetailPane::clear()
{ {
m_currentStorage = AgentConfig{}; m_currentStorage = AgentConfig{};
m_current = nullptr; m_current = nullptr;
setDetailsVisible(false);
m_name->setText(tr("Select an agent")); m_name->setText(tr("Select an agent"));
m_path->clear(); m_path->clear();
m_description->setText(tr("Pick an agent from the list to see its details.")); m_description->setText(tr("Pick an agent from the list to see its details."));
@@ -638,6 +662,9 @@ void AgentDetailPane::clear()
m_toolsResetBtn->setVisible(false); m_toolsResetBtn->setVisible(false);
m_filePatternsValue->clear(); m_filePatternsValue->clear();
m_rawToml->clear(); m_rawToml->clear();
m_baseRawToml->clear();
m_baseRawToggle->setVisible(false);
m_baseRawToml->setVisible(false);
m_openBtn->setEnabled(false); m_openBtn->setEnabled(false);
m_dupBtn->setEnabled(false); m_dupBtn->setEnabled(false);
m_deleteBtn->setEnabled(false); m_deleteBtn->setEnabled(false);
@@ -661,6 +688,35 @@ QLineEdit *AgentDetailPane::makeReadOnlyLine(bool mono)
return e; return e;
} }
void AgentDetailPane::fillRawToml(QPlainTextEdit *view, const QString &path)
{
const FileReadResult raw = readFileTextCapped(path, kRawTomlMaxBytes);
switch (raw.status) {
case FileReadStatus::Ok:
view->setPlainText(raw.content);
break;
case FileReadStatus::Truncated:
view->setPlainText(
raw.content + QStringLiteral("\n\n")
+ tr("(truncated at %1 bytes)").arg(kRawTomlMaxBytes));
break;
case FileReadStatus::Empty:
view->setPlainText(tr("(source file is empty)"));
break;
case FileReadStatus::OpenFailed:
view->setPlainText(tr("(source file unavailable: %1)").arg(raw.error));
break;
}
}
void AgentDetailPane::setDetailsVisible(bool visible)
{
m_headerSep->setVisible(visible);
m_path->setVisible(visible);
m_actionsHolder->setVisible(visible);
m_body->setVisible(visible);
}
void AgentDetailPane::applyCodePalette() void AgentDetailPane::applyCodePalette()
{ {
QScopedValueRollback<bool> guard(m_inApplyPalette, true); QScopedValueRollback<bool> guard(m_inApplyPalette, true);

View File

@@ -12,6 +12,7 @@
class QCheckBox; class QCheckBox;
class QComboBox; class QComboBox;
class QFrame;
class QLabel; class QLabel;
class QLineEdit; class QLineEdit;
class QPlainTextEdit; class QPlainTextEdit;
@@ -51,6 +52,8 @@ protected:
private: private:
QLineEdit *makeReadOnlyLine(bool mono = false); QLineEdit *makeReadOnlyLine(bool mono = false);
void fillRawToml(QPlainTextEdit *view, const QString &path);
void setDetailsVisible(bool visible);
void applyCodePalette(); void applyCodePalette();
void populateProviderCombo(); void populateProviderCombo();
void onChangeModel(); void onChangeModel();
@@ -69,6 +72,9 @@ private:
QLabel *m_name = nullptr; QLabel *m_name = nullptr;
QLabel *m_path = nullptr; QLabel *m_path = nullptr;
QFrame *m_headerSep = nullptr;
QWidget *m_actionsHolder = nullptr;
QWidget *m_body = nullptr;
QPushButton *m_openBtn = nullptr; QPushButton *m_openBtn = nullptr;
QPushButton *m_dupBtn = nullptr; QPushButton *m_dupBtn = nullptr;
QPushButton *m_deleteBtn = nullptr; QPushButton *m_deleteBtn = nullptr;
@@ -99,6 +105,9 @@ private:
QToolButton *m_rawToggle = nullptr; QToolButton *m_rawToggle = nullptr;
QPlainTextEdit *m_rawToml = nullptr; QPlainTextEdit *m_rawToml = nullptr;
QToolButton *m_baseRawToggle = nullptr;
QPlainTextEdit *m_baseRawToml = nullptr;
}; };
} // namespace QodeAssist::Settings } // namespace QodeAssist::Settings

View File

@@ -193,7 +193,7 @@ private:
this, this,
tr("Open agent"), tr("Open agent"),
tr("'%1' is bundled with the plugin and read-only.\n" tr("'%1' is bundled with the plugin and read-only.\n"
"Use Duplicate to create an editable user copy.") "Use Customize a copy… to create an editable user agent.")
.arg(name)); .arg(name));
return; return;
} }
@@ -211,7 +211,7 @@ private:
{ {
const AgentDuplicateResult res = duplicateAgentInUserDir(parent, *m_agentFactory); const AgentDuplicateResult res = duplicateAgentInUserDir(parent, *m_agentFactory);
if (!res.ok) { if (!res.ok) {
QMessageBox::warning(this, tr("Duplicate"), res.error); QMessageBox::warning(this, tr("Customize a copy"), res.error);
return; return;
} }
const QString newName = res.newName; const QString newName = res.newName;

View File

@@ -155,6 +155,7 @@ void AgentFactory::reload()
QDir().mkpath(userAgentsDir()); QDir().mkpath(userAgentsDir());
auto result = Agents::AgentLoader::load(agentQrcPrefix(), userAgentsDir()); auto result = Agents::AgentLoader::load(agentQrcPrefix(), userAgentsDir());
m_sourcePathByName = std::move(result.sourcePathByName);
for (const QString &err : result.errors) for (const QString &err : result.errors)
LOG_MESSAGE(QString("[Agents] error: %1").arg(err)); LOG_MESSAGE(QString("[Agents] error: %1").arg(err));
for (const QString &warn : result.warnings) for (const QString &warn : result.warnings)
@@ -212,6 +213,11 @@ const AgentConfig *AgentFactory::configByName(const QString &name) const
return &m_configs[it.value()]; return &m_configs[it.value()];
} }
QString AgentFactory::sourcePathForName(const QString &name) const
{
return m_sourcePathByName.value(name);
}
QStringList AgentFactory::configNames() const QStringList AgentFactory::configNames() const
{ {
QStringList out; QStringList out;
@@ -323,6 +329,7 @@ void AgentFactory::clear()
Q_ASSERT(thread() == QThread::currentThread()); Q_ASSERT(thread() == QThread::currentThread());
m_configs.clear(); m_configs.clear();
m_indexByName.clear(); m_indexByName.clear();
m_sourcePathByName.clear();
m_baseModelByName.clear(); m_baseModelByName.clear();
m_baseProviderByName.clear(); m_baseProviderByName.clear();
m_baseToolsByName.clear(); m_baseToolsByName.clear();

View File

@@ -41,6 +41,7 @@ public:
[[nodiscard]] static QString userConfigDir(); [[nodiscard]] static QString userConfigDir();
[[nodiscard]] const AgentConfig *configByName(const QString &name) const; [[nodiscard]] const AgentConfig *configByName(const QString &name) const;
[[nodiscard]] QString sourcePathForName(const QString &name) const;
[[nodiscard]] QStringList configNames() const; [[nodiscard]] QStringList configNames() const;
[[nodiscard]] const std::vector<AgentConfig> &configs() const noexcept { return m_configs; } [[nodiscard]] const std::vector<AgentConfig> &configs() const noexcept { return m_configs; }
@@ -77,6 +78,7 @@ signals:
private: private:
std::vector<AgentConfig> m_configs; std::vector<AgentConfig> m_configs;
QHash<QString, qsizetype> m_indexByName; QHash<QString, qsizetype> m_indexByName;
QHash<QString, QString> m_sourcePathByName;
QHash<QString, QString> m_baseModelByName; QHash<QString, QString> m_baseModelByName;
QHash<QString, QString> m_baseProviderByName; QHash<QString, QString> m_baseProviderByName;
QHash<QString, bool> m_baseToolsByName; QHash<QString, bool> m_baseToolsByName;

View File

@@ -348,6 +348,9 @@ AgentLoader::LoadResult AgentLoader::load(const QString &qrcPrefix, const QStrin
scanDir(qrcPrefix, /*isUserLayer=*/false, raw, result.errors, &result.warnings); scanDir(qrcPrefix, /*isUserLayer=*/false, raw, result.errors, &result.warnings);
scanDir(userDir, /*isUserLayer=*/true, raw, result.errors, &result.warnings); scanDir(userDir, /*isUserLayer=*/true, raw, result.errors, &result.warnings);
for (auto it = raw.constBegin(); it != raw.constEnd(); ++it)
result.sourcePathByName.insert(it.key(), it.value().filePath);
for (auto it = raw.constBegin(); it != raw.constEnd(); ++it) { for (auto it = raw.constBegin(); it != raw.constEnd(); ++it) {
const QString &name = it.key(); const QString &name = it.key();

View File

@@ -4,6 +4,7 @@
#pragma once #pragma once
#include <QHash>
#include <QString> #include <QString>
#include <QStringList> #include <QStringList>
#include <vector> #include <vector>
@@ -18,6 +19,7 @@ public:
struct LoadResult struct LoadResult
{ {
std::vector<AgentConfig> configs; std::vector<AgentConfig> configs;
QHash<QString, QString> sourcePathByName;
QStringList errors; QStringList errors;
QStringList warnings; QStringList warnings;
}; };