mirror of
https://github.com/Palm1r/QodeAssist.git
synced 2026-07-18 09:11:01 -04:00
refactor: Improve UX
This commit is contained in:
@@ -4,13 +4,11 @@
|
|||||||
|
|
||||||
#include "AgentDetailPane.hpp"
|
#include "AgentDetailPane.hpp"
|
||||||
|
|
||||||
#include "AgentModelDialog.hpp"
|
#include "ModelSelector.hpp"
|
||||||
#include "SectionBox.hpp"
|
#include "SectionBox.hpp"
|
||||||
#include "SettingsTheme.hpp"
|
#include "SettingsTheme.hpp"
|
||||||
#include "SettingsUiBuilders.hpp"
|
#include "SettingsUiBuilders.hpp"
|
||||||
|
|
||||||
#include <optional>
|
|
||||||
|
|
||||||
#include <utils/theme/theme.h>
|
#include <utils/theme/theme.h>
|
||||||
|
|
||||||
#include <AgentFactory.hpp>
|
#include <AgentFactory.hpp>
|
||||||
@@ -84,6 +82,9 @@ AgentDetailPane::AgentDetailPane(QWidget *parent)
|
|||||||
m_name->setFont(nf);
|
m_name->setFont(nf);
|
||||||
m_name->setTextInteractionFlags(Qt::TextSelectableByMouse);
|
m_name->setTextInteractionFlags(Qt::TextSelectableByMouse);
|
||||||
|
|
||||||
|
m_sourceBadge = new QLabel(this);
|
||||||
|
m_sourceBadge->setVisible(false);
|
||||||
|
|
||||||
m_path = new QLabel(this);
|
m_path = new QLabel(this);
|
||||||
m_path->setFont(monospaceFont(11));
|
m_path->setFont(monospaceFont(11));
|
||||||
m_path->setTextInteractionFlags(Qt::TextSelectableByMouse);
|
m_path->setTextInteractionFlags(Qt::TextSelectableByMouse);
|
||||||
@@ -91,20 +92,20 @@ AgentDetailPane::AgentDetailPane(QWidget *parent)
|
|||||||
pp.setColor(QPalette::WindowText, Utils::creatorColor(Utils::Theme::PanelTextColorMid));
|
pp.setColor(QPalette::WindowText, Utils::creatorColor(Utils::Theme::PanelTextColorMid));
|
||||||
m_path->setPalette(pp);
|
m_path->setPalette(pp);
|
||||||
|
|
||||||
m_openBtn = new QPushButton(tr("Open in editor"), this);
|
|
||||||
m_dupBtn = new QPushButton(tr("Customize a copy…"), this);
|
m_dupBtn = new QPushButton(tr("Customize a copy…"), this);
|
||||||
m_dupBtn->setToolTip(
|
m_dupBtn->setToolTip(
|
||||||
tr("Create an editable user agent that inherits from this one — "
|
tr("Create an editable user agent that inherits from this one — "
|
||||||
"override only the fields you want."));
|
"override only the fields you want."));
|
||||||
|
m_openBtn = new QPushButton(tr("Open in editor"), this);
|
||||||
m_deleteBtn = new QPushButton(tr("Delete"), this);
|
m_deleteBtn = new QPushButton(tr("Delete"), this);
|
||||||
connect(m_openBtn, &QPushButton::clicked, this, [this] {
|
|
||||||
if (m_current)
|
|
||||||
emit openInEditorRequested(*m_current);
|
|
||||||
});
|
|
||||||
connect(m_dupBtn, &QPushButton::clicked, this, [this] {
|
connect(m_dupBtn, &QPushButton::clicked, this, [this] {
|
||||||
if (m_current)
|
if (m_current)
|
||||||
emit customizeRequested(*m_current);
|
emit customizeRequested(*m_current);
|
||||||
});
|
});
|
||||||
|
connect(m_openBtn, &QPushButton::clicked, this, [this] {
|
||||||
|
if (m_current)
|
||||||
|
emit openInEditorRequested(*m_current);
|
||||||
|
});
|
||||||
connect(m_deleteBtn, &QPushButton::clicked, this, [this] {
|
connect(m_deleteBtn, &QPushButton::clicked, this, [this] {
|
||||||
if (m_current)
|
if (m_current)
|
||||||
emit deleteRequested(*m_current);
|
emit deleteRequested(*m_current);
|
||||||
@@ -114,14 +115,15 @@ AgentDetailPane::AgentDetailPane(QWidget *parent)
|
|||||||
auto *actions = new QHBoxLayout(m_actionsHolder);
|
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_dupBtn);
|
actions->addWidget(m_dupBtn);
|
||||||
|
actions->addWidget(m_openBtn);
|
||||||
actions->addWidget(m_deleteBtn);
|
actions->addWidget(m_deleteBtn);
|
||||||
|
|
||||||
auto *titleRow = new QHBoxLayout;
|
auto *titleRow = new QHBoxLayout;
|
||||||
titleRow->setContentsMargins(0, 0, 0, 0);
|
titleRow->setContentsMargins(0, 0, 0, 0);
|
||||||
titleRow->setSpacing(8);
|
titleRow->setSpacing(8);
|
||||||
titleRow->addWidget(m_name);
|
titleRow->addWidget(m_name);
|
||||||
|
titleRow->addWidget(m_sourceBadge, 0, Qt::AlignVCenter);
|
||||||
titleRow->addStretch(1);
|
titleRow->addStretch(1);
|
||||||
|
|
||||||
auto *headerLeft = new QVBoxLayout;
|
auto *headerLeft = new QVBoxLayout;
|
||||||
@@ -144,41 +146,32 @@ AgentDetailPane::AgentDetailPane(QWidget *parent)
|
|||||||
m_description->setWordWrap(true);
|
m_description->setWordWrap(true);
|
||||||
m_description->setTextInteractionFlags(Qt::TextSelectableByMouse);
|
m_description->setTextInteractionFlags(Qt::TextSelectableByMouse);
|
||||||
|
|
||||||
auto *identity = new SectionBox(tr("Identity"), this);
|
auto *setup = new SectionBox(tr("Configuration"), this);
|
||||||
m_nameValue = makeReadOnlyLine();
|
|
||||||
m_extendsLabel = new QLabel(tr("Extends:"), this);
|
|
||||||
m_extendsLabel->setMinimumWidth(96);
|
|
||||||
m_extendsLabel->setAlignment(Qt::AlignLeft | Qt::AlignTop);
|
|
||||||
m_extendsValue = makeReadOnlyLine();
|
|
||||||
m_descriptionEdit = new QPlainTextEdit(this);
|
|
||||||
m_descriptionEdit->setReadOnly(true);
|
|
||||||
m_descriptionEdit->setMaximumHeight(56);
|
|
||||||
m_tagsValue = makeReadOnlyLine();
|
|
||||||
|
|
||||||
auto *idGrid = new QGridLayout;
|
m_modelSelector = new ModelSelector(this);
|
||||||
idGrid->setContentsMargins(0, 0, 0, 0);
|
connect(m_modelSelector, &ModelSelector::modelSubmitted, this, [this](const QString &model) {
|
||||||
idGrid->setHorizontalSpacing(8);
|
onModelSubmitted(model);
|
||||||
idGrid->setVerticalSpacing(4);
|
});
|
||||||
FormBuilder idForm(idGrid);
|
|
||||||
idForm.row(tr("Name:"), m_nameValue);
|
m_modelResetBtn = new QPushButton(tr("Reset"), this);
|
||||||
{
|
m_modelResetBtn->setToolTip(tr("Remove the model override and restore the agent's default"));
|
||||||
auto *holder = new QWidget;
|
m_modelResetBtn->setVisible(false);
|
||||||
holder->setLayout(singleField(m_extendsValue));
|
connect(m_modelResetBtn, &QPushButton::clicked, this, [this] { onResetModel(); });
|
||||||
const int row = idForm.currentRow();
|
|
||||||
idGrid->addWidget(m_extendsLabel, row, 0, Qt::AlignTop);
|
auto *modelHolder = new QWidget(this);
|
||||||
idGrid->addWidget(holder, row, 1);
|
auto *modelRow = new QHBoxLayout(modelHolder);
|
||||||
m_extendsHolder = holder;
|
modelRow->setContentsMargins(0, 0, 0, 0);
|
||||||
idForm = FormBuilder(idGrid, row + 1);
|
modelRow->setSpacing(6);
|
||||||
}
|
modelRow->addWidget(m_modelSelector, 1);
|
||||||
idForm.row(tr("Description:"), m_descriptionEdit);
|
modelRow->addWidget(m_modelResetBtn);
|
||||||
idForm.row(
|
|
||||||
tr("Tags:"),
|
m_modelStatus = makeHintLabel(QString(), this);
|
||||||
m_tagsValue,
|
m_modelStatus->setVisible(false);
|
||||||
tr("Comma-separated. Free-form — used to filter and "
|
connect(m_modelSelector, &ModelSelector::statusChanged, this, [this](const QString &status) {
|
||||||
"group the agent list."));
|
m_modelStatus->setText(status);
|
||||||
identity->bodyLayout()->addLayout(idGrid);
|
m_modelStatus->setVisible(!status.isEmpty());
|
||||||
|
});
|
||||||
|
|
||||||
auto *connection = new SectionBox(tr("Connection"), this);
|
|
||||||
m_providerCombo = new QComboBox(this);
|
m_providerCombo = new QComboBox(this);
|
||||||
m_providerCombo->setSizeAdjustPolicy(QComboBox::AdjustToContents);
|
m_providerCombo->setSizeAdjustPolicy(QComboBox::AdjustToContents);
|
||||||
m_providerCombo->setEnabled(false);
|
m_providerCombo->setEnabled(false);
|
||||||
@@ -199,65 +192,6 @@ AgentDetailPane::AgentDetailPane(QWidget *parent)
|
|||||||
providerRow->addWidget(m_providerCombo, 1);
|
providerRow->addWidget(m_providerCombo, 1);
|
||||||
providerRow->addWidget(m_providerResetBtn);
|
providerRow->addWidget(m_providerResetBtn);
|
||||||
|
|
||||||
m_endpointValue = makeReadOnlyLine(true);
|
|
||||||
|
|
||||||
m_modelValue = makeReadOnlyLine(true);
|
|
||||||
m_modelValue->setPlaceholderText(tr("(set a model)"));
|
|
||||||
|
|
||||||
m_modelChangeBtn = new QPushButton(tr("Change…"), this);
|
|
||||||
m_modelChangeBtn->setToolTip(tr("Pick a model from the provider or type one"));
|
|
||||||
m_modelChangeBtn->setEnabled(false);
|
|
||||||
connect(m_modelChangeBtn, &QPushButton::clicked, this, [this] { onChangeModel(); });
|
|
||||||
|
|
||||||
m_modelResetBtn = new QPushButton(tr("Reset"), this);
|
|
||||||
m_modelResetBtn->setToolTip(tr("Remove the model override and restore the agent's default"));
|
|
||||||
m_modelResetBtn->setVisible(false);
|
|
||||||
connect(m_modelResetBtn, &QPushButton::clicked, this, [this] { onResetModel(); });
|
|
||||||
|
|
||||||
auto *modelHolder = new QWidget(this);
|
|
||||||
auto *modelRow = new QHBoxLayout(modelHolder);
|
|
||||||
modelRow->setContentsMargins(0, 0, 0, 0);
|
|
||||||
modelRow->setSpacing(6);
|
|
||||||
modelRow->addWidget(m_modelValue, 1);
|
|
||||||
modelRow->addWidget(m_modelChangeBtn);
|
|
||||||
modelRow->addWidget(m_modelResetBtn);
|
|
||||||
|
|
||||||
auto *connGrid = new QGridLayout;
|
|
||||||
connGrid->setContentsMargins(0, 0, 0, 0);
|
|
||||||
connGrid->setHorizontalSpacing(8);
|
|
||||||
connGrid->setVerticalSpacing(4);
|
|
||||||
FormBuilder(connGrid)
|
|
||||||
.row(
|
|
||||||
tr("Provider:"),
|
|
||||||
providerHolder,
|
|
||||||
tr("The provider instance this agent uses. URL is "
|
|
||||||
"inherited from the instance. Switching it is saved as a "
|
|
||||||
"per-agent override; Reset restores the agent's default."))
|
|
||||||
.row(
|
|
||||||
tr("Endpoint:"),
|
|
||||||
m_endpointValue,
|
|
||||||
tr("Appended to the provider's URL. Blank uses the "
|
|
||||||
"provider default."))
|
|
||||||
.row(
|
|
||||||
tr("Model:"),
|
|
||||||
modelHolder,
|
|
||||||
tr("Model sent to the provider. Click Change… to pick from the "
|
|
||||||
"provider's available models or type one. The choice is saved "
|
|
||||||
"as a per-agent override in settings; Reset restores the "
|
|
||||||
"agent's default."));
|
|
||||||
connection->bodyLayout()->addLayout(connGrid);
|
|
||||||
|
|
||||||
m_effectiveUrl = new QLabel(this);
|
|
||||||
m_effectiveUrl->setFont(monospaceFont(11));
|
|
||||||
m_effectiveUrl->setTextInteractionFlags(Qt::TextSelectableByMouse);
|
|
||||||
m_effectiveUrl->setWordWrap(true);
|
|
||||||
m_effectiveUrl->setContentsMargins(6, 4, 6, 4);
|
|
||||||
m_effectiveUrl->setAutoFillBackground(true);
|
|
||||||
connection->bodyLayout()->addWidget(m_effectiveUrl);
|
|
||||||
|
|
||||||
auto *capabilities = new SectionBox(tr("Capabilities"), this);
|
|
||||||
m_thinkingValue = new QLabel(this);
|
|
||||||
|
|
||||||
m_toolsCheck = new QCheckBox(tr("Allow tool calls"), this);
|
m_toolsCheck = new QCheckBox(tr("Allow tool calls"), this);
|
||||||
m_toolsCheck->setEnabled(false);
|
m_toolsCheck->setEnabled(false);
|
||||||
connect(m_toolsCheck, &QCheckBox::clicked, this, [this](bool on) { onToggleTools(on); });
|
connect(m_toolsCheck, &QCheckBox::clicked, this, [this](bool on) { onToggleTools(on); });
|
||||||
@@ -275,38 +209,46 @@ AgentDetailPane::AgentDetailPane(QWidget *parent)
|
|||||||
toolsRow->addStretch(1);
|
toolsRow->addStretch(1);
|
||||||
toolsRow->addWidget(m_toolsResetBtn);
|
toolsRow->addWidget(m_toolsResetBtn);
|
||||||
|
|
||||||
auto *capGrid = new QGridLayout;
|
m_thinkingValue = new QLabel(this);
|
||||||
capGrid->setContentsMargins(0, 0, 0, 0);
|
|
||||||
capGrid->setHorizontalSpacing(8);
|
|
||||||
capGrid->setVerticalSpacing(4);
|
|
||||||
FormBuilder(capGrid)
|
|
||||||
.row(
|
|
||||||
tr("Thinking:"),
|
|
||||||
m_thinkingValue,
|
|
||||||
tr("Whether this agent requests extended thinking. Defined by the "
|
|
||||||
"agent template."))
|
|
||||||
.row(
|
|
||||||
tr("Tools:"),
|
|
||||||
toolsHolder,
|
|
||||||
tr("Whether the agent may call tools. Toggle to save a per-agent "
|
|
||||||
"override in settings; Reset restores the agent's default."));
|
|
||||||
capabilities->bodyLayout()->addLayout(capGrid);
|
|
||||||
|
|
||||||
auto *match = new SectionBox(tr("Match"), this);
|
auto *setupGrid = new QGridLayout;
|
||||||
auto *matchHint = makeHintLabel(
|
setupGrid->setContentsMargins(0, 0, 0, 0);
|
||||||
tr("When a feature slot has multiple bound agents, the first whose "
|
setupGrid->setHorizontalSpacing(8);
|
||||||
"match rules satisfy the current context wins."));
|
setupGrid->setVerticalSpacing(4);
|
||||||
|
FormBuilder setupForm(setupGrid);
|
||||||
|
setupForm.row(
|
||||||
|
tr("Model:"),
|
||||||
|
modelHolder,
|
||||||
|
tr("Click to pick from the provider's models, or type a name and press Enter."));
|
||||||
|
{
|
||||||
|
const int row = setupForm.currentRow();
|
||||||
|
setupGrid->addWidget(m_modelStatus, row, 1);
|
||||||
|
setupForm = FormBuilder(setupGrid, row + 1);
|
||||||
|
}
|
||||||
|
setupForm.row(tr("Provider:"), providerHolder)
|
||||||
|
.row(tr("Tools:"), toolsHolder)
|
||||||
|
.row(tr("Thinking:"), m_thinkingValue);
|
||||||
|
setup->bodyLayout()->addLayout(setupGrid);
|
||||||
|
setup->bodyLayout()->addWidget(makeHintLabel(
|
||||||
|
tr("Changes apply immediately and are saved as per-agent overrides in settings; "
|
||||||
|
"Reset restores the value from the agent file."),
|
||||||
|
this));
|
||||||
|
|
||||||
|
m_extendsValue = makeReadOnlyLine();
|
||||||
|
m_extendsValue->setPlaceholderText(tr("(none)"));
|
||||||
|
m_tagsValue = makeReadOnlyLine();
|
||||||
|
m_tagsValue->setPlaceholderText(tr("(none)"));
|
||||||
|
m_endpointValue = makeReadOnlyLine(true);
|
||||||
|
m_endpointValue->setPlaceholderText(tr("(provider default)"));
|
||||||
m_filePatternsValue = makeReadOnlyLine(true);
|
m_filePatternsValue = makeReadOnlyLine(true);
|
||||||
auto *matchGrid = new QGridLayout;
|
m_filePatternsValue->setPlaceholderText(tr("(matches every file)"));
|
||||||
matchGrid->setContentsMargins(0, 0, 0, 0);
|
|
||||||
matchGrid->setHorizontalSpacing(8);
|
m_effectiveUrl = new QLabel(this);
|
||||||
matchGrid->setVerticalSpacing(4);
|
m_effectiveUrl->setFont(monospaceFont(11));
|
||||||
FormBuilder(matchGrid).row(
|
m_effectiveUrl->setTextInteractionFlags(Qt::TextSelectableByMouse);
|
||||||
tr("File patterns:"),
|
m_effectiveUrl->setWordWrap(true);
|
||||||
m_filePatternsValue,
|
m_effectiveUrl->setContentsMargins(6, 4, 6, 4);
|
||||||
tr("Globs, comma-separated. Empty matches every file."));
|
m_effectiveUrl->setAutoFillBackground(true);
|
||||||
match->bodyLayout()->addWidget(matchHint);
|
|
||||||
match->bodyLayout()->addLayout(matchGrid);
|
|
||||||
|
|
||||||
m_rawToggle = new QToolButton(this);
|
m_rawToggle = new QToolButton(this);
|
||||||
m_rawToggle->setText(tr("▸ Show raw TOML"));
|
m_rawToggle->setText(tr("▸ Show raw TOML"));
|
||||||
@@ -338,18 +280,49 @@ AgentDetailPane::AgentDetailPane(QWidget *parent)
|
|||||||
m_baseRawToggle->setText(on ? tr("▾ Hide base agent TOML") : tr("▸ Show base agent TOML"));
|
m_baseRawToggle->setText(on ? tr("▾ Hide base agent TOML") : tr("▸ Show base agent TOML"));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
m_detailsBody = new QWidget(this);
|
||||||
|
auto *detailsLayout = new QVBoxLayout(m_detailsBody);
|
||||||
|
detailsLayout->setContentsMargins(0, 0, 0, 0);
|
||||||
|
detailsLayout->setSpacing(10);
|
||||||
|
|
||||||
|
auto *detailsGrid = new QGridLayout;
|
||||||
|
detailsGrid->setContentsMargins(0, 0, 0, 0);
|
||||||
|
detailsGrid->setHorizontalSpacing(8);
|
||||||
|
detailsGrid->setVerticalSpacing(4);
|
||||||
|
FormBuilder(detailsGrid)
|
||||||
|
.row(tr("Extends:"), m_extendsValue)
|
||||||
|
.row(tr("Tags:"), m_tagsValue, tr("Free-form — used to filter and group the agent list."))
|
||||||
|
.row(tr("Endpoint:"), m_endpointValue, tr("Appended to the provider's URL."))
|
||||||
|
.row(
|
||||||
|
tr("File patterns:"),
|
||||||
|
m_filePatternsValue,
|
||||||
|
tr("Globs, comma-separated. When a feature slot has multiple bound "
|
||||||
|
"agents, the first whose match rules satisfy the context wins."));
|
||||||
|
detailsLayout->addLayout(detailsGrid);
|
||||||
|
detailsLayout->addWidget(m_effectiveUrl);
|
||||||
|
detailsLayout->addWidget(m_rawToggle, 0, Qt::AlignLeft);
|
||||||
|
detailsLayout->addWidget(m_rawToml);
|
||||||
|
detailsLayout->addWidget(m_baseRawToggle, 0, Qt::AlignLeft);
|
||||||
|
detailsLayout->addWidget(m_baseRawToml);
|
||||||
|
m_detailsBody->setVisible(false);
|
||||||
|
|
||||||
|
m_detailsToggle = new QToolButton(this);
|
||||||
|
m_detailsToggle->setText(tr("▸ More details"));
|
||||||
|
m_detailsToggle->setCursor(Qt::PointingHandCursor);
|
||||||
|
m_detailsToggle->setAutoRaise(true);
|
||||||
|
m_detailsToggle->setCheckable(true);
|
||||||
|
connect(m_detailsToggle, &QToolButton::toggled, this, [this](bool on) {
|
||||||
|
m_detailsBody->setVisible(on);
|
||||||
|
m_detailsToggle->setText(on ? tr("▾ More details") : tr("▸ More details"));
|
||||||
|
});
|
||||||
|
|
||||||
m_body = new QWidget(this);
|
m_body = new QWidget(this);
|
||||||
auto *bodyLayout = new QVBoxLayout(m_body);
|
auto *bodyLayout = new QVBoxLayout(m_body);
|
||||||
bodyLayout->setContentsMargins(0, 0, 0, 0);
|
bodyLayout->setContentsMargins(0, 0, 0, 0);
|
||||||
bodyLayout->setSpacing(10);
|
bodyLayout->setSpacing(10);
|
||||||
bodyLayout->addWidget(identity);
|
bodyLayout->addWidget(setup);
|
||||||
bodyLayout->addWidget(connection);
|
bodyLayout->addWidget(m_detailsToggle, 0, Qt::AlignLeft);
|
||||||
bodyLayout->addWidget(capabilities);
|
bodyLayout->addWidget(m_detailsBody);
|
||||||
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);
|
||||||
@@ -407,25 +380,19 @@ void AgentDetailPane::populateProviderCombo()
|
|||||||
m_providerComboPopulated = true;
|
m_providerComboPopulated = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AgentDetailPane::onChangeModel()
|
void AgentDetailPane::onModelSubmitted(const QString &model)
|
||||||
{
|
{
|
||||||
if (!m_agentFactory || !m_current)
|
if (!m_agentFactory || !m_current)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const QString name = m_current->name;
|
const QString name = m_current->name;
|
||||||
AgentModelDialog dialog(m_agentFactory, name, m_current->model, this);
|
|
||||||
if (dialog.exec() != QDialog::Accepted)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const QString model = dialog.selectedModel();
|
|
||||||
if (model == m_current->model)
|
if (model == m_current->model)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
QString err;
|
QString err;
|
||||||
if (!m_agentFactory->setAgentModelOverride(name, model, &err)) {
|
const bool ok = m_agentFactory->setAgentModelOverride(name, model, &err);
|
||||||
|
if (!ok)
|
||||||
QMessageBox::warning(this, tr("Set model"), err);
|
QMessageBox::warning(this, tr("Set model"), err);
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (const AgentConfig *cfg = m_agentFactory->configByName(name))
|
if (const AgentConfig *cfg = m_agentFactory->configByName(name))
|
||||||
setAgent(*cfg);
|
setAgent(*cfg);
|
||||||
}
|
}
|
||||||
@@ -540,22 +507,11 @@ void AgentDetailPane::setAgent(const AgentConfig &cfg)
|
|||||||
setDetailsVisible(true);
|
setDetailsVisible(true);
|
||||||
|
|
||||||
m_name->setText(cfg.name);
|
m_name->setText(cfg.name);
|
||||||
|
m_sourceBadge->setText(user ? tr("User") : tr("Bundled"));
|
||||||
m_path->setText(cfg.sourcePath);
|
m_path->setText(cfg.sourcePath);
|
||||||
m_description->setText(
|
m_description->setText(
|
||||||
cfg.description.isEmpty() ? tr("No description provided.") : cfg.description);
|
cfg.description.isEmpty() ? tr("No description provided.") : cfg.description);
|
||||||
|
|
||||||
m_nameValue->setText(cfg.name);
|
|
||||||
if (cfg.extendsName.isEmpty()) {
|
|
||||||
m_extendsLabel->setVisible(false);
|
|
||||||
m_extendsHolder->setVisible(false);
|
|
||||||
} else {
|
|
||||||
m_extendsLabel->setVisible(true);
|
|
||||||
m_extendsHolder->setVisible(true);
|
|
||||||
m_extendsValue->setText(cfg.extendsName);
|
|
||||||
}
|
|
||||||
m_descriptionEdit->setPlainText(cfg.description);
|
|
||||||
m_tagsValue->setText(cfg.tags.join(QStringLiteral(", ")));
|
|
||||||
|
|
||||||
populateProviderCombo();
|
populateProviderCombo();
|
||||||
|
|
||||||
if (m_providerComboHasSentinel) {
|
if (m_providerComboHasSentinel) {
|
||||||
@@ -589,14 +545,11 @@ void AgentDetailPane::setAgent(const AgentConfig &cfg)
|
|||||||
? tr("Overridden in settings. Reset to use the agent's default provider.")
|
? tr("Overridden in settings. Reset to use the agent's default provider.")
|
||||||
: QString());
|
: QString());
|
||||||
|
|
||||||
m_endpointValue->setText(cfg.endpoint);
|
m_modelSelector->setAgent(m_agentFactory, cfg.name, cfg.providerInstance, cfg.model);
|
||||||
m_endpointValue->setPlaceholderText(tr("(provider default)"));
|
|
||||||
m_modelValue->setText(cfg.model);
|
|
||||||
m_modelChangeBtn->setEnabled(m_agentFactory != nullptr);
|
|
||||||
const bool hasModelOverride = m_agentFactory
|
const bool hasModelOverride = m_agentFactory
|
||||||
&& !m_agentFactory->agentModelOverride(cfg.name).isEmpty();
|
&& !m_agentFactory->agentModelOverride(cfg.name).isEmpty();
|
||||||
m_modelResetBtn->setVisible(hasModelOverride);
|
m_modelResetBtn->setVisible(hasModelOverride);
|
||||||
m_modelValue->setToolTip(
|
m_modelSelector->setToolTip(
|
||||||
hasModelOverride ? tr("Overridden in settings. Reset to use the agent's default model.")
|
hasModelOverride ? tr("Overridden in settings. Reset to use the agent's default model.")
|
||||||
: QString());
|
: QString());
|
||||||
|
|
||||||
@@ -613,14 +566,16 @@ void AgentDetailPane::setAgent(const AgentConfig &cfg)
|
|||||||
hasToolsOverride ? tr("Overridden in settings. Reset to use the agent's default.")
|
hasToolsOverride ? tr("Overridden in settings. Reset to use the agent's default.")
|
||||||
: QString());
|
: QString());
|
||||||
|
|
||||||
|
m_extendsValue->setText(cfg.extendsName);
|
||||||
|
m_tagsValue->setText(cfg.tags.join(QStringLiteral(", ")));
|
||||||
|
m_endpointValue->setText(cfg.endpoint);
|
||||||
|
m_filePatternsValue->setText(cfg.match.filePatterns.join(QStringLiteral(", ")));
|
||||||
|
|
||||||
const QString eff = resolvedUrl + cfg.endpoint;
|
const QString eff = resolvedUrl + cfg.endpoint;
|
||||||
m_effectiveUrl->setText(
|
m_effectiveUrl->setText(
|
||||||
eff.isEmpty() ? tr("# effective request line\n(unknown — provider instance not found)")
|
eff.isEmpty() ? tr("# effective request line\n(unknown — provider instance not found)")
|
||||||
: QStringLiteral("# %1\nPOST %2").arg(tr("effective request line"), eff));
|
: QStringLiteral("# %1\nPOST %2").arg(tr("effective request line"), eff));
|
||||||
|
|
||||||
m_filePatternsValue->setText(cfg.match.filePatterns.join(QStringLiteral(", ")));
|
|
||||||
m_filePatternsValue->setPlaceholderText(tr("(matches every file)"));
|
|
||||||
|
|
||||||
fillRawToml(m_rawToml, cfg.sourcePath);
|
fillRawToml(m_rawToml, cfg.sourcePath);
|
||||||
|
|
||||||
const QString basePath = m_agentFactory ? m_agentFactory->sourcePathForName(cfg.extendsName)
|
const QString basePath = m_agentFactory ? m_agentFactory->sourcePathForName(cfg.extendsName)
|
||||||
@@ -652,11 +607,6 @@ void AgentDetailPane::clear()
|
|||||||
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."));
|
||||||
m_nameValue->clear();
|
|
||||||
m_extendsLabel->setVisible(false);
|
|
||||||
m_extendsHolder->setVisible(false);
|
|
||||||
m_descriptionEdit->clear();
|
|
||||||
m_tagsValue->clear();
|
|
||||||
if (m_providerComboHasSentinel) {
|
if (m_providerComboHasSentinel) {
|
||||||
m_providerCombo->removeItem(0);
|
m_providerCombo->removeItem(0);
|
||||||
m_providerComboHasSentinel = false;
|
m_providerComboHasSentinel = false;
|
||||||
@@ -664,11 +614,8 @@ void AgentDetailPane::clear()
|
|||||||
m_providerCombo->setCurrentIndex(-1);
|
m_providerCombo->setCurrentIndex(-1);
|
||||||
m_providerCombo->setEnabled(false);
|
m_providerCombo->setEnabled(false);
|
||||||
m_providerResetBtn->setVisible(false);
|
m_providerResetBtn->setVisible(false);
|
||||||
m_endpointValue->clear();
|
m_modelSelector->clearAgent();
|
||||||
m_modelValue->clear();
|
|
||||||
m_modelChangeBtn->setEnabled(false);
|
|
||||||
m_modelResetBtn->setVisible(false);
|
m_modelResetBtn->setVisible(false);
|
||||||
m_effectiveUrl->clear();
|
|
||||||
m_thinkingValue->clear();
|
m_thinkingValue->clear();
|
||||||
{
|
{
|
||||||
QSignalBlocker block(m_toolsCheck);
|
QSignalBlocker block(m_toolsCheck);
|
||||||
@@ -676,7 +623,11 @@ void AgentDetailPane::clear()
|
|||||||
}
|
}
|
||||||
m_toolsCheck->setEnabled(false);
|
m_toolsCheck->setEnabled(false);
|
||||||
m_toolsResetBtn->setVisible(false);
|
m_toolsResetBtn->setVisible(false);
|
||||||
|
m_extendsValue->clear();
|
||||||
|
m_tagsValue->clear();
|
||||||
|
m_endpointValue->clear();
|
||||||
m_filePatternsValue->clear();
|
m_filePatternsValue->clear();
|
||||||
|
m_effectiveUrl->clear();
|
||||||
m_rawToml->clear();
|
m_rawToml->clear();
|
||||||
m_baseRawToml->clear();
|
m_baseRawToml->clear();
|
||||||
m_baseRawToggle->setVisible(false);
|
m_baseRawToggle->setVisible(false);
|
||||||
@@ -728,6 +679,7 @@ void AgentDetailPane::fillRawToml(QPlainTextEdit *view, const QString &path)
|
|||||||
void AgentDetailPane::setDetailsVisible(bool visible)
|
void AgentDetailPane::setDetailsVisible(bool visible)
|
||||||
{
|
{
|
||||||
m_headerSep->setVisible(visible);
|
m_headerSep->setVisible(visible);
|
||||||
|
m_sourceBadge->setVisible(visible);
|
||||||
m_path->setVisible(visible);
|
m_path->setVisible(visible);
|
||||||
m_actionsHolder->setVisible(visible);
|
m_actionsHolder->setVisible(visible);
|
||||||
m_body->setVisible(visible);
|
m_body->setVisible(visible);
|
||||||
@@ -744,6 +696,8 @@ void AgentDetailPane::applyCodePalette()
|
|||||||
m_effectiveUrl->setStyleSheet(
|
m_effectiveUrl->setStyleSheet(
|
||||||
QStringLiteral("QLabel { background:%1; border:1px solid %2; }")
|
QStringLiteral("QLabel { background:%1; border:1px solid %2; }")
|
||||||
.arg(cssColor(codeBg), cssColor(Utils::creatorColor(Utils::Theme::SplitterColor))));
|
.arg(cssColor(codeBg), cssColor(Utils::creatorColor(Utils::Theme::SplitterColor))));
|
||||||
|
if (m_current)
|
||||||
|
styleSourceBadge(m_sourceBadge, m_current->isUserSource());
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace QodeAssist::Settings
|
} // namespace QodeAssist::Settings
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ class ProviderInstanceFactory;
|
|||||||
|
|
||||||
namespace QodeAssist::Settings {
|
namespace QodeAssist::Settings {
|
||||||
|
|
||||||
|
class ModelSelector;
|
||||||
class SectionBox;
|
class SectionBox;
|
||||||
|
|
||||||
class AgentDetailPane : public QWidget
|
class AgentDetailPane : public QWidget
|
||||||
@@ -56,7 +57,7 @@ private:
|
|||||||
void setDetailsVisible(bool visible);
|
void setDetailsVisible(bool visible);
|
||||||
void applyCodePalette();
|
void applyCodePalette();
|
||||||
void populateProviderCombo();
|
void populateProviderCombo();
|
||||||
void onChangeModel();
|
void onModelSubmitted(const QString &model);
|
||||||
void onResetModel();
|
void onResetModel();
|
||||||
void onChangeProvider(int index);
|
void onChangeProvider(int index);
|
||||||
void onResetProvider();
|
void onResetProvider();
|
||||||
@@ -70,7 +71,11 @@ private:
|
|||||||
AgentConfig m_currentStorage;
|
AgentConfig m_currentStorage;
|
||||||
const AgentConfig *m_current = nullptr;
|
const AgentConfig *m_current = nullptr;
|
||||||
|
|
||||||
|
QPointer<Providers::ProviderInstanceFactory> m_instanceFactory;
|
||||||
|
QPointer<AgentFactory> m_agentFactory;
|
||||||
|
|
||||||
QLabel *m_name = nullptr;
|
QLabel *m_name = nullptr;
|
||||||
|
QLabel *m_sourceBadge = nullptr;
|
||||||
QLabel *m_path = nullptr;
|
QLabel *m_path = nullptr;
|
||||||
QFrame *m_headerSep = nullptr;
|
QFrame *m_headerSep = nullptr;
|
||||||
QWidget *m_actionsHolder = nullptr;
|
QWidget *m_actionsHolder = nullptr;
|
||||||
@@ -80,32 +85,25 @@ private:
|
|||||||
QPushButton *m_deleteBtn = nullptr;
|
QPushButton *m_deleteBtn = nullptr;
|
||||||
QLabel *m_description = nullptr;
|
QLabel *m_description = nullptr;
|
||||||
|
|
||||||
QLineEdit *m_nameValue = nullptr;
|
ModelSelector *m_modelSelector = nullptr;
|
||||||
QLabel *m_extendsLabel = nullptr;
|
QPushButton *m_modelResetBtn = nullptr;
|
||||||
QWidget *m_extendsHolder = nullptr;
|
QLabel *m_modelStatus = nullptr;
|
||||||
QLineEdit *m_extendsValue = nullptr;
|
|
||||||
QPlainTextEdit *m_descriptionEdit = nullptr;
|
|
||||||
QLineEdit *m_tagsValue = nullptr;
|
|
||||||
|
|
||||||
QComboBox *m_providerCombo = nullptr;
|
QComboBox *m_providerCombo = nullptr;
|
||||||
QPushButton *m_providerResetBtn = nullptr;
|
QPushButton *m_providerResetBtn = nullptr;
|
||||||
QPointer<Providers::ProviderInstanceFactory> m_instanceFactory;
|
|
||||||
QPointer<AgentFactory> m_agentFactory;
|
|
||||||
QLineEdit *m_endpointValue = nullptr;
|
|
||||||
QLineEdit *m_modelValue = nullptr;
|
|
||||||
QPushButton *m_modelChangeBtn = nullptr;
|
|
||||||
QPushButton *m_modelResetBtn = nullptr;
|
|
||||||
QLabel *m_effectiveUrl = nullptr;
|
|
||||||
|
|
||||||
QLabel *m_thinkingValue = nullptr;
|
|
||||||
QCheckBox *m_toolsCheck = nullptr;
|
QCheckBox *m_toolsCheck = nullptr;
|
||||||
QPushButton *m_toolsResetBtn = nullptr;
|
QPushButton *m_toolsResetBtn = nullptr;
|
||||||
|
QLabel *m_thinkingValue = nullptr;
|
||||||
|
|
||||||
|
QToolButton *m_detailsToggle = nullptr;
|
||||||
|
QWidget *m_detailsBody = nullptr;
|
||||||
|
QLineEdit *m_extendsValue = nullptr;
|
||||||
|
QLineEdit *m_tagsValue = nullptr;
|
||||||
|
QLineEdit *m_endpointValue = nullptr;
|
||||||
|
QLabel *m_effectiveUrl = nullptr;
|
||||||
QLineEdit *m_filePatternsValue = nullptr;
|
QLineEdit *m_filePatternsValue = nullptr;
|
||||||
|
|
||||||
QToolButton *m_rawToggle = nullptr;
|
QToolButton *m_rawToggle = nullptr;
|
||||||
QPlainTextEdit *m_rawToml = nullptr;
|
QPlainTextEdit *m_rawToml = nullptr;
|
||||||
|
|
||||||
QToolButton *m_baseRawToggle = nullptr;
|
QToolButton *m_baseRawToggle = nullptr;
|
||||||
QPlainTextEdit *m_baseRawToml = nullptr;
|
QPlainTextEdit *m_baseRawToml = nullptr;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,8 +5,9 @@
|
|||||||
#include "AgentListItem.hpp"
|
#include "AgentListItem.hpp"
|
||||||
|
|
||||||
#include "SettingsTheme.hpp"
|
#include "SettingsTheme.hpp"
|
||||||
#include "TagChip.hpp"
|
#include "SettingsUiBuilders.hpp"
|
||||||
|
|
||||||
|
#include <utils/elidinglabel.h>
|
||||||
#include <utils/theme/theme.h>
|
#include <utils/theme/theme.h>
|
||||||
|
|
||||||
#include <QEvent>
|
#include <QEvent>
|
||||||
@@ -16,73 +17,52 @@
|
|||||||
#include <QMouseEvent>
|
#include <QMouseEvent>
|
||||||
#include <QPalette>
|
#include <QPalette>
|
||||||
#include <QScopedValueRollback>
|
#include <QScopedValueRollback>
|
||||||
#include <QVBoxLayout>
|
|
||||||
|
|
||||||
namespace QodeAssist::Settings {
|
namespace QodeAssist::Settings {
|
||||||
|
|
||||||
AgentListItem::AgentListItem(const AgentConfig &cfg, QWidget *parent)
|
AgentListItem::AgentListItem(const AgentConfig &cfg, QWidget *parent)
|
||||||
: QFrame(parent)
|
: QFrame(parent)
|
||||||
, m_name(cfg.name)
|
, m_name(cfg.name)
|
||||||
|
, m_model(cfg.model)
|
||||||
{
|
{
|
||||||
setObjectName(QStringLiteral("AgentListItem"));
|
setObjectName(QStringLiteral("AgentListItem"));
|
||||||
setFrameShape(QFrame::NoFrame);
|
setFrameShape(QFrame::NoFrame);
|
||||||
setAutoFillBackground(true);
|
setAutoFillBackground(true);
|
||||||
setCursor(Qt::PointingHandCursor);
|
setCursor(Qt::PointingHandCursor);
|
||||||
|
|
||||||
auto *dot = new QLabel(QStringLiteral("●"), this);
|
m_nameLabel = new QLabel(this);
|
||||||
QFont df = dot->font();
|
QFont nf = m_nameLabel->font();
|
||||||
df.setPixelSize(10);
|
|
||||||
dot->setFont(df);
|
|
||||||
QPalette dp = dot->palette();
|
|
||||||
dp.setColor(QPalette::WindowText, Utils::creatorColor(Utils::Theme::PanelTextColorMid));
|
|
||||||
dot->setPalette(dp);
|
|
||||||
|
|
||||||
auto *nameLbl = new QLabel(cfg.name, this);
|
|
||||||
QFont nf = nameLbl->font();
|
|
||||||
nf.setBold(true);
|
nf.setBold(true);
|
||||||
nf.setPixelSize(12);
|
nf.setPixelSize(12);
|
||||||
nameLbl->setFont(nf);
|
m_nameLabel->setFont(nf);
|
||||||
|
m_nameLabel->setTextFormat(Qt::RichText);
|
||||||
|
|
||||||
auto *headerRow = new QHBoxLayout;
|
m_modelLabel = new Utils::ElidingLabel(this);
|
||||||
headerRow->setContentsMargins(0, 0, 0, 0);
|
m_modelLabel->setElideMode(Qt::ElideMiddle);
|
||||||
headerRow->setSpacing(6);
|
|
||||||
headerRow->addWidget(dot, 0, Qt::AlignVCenter);
|
|
||||||
headerRow->addWidget(nameLbl, 1);
|
|
||||||
|
|
||||||
auto *col = new QVBoxLayout;
|
|
||||||
col->setContentsMargins(0, 0, 0, 0);
|
|
||||||
col->setSpacing(2);
|
|
||||||
col->addLayout(headerRow);
|
|
||||||
|
|
||||||
m_modelLabel = new QLabel(cfg.model, this);
|
|
||||||
m_modelLabel->setFont(monospaceFont(11));
|
m_modelLabel->setFont(monospaceFont(11));
|
||||||
m_modelLabel->setContentsMargins(16, 0, 0, 0);
|
m_modelLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
|
||||||
QPalette mp = m_modelLabel->palette();
|
QPalette mp = m_modelLabel->palette();
|
||||||
mp.setColor(QPalette::WindowText, Utils::creatorColor(Utils::Theme::PanelTextColorMid));
|
mp.setColor(QPalette::WindowText, Utils::creatorColor(Utils::Theme::PanelTextColorMid));
|
||||||
m_modelLabel->setPalette(mp);
|
m_modelLabel->setPalette(mp);
|
||||||
|
m_modelLabel->setText(cfg.model);
|
||||||
m_modelLabel->setVisible(!cfg.model.isEmpty());
|
m_modelLabel->setVisible(!cfg.model.isEmpty());
|
||||||
col->addWidget(m_modelLabel);
|
|
||||||
|
|
||||||
if (!cfg.tags.isEmpty()) {
|
auto *row = new QHBoxLayout(this);
|
||||||
auto *tagsHolder = new QWidget(this);
|
row->setContentsMargins(8, 4, 8, 4);
|
||||||
auto *tagsLay = new QHBoxLayout(tagsHolder);
|
row->setSpacing(8);
|
||||||
tagsLay->setContentsMargins(16, 2, 0, 0);
|
row->addWidget(m_nameLabel, 0);
|
||||||
tagsLay->setSpacing(3);
|
row->addWidget(m_modelLabel, 1);
|
||||||
for (const QString &t : cfg.tags) {
|
|
||||||
auto *chip = new TagChip(t, -1, tagsHolder);
|
|
||||||
connect(chip, &TagChip::clicked, this, &AgentListItem::tagClicked);
|
|
||||||
m_chips.append(chip);
|
|
||||||
tagsLay->addWidget(chip);
|
|
||||||
}
|
|
||||||
tagsLay->addStretch(1);
|
|
||||||
col->addWidget(tagsHolder);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto *outer = new QVBoxLayout(this);
|
QStringList tipLines;
|
||||||
outer->setContentsMargins(5, 6, 8, 6);
|
if (!cfg.description.isEmpty())
|
||||||
outer->setSpacing(0);
|
tipLines << cfg.description;
|
||||||
outer->addLayout(col);
|
if (!cfg.model.isEmpty())
|
||||||
|
tipLines << tr("Model: %1").arg(cfg.model);
|
||||||
|
if (!cfg.tags.isEmpty())
|
||||||
|
tipLines << tr("Tags: %1").arg(cfg.tags.join(QStringLiteral(", ")));
|
||||||
|
setToolTip(tipLines.join(QStringLiteral("\n")));
|
||||||
|
|
||||||
|
updateNameText();
|
||||||
applyTheme();
|
applyTheme();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,20 +74,26 @@ void AgentListItem::setSelected(bool selected)
|
|||||||
applyTheme();
|
applyTheme();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AgentListItem::setActiveTags(const QSet<QString> &active)
|
|
||||||
{
|
|
||||||
for (auto *chip : m_chips)
|
|
||||||
chip->setActive(active.contains(chip->tag()));
|
|
||||||
}
|
|
||||||
|
|
||||||
void AgentListItem::setModel(const QString &model)
|
void AgentListItem::setModel(const QString &model)
|
||||||
{
|
{
|
||||||
if (!m_modelLabel)
|
m_model = model;
|
||||||
return;
|
|
||||||
m_modelLabel->setText(model);
|
m_modelLabel->setText(model);
|
||||||
m_modelLabel->setVisible(!model.isEmpty());
|
m_modelLabel->setVisible(!model.isEmpty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AgentListItem::setFilterHighlight(const QString &lowerFilter)
|
||||||
|
{
|
||||||
|
if (m_filter == lowerFilter)
|
||||||
|
return;
|
||||||
|
m_filter = lowerFilter;
|
||||||
|
updateNameText();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AgentListItem::updateNameText()
|
||||||
|
{
|
||||||
|
m_nameLabel->setText(filterHighlightedHtml(m_name, m_filter));
|
||||||
|
}
|
||||||
|
|
||||||
void AgentListItem::mouseReleaseEvent(QMouseEvent *event)
|
void AgentListItem::mouseReleaseEvent(QMouseEvent *event)
|
||||||
{
|
{
|
||||||
if (event->button() == Qt::LeftButton)
|
if (event->button() == Qt::LeftButton)
|
||||||
@@ -133,6 +119,7 @@ void AgentListItem::applyTheme()
|
|||||||
"#AgentListItem { background:transparent;"
|
"#AgentListItem { background:transparent;"
|
||||||
" border-top:1px solid %1; border-left:3px solid %2; }")
|
" border-top:1px solid %1; border-left:3px solid %2; }")
|
||||||
.arg(cssColor(Utils::creatorColor(Utils::Theme::SplitterColor)), accent));
|
.arg(cssColor(Utils::creatorColor(Utils::Theme::SplitterColor)), accent));
|
||||||
|
updateNameText();
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace QodeAssist::Settings
|
} // namespace QodeAssist::Settings
|
||||||
|
|||||||
@@ -5,8 +5,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <QFrame>
|
#include <QFrame>
|
||||||
#include <QList>
|
|
||||||
#include <QSet>
|
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
|
||||||
#include <AgentConfig.hpp>
|
#include <AgentConfig.hpp>
|
||||||
@@ -15,9 +13,11 @@ QT_BEGIN_NAMESPACE
|
|||||||
class QLabel;
|
class QLabel;
|
||||||
QT_END_NAMESPACE
|
QT_END_NAMESPACE
|
||||||
|
|
||||||
namespace QodeAssist::Settings {
|
namespace Utils {
|
||||||
|
class ElidingLabel;
|
||||||
|
}
|
||||||
|
|
||||||
class TagChip;
|
namespace QodeAssist::Settings {
|
||||||
|
|
||||||
class AgentListItem : public QFrame
|
class AgentListItem : public QFrame
|
||||||
{
|
{
|
||||||
@@ -27,12 +27,11 @@ public:
|
|||||||
|
|
||||||
QString agentName() const { return m_name; }
|
QString agentName() const { return m_name; }
|
||||||
void setSelected(bool selected);
|
void setSelected(bool selected);
|
||||||
void setActiveTags(const QSet<QString> &active);
|
|
||||||
void setModel(const QString &model);
|
void setModel(const QString &model);
|
||||||
|
void setFilterHighlight(const QString &lowerFilter);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void clicked(const QString &name);
|
void clicked(const QString &name);
|
||||||
void tagClicked(const QString &tag);
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void mouseReleaseEvent(QMouseEvent *event) override;
|
void mouseReleaseEvent(QMouseEvent *event) override;
|
||||||
@@ -40,12 +39,15 @@ protected:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
void applyTheme();
|
void applyTheme();
|
||||||
|
void updateNameText();
|
||||||
|
|
||||||
QString m_name;
|
QString m_name;
|
||||||
|
QString m_model;
|
||||||
|
QString m_filter;
|
||||||
bool m_selected = false;
|
bool m_selected = false;
|
||||||
bool m_inApplyTheme = false;
|
bool m_inApplyTheme = false;
|
||||||
QLabel *m_modelLabel = nullptr;
|
QLabel *m_nameLabel = nullptr;
|
||||||
QList<TagChip *> m_chips;
|
Utils::ElidingLabel *m_modelLabel = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace QodeAssist::Settings
|
} // namespace QodeAssist::Settings
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
#include <QEvent>
|
#include <QEvent>
|
||||||
#include <QFont>
|
#include <QFont>
|
||||||
#include <QHBoxLayout>
|
#include <QHBoxLayout>
|
||||||
|
#include <QKeyEvent>
|
||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
#include <QLineEdit>
|
#include <QLineEdit>
|
||||||
#include <QMap>
|
#include <QMap>
|
||||||
@@ -37,8 +38,13 @@ AgentListPane::AgentListPane(AgentFactory *factory, QWidget *parent)
|
|||||||
setFrameShape(QFrame::StyledPanel);
|
setFrameShape(QFrame::StyledPanel);
|
||||||
|
|
||||||
m_filterEdit = new QLineEdit(this);
|
m_filterEdit = new QLineEdit(this);
|
||||||
m_filterEdit->setPlaceholderText(tr("Filter agents…"));
|
m_filterEdit->setPlaceholderText(tr("Filter by name, model, provider, tag…"));
|
||||||
m_filterEdit->setClearButtonEnabled(true);
|
m_filterEdit->setClearButtonEnabled(true);
|
||||||
|
m_filterEdit->setToolTip(tr("Type to filter; Up/Down moves through the list."));
|
||||||
|
m_filterEdit->installEventFilter(this);
|
||||||
|
setFocusProxy(m_filterEdit);
|
||||||
|
|
||||||
|
m_tagStrip = new TagFilterStrip(this);
|
||||||
|
|
||||||
auto *filterRow = new QHBoxLayout;
|
auto *filterRow = new QHBoxLayout;
|
||||||
filterRow->setContentsMargins(6, 6, 6, 6);
|
filterRow->setContentsMargins(6, 6, 6, 6);
|
||||||
@@ -48,8 +54,6 @@ AgentListPane::AgentListPane(AgentFactory *factory, QWidget *parent)
|
|||||||
m_filterHolder->setLayout(filterRow);
|
m_filterHolder->setLayout(filterRow);
|
||||||
m_filterHolder->setAutoFillBackground(true);
|
m_filterHolder->setAutoFillBackground(true);
|
||||||
|
|
||||||
m_tagStrip = new TagFilterStrip(this);
|
|
||||||
|
|
||||||
m_listScroll = new QScrollArea(this);
|
m_listScroll = new QScrollArea(this);
|
||||||
m_listScroll->setWidgetResizable(true);
|
m_listScroll->setWidgetResizable(true);
|
||||||
m_listScroll->setFrameShape(QFrame::NoFrame);
|
m_listScroll->setFrameShape(QFrame::NoFrame);
|
||||||
@@ -59,17 +63,20 @@ AgentListPane::AgentListPane(AgentFactory *factory, QWidget *parent)
|
|||||||
outer->setContentsMargins(0, 0, 0, 0);
|
outer->setContentsMargins(0, 0, 0, 0);
|
||||||
outer->setSpacing(0);
|
outer->setSpacing(0);
|
||||||
outer->addWidget(m_filterHolder);
|
outer->addWidget(m_filterHolder);
|
||||||
outer->addWidget(m_tagStrip);
|
|
||||||
outer->addWidget(m_listScroll, 1);
|
outer->addWidget(m_listScroll, 1);
|
||||||
|
|
||||||
m_filterDebounce = new QTimer(this);
|
m_filterDebounce = new QTimer(this);
|
||||||
m_filterDebounce->setSingleShot(true);
|
m_filterDebounce->setSingleShot(true);
|
||||||
m_filterDebounce->setInterval(100);
|
m_filterDebounce->setInterval(100);
|
||||||
connect(m_filterDebounce, &QTimer::timeout, this, &AgentListPane::rebuildList);
|
connect(m_filterDebounce, &QTimer::timeout, this, &AgentListPane::rebuildList);
|
||||||
connect(m_filterEdit, &QLineEdit::textChanged, this,
|
connect(m_filterEdit, &QLineEdit::textChanged, this, [this](const QString &) {
|
||||||
[this](const QString &) { m_filterDebounce->start(); });
|
m_filterDebounce->start();
|
||||||
|
});
|
||||||
|
|
||||||
connect(m_tagStrip, &TagFilterStrip::activeTagsChanged, this,
|
connect(
|
||||||
|
m_tagStrip,
|
||||||
|
&TagFilterStrip::activeTagsChanged,
|
||||||
|
this,
|
||||||
[this](const QSet<QString> &) { rebuildList(); },
|
[this](const QSet<QString> &) { rebuildList(); },
|
||||||
Qt::QueuedConnection);
|
Qt::QueuedConnection);
|
||||||
|
|
||||||
@@ -93,7 +100,7 @@ void AgentListPane::selectByName(const QString &name)
|
|||||||
return;
|
return;
|
||||||
if (m_factory) {
|
if (m_factory) {
|
||||||
if (const AgentConfig *cfg = m_factory->configByName(name))
|
if (const AgentConfig *cfg = m_factory->configByName(name))
|
||||||
m_expandedGroups.insert(groupKey(*cfg));
|
m_collapsedGroups.remove(groupKey(*cfg));
|
||||||
}
|
}
|
||||||
setCurrentNameInternal(name, false);
|
setCurrentNameInternal(name, false);
|
||||||
m_notifyOnRebuild = true;
|
m_notifyOnRebuild = true;
|
||||||
@@ -121,6 +128,12 @@ void AgentListPane::refresh()
|
|||||||
rebuildList();
|
rebuildList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AgentListPane::focusFilter()
|
||||||
|
{
|
||||||
|
m_filterEdit->setFocus(Qt::OtherFocusReason);
|
||||||
|
m_filterEdit->selectAll();
|
||||||
|
}
|
||||||
|
|
||||||
void AgentListPane::changeEvent(QEvent *event)
|
void AgentListPane::changeEvent(QEvent *event)
|
||||||
{
|
{
|
||||||
QFrame::changeEvent(event);
|
QFrame::changeEvent(event);
|
||||||
@@ -128,6 +141,41 @@ void AgentListPane::changeEvent(QEvent *event)
|
|||||||
applyFilterHolderTheme();
|
applyFilterHolderTheme();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool AgentListPane::eventFilter(QObject *watched, QEvent *event)
|
||||||
|
{
|
||||||
|
if (watched == m_filterEdit && event->type() == QEvent::KeyPress) {
|
||||||
|
auto *ke = static_cast<QKeyEvent *>(event);
|
||||||
|
if (ke->key() == Qt::Key_Down) {
|
||||||
|
moveSelection(1);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (ke->key() == Qt::Key_Up) {
|
||||||
|
moveSelection(-1);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return QFrame::eventFilter(watched, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AgentListPane::moveSelection(int delta)
|
||||||
|
{
|
||||||
|
if (m_rows.isEmpty())
|
||||||
|
return;
|
||||||
|
int cur = -1;
|
||||||
|
for (int i = 0; i < m_rows.size(); ++i) {
|
||||||
|
if (m_rows.at(i)->agentName() == m_currentName) {
|
||||||
|
cur = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int next = cur < 0 ? (delta > 0 ? 0 : int(m_rows.size()) - 1) : cur + delta;
|
||||||
|
next = qBound(0, next, int(m_rows.size()) - 1);
|
||||||
|
if (next == cur)
|
||||||
|
return;
|
||||||
|
setCurrentNameInternal(m_rows.at(next)->agentName(), true);
|
||||||
|
m_listScroll->ensureWidgetVisible(m_rows.at(next), 0, 40);
|
||||||
|
}
|
||||||
|
|
||||||
void AgentListPane::applyFilterHolderTheme()
|
void AgentListPane::applyFilterHolderTheme()
|
||||||
{
|
{
|
||||||
if (!m_filterHolder)
|
if (!m_filterHolder)
|
||||||
@@ -156,9 +204,14 @@ std::vector<const AgentConfig *> AgentListPane::visibleAgents() const
|
|||||||
|
|
||||||
bool AgentListPane::matchesFilters(const AgentConfig &a, const QString &lowerFilter) const
|
bool AgentListPane::matchesFilters(const AgentConfig &a, const QString &lowerFilter) const
|
||||||
{
|
{
|
||||||
if (!lowerFilter.isEmpty()
|
if (!lowerFilter.isEmpty()) {
|
||||||
&& !(a.name + QLatin1Char(' ') + a.model).toLower().contains(lowerFilter))
|
const QString haystack = (a.name + QLatin1Char(' ') + a.model + QLatin1Char(' ')
|
||||||
|
+ a.providerInstance + QLatin1Char(' ')
|
||||||
|
+ a.tags.join(QLatin1Char(' ')))
|
||||||
|
.toLower();
|
||||||
|
if (!haystack.contains(lowerFilter))
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
const QSet<QString> &active = m_tagStrip->activeTags();
|
const QSet<QString> &active = m_tagStrip->activeTags();
|
||||||
for (const QString &t : active)
|
for (const QString &t : active)
|
||||||
if (!a.tags.contains(t))
|
if (!a.tags.contains(t))
|
||||||
@@ -199,14 +252,8 @@ void AgentListPane::rebuildList()
|
|||||||
for (const AgentConfig *cfg : agents) {
|
for (const AgentConfig *cfg : agents) {
|
||||||
auto *item = new AgentListItem(*cfg, content);
|
auto *item = new AgentListItem(*cfg, content);
|
||||||
item->setSelected(cfg->name == m_currentName);
|
item->setSelected(cfg->name == m_currentName);
|
||||||
item->setActiveTags(activeTags);
|
item->setFilterHighlight(lowerFilter);
|
||||||
connect(item, &AgentListItem::clicked, this, &AgentListPane::onRowClicked);
|
connect(item, &AgentListItem::clicked, this, &AgentListPane::onRowClicked);
|
||||||
connect(
|
|
||||||
item,
|
|
||||||
&AgentListItem::tagClicked,
|
|
||||||
this,
|
|
||||||
[this](const QString &tag) { m_tagStrip->toggleTag(tag); },
|
|
||||||
Qt::QueuedConnection);
|
|
||||||
contentLayout->addWidget(item);
|
contentLayout->addWidget(item);
|
||||||
newRows.append(item);
|
newRows.append(item);
|
||||||
}
|
}
|
||||||
@@ -231,7 +278,7 @@ void AgentListPane::rebuildList()
|
|||||||
const std::vector<const AgentConfig *> &group = byProvider[provider];
|
const std::vector<const AgentConfig *> &group = byProvider[provider];
|
||||||
const QString key = sectionKey + QLatin1Char('/') + provider;
|
const QString key = sectionKey + QLatin1Char('/') + provider;
|
||||||
liveKeys.insert(key);
|
liveKeys.insert(key);
|
||||||
const bool expanded = filtersActive || m_expandedGroups.contains(key);
|
const bool expanded = filtersActive || !m_collapsedGroups.contains(key);
|
||||||
|
|
||||||
auto *header = new CollapsibleHeader(provider, int(group.size()), content);
|
auto *header = new CollapsibleHeader(provider, int(group.size()), content);
|
||||||
header->setExpanded(expanded);
|
header->setExpanded(expanded);
|
||||||
@@ -242,8 +289,8 @@ void AgentListPane::rebuildList()
|
|||||||
&CollapsibleHeader::toggled,
|
&CollapsibleHeader::toggled,
|
||||||
this,
|
this,
|
||||||
[this, key] {
|
[this, key] {
|
||||||
if (!m_expandedGroups.remove(key))
|
if (!m_collapsedGroups.remove(key))
|
||||||
m_expandedGroups.insert(key);
|
m_collapsedGroups.insert(key);
|
||||||
rebuildList();
|
rebuildList();
|
||||||
},
|
},
|
||||||
Qt::QueuedConnection);
|
Qt::QueuedConnection);
|
||||||
@@ -258,7 +305,7 @@ void AgentListPane::rebuildList()
|
|||||||
addSection(tr("User"), QStringLiteral("user"), userAgents);
|
addSection(tr("User"), QStringLiteral("user"), userAgents);
|
||||||
addSection(tr("Bundled"), QStringLiteral("bundled"), bundledAgents);
|
addSection(tr("Bundled"), QStringLiteral("bundled"), bundledAgents);
|
||||||
if (!filtersActive)
|
if (!filtersActive)
|
||||||
m_expandedGroups.intersect(liveKeys);
|
m_collapsedGroups.intersect(liveKeys);
|
||||||
if (userAgents.empty() && bundledAgents.empty()) {
|
if (userAgents.empty() && bundledAgents.empty()) {
|
||||||
auto *empty = new QLabel(tr("No agents match these filters."), content);
|
auto *empty = new QLabel(tr("No agents match these filters."), content);
|
||||||
empty->setAlignment(Qt::AlignCenter);
|
empty->setAlignment(Qt::AlignCenter);
|
||||||
@@ -273,8 +320,7 @@ void AgentListPane::rebuildList()
|
|||||||
m_rows = newRows;
|
m_rows = newRows;
|
||||||
m_listScroll->setWidget(content);
|
m_listScroll->setWidget(content);
|
||||||
|
|
||||||
const AgentConfig *current
|
const AgentConfig *current = m_currentName.isEmpty() || !m_factory
|
||||||
= m_currentName.isEmpty() || !m_factory
|
|
||||||
? nullptr
|
? nullptr
|
||||||
: m_factory->configByName(m_currentName);
|
: m_factory->configByName(m_currentName);
|
||||||
if (!current && !m_rows.isEmpty()) {
|
if (!current && !m_rows.isEmpty()) {
|
||||||
|
|||||||
@@ -4,11 +4,11 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
#include <QFrame>
|
#include <QFrame>
|
||||||
#include <QList>
|
#include <QList>
|
||||||
#include <QSet>
|
#include <QSet>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include <AgentConfig.hpp>
|
#include <AgentConfig.hpp>
|
||||||
|
|
||||||
@@ -35,15 +35,19 @@ public:
|
|||||||
QString currentName() const { return m_currentName; }
|
QString currentName() const { return m_currentName; }
|
||||||
void selectByName(const QString &name);
|
void selectByName(const QString &name);
|
||||||
void refresh();
|
void refresh();
|
||||||
|
void focusFilter();
|
||||||
|
TagFilterStrip *tagStrip() const { return m_tagStrip; }
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void currentAgentChanged(const QString &name);
|
void currentAgentChanged(const QString &name);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void changeEvent(QEvent *event) override;
|
void changeEvent(QEvent *event) override;
|
||||||
|
bool eventFilter(QObject *watched, QEvent *event) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void rebuildList();
|
void rebuildList();
|
||||||
|
void moveSelection(int delta);
|
||||||
void applyFilterHolderTheme();
|
void applyFilterHolderTheme();
|
||||||
bool matchesFilters(const AgentConfig &a, const QString &lowerFilter) const;
|
bool matchesFilters(const AgentConfig &a, const QString &lowerFilter) const;
|
||||||
std::vector<const AgentConfig *> visibleAgents() const;
|
std::vector<const AgentConfig *> visibleAgents() const;
|
||||||
@@ -60,7 +64,7 @@ private:
|
|||||||
QScrollArea *m_listScroll = nullptr;
|
QScrollArea *m_listScroll = nullptr;
|
||||||
QList<AgentListItem *> m_rows;
|
QList<AgentListItem *> m_rows;
|
||||||
QString m_currentName;
|
QString m_currentName;
|
||||||
QSet<QString> m_expandedGroups;
|
QSet<QString> m_collapsedGroups;
|
||||||
bool m_notifyOnRebuild = false;
|
bool m_notifyOnRebuild = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,139 +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 "AgentModelDialog.hpp"
|
|
||||||
|
|
||||||
#include "SettingsTheme.hpp"
|
|
||||||
|
|
||||||
#include <utils/theme/theme.h>
|
|
||||||
|
|
||||||
#include <Agent.hpp>
|
|
||||||
#include <AgentFactory.hpp>
|
|
||||||
|
|
||||||
#include <QDialogButtonBox>
|
|
||||||
#include <QHBoxLayout>
|
|
||||||
#include <QLabel>
|
|
||||||
#include <QLineEdit>
|
|
||||||
#include <QListWidget>
|
|
||||||
#include <QPalette>
|
|
||||||
#include <QPushButton>
|
|
||||||
#include <QSignalBlocker>
|
|
||||||
#include <QVBoxLayout>
|
|
||||||
|
|
||||||
namespace QodeAssist::Settings {
|
|
||||||
|
|
||||||
AgentModelDialog::AgentModelDialog(
|
|
||||||
AgentFactory *factory, const QString &agentName, const QString ¤tModel, QWidget *parent)
|
|
||||||
: QDialog(parent)
|
|
||||||
, m_factory(factory)
|
|
||||||
, m_agentName(agentName)
|
|
||||||
{
|
|
||||||
setWindowTitle(tr("Select Model"));
|
|
||||||
resize(440, 380);
|
|
||||||
|
|
||||||
m_modelEdit = new QLineEdit(currentModel, this);
|
|
||||||
m_modelEdit->setFont(monospaceFont(11));
|
|
||||||
m_modelEdit->setPlaceholderText(tr("Type a model name or pick one below"));
|
|
||||||
m_modelEdit->setClearButtonEnabled(true);
|
|
||||||
|
|
||||||
m_fetchBtn = new QPushButton(tr("Fetch available models"), this);
|
|
||||||
m_fetchBtn->setToolTip(tr("Query the agent's provider for its installed models"));
|
|
||||||
|
|
||||||
m_status = new QLabel(this);
|
|
||||||
m_status->setFont(monospaceFont(10));
|
|
||||||
QPalette sp = m_status->palette();
|
|
||||||
sp.setColor(QPalette::WindowText, Utils::creatorColor(Utils::Theme::PanelTextColorMid));
|
|
||||||
m_status->setPalette(sp);
|
|
||||||
|
|
||||||
m_list = new QListWidget(this);
|
|
||||||
m_list->setFont(monospaceFont(11));
|
|
||||||
|
|
||||||
auto *buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
|
|
||||||
|
|
||||||
connect(m_list, &QListWidget::currentTextChanged, this, [this](const QString &text) {
|
|
||||||
if (!text.isEmpty())
|
|
||||||
m_modelEdit->setText(text);
|
|
||||||
});
|
|
||||||
connect(m_list, &QListWidget::itemDoubleClicked, this, [this](QListWidgetItem *) { accept(); });
|
|
||||||
connect(m_fetchBtn, &QPushButton::clicked, this, [this] { fetchModels(); });
|
|
||||||
connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
|
||||||
connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
|
||||||
|
|
||||||
auto *fetchRow = new QHBoxLayout;
|
|
||||||
fetchRow->setContentsMargins(0, 0, 0, 0);
|
|
||||||
fetchRow->setSpacing(8);
|
|
||||||
fetchRow->addWidget(m_fetchBtn);
|
|
||||||
fetchRow->addWidget(m_status, 1);
|
|
||||||
|
|
||||||
auto *root = new QVBoxLayout(this);
|
|
||||||
root->addWidget(new QLabel(tr("Model:"), this));
|
|
||||||
root->addWidget(m_modelEdit);
|
|
||||||
root->addLayout(fetchRow);
|
|
||||||
root->addWidget(m_list, 1);
|
|
||||||
root->addWidget(buttons);
|
|
||||||
|
|
||||||
fetchModels();
|
|
||||||
}
|
|
||||||
|
|
||||||
QString AgentModelDialog::selectedModel() const
|
|
||||||
{
|
|
||||||
return m_modelEdit->text().trimmed();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AgentModelDialog::fetchModels()
|
|
||||||
{
|
|
||||||
if (!m_factory)
|
|
||||||
return;
|
|
||||||
if (m_watcher && m_watcher->isRunning())
|
|
||||||
return;
|
|
||||||
|
|
||||||
QString err;
|
|
||||||
Agent *probe = m_factory->create(m_agentName, this, &err);
|
|
||||||
if (!probe) {
|
|
||||||
m_status->setText(
|
|
||||||
err.isEmpty() ? tr("Provider is not available.")
|
|
||||||
: tr("Provider is not available: %1").arg(err));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
m_probe = probe;
|
|
||||||
|
|
||||||
if (!m_watcher) {
|
|
||||||
m_watcher = new QFutureWatcher<QList<QString>>(this);
|
|
||||||
connect(m_watcher, &QFutureWatcher<QList<QString>>::finished, this, [this] {
|
|
||||||
onModelsFetched();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
m_fetchBtn->setEnabled(false);
|
|
||||||
m_status->setText(tr("Loading models…"));
|
|
||||||
m_watcher->setFuture(probe->installedModels());
|
|
||||||
}
|
|
||||||
|
|
||||||
void AgentModelDialog::onModelsFetched()
|
|
||||||
{
|
|
||||||
QList<QString> models;
|
|
||||||
if (m_watcher->future().resultCount() > 0)
|
|
||||||
models = m_watcher->result();
|
|
||||||
|
|
||||||
if (m_probe) {
|
|
||||||
m_probe->deleteLater();
|
|
||||||
m_probe.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
m_fetchBtn->setEnabled(true);
|
|
||||||
|
|
||||||
const QString keep = m_modelEdit->text();
|
|
||||||
{
|
|
||||||
QSignalBlocker block(m_list);
|
|
||||||
m_list->clear();
|
|
||||||
m_list->addItems(models);
|
|
||||||
}
|
|
||||||
m_modelEdit->setText(keep);
|
|
||||||
|
|
||||||
m_status->setText(
|
|
||||||
models.isEmpty() ? tr("No models returned — type the model name manually.")
|
|
||||||
: tr("%n model(s) available.", nullptr, static_cast<int>(models.size())));
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace QodeAssist::Settings
|
|
||||||
@@ -8,24 +8,21 @@
|
|||||||
#include "AgentDuplicator.hpp"
|
#include "AgentDuplicator.hpp"
|
||||||
#include "AgentListPane.hpp"
|
#include "AgentListPane.hpp"
|
||||||
#include "SettingsConstants.hpp"
|
#include "SettingsConstants.hpp"
|
||||||
#include "SettingsTheme.hpp"
|
#include "TagFilterStrip.hpp"
|
||||||
|
|
||||||
#include <coreplugin/dialogs/ioptionspage.h>
|
#include <coreplugin/dialogs/ioptionspage.h>
|
||||||
#include <coreplugin/editormanager/editormanager.h>
|
#include <coreplugin/editormanager/editormanager.h>
|
||||||
|
|
||||||
#include <utils/filepath.h>
|
#include <utils/filepath.h>
|
||||||
#include <utils/theme/theme.h>
|
|
||||||
|
|
||||||
#include <QDesktopServices>
|
#include <QDesktopServices>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QFont>
|
#include <QFont>
|
||||||
#include <QFontMetrics>
|
|
||||||
#include <QFrame>
|
#include <QFrame>
|
||||||
#include <QHBoxLayout>
|
#include <QHBoxLayout>
|
||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
#include <QPalette>
|
|
||||||
#include <QPointer>
|
#include <QPointer>
|
||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
#include <QScrollArea>
|
#include <QScrollArea>
|
||||||
@@ -76,13 +73,7 @@ public:
|
|||||||
|
|
||||||
m_reload = new QPushButton(tr("Reload from disk"), this);
|
m_reload = new QPushButton(tr("Reload from disk"), this);
|
||||||
m_openUserDir = new QPushButton(tr("Open agents folder"), this);
|
m_openUserDir = new QPushButton(tr("Open agents folder"), this);
|
||||||
|
m_openUserDir->setToolTip(QodeAssist::AgentFactory::userAgentsDir());
|
||||||
m_userPathLabel = new QLabel(this);
|
|
||||||
m_userPathLabel->setFont(monospaceFont(11));
|
|
||||||
QPalette mutedPal = m_userPathLabel->palette();
|
|
||||||
mutedPal.setColor(QPalette::WindowText, Utils::creatorColor(Utils::Theme::PanelTextColorMid));
|
|
||||||
m_userPathLabel->setPalette(mutedPal);
|
|
||||||
m_userPathLabel->setMaximumWidth(260);
|
|
||||||
|
|
||||||
auto *headerRow = new QHBoxLayout;
|
auto *headerRow = new QHBoxLayout;
|
||||||
headerRow->setContentsMargins(0, 0, 0, 0);
|
headerRow->setContentsMargins(0, 0, 0, 0);
|
||||||
@@ -90,7 +81,6 @@ public:
|
|||||||
headerRow->addWidget(m_titleLabel);
|
headerRow->addWidget(m_titleLabel);
|
||||||
headerRow->addStretch(1);
|
headerRow->addStretch(1);
|
||||||
headerRow->addWidget(m_reload);
|
headerRow->addWidget(m_reload);
|
||||||
headerRow->addWidget(m_userPathLabel);
|
|
||||||
headerRow->addWidget(m_openUserDir);
|
headerRow->addWidget(m_openUserDir);
|
||||||
|
|
||||||
auto *headerSep = new QFrame(this);
|
auto *headerSep = new QFrame(this);
|
||||||
@@ -98,6 +88,7 @@ public:
|
|||||||
headerSep->setFrameShadow(QFrame::Sunken);
|
headerSep->setFrameShadow(QFrame::Sunken);
|
||||||
|
|
||||||
m_listPane = new AgentListPane(m_agentFactory, this);
|
m_listPane = new AgentListPane(m_agentFactory, this);
|
||||||
|
m_listPane->tagStrip()->setMaxColumns(8);
|
||||||
|
|
||||||
m_detail = new AgentDetailPane(this);
|
m_detail = new AgentDetailPane(this);
|
||||||
m_detail->setInstanceFactory(m_agentFactory->instanceFactory());
|
m_detail->setInstanceFactory(m_agentFactory->instanceFactory());
|
||||||
@@ -119,6 +110,7 @@ public:
|
|||||||
root->setSpacing(6);
|
root->setSpacing(6);
|
||||||
root->addLayout(headerRow);
|
root->addLayout(headerRow);
|
||||||
root->addWidget(headerSep);
|
root->addWidget(headerSep);
|
||||||
|
root->addWidget(m_listPane->tagStrip());
|
||||||
root->addWidget(splitter, 1);
|
root->addWidget(splitter, 1);
|
||||||
|
|
||||||
connect(m_reload, &QPushButton::clicked, this, &AgentsWidget::reloadFromDisk);
|
connect(m_reload, &QPushButton::clicked, this, &AgentsWidget::reloadFromDisk);
|
||||||
@@ -153,6 +145,8 @@ public:
|
|||||||
|
|
||||||
reloadFromDisk();
|
reloadFromDisk();
|
||||||
|
|
||||||
|
QTimer::singleShot(0, this, [this] { m_listPane->focusFilter(); });
|
||||||
|
|
||||||
if (m_navigator) {
|
if (m_navigator) {
|
||||||
QTimer::singleShot(0, this, [this] {
|
QTimer::singleShot(0, this, [this] {
|
||||||
if (!m_navigator)
|
if (!m_navigator)
|
||||||
@@ -170,18 +164,9 @@ private:
|
|||||||
void reloadFromDisk()
|
void reloadFromDisk()
|
||||||
{
|
{
|
||||||
m_agentFactory->reload();
|
m_agentFactory->reload();
|
||||||
updateUserPathLabel();
|
|
||||||
m_listPane->refresh();
|
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)
|
void openAgentInEditor(const AgentConfig &agent)
|
||||||
{
|
{
|
||||||
const QString name = agent.name;
|
const QString name = agent.name;
|
||||||
@@ -253,7 +238,6 @@ private:
|
|||||||
QLabel *m_titleLabel = nullptr;
|
QLabel *m_titleLabel = nullptr;
|
||||||
QPushButton *m_reload = nullptr;
|
QPushButton *m_reload = nullptr;
|
||||||
QPushButton *m_openUserDir = nullptr;
|
QPushButton *m_openUserDir = nullptr;
|
||||||
QLabel *m_userPathLabel = nullptr;
|
|
||||||
|
|
||||||
AgentListPane *m_listPane = nullptr;
|
AgentListPane *m_listPane = nullptr;
|
||||||
QScrollArea *m_detailScroll = nullptr;
|
QScrollArea *m_detailScroll = nullptr;
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ add_library(QodeAssistSettings STATIC
|
|||||||
UpdateDialog.hpp UpdateDialog.cpp
|
UpdateDialog.hpp UpdateDialog.cpp
|
||||||
AgentsSettingsPage.hpp AgentsSettingsPage.cpp
|
AgentsSettingsPage.hpp AgentsSettingsPage.cpp
|
||||||
AgentDetailPane.hpp AgentDetailPane.cpp
|
AgentDetailPane.hpp AgentDetailPane.cpp
|
||||||
AgentModelDialog.hpp AgentModelDialog.cpp
|
ModelSelector.hpp ModelSelector.cpp
|
||||||
AgentListItem.hpp AgentListItem.cpp
|
AgentListItem.hpp AgentListItem.cpp
|
||||||
AgentListPane.hpp AgentListPane.cpp
|
AgentListPane.hpp AgentListPane.cpp
|
||||||
AgentDuplicator.hpp AgentDuplicator.cpp
|
AgentDuplicator.hpp AgentDuplicator.cpp
|
||||||
|
|||||||
@@ -8,6 +8,8 @@
|
|||||||
#include <coreplugin/editormanager/editormanager.h>
|
#include <coreplugin/editormanager/editormanager.h>
|
||||||
#include <coreplugin/icore.h>
|
#include <coreplugin/icore.h>
|
||||||
#include <coreplugin/idocument.h>
|
#include <coreplugin/idocument.h>
|
||||||
|
#include <extensionsystem/pluginmanager.h>
|
||||||
|
#include <extensionsystem/pluginspec.h>
|
||||||
#include <utils/infolabel.h>
|
#include <utils/infolabel.h>
|
||||||
#include <utils/layoutbuilder.h>
|
#include <utils/layoutbuilder.h>
|
||||||
#include <QFont>
|
#include <QFont>
|
||||||
@@ -43,6 +45,16 @@ namespace {
|
|||||||
|
|
||||||
constexpr int kSaveDebounceMs = 300;
|
constexpr int kSaveDebounceMs = 300;
|
||||||
|
|
||||||
|
QString pluginVersionText()
|
||||||
|
{
|
||||||
|
const auto pluginSpecs = ExtensionSystem::PluginManager::plugins();
|
||||||
|
for (const ExtensionSystem::PluginSpec *spec : pluginSpecs) {
|
||||||
|
if (spec->name() == QLatin1String("QodeAssist"))
|
||||||
|
return QStringLiteral("QodeAssist %1").arg(spec->version());
|
||||||
|
}
|
||||||
|
return QStringLiteral("QodeAssist");
|
||||||
|
}
|
||||||
|
|
||||||
class AgentPipelinesWidget : public QWidget
|
class AgentPipelinesWidget : public QWidget
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
@@ -261,7 +273,8 @@ GeneralSettings::GeneralSettings()
|
|||||||
|
|
||||||
requestTimeout.setSettingsKey(Constants::REQUEST_TIMEOUT);
|
requestTimeout.setSettingsKey(Constants::REQUEST_TIMEOUT);
|
||||||
requestTimeout.setLabelText(Tr::tr("Request timeout (seconds):"));
|
requestTimeout.setLabelText(Tr::tr("Request timeout (seconds):"));
|
||||||
requestTimeout.setToolTip(Tr::tr(
|
requestTimeout.setToolTip(
|
||||||
|
Tr::tr(
|
||||||
"Maximum time to wait for the model to send data before a request is aborted. "
|
"Maximum time to wait for the model to send data before a request is aborted. "
|
||||||
"Applies to all requests — chat, code completion, quick refactor and chat compression. "
|
"Applies to all requests — chat, code completion, quick refactor and chat compression. "
|
||||||
"The timer resets every time data is received, so this effectively limits the "
|
"The timer resets every time data is received, so this effectively limits the "
|
||||||
@@ -282,9 +295,15 @@ GeneralSettings::GeneralSettings()
|
|||||||
setLayouter([this]() {
|
setLayouter([this]() {
|
||||||
using namespace Layouting;
|
using namespace Layouting;
|
||||||
|
|
||||||
auto networkGroup = Group{
|
auto *versionLabel = new QLabel(pluginVersionText());
|
||||||
title(Tr::tr("Network")),
|
versionLabel->setEnabled(false);
|
||||||
Column{Row{requestTimeout, Stretch{1}}}};
|
|
||||||
|
auto advancedGroup = Group{
|
||||||
|
title(Tr::tr("Advanced")),
|
||||||
|
Column{
|
||||||
|
Row{enableLogging, Stretch{1}},
|
||||||
|
Row{enableCheckUpdate, Stretch{1}},
|
||||||
|
Row{requestTimeout, Stretch{1}}}};
|
||||||
|
|
||||||
auto *supportLabel = new QLabel(Tr::tr("Support the development of QodeAssist:"));
|
auto *supportLabel = new QLabel(Tr::tr("Support the development of QodeAssist:"));
|
||||||
|
|
||||||
@@ -307,16 +326,13 @@ GeneralSettings::GeneralSettings()
|
|||||||
};
|
};
|
||||||
|
|
||||||
return Column{
|
return Column{
|
||||||
Row{supportLabel, supportLinks, Stretch{1}, checkUpdate, resetToDefaults},
|
Row{enableQodeAssist, Stretch{1}, versionLabel, checkUpdate, resetToDefaults},
|
||||||
Space{8},
|
|
||||||
Row{enableQodeAssist, Stretch{1}},
|
|
||||||
Row{enableLogging, Stretch{1}},
|
|
||||||
Row{enableCheckUpdate, Stretch{1}},
|
|
||||||
Space{8},
|
|
||||||
networkGroup,
|
|
||||||
Space{12},
|
Space{12},
|
||||||
pipelines,
|
pipelines,
|
||||||
Stretch{1}};
|
Space{12},
|
||||||
|
advancedGroup,
|
||||||
|
Stretch{1},
|
||||||
|
Row{supportLabel, supportLinks, Stretch{1}}};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
176
settings/ModelSelector.cpp
Normal file
176
settings/ModelSelector.cpp
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
// 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 "ModelSelector.hpp"
|
||||||
|
|
||||||
|
#include "SettingsTheme.hpp"
|
||||||
|
|
||||||
|
#include <Agent.hpp>
|
||||||
|
#include <AgentFactory.hpp>
|
||||||
|
|
||||||
|
#include <QAbstractItemView>
|
||||||
|
#include <QCompleter>
|
||||||
|
#include <QLineEdit>
|
||||||
|
#include <QSignalBlocker>
|
||||||
|
#include <QStandardItemModel>
|
||||||
|
|
||||||
|
namespace QodeAssist::Settings {
|
||||||
|
|
||||||
|
ModelSelector::ModelSelector(QWidget *parent)
|
||||||
|
: QComboBox(parent)
|
||||||
|
{
|
||||||
|
setEditable(true);
|
||||||
|
setInsertPolicy(QComboBox::NoInsert);
|
||||||
|
setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLengthWithIcon);
|
||||||
|
setMinimumContentsLength(24);
|
||||||
|
setFont(monospaceFont(11));
|
||||||
|
lineEdit()->setPlaceholderText(tr("(set a model)"));
|
||||||
|
lineEdit()->setClearButtonEnabled(true);
|
||||||
|
setEnabled(false);
|
||||||
|
|
||||||
|
auto *completer = new QCompleter(model(), this);
|
||||||
|
completer->setFilterMode(Qt::MatchContains);
|
||||||
|
completer->setCaseSensitivity(Qt::CaseInsensitive);
|
||||||
|
completer->setCompletionMode(QCompleter::PopupCompletion);
|
||||||
|
setCompleter(completer);
|
||||||
|
|
||||||
|
connect(this, &QComboBox::activated, this, [this](int) { submitCurrent(); });
|
||||||
|
connect(lineEdit(), &QLineEdit::returnPressed, this, [this] { submitCurrent(); });
|
||||||
|
connect(
|
||||||
|
completer,
|
||||||
|
QOverload<const QString &>::of(&QCompleter::activated),
|
||||||
|
this,
|
||||||
|
[this](const QString &text) {
|
||||||
|
setEditText(text);
|
||||||
|
submitCurrent();
|
||||||
|
},
|
||||||
|
Qt::QueuedConnection);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModelSelector::setAgent(
|
||||||
|
AgentFactory *factory,
|
||||||
|
const QString &agentName,
|
||||||
|
const QString &providerInstance,
|
||||||
|
const QString &model)
|
||||||
|
{
|
||||||
|
const bool sameTarget = factory == m_factory && agentName == m_agentName
|
||||||
|
&& providerInstance == m_providerInstance;
|
||||||
|
m_factory = factory;
|
||||||
|
m_agentName = agentName;
|
||||||
|
m_providerInstance = providerInstance;
|
||||||
|
m_model = model;
|
||||||
|
if (!sameTarget) {
|
||||||
|
m_fetched = false;
|
||||||
|
{
|
||||||
|
QSignalBlocker block(this);
|
||||||
|
clear();
|
||||||
|
}
|
||||||
|
emit statusChanged(QString());
|
||||||
|
}
|
||||||
|
{
|
||||||
|
QSignalBlocker block(this);
|
||||||
|
setCurrentIndex(findText(model));
|
||||||
|
setEditText(model);
|
||||||
|
}
|
||||||
|
setEnabled(m_factory != nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModelSelector::clearAgent()
|
||||||
|
{
|
||||||
|
m_factory = nullptr;
|
||||||
|
m_agentName.clear();
|
||||||
|
m_providerInstance.clear();
|
||||||
|
m_model.clear();
|
||||||
|
m_fetched = false;
|
||||||
|
{
|
||||||
|
QSignalBlocker block(this);
|
||||||
|
clear();
|
||||||
|
clearEditText();
|
||||||
|
}
|
||||||
|
setEnabled(false);
|
||||||
|
emit statusChanged(QString());
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModelSelector::showPopup()
|
||||||
|
{
|
||||||
|
if (!m_fetched && m_factory && !(m_watcher && m_watcher->isRunning()))
|
||||||
|
startFetch();
|
||||||
|
QComboBox::showPopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModelSelector::startFetch()
|
||||||
|
{
|
||||||
|
QString err;
|
||||||
|
Agent *probe = m_factory->create(m_agentName, this, &err);
|
||||||
|
if (!probe) {
|
||||||
|
emit statusChanged(
|
||||||
|
err.isEmpty() ? tr("Provider is not available.")
|
||||||
|
: tr("Provider is not available: %1").arg(err));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_probe = probe;
|
||||||
|
m_fetchAgent = m_agentName;
|
||||||
|
m_fetchProvider = m_providerInstance;
|
||||||
|
|
||||||
|
if (!m_watcher) {
|
||||||
|
m_watcher = new QFutureWatcher<QList<QString>>(this);
|
||||||
|
connect(m_watcher, &QFutureWatcher<QList<QString>>::finished, this, [this] { onFetched(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString keep = currentText();
|
||||||
|
{
|
||||||
|
QSignalBlocker block(this);
|
||||||
|
clear();
|
||||||
|
addItem(tr("Loading models…"));
|
||||||
|
if (auto *itemModel = qobject_cast<QStandardItemModel *>(model()))
|
||||||
|
itemModel->item(0)->setEnabled(false);
|
||||||
|
setCurrentIndex(-1);
|
||||||
|
setEditText(keep);
|
||||||
|
}
|
||||||
|
emit statusChanged(tr("Loading models…"));
|
||||||
|
m_watcher->setFuture(probe->installedModels());
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModelSelector::onFetched()
|
||||||
|
{
|
||||||
|
QList<QString> models;
|
||||||
|
if (m_watcher->future().resultCount() > 0)
|
||||||
|
models = m_watcher->result();
|
||||||
|
|
||||||
|
if (m_probe) {
|
||||||
|
m_probe->deleteLater();
|
||||||
|
m_probe.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_fetchAgent != m_agentName || m_fetchProvider != m_providerInstance)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_fetched = true;
|
||||||
|
const QString keep = currentText();
|
||||||
|
const bool popupOpen = view() && view()->isVisible();
|
||||||
|
{
|
||||||
|
QSignalBlocker block(this);
|
||||||
|
clear();
|
||||||
|
addItems(models);
|
||||||
|
setCurrentIndex(findText(keep));
|
||||||
|
setEditText(keep);
|
||||||
|
}
|
||||||
|
emit statusChanged(
|
||||||
|
models.isEmpty() ? tr("No models returned — type the model name manually.")
|
||||||
|
: tr("%n model(s) available.", nullptr, static_cast<int>(models.size())));
|
||||||
|
if (popupOpen) {
|
||||||
|
hidePopup();
|
||||||
|
QComboBox::showPopup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModelSelector::submitCurrent()
|
||||||
|
{
|
||||||
|
const QString model = currentText().trimmed();
|
||||||
|
if (model.isEmpty() || model == m_model)
|
||||||
|
return;
|
||||||
|
emit modelSubmitted(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace QodeAssist::Settings
|
||||||
@@ -4,52 +4,53 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <QDialog>
|
#include <QComboBox>
|
||||||
#include <QFutureWatcher>
|
#include <QFutureWatcher>
|
||||||
#include <QList>
|
#include <QList>
|
||||||
#include <QPointer>
|
#include <QPointer>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
|
||||||
QT_BEGIN_NAMESPACE
|
|
||||||
class QLabel;
|
|
||||||
class QLineEdit;
|
|
||||||
class QListWidget;
|
|
||||||
class QPushButton;
|
|
||||||
QT_END_NAMESPACE
|
|
||||||
|
|
||||||
namespace QodeAssist {
|
namespace QodeAssist {
|
||||||
class AgentFactory;
|
|
||||||
class Agent;
|
class Agent;
|
||||||
|
class AgentFactory;
|
||||||
} // namespace QodeAssist
|
} // namespace QodeAssist
|
||||||
|
|
||||||
namespace QodeAssist::Settings {
|
namespace QodeAssist::Settings {
|
||||||
|
|
||||||
class AgentModelDialog : public QDialog
|
class ModelSelector : public QComboBox
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
AgentModelDialog(
|
explicit ModelSelector(QWidget *parent = nullptr);
|
||||||
|
|
||||||
|
void setAgent(
|
||||||
AgentFactory *factory,
|
AgentFactory *factory,
|
||||||
const QString &agentName,
|
const QString &agentName,
|
||||||
const QString ¤tModel,
|
const QString &providerInstance,
|
||||||
QWidget *parent = nullptr);
|
const QString &model);
|
||||||
|
void clearAgent();
|
||||||
|
|
||||||
[[nodiscard]] QString selectedModel() const;
|
signals:
|
||||||
|
void modelSubmitted(const QString &model);
|
||||||
|
void statusChanged(const QString &status);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void showPopup() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void fetchModels();
|
void startFetch();
|
||||||
void onModelsFetched();
|
void onFetched();
|
||||||
|
void submitCurrent();
|
||||||
AgentFactory *m_factory = nullptr;
|
|
||||||
QString m_agentName;
|
|
||||||
|
|
||||||
QLineEdit *m_modelEdit = nullptr;
|
|
||||||
QListWidget *m_list = nullptr;
|
|
||||||
QLabel *m_status = nullptr;
|
|
||||||
QPushButton *m_fetchBtn = nullptr;
|
|
||||||
|
|
||||||
|
QPointer<AgentFactory> m_factory;
|
||||||
QPointer<Agent> m_probe;
|
QPointer<Agent> m_probe;
|
||||||
QFutureWatcher<QList<QString>> *m_watcher = nullptr;
|
QFutureWatcher<QList<QString>> *m_watcher = nullptr;
|
||||||
|
QString m_agentName;
|
||||||
|
QString m_providerInstance;
|
||||||
|
QString m_model;
|
||||||
|
QString m_fetchAgent;
|
||||||
|
QString m_fetchProvider;
|
||||||
|
bool m_fetched = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace QodeAssist::Settings
|
} // namespace QodeAssist::Settings
|
||||||
@@ -36,6 +36,9 @@ ProviderDetailPane::ProviderDetailPane(QWidget *parent)
|
|||||||
nf.setPixelSize(15);
|
nf.setPixelSize(15);
|
||||||
m_nameLabel->setFont(nf);
|
m_nameLabel->setFont(nf);
|
||||||
|
|
||||||
|
m_sourceBadge = new QLabel(this);
|
||||||
|
m_sourceBadge->setVisible(false);
|
||||||
|
|
||||||
m_sourcePathLabel = new QLabel(this);
|
m_sourcePathLabel = new QLabel(this);
|
||||||
m_sourcePathLabel->setFont(monospaceFont(11));
|
m_sourcePathLabel->setFont(monospaceFont(11));
|
||||||
QPalette spp = m_sourcePathLabel->palette();
|
QPalette spp = m_sourcePathLabel->palette();
|
||||||
@@ -66,11 +69,10 @@ ProviderDetailPane::ProviderDetailPane(QWidget *parent)
|
|||||||
setEditing(false);
|
setEditing(false);
|
||||||
populate(m_current, m_currentHasStoredKey);
|
populate(m_current, m_currentHasStoredKey);
|
||||||
});
|
});
|
||||||
connect(m_saveBtn, &QPushButton::clicked, this, [this] {
|
connect(m_saveBtn, &QPushButton::clicked, this, [this] { emit saveRequested(collectEdits()); });
|
||||||
emit saveRequested(collectEdits());
|
connect(m_openInEditorBtn, &QPushButton::clicked, this, [this] {
|
||||||
|
emit openInEditorRequested(m_current.sourcePath);
|
||||||
});
|
});
|
||||||
connect(m_openInEditorBtn, &QPushButton::clicked, this,
|
|
||||||
[this] { emit openInEditorRequested(m_current.sourcePath); });
|
|
||||||
connect(m_dupBtn, &QPushButton::clicked, this, [this] { emit duplicateRequested(); });
|
connect(m_dupBtn, &QPushButton::clicked, this, [this] { emit duplicateRequested(); });
|
||||||
connect(m_deleteBtn, &QPushButton::clicked, this, [this] { emit deleteRequested(); });
|
connect(m_deleteBtn, &QPushButton::clicked, this, [this] { emit deleteRequested(); });
|
||||||
|
|
||||||
@@ -88,6 +90,7 @@ ProviderDetailPane::ProviderDetailPane(QWidget *parent)
|
|||||||
titleRow->setContentsMargins(0, 0, 0, 0);
|
titleRow->setContentsMargins(0, 0, 0, 0);
|
||||||
titleRow->setSpacing(8);
|
titleRow->setSpacing(8);
|
||||||
titleRow->addWidget(m_nameLabel);
|
titleRow->addWidget(m_nameLabel);
|
||||||
|
titleRow->addWidget(m_sourceBadge, 0, Qt::AlignVCenter);
|
||||||
titleRow->addStretch(1);
|
titleRow->addStretch(1);
|
||||||
|
|
||||||
auto *headerLeft = new QVBoxLayout;
|
auto *headerLeft = new QVBoxLayout;
|
||||||
@@ -111,7 +114,7 @@ ProviderDetailPane::ProviderDetailPane(QWidget *parent)
|
|||||||
m_descriptionLabel->setWordWrap(true);
|
m_descriptionLabel->setWordWrap(true);
|
||||||
m_descriptionLabel->setTextInteractionFlags(Qt::TextSelectableByMouse);
|
m_descriptionLabel->setTextInteractionFlags(Qt::TextSelectableByMouse);
|
||||||
|
|
||||||
auto *identitySection = new SectionBox(tr("Identity"), this);
|
m_identitySection = new SectionBox(tr("Identity"), this);
|
||||||
m_nameEdit = new QLineEdit(this);
|
m_nameEdit = new QLineEdit(this);
|
||||||
m_descriptionEdit = new QPlainTextEdit(this);
|
m_descriptionEdit = new QPlainTextEdit(this);
|
||||||
m_descriptionEdit->setMaximumHeight(60);
|
m_descriptionEdit->setMaximumHeight(60);
|
||||||
@@ -121,29 +124,12 @@ ProviderDetailPane::ProviderDetailPane(QWidget *parent)
|
|||||||
identityGrid->setHorizontalSpacing(8);
|
identityGrid->setHorizontalSpacing(8);
|
||||||
identityGrid->setVerticalSpacing(4);
|
identityGrid->setVerticalSpacing(4);
|
||||||
FormBuilder(identityGrid).row(tr("Name:"), m_nameEdit).row(tr("Description:"), m_descriptionEdit);
|
FormBuilder(identityGrid).row(tr("Name:"), m_nameEdit).row(tr("Description:"), m_descriptionEdit);
|
||||||
identitySection->bodyLayout()->addLayout(identityGrid);
|
m_identitySection->bodyLayout()->addLayout(identityGrid);
|
||||||
|
m_identitySection->setVisible(false);
|
||||||
|
|
||||||
auto *endpointSection = new SectionBox(tr("Endpoint"), this);
|
auto *configSection = new SectionBox(tr("Configuration"), this);
|
||||||
m_urlEdit = new QLineEdit(this);
|
m_urlEdit = new QLineEdit(this);
|
||||||
m_urlEdit->setFont(monospaceFont(11));
|
m_urlEdit->setFont(monospaceFont(11));
|
||||||
auto *endpointGrid = new QGridLayout;
|
|
||||||
endpointGrid->setContentsMargins(0, 0, 0, 0);
|
|
||||||
endpointGrid->setHorizontalSpacing(8);
|
|
||||||
endpointGrid->setVerticalSpacing(4);
|
|
||||||
FormBuilder(endpointGrid).row(tr("URL:"), m_urlEdit,
|
|
||||||
tr("Base URL. Agents append their endpoint path "
|
|
||||||
"(e.g. /chat/completions) to this."));
|
|
||||||
endpointSection->bodyLayout()->addLayout(endpointGrid);
|
|
||||||
|
|
||||||
m_samplePreview = new QLabel(this);
|
|
||||||
m_samplePreview->setFont(monospaceFont(11));
|
|
||||||
m_samplePreview->setTextInteractionFlags(Qt::TextSelectableByMouse);
|
|
||||||
m_samplePreview->setWordWrap(true);
|
|
||||||
m_samplePreview->setContentsMargins(6, 4, 6, 4);
|
|
||||||
m_samplePreview->setAutoFillBackground(true);
|
|
||||||
endpointSection->bodyLayout()->addWidget(m_samplePreview);
|
|
||||||
|
|
||||||
auto *credSection = new SectionBox(tr("Credentials"), this);
|
|
||||||
m_apiKeyEdit = new QLineEdit(this);
|
m_apiKeyEdit = new QLineEdit(this);
|
||||||
m_apiKeyEdit->setEchoMode(QLineEdit::Password);
|
m_apiKeyEdit->setEchoMode(QLineEdit::Password);
|
||||||
m_apiKeyEdit->setPlaceholderText(tr("Enter API key…"));
|
m_apiKeyEdit->setPlaceholderText(tr("Enter API key…"));
|
||||||
@@ -182,8 +168,7 @@ ProviderDetailPane::ProviderDetailPane(QWidget *parent)
|
|||||||
emit apiKeySaveRequested(key);
|
emit apiKeySaveRequested(key);
|
||||||
m_apiKeyEdit->clear();
|
m_apiKeyEdit->clear();
|
||||||
});
|
});
|
||||||
connect(m_apiKeyClearBtn, &QPushButton::clicked, this,
|
connect(m_apiKeyClearBtn, &QPushButton::clicked, this, [this] { emit apiKeyClearRequested(); });
|
||||||
[this] { emit apiKeyClearRequested(); });
|
|
||||||
m_keyHint = makeHintLabel(QString{}, this);
|
m_keyHint = makeHintLabel(QString{}, this);
|
||||||
|
|
||||||
auto *keyRow = new QHBoxLayout;
|
auto *keyRow = new QHBoxLayout;
|
||||||
@@ -194,15 +179,24 @@ ProviderDetailPane::ProviderDetailPane(QWidget *parent)
|
|||||||
keyRow->addWidget(m_apiKeySaveBtn);
|
keyRow->addWidget(m_apiKeySaveBtn);
|
||||||
keyRow->addWidget(m_apiKeyClearBtn);
|
keyRow->addWidget(m_apiKeyClearBtn);
|
||||||
|
|
||||||
auto *credGrid = new QGridLayout;
|
auto *configGrid = new QGridLayout;
|
||||||
credGrid->setContentsMargins(0, 0, 0, 0);
|
configGrid->setContentsMargins(0, 0, 0, 0);
|
||||||
credGrid->setHorizontalSpacing(8);
|
configGrid->setHorizontalSpacing(8);
|
||||||
credGrid->setVerticalSpacing(4);
|
configGrid->setVerticalSpacing(4);
|
||||||
FormBuilder credForm(credGrid);
|
FormBuilder configForm(configGrid);
|
||||||
credForm.row(tr("API key:"), keyRow);
|
configForm.row(tr("API key:"), keyRow);
|
||||||
credGrid->addWidget(m_legacyKeyBtn, credForm.currentRow(), 1, Qt::AlignLeft);
|
{
|
||||||
credGrid->addWidget(m_keyHint, credForm.currentRow() + 1, 1);
|
int row = configForm.currentRow();
|
||||||
credSection->bodyLayout()->addLayout(credGrid);
|
configGrid->addWidget(m_legacyKeyBtn, row, 1, Qt::AlignLeft);
|
||||||
|
configGrid->addWidget(m_keyHint, row + 1, 1);
|
||||||
|
configForm = FormBuilder(configGrid, row + 2);
|
||||||
|
}
|
||||||
|
configForm.row(
|
||||||
|
tr("URL:"),
|
||||||
|
m_urlEdit,
|
||||||
|
tr("Base URL. Agents append their endpoint path "
|
||||||
|
"(e.g. /chat/completions) to this. Editable via Edit…"));
|
||||||
|
configSection->bodyLayout()->addLayout(configGrid);
|
||||||
|
|
||||||
m_launchSection = new SectionBox(tr("Launch"), this);
|
m_launchSection = new SectionBox(tr("Launch"), this);
|
||||||
m_launchEmptyHint = new QLabel(this);
|
m_launchEmptyHint = new QLabel(this);
|
||||||
@@ -218,12 +212,15 @@ ProviderDetailPane::ProviderDetailPane(QWidget *parent)
|
|||||||
m_startBtn = new QPushButton(tr("Start"), this);
|
m_startBtn = new QPushButton(tr("Start"), this);
|
||||||
m_stopBtn = new QPushButton(tr("Stop"), this);
|
m_stopBtn = new QPushButton(tr("Stop"), this);
|
||||||
m_restartBtn = new QPushButton(tr("Restart"), this);
|
m_restartBtn = new QPushButton(tr("Restart"), this);
|
||||||
connect(m_startBtn, &QPushButton::clicked, this,
|
connect(m_startBtn, &QPushButton::clicked, this, [this] {
|
||||||
[this] { emit launchStartRequested(m_current.name); });
|
emit launchStartRequested(m_current.name);
|
||||||
connect(m_stopBtn, &QPushButton::clicked, this,
|
});
|
||||||
[this] { emit launchStopRequested(m_current.name); });
|
connect(m_stopBtn, &QPushButton::clicked, this, [this] {
|
||||||
connect(m_restartBtn, &QPushButton::clicked, this,
|
emit launchStopRequested(m_current.name);
|
||||||
[this] { emit launchRestartRequested(m_current.name); });
|
});
|
||||||
|
connect(m_restartBtn, &QPushButton::clicked, this, [this] {
|
||||||
|
emit launchRestartRequested(m_current.name);
|
||||||
|
});
|
||||||
auto *launchBtnRow = new QHBoxLayout;
|
auto *launchBtnRow = new QHBoxLayout;
|
||||||
launchBtnRow->setContentsMargins(0, 0, 0, 0);
|
launchBtnRow->setContentsMargins(0, 0, 0, 0);
|
||||||
launchBtnRow->setSpacing(6);
|
launchBtnRow->setSpacing(6);
|
||||||
@@ -283,9 +280,8 @@ ProviderDetailPane::ProviderDetailPane(QWidget *parent)
|
|||||||
root->addLayout(headerRow);
|
root->addLayout(headerRow);
|
||||||
root->addWidget(headerSep);
|
root->addWidget(headerSep);
|
||||||
root->addWidget(m_descriptionLabel);
|
root->addWidget(m_descriptionLabel);
|
||||||
root->addWidget(identitySection);
|
root->addWidget(m_identitySection);
|
||||||
root->addWidget(endpointSection);
|
root->addWidget(configSection);
|
||||||
root->addWidget(credSection);
|
|
||||||
root->addWidget(m_launchSection);
|
root->addWidget(m_launchSection);
|
||||||
root->addWidget(m_rawToggle, 0, Qt::AlignLeft);
|
root->addWidget(m_rawToggle, 0, Qt::AlignLeft);
|
||||||
root->addWidget(m_rawToml);
|
root->addWidget(m_rawToml);
|
||||||
@@ -302,6 +298,9 @@ void ProviderDetailPane::populate(const Providers::ProviderInstance &inst, bool
|
|||||||
const bool needsKey = !inst.apiKeyRef.isEmpty();
|
const bool needsKey = !inst.apiKeyRef.isEmpty();
|
||||||
|
|
||||||
m_nameLabel->setText(inst.name);
|
m_nameLabel->setText(inst.name);
|
||||||
|
m_sourceBadge->setText(isUser ? tr("User") : tr("Bundled"));
|
||||||
|
m_sourceBadge->setVisible(true);
|
||||||
|
styleSourceBadge(m_sourceBadge, isUser);
|
||||||
m_sourcePathLabel->setText(inst.sourcePath);
|
m_sourcePathLabel->setText(inst.sourcePath);
|
||||||
|
|
||||||
m_descriptionLabel->setText(
|
m_descriptionLabel->setText(
|
||||||
@@ -323,7 +322,8 @@ void ProviderDetailPane::populate(const Providers::ProviderInstance &inst, bool
|
|||||||
m_keyHint->setText(tr("This provider type does not use a key."));
|
m_keyHint->setText(tr("This provider type does not use a key."));
|
||||||
} else if (hasStoredKey) {
|
} else if (hasStoredKey) {
|
||||||
m_apiKeyEdit->setPlaceholderText(tr("Stored — enter a new key to replace it."));
|
m_apiKeyEdit->setPlaceholderText(tr("Stored — enter a new key to replace it."));
|
||||||
m_keyHint->setText(tr("A key is stored. Type a new key and press Save key to "
|
m_keyHint->setText(
|
||||||
|
tr("A key is stored. Type a new key and press Save key to "
|
||||||
"replace it, or Clear to erase it."));
|
"replace it, or Clear to erase it."));
|
||||||
} else {
|
} else {
|
||||||
m_apiKeyEdit->setPlaceholderText(tr("Enter API key…"));
|
m_apiKeyEdit->setPlaceholderText(tr("Enter API key…"));
|
||||||
@@ -341,10 +341,6 @@ void ProviderDetailPane::populate(const Providers::ProviderInstance &inst, bool
|
|||||||
m_legacyKeyBtn->setVisible(false);
|
m_legacyKeyBtn->setVisible(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
m_samplePreview->setText(
|
|
||||||
QStringLiteral("# sample request line\nPOST %1/<agent endpoint>").arg(inst.url));
|
|
||||||
applyPreviewPalette();
|
|
||||||
|
|
||||||
m_deleteBtn->setEnabled(isUser);
|
m_deleteBtn->setEnabled(isUser);
|
||||||
m_dupBtn->setEnabled(true);
|
m_dupBtn->setEnabled(true);
|
||||||
m_editBtn->setVisible(isUser);
|
m_editBtn->setVisible(isUser);
|
||||||
@@ -360,6 +356,7 @@ void ProviderDetailPane::clear()
|
|||||||
{
|
{
|
||||||
m_current = {};
|
m_current = {};
|
||||||
m_nameLabel->setText(tr("Select a provider"));
|
m_nameLabel->setText(tr("Select a provider"));
|
||||||
|
m_sourceBadge->setVisible(false);
|
||||||
m_sourcePathLabel->clear();
|
m_sourcePathLabel->clear();
|
||||||
m_descriptionLabel->clear();
|
m_descriptionLabel->clear();
|
||||||
m_nameEdit->clear();
|
m_nameEdit->clear();
|
||||||
@@ -373,7 +370,6 @@ void ProviderDetailPane::clear()
|
|||||||
m_revealKeyBtn->setEnabled(false);
|
m_revealKeyBtn->setEnabled(false);
|
||||||
m_legacyKeyValue.clear();
|
m_legacyKeyValue.clear();
|
||||||
m_legacyKeyBtn->setVisible(false);
|
m_legacyKeyBtn->setVisible(false);
|
||||||
m_samplePreview->clear();
|
|
||||||
m_rawToml->clear();
|
m_rawToml->clear();
|
||||||
m_editBtn->setVisible(false);
|
m_editBtn->setVisible(false);
|
||||||
m_dupBtn->setEnabled(false);
|
m_dupBtn->setEnabled(false);
|
||||||
@@ -390,7 +386,8 @@ void ProviderDetailPane::refreshKeyStatus(bool hasStoredKey)
|
|||||||
return;
|
return;
|
||||||
if (hasStoredKey) {
|
if (hasStoredKey) {
|
||||||
m_apiKeyEdit->setPlaceholderText(tr("Stored — enter a new key to replace it."));
|
m_apiKeyEdit->setPlaceholderText(tr("Stored — enter a new key to replace it."));
|
||||||
m_keyHint->setText(tr("A key is stored. Type a new key and press Save key to "
|
m_keyHint->setText(
|
||||||
|
tr("A key is stored. Type a new key and press Save key to "
|
||||||
"replace it, or Clear to erase it."));
|
"replace it, or Clear to erase it."));
|
||||||
} else {
|
} else {
|
||||||
m_apiKeyEdit->setPlaceholderText(tr("Enter API key…"));
|
m_apiKeyEdit->setPlaceholderText(tr("Enter API key…"));
|
||||||
@@ -412,8 +409,8 @@ void ProviderDetailPane::setLaunchState(
|
|||||||
m_launchTerminalToggle->setVisible(hasLaunch);
|
m_launchTerminalToggle->setVisible(hasLaunch);
|
||||||
|
|
||||||
if (!hasLaunch) {
|
if (!hasLaunch) {
|
||||||
m_launchEmptyHint->setText(tr(
|
m_launchEmptyHint->setText(
|
||||||
"No [launch] block. This provider is treated as external — "
|
tr("No [launch] block. This provider is treated as external — "
|
||||||
"the plugin will not spawn or supervise any process. "
|
"the plugin will not spawn or supervise any process. "
|
||||||
"Add a [launch] block to the TOML to have the plugin manage "
|
"Add a [launch] block to the TOML to have the plugin manage "
|
||||||
"a local server here."));
|
"a local server here."));
|
||||||
@@ -429,22 +426,31 @@ void ProviderDetailPane::setLaunchState(
|
|||||||
Utils::creatorColor(Utils::Theme::PanelTextColorMid).name(),
|
Utils::creatorColor(Utils::Theme::PanelTextColorMid).name(),
|
||||||
tr("(detached — survives Qt Creator restart)"))
|
tr("(detached — survives Qt Creator restart)"))
|
||||||
: QString();
|
: QString();
|
||||||
m_launchCmdLabel->setText(
|
m_launchCmdLabel->setText(QStringLiteral("<b>%1</b> %2%3")
|
||||||
QStringLiteral("<b>%1</b> %2%3")
|
.arg(
|
||||||
.arg(m_current.launch.command.toHtmlEscaped(),
|
m_current.launch.command.toHtmlEscaped(),
|
||||||
m_current.launch.args.join(QLatin1Char(' ')).toHtmlEscaped(),
|
m_current.launch.args.join(QLatin1Char(' ')).toHtmlEscaped(),
|
||||||
detachedNote));
|
detachedNote));
|
||||||
|
|
||||||
QString statusText;
|
QString statusText;
|
||||||
switch (st) {
|
switch (st) {
|
||||||
case Providers::ProviderLauncher::Idle: statusText = tr("idle"); break;
|
case Providers::ProviderLauncher::Idle:
|
||||||
case Providers::ProviderLauncher::Starting: statusText = tr("starting…"); break;
|
statusText = tr("idle");
|
||||||
case Providers::ProviderLauncher::Probing: statusText = tr("probing…"); break;
|
break;
|
||||||
case Providers::ProviderLauncher::Ready: statusText = tr("ready"); break;
|
case Providers::ProviderLauncher::Starting:
|
||||||
case Providers::ProviderLauncher::Stopping: statusText = tr("stopping…"); break;
|
statusText = tr("starting…");
|
||||||
|
break;
|
||||||
|
case Providers::ProviderLauncher::Probing:
|
||||||
|
statusText = tr("probing…");
|
||||||
|
break;
|
||||||
|
case Providers::ProviderLauncher::Ready:
|
||||||
|
statusText = tr("ready");
|
||||||
|
break;
|
||||||
|
case Providers::ProviderLauncher::Stopping:
|
||||||
|
statusText = tr("stopping…");
|
||||||
|
break;
|
||||||
case Providers::ProviderLauncher::Failed:
|
case Providers::ProviderLauncher::Failed:
|
||||||
statusText = lastError.isEmpty() ? tr("failed")
|
statusText = lastError.isEmpty() ? tr("failed") : tr("failed — %1").arg(lastError);
|
||||||
: tr("failed — %1").arg(lastError);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
m_launchStatusPill->setText(statusText);
|
m_launchStatusPill->setText(statusText);
|
||||||
@@ -473,8 +479,9 @@ void ProviderDetailPane::changeEvent(QEvent *event)
|
|||||||
{
|
{
|
||||||
QWidget::changeEvent(event);
|
QWidget::changeEvent(event);
|
||||||
if (event->type() == QEvent::PaletteChange || event->type() == QEvent::StyleChange) {
|
if (event->type() == QEvent::PaletteChange || event->type() == QEvent::StyleChange) {
|
||||||
applyPreviewPalette();
|
|
||||||
applyTerminalPalette();
|
applyTerminalPalette();
|
||||||
|
if (!m_current.name.isEmpty())
|
||||||
|
styleSourceBadge(m_sourceBadge, m_current.isUserSource());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -491,6 +498,7 @@ void ProviderDetailPane::showRevealedKey(const QString &key)
|
|||||||
void ProviderDetailPane::setEditing(bool on)
|
void ProviderDetailPane::setEditing(bool on)
|
||||||
{
|
{
|
||||||
m_editing = on;
|
m_editing = on;
|
||||||
|
m_identitySection->setVisible(on);
|
||||||
m_nameEdit->setReadOnly(!on);
|
m_nameEdit->setReadOnly(!on);
|
||||||
m_descriptionEdit->setReadOnly(!on);
|
m_descriptionEdit->setReadOnly(!on);
|
||||||
m_urlEdit->setReadOnly(!on);
|
m_urlEdit->setReadOnly(!on);
|
||||||
@@ -510,15 +518,6 @@ Providers::ProviderInstance ProviderDetailPane::collectEdits() const
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProviderDetailPane::applyPreviewPalette()
|
|
||||||
{
|
|
||||||
m_samplePreview->setStyleSheet(
|
|
||||||
QStringLiteral("QLabel { background:%1; border:1px solid %2; }")
|
|
||||||
.arg(
|
|
||||||
cssColor(Utils::creatorColor(Utils::Theme::BackgroundColorNormal)),
|
|
||||||
cssColor(Utils::creatorColor(Utils::Theme::SplitterColor))));
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProviderDetailPane::applyTerminalPalette()
|
void ProviderDetailPane::applyTerminalPalette()
|
||||||
{
|
{
|
||||||
if (!m_launchTerminal)
|
if (!m_launchTerminal)
|
||||||
|
|||||||
@@ -57,7 +57,6 @@ protected:
|
|||||||
private:
|
private:
|
||||||
void setEditing(bool on);
|
void setEditing(bool on);
|
||||||
Providers::ProviderInstance collectEdits() const;
|
Providers::ProviderInstance collectEdits() const;
|
||||||
void applyPreviewPalette();
|
|
||||||
void applyTerminalPalette();
|
void applyTerminalPalette();
|
||||||
|
|
||||||
bool m_editing = false;
|
bool m_editing = false;
|
||||||
@@ -65,6 +64,7 @@ private:
|
|||||||
Providers::ProviderInstance m_current;
|
Providers::ProviderInstance m_current;
|
||||||
|
|
||||||
QLabel *m_nameLabel = nullptr;
|
QLabel *m_nameLabel = nullptr;
|
||||||
|
QLabel *m_sourceBadge = nullptr;
|
||||||
QLabel *m_sourcePathLabel = nullptr;
|
QLabel *m_sourcePathLabel = nullptr;
|
||||||
QPushButton *m_editBtn = nullptr;
|
QPushButton *m_editBtn = nullptr;
|
||||||
QPushButton *m_openInEditorBtn = nullptr;
|
QPushButton *m_openInEditorBtn = nullptr;
|
||||||
@@ -75,11 +75,11 @@ private:
|
|||||||
|
|
||||||
QLabel *m_descriptionLabel = nullptr;
|
QLabel *m_descriptionLabel = nullptr;
|
||||||
|
|
||||||
|
SectionBox *m_identitySection = nullptr;
|
||||||
QLineEdit *m_nameEdit = nullptr;
|
QLineEdit *m_nameEdit = nullptr;
|
||||||
QLabel *m_protocolLabel = nullptr;
|
QLabel *m_protocolLabel = nullptr;
|
||||||
QPlainTextEdit *m_descriptionEdit = nullptr;
|
QPlainTextEdit *m_descriptionEdit = nullptr;
|
||||||
QLineEdit *m_urlEdit = nullptr;
|
QLineEdit *m_urlEdit = nullptr;
|
||||||
QLabel *m_samplePreview = nullptr;
|
|
||||||
|
|
||||||
QLineEdit *m_apiKeyEdit = nullptr;
|
QLineEdit *m_apiKeyEdit = nullptr;
|
||||||
QToolButton *m_revealKeyBtn = nullptr;
|
QToolButton *m_revealKeyBtn = nullptr;
|
||||||
|
|||||||
@@ -4,21 +4,22 @@
|
|||||||
|
|
||||||
#include "ProviderListItem.hpp"
|
#include "ProviderListItem.hpp"
|
||||||
|
|
||||||
|
#include <utils/elidinglabel.h>
|
||||||
#include <utils/theme/theme.h>
|
#include <utils/theme/theme.h>
|
||||||
|
|
||||||
#include <QHBoxLayout>
|
#include <QHBoxLayout>
|
||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
#include <QMouseEvent>
|
#include <QMouseEvent>
|
||||||
#include <QScopedValueRollback>
|
#include <QScopedValueRollback>
|
||||||
#include <QVBoxLayout>
|
#include <QStringList>
|
||||||
|
|
||||||
#include "ProviderInstance.hpp"
|
#include "ProviderInstance.hpp"
|
||||||
#include "SettingsTheme.hpp"
|
#include "SettingsTheme.hpp"
|
||||||
|
#include "SettingsUiBuilders.hpp"
|
||||||
|
|
||||||
namespace QodeAssist::Settings {
|
namespace QodeAssist::Settings {
|
||||||
|
|
||||||
ProviderListItem::ProviderListItem(
|
ProviderListItem::ProviderListItem(const Providers::ProviderInstance &inst, QWidget *parent)
|
||||||
const Providers::ProviderInstance &inst, QWidget *parent)
|
|
||||||
: QFrame(parent)
|
: QFrame(parent)
|
||||||
, m_name(inst.name)
|
, m_name(inst.name)
|
||||||
{
|
{
|
||||||
@@ -27,35 +28,45 @@ ProviderListItem::ProviderListItem(
|
|||||||
setAutoFillBackground(true);
|
setAutoFillBackground(true);
|
||||||
setCursor(Qt::PointingHandCursor);
|
setCursor(Qt::PointingHandCursor);
|
||||||
|
|
||||||
auto *headerRow = new QHBoxLayout;
|
|
||||||
headerRow->setContentsMargins(0, 0, 0, 0);
|
|
||||||
headerRow->setSpacing(6);
|
|
||||||
m_statusDot = new QLabel(QStringLiteral("●"), this);
|
m_statusDot = new QLabel(QStringLiteral("●"), this);
|
||||||
QFont df = m_statusDot->font();
|
QFont df = m_statusDot->font();
|
||||||
df.setPixelSize(11);
|
df.setPixelSize(11);
|
||||||
m_statusDot->setFont(df);
|
m_statusDot->setFont(df);
|
||||||
m_statusDot->setStyleSheet(QStringLiteral("color: %1;").arg(statusColor(Status::Unknown)));
|
m_statusDot->setStyleSheet(QStringLiteral("color: %1;").arg(statusColor(Status::Unknown)));
|
||||||
m_nameLabel = new QLabel(inst.name, this);
|
|
||||||
|
m_nameLabel = new QLabel(this);
|
||||||
QFont nf = m_nameLabel->font();
|
QFont nf = m_nameLabel->font();
|
||||||
nf.setBold(true);
|
nf.setBold(true);
|
||||||
nf.setPixelSize(12);
|
nf.setPixelSize(12);
|
||||||
m_nameLabel->setFont(nf);
|
m_nameLabel->setFont(nf);
|
||||||
headerRow->addWidget(m_statusDot, 0, Qt::AlignVCenter);
|
m_nameLabel->setTextFormat(Qt::RichText);
|
||||||
headerRow->addWidget(m_nameLabel, 1);
|
|
||||||
|
|
||||||
m_urlLabel = new QLabel(inst.url, this);
|
m_urlLabel = new Utils::ElidingLabel(this);
|
||||||
|
m_urlLabel->setElideMode(Qt::ElideMiddle);
|
||||||
m_urlLabel->setFont(monospaceFont(10));
|
m_urlLabel->setFont(monospaceFont(10));
|
||||||
|
m_urlLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
|
||||||
QPalette up = m_urlLabel->palette();
|
QPalette up = m_urlLabel->palette();
|
||||||
up.setColor(QPalette::WindowText, Utils::creatorColor(Utils::Theme::PanelTextColorMid));
|
up.setColor(QPalette::WindowText, Utils::creatorColor(Utils::Theme::PanelTextColorMid));
|
||||||
m_urlLabel->setPalette(up);
|
m_urlLabel->setPalette(up);
|
||||||
m_urlLabel->setContentsMargins(17, 0, 0, 0);
|
m_urlLabel->setText(inst.url);
|
||||||
|
m_urlLabel->setVisible(!inst.url.isEmpty());
|
||||||
|
|
||||||
auto *outer = new QVBoxLayout(this);
|
auto *row = new QHBoxLayout(this);
|
||||||
outer->setContentsMargins(5, 6, 8, 6);
|
row->setContentsMargins(8, 4, 8, 4);
|
||||||
outer->setSpacing(2);
|
row->setSpacing(6);
|
||||||
outer->addLayout(headerRow);
|
row->addWidget(m_statusDot, 0, Qt::AlignVCenter);
|
||||||
outer->addWidget(m_urlLabel);
|
row->addWidget(m_nameLabel, 0);
|
||||||
|
row->addWidget(m_urlLabel, 1);
|
||||||
|
|
||||||
|
QStringList tipLines;
|
||||||
|
if (!inst.description.isEmpty())
|
||||||
|
tipLines << inst.description;
|
||||||
|
tipLines << tr("API: %1").arg(inst.clientApi);
|
||||||
|
if (!inst.url.isEmpty())
|
||||||
|
tipLines << tr("URL: %1").arg(inst.url);
|
||||||
|
setToolTip(tipLines.join(QStringLiteral("\n")));
|
||||||
|
|
||||||
|
updateNameText();
|
||||||
applyTheme();
|
applyTheme();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,6 +84,19 @@ void ProviderListItem::setSelected(bool s)
|
|||||||
applyTheme();
|
applyTheme();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ProviderListItem::setFilterHighlight(const QString &lowerFilter)
|
||||||
|
{
|
||||||
|
if (m_filter == lowerFilter)
|
||||||
|
return;
|
||||||
|
m_filter = lowerFilter;
|
||||||
|
updateNameText();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProviderListItem::updateNameText()
|
||||||
|
{
|
||||||
|
m_nameLabel->setText(filterHighlightedHtml(m_name, m_filter));
|
||||||
|
}
|
||||||
|
|
||||||
void ProviderListItem::mouseReleaseEvent(QMouseEvent *event)
|
void ProviderListItem::mouseReleaseEvent(QMouseEvent *event)
|
||||||
{
|
{
|
||||||
if (event->button() == Qt::LeftButton)
|
if (event->button() == Qt::LeftButton)
|
||||||
@@ -111,6 +135,7 @@ void ProviderListItem::applyTheme()
|
|||||||
"#ProvListItem { background:transparent;"
|
"#ProvListItem { background:transparent;"
|
||||||
" border-top:1px solid %1; border-left:3px solid %2; }")
|
" border-top:1px solid %1; border-left:3px solid %2; }")
|
||||||
.arg(cssColor(Utils::creatorColor(Utils::Theme::SplitterColor)), accent));
|
.arg(cssColor(Utils::creatorColor(Utils::Theme::SplitterColor)), accent));
|
||||||
|
updateNameText();
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace QodeAssist::Settings
|
} // namespace QodeAssist::Settings
|
||||||
|
|||||||
@@ -9,6 +9,10 @@
|
|||||||
class QLabel;
|
class QLabel;
|
||||||
class QMouseEvent;
|
class QMouseEvent;
|
||||||
|
|
||||||
|
namespace Utils {
|
||||||
|
class ElidingLabel;
|
||||||
|
}
|
||||||
|
|
||||||
namespace QodeAssist::Providers {
|
namespace QodeAssist::Providers {
|
||||||
struct ProviderInstance;
|
struct ProviderInstance;
|
||||||
}
|
}
|
||||||
@@ -21,11 +25,11 @@ class ProviderListItem : public QFrame
|
|||||||
public:
|
public:
|
||||||
enum class Status : int { Unknown, Ok, Fail };
|
enum class Status : int { Unknown, Ok, Fail };
|
||||||
|
|
||||||
explicit ProviderListItem(
|
explicit ProviderListItem(const Providers::ProviderInstance &inst, QWidget *parent = nullptr);
|
||||||
const Providers::ProviderInstance &inst, QWidget *parent = nullptr);
|
|
||||||
|
|
||||||
void setStatus(Status s);
|
void setStatus(Status s);
|
||||||
void setSelected(bool s);
|
void setSelected(bool s);
|
||||||
|
void setFilterHighlight(const QString &lowerFilter);
|
||||||
QString providerName() const { return m_name; }
|
QString providerName() const { return m_name; }
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
@@ -38,14 +42,16 @@ protected:
|
|||||||
private:
|
private:
|
||||||
static QString statusColor(Status s);
|
static QString statusColor(Status s);
|
||||||
void applyTheme();
|
void applyTheme();
|
||||||
|
void updateNameText();
|
||||||
|
|
||||||
QString m_name;
|
QString m_name;
|
||||||
|
QString m_filter;
|
||||||
Status m_status = Status::Unknown;
|
Status m_status = Status::Unknown;
|
||||||
bool m_selected = false;
|
bool m_selected = false;
|
||||||
bool m_inApplyTheme = false;
|
bool m_inApplyTheme = false;
|
||||||
QLabel *m_statusDot = nullptr;
|
QLabel *m_statusDot = nullptr;
|
||||||
QLabel *m_nameLabel = nullptr;
|
QLabel *m_nameLabel = nullptr;
|
||||||
QLabel *m_urlLabel = nullptr;
|
Utils::ElidingLabel *m_urlLabel = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace QodeAssist::Settings
|
} // namespace QodeAssist::Settings
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
#include <QFrame>
|
#include <QFrame>
|
||||||
#include <QHBoxLayout>
|
#include <QHBoxLayout>
|
||||||
#include <QInputDialog>
|
#include <QInputDialog>
|
||||||
|
#include <QKeyEvent>
|
||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
#include <QLineEdit>
|
#include <QLineEdit>
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
@@ -103,7 +104,10 @@ public:
|
|||||||
headerSep->setFrameShadow(QFrame::Sunken);
|
headerSep->setFrameShadow(QFrame::Sunken);
|
||||||
|
|
||||||
m_filterEdit = new QLineEdit(this);
|
m_filterEdit = new QLineEdit(this);
|
||||||
m_filterEdit->setPlaceholderText(tr("Filter providers…"));
|
m_filterEdit->setPlaceholderText(tr("Filter by name, API, URL…"));
|
||||||
|
m_filterEdit->setClearButtonEnabled(true);
|
||||||
|
m_filterEdit->setToolTip(tr("Type to filter; Up/Down moves through the list."));
|
||||||
|
m_filterEdit->installEventFilter(this);
|
||||||
|
|
||||||
m_listScroll = new QScrollArea(this);
|
m_listScroll = new QScrollArea(this);
|
||||||
m_listScroll->setWidgetResizable(true);
|
m_listScroll->setWidgetResizable(true);
|
||||||
@@ -127,32 +131,60 @@ public:
|
|||||||
leftLay->addWidget(m_listScroll, 1);
|
leftLay->addWidget(m_listScroll, 1);
|
||||||
|
|
||||||
m_detailPane = new ProviderDetailPane(this);
|
m_detailPane = new ProviderDetailPane(this);
|
||||||
connect(m_detailPane, &ProviderDetailPane::saveRequested,
|
connect(
|
||||||
this, &ProvidersPageWidget::onSaveEdited);
|
m_detailPane,
|
||||||
connect(m_detailPane, &ProviderDetailPane::duplicateRequested,
|
&ProviderDetailPane::saveRequested,
|
||||||
this, &ProvidersPageWidget::onDuplicateClicked);
|
this,
|
||||||
connect(m_detailPane, &ProviderDetailPane::deleteRequested,
|
&ProvidersPageWidget::onSaveEdited);
|
||||||
this, &ProvidersPageWidget::onRemoveClicked);
|
connect(
|
||||||
connect(m_detailPane, &ProviderDetailPane::apiKeySaveRequested,
|
m_detailPane,
|
||||||
this, &ProvidersPageWidget::onApiKeySave);
|
&ProviderDetailPane::duplicateRequested,
|
||||||
connect(m_detailPane, &ProviderDetailPane::apiKeyClearRequested,
|
this,
|
||||||
this, &ProvidersPageWidget::onApiKeyClear);
|
&ProvidersPageWidget::onDuplicateClicked);
|
||||||
|
connect(
|
||||||
|
m_detailPane,
|
||||||
|
&ProviderDetailPane::deleteRequested,
|
||||||
|
this,
|
||||||
|
&ProvidersPageWidget::onRemoveClicked);
|
||||||
|
connect(
|
||||||
|
m_detailPane,
|
||||||
|
&ProviderDetailPane::apiKeySaveRequested,
|
||||||
|
this,
|
||||||
|
&ProvidersPageWidget::onApiKeySave);
|
||||||
|
connect(
|
||||||
|
m_detailPane,
|
||||||
|
&ProviderDetailPane::apiKeyClearRequested,
|
||||||
|
this,
|
||||||
|
&ProvidersPageWidget::onApiKeyClear);
|
||||||
connect(
|
connect(
|
||||||
m_detailPane,
|
m_detailPane,
|
||||||
&ProviderDetailPane::apiKeyRevealRequested,
|
&ProviderDetailPane::apiKeyRevealRequested,
|
||||||
this,
|
this,
|
||||||
&ProvidersPageWidget::onApiKeyReveal);
|
&ProvidersPageWidget::onApiKeyReveal);
|
||||||
connect(m_detailPane, &ProviderDetailPane::launchStartRequested,
|
connect(
|
||||||
this, &ProvidersPageWidget::onLaunchStart);
|
m_detailPane,
|
||||||
connect(m_detailPane, &ProviderDetailPane::launchStopRequested,
|
&ProviderDetailPane::launchStartRequested,
|
||||||
this, &ProvidersPageWidget::onLaunchStop);
|
this,
|
||||||
connect(m_detailPane, &ProviderDetailPane::launchRestartRequested,
|
&ProvidersPageWidget::onLaunchStart);
|
||||||
this, &ProvidersPageWidget::onLaunchRestart);
|
connect(
|
||||||
connect(m_detailPane, &ProviderDetailPane::openInEditorRequested,
|
m_detailPane,
|
||||||
this, [this](const QString &path) {
|
&ProviderDetailPane::launchStopRequested,
|
||||||
|
this,
|
||||||
|
&ProvidersPageWidget::onLaunchStop);
|
||||||
|
connect(
|
||||||
|
m_detailPane,
|
||||||
|
&ProviderDetailPane::launchRestartRequested,
|
||||||
|
this,
|
||||||
|
&ProvidersPageWidget::onLaunchRestart);
|
||||||
|
connect(
|
||||||
|
m_detailPane,
|
||||||
|
&ProviderDetailPane::openInEditorRequested,
|
||||||
|
this,
|
||||||
|
[this](const QString &path) {
|
||||||
if (path.isEmpty() || path.startsWith(QLatin1String(":/"))) {
|
if (path.isEmpty() || path.startsWith(QLatin1String(":/"))) {
|
||||||
QMessageBox::information(
|
QMessageBox::information(
|
||||||
this, tr("Open in editor"),
|
this,
|
||||||
|
tr("Open in editor"),
|
||||||
tr("Bundled providers are read-only. "
|
tr("Bundled providers are read-only. "
|
||||||
"Use Duplicate to create an editable user copy first."));
|
"Use Duplicate to create an editable user copy first."));
|
||||||
return;
|
return;
|
||||||
@@ -160,9 +192,11 @@ public:
|
|||||||
Core::EditorManager::openEditor(Utils::FilePath::fromString(path));
|
Core::EditorManager::openEditor(Utils::FilePath::fromString(path));
|
||||||
});
|
});
|
||||||
if (m_launcher) {
|
if (m_launcher) {
|
||||||
connect(m_launcher.data(), &Providers::ProviderLauncher::stateChanged,
|
connect(
|
||||||
this, [this](const QString &name,
|
m_launcher.data(),
|
||||||
Providers::ProviderLauncher::State newState) {
|
&Providers::ProviderLauncher::stateChanged,
|
||||||
|
this,
|
||||||
|
[this](const QString &name, Providers::ProviderLauncher::State newState) {
|
||||||
if (name == m_currentName)
|
if (name == m_currentName)
|
||||||
refreshDetailLaunch();
|
refreshDetailLaunch();
|
||||||
const ProviderListItem::Status status = rowStatusFromState(newState);
|
const ProviderListItem::Status status = rowStatusFromState(newState);
|
||||||
@@ -171,8 +205,11 @@ public:
|
|||||||
row->setStatus(status);
|
row->setStatus(status);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
connect(m_launcher.data(), &Providers::ProviderLauncher::bytesReceived,
|
connect(
|
||||||
this, [this](const QString &name, const QByteArray &chunk) {
|
m_launcher.data(),
|
||||||
|
&Providers::ProviderLauncher::bytesReceived,
|
||||||
|
this,
|
||||||
|
[this](const QString &name, const QByteArray &chunk) {
|
||||||
if (name == m_currentName)
|
if (name == m_currentName)
|
||||||
m_detailPane->appendLaunchBytes(chunk);
|
m_detailPane->appendLaunchBytes(chunk);
|
||||||
});
|
});
|
||||||
@@ -199,26 +236,34 @@ public:
|
|||||||
m_filterDebounce = new QTimer(this);
|
m_filterDebounce = new QTimer(this);
|
||||||
m_filterDebounce->setSingleShot(true);
|
m_filterDebounce->setSingleShot(true);
|
||||||
m_filterDebounce->setInterval(100);
|
m_filterDebounce->setInterval(100);
|
||||||
connect(m_filterDebounce, &QTimer::timeout, this,
|
connect(m_filterDebounce, &QTimer::timeout, this, &ProvidersPageWidget::rebuildList);
|
||||||
&ProvidersPageWidget::rebuildList);
|
connect(m_filterEdit, &QLineEdit::textChanged, this, [this](const QString &) {
|
||||||
connect(m_filterEdit, &QLineEdit::textChanged, this,
|
m_filterDebounce->start();
|
||||||
[this](const QString &) { m_filterDebounce->start(); });
|
});
|
||||||
|
|
||||||
if (m_factory) {
|
if (m_factory) {
|
||||||
connect(m_factory.data(),
|
connect(
|
||||||
|
m_factory.data(),
|
||||||
&Providers::ProviderInstanceFactory::instancesReloaded,
|
&Providers::ProviderInstanceFactory::instancesReloaded,
|
||||||
this, &ProvidersPageWidget::rebuildList);
|
this,
|
||||||
|
&ProvidersPageWidget::rebuildList);
|
||||||
}
|
}
|
||||||
if (m_navigator) {
|
if (m_navigator) {
|
||||||
connect(m_navigator.data(),
|
connect(
|
||||||
|
m_navigator.data(),
|
||||||
&ProvidersPageNavigator::selectInstanceRequested,
|
&ProvidersPageNavigator::selectInstanceRequested,
|
||||||
this, &ProvidersPageWidget::selectInstance);
|
this,
|
||||||
|
&ProvidersPageWidget::selectInstance);
|
||||||
}
|
}
|
||||||
|
|
||||||
rebuildList();
|
rebuildList();
|
||||||
|
|
||||||
const QString pending
|
QTimer::singleShot(0, this, [this] {
|
||||||
= m_navigator ? m_navigator->takePendingSelection() : QString{};
|
m_filterEdit->setFocus(Qt::OtherFocusReason);
|
||||||
|
m_filterEdit->selectAll();
|
||||||
|
});
|
||||||
|
|
||||||
|
const QString pending = m_navigator ? m_navigator->takePendingSelection() : QString{};
|
||||||
if (!pending.isEmpty())
|
if (!pending.isEmpty())
|
||||||
selectInstance(pending);
|
selectInstance(pending);
|
||||||
else if (m_factory && !m_factory->instances().empty())
|
else if (m_factory && !m_factory->instances().empty())
|
||||||
@@ -227,6 +272,23 @@ public:
|
|||||||
|
|
||||||
void apply() final {}
|
void apply() final {}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool eventFilter(QObject *watched, QEvent *event) override
|
||||||
|
{
|
||||||
|
if (watched == m_filterEdit && event->type() == QEvent::KeyPress) {
|
||||||
|
auto *ke = static_cast<QKeyEvent *>(event);
|
||||||
|
if (ke->key() == Qt::Key_Down) {
|
||||||
|
moveSelection(1);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (ke->key() == Qt::Key_Up) {
|
||||||
|
moveSelection(-1);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Core::IOptionsPageWidget::eventFilter(watched, event);
|
||||||
|
}
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void rebuildList()
|
void rebuildList()
|
||||||
{
|
{
|
||||||
@@ -245,9 +307,9 @@ private slots:
|
|||||||
auto matches = [&](const Providers::ProviderInstance &inst) {
|
auto matches = [&](const Providers::ProviderInstance &inst) {
|
||||||
if (filter.isEmpty())
|
if (filter.isEmpty())
|
||||||
return true;
|
return true;
|
||||||
return inst.name.toLower().contains(filter)
|
return inst.name.toLower().contains(filter) || inst.clientApi.toLower().contains(filter)
|
||||||
|| inst.clientApi.toLower().contains(filter)
|
|| inst.url.toLower().contains(filter)
|
||||||
|| inst.url.toLower().contains(filter);
|
|| inst.description.toLower().contains(filter);
|
||||||
};
|
};
|
||||||
|
|
||||||
auto addSection = [&](const QString &title, bool userSection) {
|
auto addSection = [&](const QString &title, bool userSection) {
|
||||||
@@ -262,17 +324,18 @@ private slots:
|
|||||||
continue;
|
continue;
|
||||||
sorted.push_back(&inst);
|
sorted.push_back(&inst);
|
||||||
}
|
}
|
||||||
std::sort(sorted.begin(), sorted.end(),
|
std::sort(
|
||||||
[](const Providers::ProviderInstance *a,
|
sorted.begin(),
|
||||||
const Providers::ProviderInstance *b) {
|
sorted.end(),
|
||||||
|
[](const Providers::ProviderInstance *a, const Providers::ProviderInstance *b) {
|
||||||
return a->name.compare(b->name, Qt::CaseInsensitive) < 0;
|
return a->name.compare(b->name, Qt::CaseInsensitive) < 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
int shown = 0;
|
int shown = 0;
|
||||||
for (const auto *inst : sorted) {
|
for (const auto *inst : sorted) {
|
||||||
auto *row = new ProviderListItem(*inst, m_listContent);
|
auto *row = new ProviderListItem(*inst, m_listContent);
|
||||||
connect(row, &ProviderListItem::clicked,
|
row->setFilterHighlight(filter);
|
||||||
this, &ProvidersPageWidget::selectInstance);
|
connect(row, &ProviderListItem::clicked, this, &ProvidersPageWidget::selectInstance);
|
||||||
if (m_launcher)
|
if (m_launcher)
|
||||||
row->setStatus(rowStatusFromLauncher(inst->name));
|
row->setStatus(rowStatusFromLauncher(inst->name));
|
||||||
m_rows.append(row);
|
m_rows.append(row);
|
||||||
@@ -281,8 +344,7 @@ private slots:
|
|||||||
}
|
}
|
||||||
if (shown == 0) {
|
if (shown == 0) {
|
||||||
auto *empty = new QLabel(
|
auto *empty = new QLabel(
|
||||||
userSection ? tr("No user instances yet.")
|
userSection ? tr("No user instances yet.") : tr("No bundled instances loaded."),
|
||||||
: tr("No bundled instances loaded."),
|
|
||||||
m_listContent);
|
m_listContent);
|
||||||
empty->setContentsMargins(10, 6, 10, 6);
|
empty->setContentsMargins(10, 6, 10, 6);
|
||||||
QPalette ep = empty->palette();
|
QPalette ep = empty->palette();
|
||||||
@@ -322,20 +384,24 @@ private slots:
|
|||||||
{
|
{
|
||||||
if (!m_factory || m_currentName.isEmpty())
|
if (!m_factory || m_currentName.isEmpty())
|
||||||
return;
|
return;
|
||||||
const Providers::ProviderInstance *srcPtr
|
const Providers::ProviderInstance *srcPtr = m_factory->instanceByName(m_currentName);
|
||||||
= m_factory->instanceByName(m_currentName);
|
|
||||||
if (!srcPtr)
|
if (!srcPtr)
|
||||||
return;
|
return;
|
||||||
const Providers::ProviderInstance srcCopy = *srcPtr;
|
const Providers::ProviderInstance srcCopy = *srcPtr;
|
||||||
bool ok = false;
|
bool ok = false;
|
||||||
const QString name = QInputDialog::getText(
|
const QString name = QInputDialog::getText(
|
||||||
this, tr("Duplicate provider"),
|
this,
|
||||||
tr("Name for the new provider:"), QLineEdit::Normal,
|
tr("Duplicate provider"),
|
||||||
QStringLiteral("%1 (copy)").arg(srcCopy.name), &ok);
|
tr("Name for the new provider:"),
|
||||||
|
QLineEdit::Normal,
|
||||||
|
QStringLiteral("%1 (copy)").arg(srcCopy.name),
|
||||||
|
&ok);
|
||||||
if (!ok || name.trimmed().isEmpty())
|
if (!ok || name.trimmed().isEmpty())
|
||||||
return;
|
return;
|
||||||
if (m_factory->instanceByName(name.trimmed())) {
|
if (m_factory->instanceByName(name.trimmed())) {
|
||||||
QMessageBox::warning(this, tr("Duplicate provider"),
|
QMessageBox::warning(
|
||||||
|
this,
|
||||||
|
tr("Duplicate provider"),
|
||||||
tr("An instance named '%1' already exists.").arg(name.trimmed()));
|
tr("An instance named '%1' already exists.").arg(name.trimmed()));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -346,7 +412,8 @@ private slots:
|
|||||||
copy.overridesBundled = false;
|
copy.overridesBundled = false;
|
||||||
QString writeErr;
|
QString writeErr;
|
||||||
if (Providers::ProviderInstanceWriter::writeToUserDir(
|
if (Providers::ProviderInstanceWriter::writeToUserDir(
|
||||||
copy, /*previousPath=*/QString{}, &writeErr).isEmpty()) {
|
copy, /*previousPath=*/QString{}, &writeErr)
|
||||||
|
.isEmpty()) {
|
||||||
QMessageBox::warning(this, tr("Duplicate provider"), writeErr);
|
QMessageBox::warning(this, tr("Duplicate provider"), writeErr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -370,8 +437,7 @@ private slots:
|
|||||||
{
|
{
|
||||||
if (!m_factory || m_currentName.isEmpty())
|
if (!m_factory || m_currentName.isEmpty())
|
||||||
return;
|
return;
|
||||||
const Providers::ProviderInstance *instPtr
|
const Providers::ProviderInstance *instPtr = m_factory->instanceByName(m_currentName);
|
||||||
= m_factory->instanceByName(m_currentName);
|
|
||||||
if (!instPtr || !instPtr->isUserSource())
|
if (!instPtr || !instPtr->isUserSource())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -391,8 +457,8 @@ private slots:
|
|||||||
if (QMessageBox::question(this, tr("Delete provider"), question) != QMessageBox::Yes)
|
if (QMessageBox::question(this, tr("Delete provider"), question) != QMessageBox::Yes)
|
||||||
return;
|
return;
|
||||||
if (!QFile::remove(sourcePath)) {
|
if (!QFile::remove(sourcePath)) {
|
||||||
QMessageBox::warning(this, tr("Delete provider"),
|
QMessageBox::warning(
|
||||||
tr("Failed to delete file:\n%1").arg(sourcePath));
|
this, tr("Delete provider"), tr("Failed to delete file:\n%1").arg(sourcePath));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (m_secrets && !apiKeyRef.isEmpty())
|
if (m_secrets && !apiKeyRef.isEmpty())
|
||||||
@@ -418,8 +484,8 @@ private slots:
|
|||||||
if (e.apiKeyRef.isEmpty() || (nameChanged && e.apiKeyRef == priorRef))
|
if (e.apiKeyRef.isEmpty() || (nameChanged && e.apiKeyRef == priorRef))
|
||||||
e.apiKeyRef = QStringLiteral("qodeassist/providers/%1").arg(e.name);
|
e.apiKeyRef = QStringLiteral("qodeassist/providers/%1").arg(e.name);
|
||||||
|
|
||||||
const QString validation = Providers::ProviderInstance::validate(
|
const QString validation
|
||||||
e, m_factory->knownClientApis());
|
= Providers::ProviderInstance::validate(e, m_factory->knownClientApis());
|
||||||
if (!validation.isEmpty()) {
|
if (!validation.isEmpty()) {
|
||||||
QMessageBox::warning(this, tr("Save"), validation);
|
QMessageBox::warning(this, tr("Save"), validation);
|
||||||
return;
|
return;
|
||||||
@@ -427,8 +493,8 @@ private slots:
|
|||||||
if (nameChanged) {
|
if (nameChanged) {
|
||||||
const auto *clash = m_factory->instanceByName(e.name);
|
const auto *clash = m_factory->instanceByName(e.name);
|
||||||
if (clash) {
|
if (clash) {
|
||||||
QMessageBox::warning(this, tr("Save"),
|
QMessageBox::warning(
|
||||||
tr("An instance named '%1' already exists.").arg(e.name));
|
this, tr("Save"), tr("An instance named '%1' already exists.").arg(e.name));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const QStringList referencing = agentsReferencing(priorName);
|
const QStringList referencing = agentsReferencing(priorName);
|
||||||
@@ -449,20 +515,21 @@ private slots:
|
|||||||
}
|
}
|
||||||
const QString softWarning = Providers::ProviderInstance::warnings(e);
|
const QString softWarning = Providers::ProviderInstance::warnings(e);
|
||||||
if (!softWarning.isEmpty()) {
|
if (!softWarning.isEmpty()) {
|
||||||
if (QMessageBox::warning(this, tr("Save"),
|
if (QMessageBox::warning(
|
||||||
softWarning + QStringLiteral("\n\n")
|
this,
|
||||||
+ tr("Save anyway?"),
|
tr("Save"),
|
||||||
|
softWarning + QStringLiteral("\n\n") + tr("Save anyway?"),
|
||||||
QMessageBox::Yes | QMessageBox::No,
|
QMessageBox::Yes | QMessageBox::No,
|
||||||
QMessageBox::No)
|
QMessageBox::No)
|
||||||
!= QMessageBox::Yes)
|
!= QMessageBox::Yes)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const QString previousPath
|
const QString previousPath = (prior && prior->isUserSource()) ? prior->sourcePath
|
||||||
= (prior && prior->isUserSource()) ? prior->sourcePath : QString{};
|
: QString{};
|
||||||
QString writeErr;
|
QString writeErr;
|
||||||
const QString writtenPath = Providers::ProviderInstanceWriter::writeToUserDir(
|
const QString writtenPath
|
||||||
e, previousPath, &writeErr);
|
= Providers::ProviderInstanceWriter::writeToUserDir(e, previousPath, &writeErr);
|
||||||
if (writtenPath.isEmpty()) {
|
if (writtenPath.isEmpty()) {
|
||||||
QMessageBox::warning(this, tr("Save"), writeErr);
|
QMessageBox::warning(this, tr("Save"), writeErr);
|
||||||
return;
|
return;
|
||||||
@@ -472,7 +539,8 @@ private slots:
|
|||||||
!= QFileInfo(previousPath).absoluteFilePath()) {
|
!= QFileInfo(previousPath).absoluteFilePath()) {
|
||||||
if (!QFile::remove(previousPath)) {
|
if (!QFile::remove(previousPath)) {
|
||||||
QMessageBox::warning(
|
QMessageBox::warning(
|
||||||
this, tr("Save"),
|
this,
|
||||||
|
tr("Save"),
|
||||||
tr("Saved to:\n%1\n\nbut could not remove the old file:\n%2\n\n"
|
tr("Saved to:\n%1\n\nbut could not remove the old file:\n%2\n\n"
|
||||||
"Two provider files now describe this instance — delete the "
|
"Two provider files now describe this instance — delete the "
|
||||||
"old file manually to avoid a duplicate-name error.")
|
"old file manually to avoid a duplicate-name error.")
|
||||||
@@ -515,15 +583,13 @@ private slots:
|
|||||||
{
|
{
|
||||||
if (!m_factory || !m_secrets || m_currentName.isEmpty())
|
if (!m_factory || !m_secrets || m_currentName.isEmpty())
|
||||||
return;
|
return;
|
||||||
const Providers::ProviderInstance *instPtr
|
const Providers::ProviderInstance *instPtr = m_factory->instanceByName(m_currentName);
|
||||||
= m_factory->instanceByName(m_currentName);
|
|
||||||
if (!instPtr || instPtr->apiKeyRef.isEmpty())
|
if (!instPtr || instPtr->apiKeyRef.isEmpty())
|
||||||
return;
|
return;
|
||||||
const QString instName = instPtr->name;
|
const QString instName = instPtr->name;
|
||||||
const QString apiKeyRef = instPtr->apiKeyRef;
|
const QString apiKeyRef = instPtr->apiKeyRef;
|
||||||
if (QMessageBox::question(
|
if (QMessageBox::question(
|
||||||
this, tr("Clear API key"),
|
this, tr("Clear API key"), tr("Erase the stored API key for '%1'?").arg(instName))
|
||||||
tr("Erase the stored API key for '%1'?").arg(instName))
|
|
||||||
!= QMessageBox::Yes)
|
!= QMessageBox::Yes)
|
||||||
return;
|
return;
|
||||||
m_secrets->eraseKey(apiKeyRef);
|
m_secrets->eraseKey(apiKeyRef);
|
||||||
@@ -558,6 +624,25 @@ private slots:
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void moveSelection(int delta)
|
||||||
|
{
|
||||||
|
if (m_rows.isEmpty())
|
||||||
|
return;
|
||||||
|
int cur = -1;
|
||||||
|
for (int i = 0; i < m_rows.size(); ++i) {
|
||||||
|
if (m_rows.at(i)->providerName() == m_currentName) {
|
||||||
|
cur = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int next = cur < 0 ? (delta > 0 ? 0 : int(m_rows.size()) - 1) : cur + delta;
|
||||||
|
next = qBound(0, next, int(m_rows.size()) - 1);
|
||||||
|
if (next == cur)
|
||||||
|
return;
|
||||||
|
selectInstance(m_rows.at(next)->providerName());
|
||||||
|
m_listScroll->ensureWidgetVisible(m_rows.at(next), 0, 40);
|
||||||
|
}
|
||||||
|
|
||||||
void populateDetail(const QString &name)
|
void populateDetail(const QString &name)
|
||||||
{
|
{
|
||||||
if (!m_factory)
|
if (!m_factory)
|
||||||
@@ -567,14 +652,13 @@ private:
|
|||||||
m_detailPane->clear();
|
m_detailPane->clear();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const bool hasStoredKey
|
const bool hasStoredKey = m_secrets && !inst->apiKeyRef.isEmpty()
|
||||||
= m_secrets && !inst->apiKeyRef.isEmpty() && m_secrets->hasKey(inst->apiKeyRef);
|
&& m_secrets->hasKey(inst->apiKeyRef);
|
||||||
m_detailPane->populate(*inst, hasStoredKey);
|
m_detailPane->populate(*inst, hasStoredKey);
|
||||||
|
|
||||||
if (m_launcher) {
|
if (m_launcher) {
|
||||||
m_detailPane->setLaunchState(
|
m_detailPane
|
||||||
m_launcher->state(inst->name),
|
->setLaunchState(m_launcher->state(inst->name), m_launcher->lastError(inst->name));
|
||||||
m_launcher->lastError(inst->name));
|
|
||||||
m_detailPane->resetLaunchTerminal(m_launcher->scrollback(inst->name));
|
m_detailPane->resetLaunchTerminal(m_launcher->scrollback(inst->name));
|
||||||
} else {
|
} else {
|
||||||
m_detailPane->setLaunchState(Providers::ProviderLauncher::Idle, {});
|
m_detailPane->setLaunchState(Providers::ProviderLauncher::Idle, {});
|
||||||
@@ -607,13 +691,11 @@ private:
|
|||||||
{
|
{
|
||||||
if (!m_launcher || m_currentName.isEmpty())
|
if (!m_launcher || m_currentName.isEmpty())
|
||||||
return;
|
return;
|
||||||
m_detailPane->setLaunchState(
|
m_detailPane
|
||||||
m_launcher->state(m_currentName),
|
->setLaunchState(m_launcher->state(m_currentName), m_launcher->lastError(m_currentName));
|
||||||
m_launcher->lastError(m_currentName));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static ProviderListItem::Status rowStatusFromState(
|
static ProviderListItem::Status rowStatusFromState(Providers::ProviderLauncher::State state)
|
||||||
Providers::ProviderLauncher::State state)
|
|
||||||
{
|
{
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case Providers::ProviderLauncher::Ready:
|
case Providers::ProviderLauncher::Ready:
|
||||||
|
|||||||
@@ -28,6 +28,49 @@ void applyMutedSmallCaps(QLabel *label)
|
|||||||
label->setPalette(p);
|
label->setPalette(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString filterHighlightedHtml(const QString &text, const QString &lowerFilter)
|
||||||
|
{
|
||||||
|
if (lowerFilter.isEmpty())
|
||||||
|
return text.toHtmlEscaped();
|
||||||
|
QColor mark = Utils::creatorColor(Utils::Theme::TextColorLink);
|
||||||
|
mark.setAlphaF(0.30);
|
||||||
|
const QString markCss
|
||||||
|
= QStringLiteral("background-color:%1;border-radius:2px;").arg(cssColor(mark));
|
||||||
|
const QString lowerText = text.toLower();
|
||||||
|
QString out;
|
||||||
|
int pos = 0;
|
||||||
|
while (true) {
|
||||||
|
const int hit = lowerText.indexOf(lowerFilter, pos);
|
||||||
|
if (hit < 0) {
|
||||||
|
out += text.mid(pos).toHtmlEscaped();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
out += text.mid(pos, hit - pos).toHtmlEscaped();
|
||||||
|
out += QStringLiteral("<span style=\"%1\">%2</span>")
|
||||||
|
.arg(markCss, text.mid(hit, lowerFilter.size()).toHtmlEscaped());
|
||||||
|
pos = hit + int(lowerFilter.size());
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
void styleSourceBadge(QLabel *label, bool user)
|
||||||
|
{
|
||||||
|
QFont f = label->font();
|
||||||
|
f.setBold(true);
|
||||||
|
f.setPixelSize(10);
|
||||||
|
label->setFont(f);
|
||||||
|
const QColor accent = Utils::creatorColor(
|
||||||
|
user ? Utils::Theme::TextColorLink : Utils::Theme::PanelTextColorMid);
|
||||||
|
QColor bg = accent;
|
||||||
|
bg.setAlphaF(0.12);
|
||||||
|
QColor border = accent;
|
||||||
|
border.setAlphaF(0.45);
|
||||||
|
label->setStyleSheet(QStringLiteral(
|
||||||
|
"QLabel { color:%1; background:%2; border:1px solid %3;"
|
||||||
|
" border-radius:8px; padding:1px 8px; }")
|
||||||
|
.arg(cssColor(accent), cssColor(bg), cssColor(border)));
|
||||||
|
}
|
||||||
|
|
||||||
QLabel *makeSectionHeader(const QString &title, QWidget *parent)
|
QLabel *makeSectionHeader(const QString &title, QWidget *parent)
|
||||||
{
|
{
|
||||||
auto *header = new QLabel(title.toUpper(), parent);
|
auto *header = new QLabel(title.toUpper(), parent);
|
||||||
|
|||||||
@@ -16,6 +16,10 @@ namespace QodeAssist::Settings {
|
|||||||
|
|
||||||
void applyMutedSmallCaps(QLabel *label);
|
void applyMutedSmallCaps(QLabel *label);
|
||||||
|
|
||||||
|
void styleSourceBadge(QLabel *label, bool user);
|
||||||
|
|
||||||
|
QString filterHighlightedHtml(const QString &text, const QString &lowerFilter);
|
||||||
|
|
||||||
QLabel *makeSectionHeader(const QString &title, QWidget *parent);
|
QLabel *makeSectionHeader(const QString &title, QWidget *parent);
|
||||||
|
|
||||||
QLabel *makeHintLabel(const QString &text, QWidget *parent = nullptr);
|
QLabel *makeHintLabel(const QString &text, QWidget *parent = nullptr);
|
||||||
|
|||||||
@@ -58,6 +58,15 @@ void TagFilterStrip::setAvailableTags(
|
|||||||
emit activeTagsChanged(m_activeTags);
|
emit activeTagsChanged(m_activeTags);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TagFilterStrip::setMaxColumns(int columns)
|
||||||
|
{
|
||||||
|
const int clamped = qMax(1, columns);
|
||||||
|
if (clamped == m_maxColumns)
|
||||||
|
return;
|
||||||
|
m_maxColumns = clamped;
|
||||||
|
rebuild();
|
||||||
|
}
|
||||||
|
|
||||||
void TagFilterStrip::setVisibleCounts(const QMap<QString, int> &countsByTag)
|
void TagFilterStrip::setVisibleCounts(const QMap<QString, int> &countsByTag)
|
||||||
{
|
{
|
||||||
for (auto it = m_chipByTag.cbegin(); it != m_chipByTag.cend(); ++it)
|
for (auto it = m_chipByTag.cbegin(); it != m_chipByTag.cend(); ++it)
|
||||||
@@ -161,14 +170,12 @@ void TagFilterStrip::rebuild()
|
|||||||
sorted.reserve(m_counts.size());
|
sorted.reserve(m_counts.size());
|
||||||
for (auto it = m_counts.cbegin(); it != m_counts.cend(); ++it)
|
for (auto it = m_counts.cbegin(); it != m_counts.cend(); ++it)
|
||||||
sorted.emplace_back(it.key(), it.value());
|
sorted.emplace_back(it.key(), it.value());
|
||||||
std::sort(sorted.begin(), sorted.end(),
|
std::sort(sorted.begin(), sorted.end(), [](const auto &a, const auto &b) {
|
||||||
[](const auto &a, const auto &b) {
|
|
||||||
if (a.second != b.second)
|
if (a.second != b.second)
|
||||||
return a.second > b.second;
|
return a.second > b.second;
|
||||||
return a.first.localeAwareCompare(b.first) < 0;
|
return a.first.localeAwareCompare(b.first) < 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
constexpr int kMaxColumns = 3;
|
|
||||||
auto *gridHost = new QWidget(this);
|
auto *gridHost = new QWidget(this);
|
||||||
auto *grid = new QGridLayout(gridHost);
|
auto *grid = new QGridLayout(gridHost);
|
||||||
grid->setContentsMargins(0, 0, 0, 0);
|
grid->setContentsMargins(0, 0, 0, 0);
|
||||||
@@ -181,7 +188,7 @@ void TagFilterStrip::rebuild()
|
|||||||
connect(chip, &TagChip::clicked, this, &TagFilterStrip::toggleTag);
|
connect(chip, &TagChip::clicked, this, &TagFilterStrip::toggleTag);
|
||||||
grid->addWidget(chip, gridRow, col, Qt::AlignLeft);
|
grid->addWidget(chip, gridRow, col, Qt::AlignLeft);
|
||||||
m_chipByTag.insert(tag, chip);
|
m_chipByTag.insert(tag, chip);
|
||||||
if (++col >= kMaxColumns) {
|
if (++col >= m_maxColumns) {
|
||||||
col = 0;
|
col = 0;
|
||||||
++gridRow;
|
++gridRow;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ public:
|
|||||||
void setAvailableTags(const QMap<QString, int> &countsByTag);
|
void setAvailableTags(const QMap<QString, int> &countsByTag);
|
||||||
void setAvailableTags(const QMap<QString, int> &countsByTag, const QSet<QString> &activeTags);
|
void setAvailableTags(const QMap<QString, int> &countsByTag, const QSet<QString> &activeTags);
|
||||||
void setVisibleCounts(const QMap<QString, int> &countsByTag);
|
void setVisibleCounts(const QMap<QString, int> &countsByTag);
|
||||||
|
void setMaxColumns(int columns);
|
||||||
const QSet<QString> &activeTags() const { return m_activeTags; }
|
const QSet<QString> &activeTags() const { return m_activeTags; }
|
||||||
void toggleTag(const QString &tag);
|
void toggleTag(const QString &tag);
|
||||||
|
|
||||||
@@ -42,6 +43,7 @@ private:
|
|||||||
|
|
||||||
QMap<QString, int> m_counts;
|
QMap<QString, int> m_counts;
|
||||||
QSet<QString> m_activeTags;
|
QSet<QString> m_activeTags;
|
||||||
|
int m_maxColumns = 3;
|
||||||
QVBoxLayout *m_layout = nullptr;
|
QVBoxLayout *m_layout = nullptr;
|
||||||
QHash<QString, TagChip *> m_chipByTag;
|
QHash<QString, TagChip *> m_chipByTag;
|
||||||
QLabel *m_clearLink = nullptr;
|
QLabel *m_clearLink = nullptr;
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#include "Pill.hpp"
|
#include "Pill.hpp"
|
||||||
#include "PipelinesConfig.hpp"
|
#include "PipelinesConfig.hpp"
|
||||||
#include "SettingsTr.hpp"
|
#include "SettingsTr.hpp"
|
||||||
|
#include "SettingsUiBuilders.hpp"
|
||||||
#include "TagFilterStrip.hpp"
|
#include "TagFilterStrip.hpp"
|
||||||
|
|
||||||
#include <coreplugin/icore.h>
|
#include <coreplugin/icore.h>
|
||||||
@@ -19,9 +20,11 @@
|
|||||||
#include <QEvent>
|
#include <QEvent>
|
||||||
#include <QFont>
|
#include <QFont>
|
||||||
#include <QHBoxLayout>
|
#include <QHBoxLayout>
|
||||||
|
#include <QKeyEvent>
|
||||||
#include <QLineEdit>
|
#include <QLineEdit>
|
||||||
#include <QMap>
|
#include <QMap>
|
||||||
#include <QMouseEvent>
|
#include <QMouseEvent>
|
||||||
|
#include <QPointer>
|
||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
#include <QScopedValueRollback>
|
#include <QScopedValueRollback>
|
||||||
#include <QScrollArea>
|
#include <QScrollArea>
|
||||||
@@ -64,6 +67,11 @@ bool ListRowCard::hasAllTags(const QSet<QString> &activeTags) const
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ListRowCard::setFilterHighlight(const QString &lowerNeedle)
|
||||||
|
{
|
||||||
|
Q_UNUSED(lowerNeedle)
|
||||||
|
}
|
||||||
|
|
||||||
void ListRowCard::buildSearchHaystack(const QStringList &parts)
|
void ListRowCard::buildSearchHaystack(const QStringList &parts)
|
||||||
{
|
{
|
||||||
m_searchHaystack = parts.join(QLatin1Char(' ')).toLower();
|
m_searchHaystack = parts.join(QLatin1Char(' ')).toLower();
|
||||||
@@ -121,9 +129,8 @@ void ListRowCard::applyTheme()
|
|||||||
} else if (m_hover) {
|
} else if (m_hover) {
|
||||||
bg = t.hoverBg;
|
bg = t.hoverBg;
|
||||||
}
|
}
|
||||||
setStyleSheet(QStringLiteral(
|
setStyleSheet(
|
||||||
"#ListRowCard { background-color: %1; border: 1px solid %2; }")
|
QStringLiteral("#ListRowCard { background-color: %1; border: 1px solid %2; }").arg(bg, bd));
|
||||||
.arg(bg, bd));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AgentRowCard::AgentRowCard(const AgentConfig &cfg, QWidget *parent)
|
AgentRowCard::AgentRowCard(const AgentConfig &cfg, QWidget *parent)
|
||||||
@@ -136,19 +143,25 @@ AgentRowCard::AgentRowCard(const AgentConfig &cfg, QWidget *parent)
|
|||||||
haystack += cfg.tags;
|
haystack += cfg.tags;
|
||||||
buildSearchHaystack(haystack);
|
buildSearchHaystack(haystack);
|
||||||
|
|
||||||
auto *name = new QLabel(cfg.name, this);
|
auto *name = new QLabel(cfg.name.toHtmlEscaped(), this);
|
||||||
QFont nameFont = name->font();
|
QFont nameFont = name->font();
|
||||||
nameFont.setBold(true);
|
nameFont.setBold(true);
|
||||||
nameFont.setPixelSize(13);
|
nameFont.setPixelSize(13);
|
||||||
name->setFont(nameFont);
|
name->setFont(nameFont);
|
||||||
name->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred);
|
name->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred);
|
||||||
|
name->setTextFormat(Qt::RichText);
|
||||||
|
m_nameLabel = name;
|
||||||
|
|
||||||
QLabel *model = nullptr;
|
QLabel *model = nullptr;
|
||||||
if (!cfg.model.isEmpty()) {
|
if (!cfg.model.isEmpty()) {
|
||||||
model = new QLabel(QStringLiteral("· %1").arg(cfg.model), this);
|
model = new QLabel(cfg.model.toHtmlEscaped(), this);
|
||||||
model->setFont(CardStyle::monoFont(11));
|
model->setFont(CardStyle::monoFont(11));
|
||||||
model->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred);
|
model->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred);
|
||||||
model->setMinimumWidth(0);
|
model->setMinimumWidth(0);
|
||||||
|
model->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
|
||||||
|
model->setTextFormat(Qt::RichText);
|
||||||
|
m_modelLabel = model;
|
||||||
|
m_model = cfg.model;
|
||||||
}
|
}
|
||||||
|
|
||||||
Pill *sourcePill = nullptr;
|
Pill *sourcePill = nullptr;
|
||||||
@@ -156,47 +169,21 @@ AgentRowCard::AgentRowCard(const AgentConfig &cfg, QWidget *parent)
|
|||||||
sourcePill = new Pill(Pill::User, Tr::tr("User"), this);
|
sourcePill = new Pill(Pill::User, Tr::tr("User"), this);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto *description = new QLabel(this);
|
|
||||||
description->setWordWrap(false);
|
|
||||||
QFont descFont = description->font();
|
|
||||||
descFont.setItalic(true);
|
|
||||||
description->setFont(descFont);
|
|
||||||
description->setText(cfg.description.isEmpty()
|
|
||||||
? Tr::tr("No description provided.")
|
|
||||||
: cfg.description);
|
|
||||||
description->setMinimumWidth(0);
|
|
||||||
description->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred);
|
|
||||||
description->setTextInteractionFlags(Qt::NoTextInteraction);
|
|
||||||
|
|
||||||
QStringList endpointParts;
|
|
||||||
if (!cfg.endpoint.isEmpty())
|
|
||||||
endpointParts << cfg.endpoint;
|
|
||||||
endpointParts << (cfg.enableThinking ? Tr::tr("thinking") : Tr::tr("no-thinking"));
|
|
||||||
endpointParts << (cfg.enableTools ? Tr::tr("tools") : Tr::tr("no-tools"));
|
|
||||||
|
|
||||||
auto *endpoint = new QLabel(endpointParts.join(QStringLiteral(" · ")), this);
|
|
||||||
endpoint->setFont(CardStyle::monoFont(11));
|
|
||||||
endpoint->setMinimumWidth(0);
|
|
||||||
endpoint->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred);
|
|
||||||
endpoint->setTextInteractionFlags(Qt::NoTextInteraction);
|
|
||||||
|
|
||||||
auto *headerRow = new QHBoxLayout;
|
auto *headerRow = new QHBoxLayout;
|
||||||
headerRow->setContentsMargins(0, 0, 0, 0);
|
headerRow->setContentsMargins(0, 0, 0, 0);
|
||||||
headerRow->setSpacing(6);
|
headerRow->setSpacing(6);
|
||||||
headerRow->addWidget(name);
|
headerRow->addWidget(name);
|
||||||
|
if (sourcePill)
|
||||||
|
headerRow->addWidget(sourcePill);
|
||||||
if (model)
|
if (model)
|
||||||
headerRow->addWidget(model, 1);
|
headerRow->addWidget(model, 1);
|
||||||
else
|
else
|
||||||
headerRow->addStretch(1);
|
headerRow->addStretch(1);
|
||||||
if (sourcePill)
|
|
||||||
headerRow->addWidget(sourcePill);
|
|
||||||
|
|
||||||
auto *outer = new QVBoxLayout(this);
|
auto *outer = new QVBoxLayout(this);
|
||||||
outer->setContentsMargins(10, 8, 10, 8);
|
outer->setContentsMargins(8, 5, 8, 5);
|
||||||
outer->setSpacing(3);
|
outer->setSpacing(2);
|
||||||
outer->addLayout(headerRow);
|
outer->addLayout(headerRow);
|
||||||
outer->addWidget(description);
|
|
||||||
outer->addWidget(endpoint);
|
|
||||||
|
|
||||||
if (!cfg.tags.isEmpty()) {
|
if (!cfg.tags.isEmpty()) {
|
||||||
constexpr int kMaxTagPills = 4;
|
constexpr int kMaxTagPills = 4;
|
||||||
@@ -207,10 +194,8 @@ AgentRowCard::AgentRowCard(const AgentConfig &cfg, QWidget *parent)
|
|||||||
for (int i = 0; i < std::min(tagCount, kMaxTagPills); ++i)
|
for (int i = 0; i < std::min(tagCount, kMaxTagPills); ++i)
|
||||||
tagsRow->addWidget(new Pill(Pill::Tag, cfg.tags.at(i), this));
|
tagsRow->addWidget(new Pill(Pill::Tag, cfg.tags.at(i), this));
|
||||||
if (tagCount > kMaxTagPills) {
|
if (tagCount > kMaxTagPills) {
|
||||||
auto *more = new Pill(
|
auto *more
|
||||||
Pill::Tag,
|
= new Pill(Pill::Tag, QStringLiteral("+%1").arg(tagCount - kMaxTagPills), this);
|
||||||
QStringLiteral("+%1").arg(tagCount - kMaxTagPills),
|
|
||||||
this);
|
|
||||||
more->setToolTip(cfg.tags.mid(kMaxTagPills).join(QStringLiteral(", ")));
|
more->setToolTip(cfg.tags.mid(kMaxTagPills).join(QStringLiteral(", ")));
|
||||||
tagsRow->addWidget(more);
|
tagsRow->addWidget(more);
|
||||||
}
|
}
|
||||||
@@ -218,32 +203,45 @@ AgentRowCard::AgentRowCard(const AgentConfig &cfg, QWidget *parent)
|
|||||||
outer->addLayout(tagsRow);
|
outer->addLayout(tagsRow);
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto t = CardStyle::toneFor(CardStyle::isDark(palette()));
|
|
||||||
QPalette descPal = description->palette();
|
|
||||||
descPal.setColor(QPalette::WindowText,
|
|
||||||
QColor(cfg.description.isEmpty() ? t.textFaint : t.textSoft));
|
|
||||||
description->setPalette(descPal);
|
|
||||||
QPalette endPal = endpoint->palette();
|
|
||||||
endPal.setColor(QPalette::WindowText, QColor(t.textFaint));
|
|
||||||
endpoint->setPalette(endPal);
|
|
||||||
if (model) {
|
if (model) {
|
||||||
|
const auto t = CardStyle::toneFor(CardStyle::isDark(palette()));
|
||||||
QPalette mp = model->palette();
|
QPalette mp = model->palette();
|
||||||
mp.setColor(QPalette::WindowText, QColor(t.textMute));
|
mp.setColor(QPalette::WindowText, QColor(t.textMute));
|
||||||
model->setPalette(mp);
|
model->setPalette(mp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QStringList capabilityParts;
|
||||||
|
if (!cfg.endpoint.isEmpty())
|
||||||
|
capabilityParts << cfg.endpoint;
|
||||||
|
capabilityParts << (cfg.enableThinking ? Tr::tr("thinking") : Tr::tr("no-thinking"));
|
||||||
|
capabilityParts << (cfg.enableTools ? Tr::tr("tools") : Tr::tr("no-tools"));
|
||||||
|
|
||||||
QString tooltip;
|
QString tooltip;
|
||||||
if (!cfg.description.isEmpty())
|
if (!cfg.description.isEmpty())
|
||||||
tooltip += cfg.description + QStringLiteral("\n\n");
|
tooltip += cfg.description + QStringLiteral("\n\n");
|
||||||
|
if (!cfg.model.isEmpty())
|
||||||
|
tooltip += Tr::tr("Model: %1\n").arg(cfg.model);
|
||||||
if (!cfg.providerInstance.isEmpty())
|
if (!cfg.providerInstance.isEmpty())
|
||||||
tooltip += Tr::tr("Provider instance: %1\n").arg(cfg.providerInstance);
|
tooltip += Tr::tr("Provider instance: %1\n").arg(cfg.providerInstance);
|
||||||
if (!cfg.systemPrompt.isEmpty())
|
if (!cfg.systemPrompt.isEmpty())
|
||||||
tooltip += Tr::tr("System prompt: %1\n").arg(cfg.systemPrompt);
|
tooltip += Tr::tr("System prompt: %1\n").arg(cfg.systemPrompt);
|
||||||
if (!cfg.endpoint.isEmpty())
|
tooltip += capabilityParts.join(QStringLiteral(" · "));
|
||||||
tooltip += Tr::tr("Endpoint: %1\n").arg(cfg.endpoint);
|
if (!cfg.tags.isEmpty())
|
||||||
|
tooltip += QStringLiteral("\n")
|
||||||
|
+ Tr::tr("Tags: %1").arg(cfg.tags.join(QStringLiteral(", ")));
|
||||||
setToolTip(tooltip.trimmed());
|
setToolTip(tooltip.trimmed());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AgentRowCard::setFilterHighlight(const QString &lowerNeedle)
|
||||||
|
{
|
||||||
|
if (m_lowerNeedle == lowerNeedle)
|
||||||
|
return;
|
||||||
|
m_lowerNeedle = lowerNeedle;
|
||||||
|
m_nameLabel->setText(filterHighlightedHtml(itemName(), lowerNeedle));
|
||||||
|
if (m_modelLabel)
|
||||||
|
m_modelLabel->setText(filterHighlightedHtml(m_model, lowerNeedle));
|
||||||
|
}
|
||||||
|
|
||||||
ProviderSection::ProviderSection(const QString &name, QWidget *parent)
|
ProviderSection::ProviderSection(const QString &name, QWidget *parent)
|
||||||
: QWidget(parent)
|
: QWidget(parent)
|
||||||
{
|
{
|
||||||
@@ -297,10 +295,12 @@ void ProviderSection::addCard(ListRowCard *card)
|
|||||||
|
|
||||||
int ProviderSection::applyFilter(const QString &needle, const QSet<QString> &activeTags)
|
int ProviderSection::applyFilter(const QString &needle, const QSet<QString> &activeTags)
|
||||||
{
|
{
|
||||||
|
const QString lowerNeedle = needle.toLower();
|
||||||
int visible = 0;
|
int visible = 0;
|
||||||
for (auto *card : m_cards) {
|
for (auto *card : m_cards) {
|
||||||
const bool show = card->matches(needle) && card->hasAllTags(activeTags);
|
const bool show = card->matches(needle) && card->hasAllTags(activeTags);
|
||||||
card->setVisible(show);
|
card->setVisible(show);
|
||||||
|
card->setFilterHighlight(lowerNeedle);
|
||||||
if (show)
|
if (show)
|
||||||
++visible;
|
++visible;
|
||||||
}
|
}
|
||||||
@@ -350,9 +350,10 @@ AgentSelectionDialog::AgentSelectionDialog(
|
|||||||
m_localConfigs = configs;
|
m_localConfigs = configs;
|
||||||
|
|
||||||
m_filter = new QLineEdit(this);
|
m_filter = new QLineEdit(this);
|
||||||
m_filter->setPlaceholderText(
|
m_filter->setPlaceholderText(Tr::tr("Filter by name, provider, model, template, description…"));
|
||||||
Tr::tr("Filter by name, provider, model, template, description…"));
|
|
||||||
m_filter->setClearButtonEnabled(true);
|
m_filter->setClearButtonEnabled(true);
|
||||||
|
m_filter->setToolTip(Tr::tr("Type to filter; Up/Down moves through the list."));
|
||||||
|
m_filter->installEventFilter(this);
|
||||||
|
|
||||||
auto *topRow = new QHBoxLayout;
|
auto *topRow = new QHBoxLayout;
|
||||||
topRow->setContentsMargins(0, 0, 0, 0);
|
topRow->setContentsMargins(0, 0, 0, 0);
|
||||||
@@ -360,6 +361,7 @@ AgentSelectionDialog::AgentSelectionDialog(
|
|||||||
topRow->addWidget(m_filter, 1);
|
topRow->addWidget(m_filter, 1);
|
||||||
|
|
||||||
m_tagStrip = new TagFilterStrip(this);
|
m_tagStrip = new TagFilterStrip(this);
|
||||||
|
m_tagStrip->setMaxColumns(6);
|
||||||
|
|
||||||
const bool dark = CardStyle::isDark(palette());
|
const bool dark = CardStyle::isDark(palette());
|
||||||
const auto tone = CardStyle::toneFor(dark);
|
const auto tone = CardStyle::toneFor(dark);
|
||||||
@@ -389,8 +391,7 @@ AgentSelectionDialog::AgentSelectionDialog(
|
|||||||
m_scroll->setFrameShape(QFrame::StyledPanel);
|
m_scroll->setFrameShape(QFrame::StyledPanel);
|
||||||
m_scroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
m_scroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||||
|
|
||||||
auto *buttons
|
auto *buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
|
||||||
= new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
|
|
||||||
m_okButton = buttons->button(QDialogButtonBox::Ok);
|
m_okButton = buttons->button(QDialogButtonBox::Ok);
|
||||||
m_okButton->setText(isChange ? Tr::tr("Change") : Tr::tr("Add"));
|
m_okButton->setText(isChange ? Tr::tr("Change") : Tr::tr("Add"));
|
||||||
m_okButton->setEnabled(false);
|
m_okButton->setEnabled(false);
|
||||||
@@ -442,6 +443,59 @@ void AgentSelectionDialog::setAllExpanded(bool expanded)
|
|||||||
section->setExpanded(expanded);
|
section->setExpanded(expanded);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool AgentSelectionDialog::eventFilter(QObject *watched, QEvent *event)
|
||||||
|
{
|
||||||
|
if (watched == m_filter && event->type() == QEvent::KeyPress) {
|
||||||
|
auto *ke = static_cast<QKeyEvent *>(event);
|
||||||
|
if (ke->key() == Qt::Key_Down) {
|
||||||
|
moveSelection(1);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (ke->key() == Qt::Key_Up) {
|
||||||
|
moveSelection(-1);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return QDialog::eventFilter(watched, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<ListRowCard *> AgentSelectionDialog::filteredCards() const
|
||||||
|
{
|
||||||
|
const QString needle = m_filter ? m_filter->text().trimmed() : QString();
|
||||||
|
const QSet<QString> activeTags = m_tagStrip ? m_tagStrip->activeTags() : QSet<QString>();
|
||||||
|
QList<ListRowCard *> out;
|
||||||
|
for (auto *section : m_sections)
|
||||||
|
for (auto *card : section->cards())
|
||||||
|
if (card->matches(needle) && card->hasAllTags(activeTags))
|
||||||
|
out.append(card);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AgentSelectionDialog::moveSelection(int delta)
|
||||||
|
{
|
||||||
|
const QList<ListRowCard *> cards = filteredCards();
|
||||||
|
if (cards.isEmpty())
|
||||||
|
return;
|
||||||
|
const int cur = m_currentCard ? int(cards.indexOf(m_currentCard)) : -1;
|
||||||
|
int next = cur < 0 ? (delta > 0 ? 0 : int(cards.size()) - 1) : cur + delta;
|
||||||
|
next = qBound(0, next, int(cards.size()) - 1);
|
||||||
|
if (next == cur)
|
||||||
|
return;
|
||||||
|
ListRowCard *card = cards.at(next);
|
||||||
|
for (auto *section : m_sections) {
|
||||||
|
if (section->cards().contains(card)) {
|
||||||
|
section->setExpanded(true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
selectCard(card);
|
||||||
|
QPointer<ListRowCard> guarded(card);
|
||||||
|
QTimer::singleShot(0, this, [this, guarded] {
|
||||||
|
if (guarded)
|
||||||
|
m_scroll->ensureWidgetVisible(guarded, 0, 40);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void AgentSelectionDialog::selectCard(ListRowCard *card)
|
void AgentSelectionDialog::selectCard(ListRowCard *card)
|
||||||
{
|
{
|
||||||
if (m_currentCard == card)
|
if (m_currentCard == card)
|
||||||
@@ -467,8 +521,7 @@ void AgentSelectionDialog::rebuild(const QString ¤tName)
|
|||||||
if (m_okButton)
|
if (m_okButton)
|
||||||
m_okButton->setEnabled(false);
|
m_okButton->setEnabled(false);
|
||||||
|
|
||||||
const auto &configs
|
const auto &configs = m_agentFactory ? m_agentFactory->configs() : m_localConfigs;
|
||||||
= m_agentFactory ? m_agentFactory->configs() : m_localConfigs;
|
|
||||||
|
|
||||||
auto *content = new QWidget;
|
auto *content = new QWidget;
|
||||||
auto *contentLayout = new QVBoxLayout(content);
|
auto *contentLayout = new QVBoxLayout(content);
|
||||||
@@ -479,8 +532,7 @@ void AgentSelectionDialog::rebuild(const QString ¤tName)
|
|||||||
for (const auto &cfg : configs) {
|
for (const auto &cfg : configs) {
|
||||||
if (cfg.hidden)
|
if (cfg.hidden)
|
||||||
continue;
|
continue;
|
||||||
const QString key = cfg.providerInstance.isEmpty()
|
const QString key = cfg.providerInstance.isEmpty() ? Tr::tr("(Unknown provider instance)")
|
||||||
? Tr::tr("(Unknown provider instance)")
|
|
||||||
: cfg.providerInstance;
|
: cfg.providerInstance;
|
||||||
byProvider[key].push_back(&cfg);
|
byProvider[key].push_back(&cfg);
|
||||||
}
|
}
|
||||||
@@ -491,12 +543,13 @@ void AgentSelectionDialog::rebuild(const QString ¤tName)
|
|||||||
for (auto it = byProvider.cbegin(); it != byProvider.cend(); ++it) {
|
for (auto it = byProvider.cbegin(); it != byProvider.cend(); ++it) {
|
||||||
auto *section = new ProviderSection(it.key());
|
auto *section = new ProviderSection(it.key());
|
||||||
auto sortedConfigs = it.value();
|
auto sortedConfigs = it.value();
|
||||||
std::sort(sortedConfigs.begin(), sortedConfigs.end(),
|
std::sort(
|
||||||
|
sortedConfigs.begin(),
|
||||||
|
sortedConfigs.end(),
|
||||||
[](const AgentConfig *a, const AgentConfig *b) { return a->name < b->name; });
|
[](const AgentConfig *a, const AgentConfig *b) { return a->name < b->name; });
|
||||||
for (const AgentConfig *cfg : sortedConfigs) {
|
for (const AgentConfig *cfg : sortedConfigs) {
|
||||||
auto *card = new AgentRowCard(*cfg);
|
auto *card = new AgentRowCard(*cfg);
|
||||||
connect(card, &ListRowCard::clicked, this,
|
connect(card, &ListRowCard::clicked, this, [this, card]() { selectCard(card); });
|
||||||
[this, card]() { selectCard(card); });
|
|
||||||
connect(card, &ListRowCard::activated, this, [this, card]() {
|
connect(card, &ListRowCard::activated, this, [this, card]() {
|
||||||
selectCard(card);
|
selectCard(card);
|
||||||
accept();
|
accept();
|
||||||
@@ -554,6 +607,8 @@ void AgentSelectionDialog::applyFilters()
|
|||||||
section->setExpanded(visible > 0);
|
section->setExpanded(visible > 0);
|
||||||
total += visible;
|
total += visible;
|
||||||
}
|
}
|
||||||
|
if (m_currentCard && (!m_currentCard->matches(needle) || !m_currentCard->hasAllTags(activeTags)))
|
||||||
|
selectCard(nullptr);
|
||||||
if (m_emptyLabel)
|
if (m_emptyLabel)
|
||||||
m_emptyLabel->setVisible(total == 0);
|
m_emptyLabel->setVisible(total == 0);
|
||||||
if (m_resultCount)
|
if (m_resultCount)
|
||||||
|
|||||||
@@ -50,16 +50,23 @@ inline bool isDark(const QPalette &p)
|
|||||||
inline Tone toneFor(bool dark)
|
inline Tone toneFor(bool dark)
|
||||||
{
|
{
|
||||||
return dark
|
return dark
|
||||||
? Tone{"#333333", "#3a3a3a", "#2a4566", "#3a6fb7", "#4a4a4a",
|
? Tone{"#333333", "#3a3a3a", "#2a4566", "#3a6fb7", "#4a4a4a", "#c2c2c2", "#9a9a9a", "#7a7a7a"}
|
||||||
"#c2c2c2", "#9a9a9a", "#7a7a7a"}
|
: Tone{
|
||||||
: Tone{"#f6f6f6", "#ececec", "#cfe1f7", "#3a6fb7", "#bdbdbd",
|
"#f6f6f6",
|
||||||
"#3a3a3a", "#5a5a5a", "#8a8a8a"};
|
"#ececec",
|
||||||
|
"#cfe1f7",
|
||||||
|
"#3a6fb7",
|
||||||
|
"#bdbdbd",
|
||||||
|
"#3a3a3a",
|
||||||
|
"#5a5a5a",
|
||||||
|
"#8a8a8a"};
|
||||||
}
|
}
|
||||||
|
|
||||||
inline QFont monoFont(int pixelSize)
|
inline QFont monoFont(int pixelSize)
|
||||||
{
|
{
|
||||||
QFont f;
|
QFont f;
|
||||||
f.setFamilies({QStringLiteral("SF Mono"),
|
f.setFamilies(
|
||||||
|
{QStringLiteral("SF Mono"),
|
||||||
QStringLiteral("Cascadia Code"),
|
QStringLiteral("Cascadia Code"),
|
||||||
QStringLiteral("Consolas"),
|
QStringLiteral("Consolas"),
|
||||||
QStringLiteral("Liberation Mono"),
|
QStringLiteral("Liberation Mono"),
|
||||||
@@ -94,6 +101,7 @@ public:
|
|||||||
bool matches(const QString &needle) const;
|
bool matches(const QString &needle) const;
|
||||||
bool hasAllTags(const QSet<QString> &activeTags) const;
|
bool hasAllTags(const QSet<QString> &activeTags) const;
|
||||||
void setSelected(bool selected);
|
void setSelected(bool selected);
|
||||||
|
virtual void setFilterHighlight(const QString &lowerNeedle);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void clicked();
|
void clicked();
|
||||||
@@ -130,6 +138,13 @@ public:
|
|||||||
explicit AgentRowCard(const AgentConfig &cfg, QWidget *parent = nullptr);
|
explicit AgentRowCard(const AgentConfig &cfg, QWidget *parent = nullptr);
|
||||||
|
|
||||||
QString agentName() const { return itemName(); }
|
QString agentName() const { return itemName(); }
|
||||||
|
void setFilterHighlight(const QString &lowerNeedle) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
QLabel *m_nameLabel = nullptr;
|
||||||
|
QLabel *m_modelLabel = nullptr;
|
||||||
|
QString m_model;
|
||||||
|
QString m_lowerNeedle;
|
||||||
};
|
};
|
||||||
|
|
||||||
class ProviderSection : public QWidget
|
class ProviderSection : public QWidget
|
||||||
@@ -171,11 +186,16 @@ public:
|
|||||||
|
|
||||||
QString selectedName() const { return m_selectedName; }
|
QString selectedName() const { return m_selectedName; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool eventFilter(QObject *watched, QEvent *event) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void rebuild(const QString ¤tName);
|
void rebuild(const QString ¤tName);
|
||||||
void selectCard(ListRowCard *card);
|
void selectCard(ListRowCard *card);
|
||||||
void applyFilters();
|
void applyFilters();
|
||||||
void setAllExpanded(bool expanded);
|
void setAllExpanded(bool expanded);
|
||||||
|
void moveSelection(int delta);
|
||||||
|
QList<ListRowCard *> filteredCards() const;
|
||||||
|
|
||||||
QLineEdit *m_filter = nullptr;
|
QLineEdit *m_filter = nullptr;
|
||||||
TagFilterStrip *m_tagStrip = nullptr;
|
TagFilterStrip *m_tagStrip = nullptr;
|
||||||
|
|||||||
Reference in New Issue
Block a user