// Copyright (C) 2024-2026 Petr Mironychev // SPDX-License-Identifier: GPL-3.0-or-later #include "AgentsSettingsPage.hpp" #include "AgentDetailPane.hpp" #include "AgentDuplicator.hpp" #include "AgentListPane.hpp" #include "SettingsTheme.hpp" #include "SettingsConstants.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace QodeAssist::Settings { AgentsPageNavigator::AgentsPageNavigator(QObject *parent) : QObject(parent) {} void AgentsPageNavigator::requestSelectAgent(const QString &name) { m_pending = name; emit selectAgentRequested(name); } QString AgentsPageNavigator::takePendingSelection() { QString p = m_pending; m_pending.clear(); return p; } namespace { class AgentsWidget : public Core::IOptionsPageWidget { Q_OBJECT public: explicit AgentsWidget(AgentFactory *agentFactory, AgentsPageNavigator *navigator) : m_agentFactory(agentFactory) , m_navigator(navigator) { Q_ASSERT(m_agentFactory); m_titleLabel = new QLabel(tr("Agents"), this); QFont tf = m_titleLabel->font(); tf.setBold(true); tf.setPixelSize(13); m_titleLabel->setFont(tf); m_reload = new QPushButton(tr("Reload from disk"), this); m_openUserDir = new QPushButton(tr("Open agents folder"), this); m_userPathLabel = new QLabel(this); m_userPathLabel->setFont(monospaceFont(11)); QPalette mutedPal = m_userPathLabel->palette(); mutedPal.setColor(QPalette::WindowText, mutedPal.color(QPalette::Mid)); m_userPathLabel->setPalette(mutedPal); m_userPathLabel->setMaximumWidth(260); auto *headerRow = new QHBoxLayout; headerRow->setContentsMargins(0, 0, 0, 0); headerRow->setSpacing(8); headerRow->addWidget(m_titleLabel); headerRow->addStretch(1); headerRow->addWidget(m_reload); headerRow->addWidget(m_userPathLabel); headerRow->addWidget(m_openUserDir); auto *headerSep = new QFrame(this); headerSep->setFrameShape(QFrame::HLine); headerSep->setFrameShadow(QFrame::Sunken); m_listPane = new AgentListPane(m_agentFactory, this); m_detail = new AgentDetailPane(this); m_detail->setInstanceFactory(m_agentFactory->instanceFactory()); m_detailScroll = new QScrollArea(this); m_detailScroll->setWidgetResizable(true); m_detailScroll->setFrameShape(QFrame::StyledPanel); m_detailScroll->setWidget(m_detail); auto *splitter = new QSplitter(Qt::Horizontal, this); splitter->addWidget(m_listPane); splitter->addWidget(m_detailScroll); splitter->setStretchFactor(0, 0); splitter->setStretchFactor(1, 1); splitter->setSizes({320, 700}); auto *root = new QVBoxLayout(this); root->setContentsMargins(8, 8, 8, 8); root->setSpacing(6); root->addLayout(headerRow); root->addWidget(headerSep); root->addWidget(splitter, 1); connect(m_reload, &QPushButton::clicked, this, &AgentsWidget::reloadFromDisk); connect(m_openUserDir, &QPushButton::clicked, this, [] { const QString dir = QodeAssist::AgentFactory::userAgentsDir(); QDir().mkpath(dir); QDesktopServices::openUrl(QUrl::fromLocalFile(dir)); }); connect(m_listPane, &AgentListPane::currentAgentChanged, this, [this](const QString &name) { if (const AgentConfig *cfg = m_agentFactory->configByName(name)) m_detail->setAgent(*cfg); else m_detail->clear(); }); connect(m_detail, &AgentDetailPane::openInEditorRequested, this, &AgentsWidget::openAgentInEditor); connect(m_detail, &AgentDetailPane::customizeRequested, this, &AgentsWidget::customizeAgent); connect(m_detail, &AgentDetailPane::deleteRequested, this, &AgentsWidget::deleteAgent); if (m_navigator) { connect(m_navigator, &AgentsPageNavigator::selectAgentRequested, m_listPane, &AgentListPane::selectByName); } reloadFromDisk(); if (m_navigator) { QTimer::singleShot(0, this, [this] { if (!m_navigator) return; const QString pending = m_navigator->takePendingSelection(); if (!pending.isEmpty()) m_listPane->selectByName(pending); }); } } void apply() final {} private: void reloadFromDisk() { m_agentFactory->reload(); m_detail->setLoadDiagnostics( m_agentFactory->lastLoadErrors(), m_agentFactory->lastLoadWarnings()); updateUserPathLabel(); m_listPane->refresh(); } void updateUserPathLabel() { const QString dir = QodeAssist::AgentFactory::userAgentsDir(); m_userPathLabel->setText( QFontMetrics(m_userPathLabel->font()).elidedText(dir, Qt::ElideLeft, 256)); m_userPathLabel->setToolTip(dir); } void openAgentInEditor(const AgentConfig &agent) { const QString name = agent.name; const QString sourcePath = agent.sourcePath; const bool isUser = agent.isUserSource(); if (!isUser) { QMessageBox::information( this, tr("Open agent"), tr("'%1' is bundled with the plugin and read-only.\n" "Use Duplicate to create an editable user copy.") .arg(name)); return; } if (sourcePath.isEmpty() || sourcePath.startsWith(QLatin1String(":/"))) { QMessageBox::warning( this, tr("Open agent"), tr("Agent '%1' has no editable source file.").arg(name)); return; } if (!Core::EditorManager::openEditor(Utils::FilePath::fromString(sourcePath))) { QMessageBox::warning( this, tr("Open agent"), tr("Could not open %1.").arg(sourcePath)); } } void customizeAgent(const AgentConfig &parent) { const AgentDuplicateResult res = duplicateAgentInUserDir(parent, *m_agentFactory); if (!res.ok) { QMessageBox::warning(this, tr("Duplicate"), res.error); return; } const QString newName = res.newName; reloadFromDisk(); m_listPane->selectByName(newName); } void deleteAgent(const AgentConfig &agent) { if (!agent.isUserSource()) return; const QString name = agent.name; const QString sourcePath = agent.sourcePath; if (QMessageBox::question( this, tr("Delete Agent"), tr("Delete agent '%1'?\n\nThis will remove the file:\n%2") .arg(name, sourcePath), QMessageBox::Yes | QMessageBox::No, QMessageBox::No) != QMessageBox::Yes) return; if (!QFile::remove(sourcePath)) { QMessageBox::warning( this, tr("Delete Agent"), tr("Could not delete the agent file:\n%1").arg(sourcePath)); return; } reloadFromDisk(); } AgentFactory *m_agentFactory; QPointer m_navigator; QLabel *m_titleLabel = nullptr; QPushButton *m_reload = nullptr; QPushButton *m_openUserDir = nullptr; QLabel *m_userPathLabel = nullptr; AgentListPane *m_listPane = nullptr; QScrollArea *m_detailScroll = nullptr; AgentDetailPane *m_detail = nullptr; }; class AgentsSettingsPage : public Core::IOptionsPage { public: AgentsSettingsPage(AgentFactory *agentFactory, AgentsPageNavigator *navigator) { setId(Constants::QODE_ASSIST_AGENTS_SETTINGS_PAGE_ID); setDisplayName(QObject::tr("Agents")); setCategory(Constants::QODE_ASSIST_GENERAL_OPTIONS_CATEGORY); setWidgetCreator([agentFactory, navigator]() { return new AgentsWidget(agentFactory, navigator); }); } }; } // namespace std::unique_ptr createAgentsSettingsPage( AgentFactory *agentFactory, AgentsPageNavigator *navigator) { return std::make_unique(agentFactory, navigator); } } // namespace QodeAssist::Settings #include "AgentsSettingsPage.moc"