From c070d653661bd6122268b84e497862d3e3293ff0 Mon Sep 17 00:00:00 2001 From: Petr Mironychev <9195189+Palm1r@users.noreply.github.com> Date: Tue, 30 Jun 2026 13:45:48 +0200 Subject: [PATCH] fix: Disable shown agent without selecting --- settings/AgentDetailPane.cpp | 116 +++++++++++++++++++++++--------- settings/AgentDetailPane.hpp | 9 +++ settings/AgentsSettingsPage.cpp | 4 +- sources/agents/AgentFactory.cpp | 7 ++ sources/agents/AgentFactory.hpp | 2 + sources/agents/AgentLoader.cpp | 3 + sources/agents/AgentLoader.hpp | 2 + 7 files changed, 111 insertions(+), 32 deletions(-) diff --git a/settings/AgentDetailPane.cpp b/settings/AgentDetailPane.cpp index 6f6b6e3..3d18420 100644 --- a/settings/AgentDetailPane.cpp +++ b/settings/AgentDetailPane.cpp @@ -92,7 +92,10 @@ AgentDetailPane::AgentDetailPane(QWidget *parent) m_path->setPalette(pp); 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); connect(m_openBtn, &QPushButton::clicked, this, [this] { if (m_current) @@ -107,7 +110,8 @@ AgentDetailPane::AgentDetailPane(QWidget *parent) 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->setSpacing(6); actions->addWidget(m_openBtn); @@ -130,11 +134,11 @@ AgentDetailPane::AgentDetailPane(QWidget *parent) headerRow->setContentsMargins(0, 0, 0, 0); headerRow->setSpacing(8); headerRow->addLayout(headerLeft, 1); - headerRow->addLayout(actions); + headerRow->addWidget(m_actionsHolder); - auto *headerSep = new QFrame(this); - headerSep->setFrameShape(QFrame::HLine); - headerSep->setFrameShadow(QFrame::Sunken); + m_headerSep = new QFrame(this); + m_headerSep->setFrameShape(QFrame::HLine); + m_headerSep->setFrameShadow(QFrame::Sunken); m_description = new QLabel(this); 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_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); root->setContentsMargins(12, 12, 12, 12); root->setSpacing(10); root->addLayout(headerRow); - root->addWidget(headerSep); + root->addWidget(m_headerSep); root->addWidget(m_description); - root->addWidget(identity); - root->addWidget(connection); - root->addWidget(capabilities); - root->addWidget(match); - root->addWidget(m_rawToggle, 0, Qt::AlignLeft); - root->addWidget(m_rawToml); + root->addWidget(m_body); root->addStretch(1); clear(); @@ -494,6 +521,8 @@ void AgentDetailPane::setAgent(const AgentConfig &cfg) m_current = &m_currentStorage; const bool user = cfg.isUserSource(); + setDetailsVisible(true); + m_name->setText(cfg.name); m_path->setText(cfg.sourcePath); m_description->setText( @@ -576,23 +605,17 @@ void AgentDetailPane::setAgent(const AgentConfig &cfg) m_filePatternsValue->setText(cfg.match.filePatterns.join(QStringLiteral(", "))); m_filePatternsValue->setPlaceholderText(tr("(matches every file)")); - const FileReadResult raw = readFileTextCapped(cfg.sourcePath, kRawTomlMaxBytes); - switch (raw.status) { - case FileReadStatus::Ok: - m_rawToml->setPlainText(raw.content); - break; - case FileReadStatus::Truncated: - m_rawToml->setPlainText( - raw.content + QStringLiteral("\n\n") - + tr("(truncated at %1 bytes)").arg(kRawTomlMaxBytes)); - break; - case FileReadStatus::Empty: - m_rawToml->setPlainText(tr("(source file is empty)")); - break; - case FileReadStatus::OpenFailed: - m_rawToml->setPlainText(tr("(source file unavailable: %1)").arg(raw.error)); - break; - } + fillRawToml(m_rawToml, cfg.sourcePath); + + const QString basePath + = m_agentFactory ? m_agentFactory->sourcePathForName(cfg.extendsName) : QString(); + const bool hasBase = !cfg.extendsName.isEmpty() && !basePath.isEmpty(); + m_baseRawToggle->setVisible(hasBase); + m_baseRawToml->setVisible(hasBase && m_baseRawToggle->isChecked()); + if (hasBase) + fillRawToml(m_baseRawToml, basePath); + else + m_baseRawToml->clear(); m_openBtn->setEnabled(user); m_openBtn->setToolTip( @@ -609,6 +632,7 @@ void AgentDetailPane::clear() { m_currentStorage = AgentConfig{}; m_current = nullptr; + setDetailsVisible(false); m_name->setText(tr("Select an agent")); m_path->clear(); 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_filePatternsValue->clear(); m_rawToml->clear(); + m_baseRawToml->clear(); + m_baseRawToggle->setVisible(false); + m_baseRawToml->setVisible(false); m_openBtn->setEnabled(false); m_dupBtn->setEnabled(false); m_deleteBtn->setEnabled(false); @@ -661,6 +688,35 @@ QLineEdit *AgentDetailPane::makeReadOnlyLine(bool mono) 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() { QScopedValueRollback guard(m_inApplyPalette, true); diff --git a/settings/AgentDetailPane.hpp b/settings/AgentDetailPane.hpp index 42322b3..2010fdd 100644 --- a/settings/AgentDetailPane.hpp +++ b/settings/AgentDetailPane.hpp @@ -12,6 +12,7 @@ class QCheckBox; class QComboBox; +class QFrame; class QLabel; class QLineEdit; class QPlainTextEdit; @@ -51,6 +52,8 @@ protected: private: QLineEdit *makeReadOnlyLine(bool mono = false); + void fillRawToml(QPlainTextEdit *view, const QString &path); + void setDetailsVisible(bool visible); void applyCodePalette(); void populateProviderCombo(); void onChangeModel(); @@ -69,6 +72,9 @@ private: QLabel *m_name = nullptr; QLabel *m_path = nullptr; + QFrame *m_headerSep = nullptr; + QWidget *m_actionsHolder = nullptr; + QWidget *m_body = nullptr; QPushButton *m_openBtn = nullptr; QPushButton *m_dupBtn = nullptr; QPushButton *m_deleteBtn = nullptr; @@ -99,6 +105,9 @@ private: QToolButton *m_rawToggle = nullptr; QPlainTextEdit *m_rawToml = nullptr; + + QToolButton *m_baseRawToggle = nullptr; + QPlainTextEdit *m_baseRawToml = nullptr; }; } // namespace QodeAssist::Settings diff --git a/settings/AgentsSettingsPage.cpp b/settings/AgentsSettingsPage.cpp index 8c05e78..a6a7d45 100644 --- a/settings/AgentsSettingsPage.cpp +++ b/settings/AgentsSettingsPage.cpp @@ -193,7 +193,7 @@ private: this, tr("Open agent"), 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)); return; } @@ -211,7 +211,7 @@ private: { const AgentDuplicateResult res = duplicateAgentInUserDir(parent, *m_agentFactory); if (!res.ok) { - QMessageBox::warning(this, tr("Duplicate"), res.error); + QMessageBox::warning(this, tr("Customize a copy"), res.error); return; } const QString newName = res.newName; diff --git a/sources/agents/AgentFactory.cpp b/sources/agents/AgentFactory.cpp index dd8eaf5..7a07fdb 100644 --- a/sources/agents/AgentFactory.cpp +++ b/sources/agents/AgentFactory.cpp @@ -155,6 +155,7 @@ void AgentFactory::reload() QDir().mkpath(userAgentsDir()); auto result = Agents::AgentLoader::load(agentQrcPrefix(), userAgentsDir()); + m_sourcePathByName = std::move(result.sourcePathByName); for (const QString &err : result.errors) LOG_MESSAGE(QString("[Agents] error: %1").arg(err)); for (const QString &warn : result.warnings) @@ -212,6 +213,11 @@ const AgentConfig *AgentFactory::configByName(const QString &name) const return &m_configs[it.value()]; } +QString AgentFactory::sourcePathForName(const QString &name) const +{ + return m_sourcePathByName.value(name); +} + QStringList AgentFactory::configNames() const { QStringList out; @@ -323,6 +329,7 @@ void AgentFactory::clear() Q_ASSERT(thread() == QThread::currentThread()); m_configs.clear(); m_indexByName.clear(); + m_sourcePathByName.clear(); m_baseModelByName.clear(); m_baseProviderByName.clear(); m_baseToolsByName.clear(); diff --git a/sources/agents/AgentFactory.hpp b/sources/agents/AgentFactory.hpp index dd6b4af..10c0cde 100644 --- a/sources/agents/AgentFactory.hpp +++ b/sources/agents/AgentFactory.hpp @@ -41,6 +41,7 @@ public: [[nodiscard]] static QString userConfigDir(); [[nodiscard]] const AgentConfig *configByName(const QString &name) const; + [[nodiscard]] QString sourcePathForName(const QString &name) const; [[nodiscard]] QStringList configNames() const; [[nodiscard]] const std::vector &configs() const noexcept { return m_configs; } @@ -77,6 +78,7 @@ signals: private: std::vector m_configs; QHash m_indexByName; + QHash m_sourcePathByName; QHash m_baseModelByName; QHash m_baseProviderByName; QHash m_baseToolsByName; diff --git a/sources/agents/AgentLoader.cpp b/sources/agents/AgentLoader.cpp index c43fef6..db95de6 100644 --- a/sources/agents/AgentLoader.cpp +++ b/sources/agents/AgentLoader.cpp @@ -348,6 +348,9 @@ AgentLoader::LoadResult AgentLoader::load(const QString &qrcPrefix, const QStrin scanDir(qrcPrefix, /*isUserLayer=*/false, 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) { const QString &name = it.key(); diff --git a/sources/agents/AgentLoader.hpp b/sources/agents/AgentLoader.hpp index 4f5a5fe..80f3eb6 100644 --- a/sources/agents/AgentLoader.hpp +++ b/sources/agents/AgentLoader.hpp @@ -4,6 +4,7 @@ #pragma once +#include #include #include #include @@ -18,6 +19,7 @@ public: struct LoadResult { std::vector configs; + QHash sourcePathByName; QStringList errors; QStringList warnings; };