refactor: Improve UX

This commit is contained in:
Petr Mironychev
2026-07-07 22:36:06 +02:00
parent bf3a67a1eb
commit 0382cd17a0
23 changed files with 1051 additions and 779 deletions

View File

@@ -36,6 +36,9 @@ ProviderDetailPane::ProviderDetailPane(QWidget *parent)
nf.setPixelSize(15);
m_nameLabel->setFont(nf);
m_sourceBadge = new QLabel(this);
m_sourceBadge->setVisible(false);
m_sourcePathLabel = new QLabel(this);
m_sourcePathLabel->setFont(monospaceFont(11));
QPalette spp = m_sourcePathLabel->palette();
@@ -66,11 +69,10 @@ ProviderDetailPane::ProviderDetailPane(QWidget *parent)
setEditing(false);
populate(m_current, m_currentHasStoredKey);
});
connect(m_saveBtn, &QPushButton::clicked, this, [this] {
emit saveRequested(collectEdits());
connect(m_saveBtn, &QPushButton::clicked, this, [this] { 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_deleteBtn, &QPushButton::clicked, this, [this] { emit deleteRequested(); });
@@ -88,6 +90,7 @@ ProviderDetailPane::ProviderDetailPane(QWidget *parent)
titleRow->setContentsMargins(0, 0, 0, 0);
titleRow->setSpacing(8);
titleRow->addWidget(m_nameLabel);
titleRow->addWidget(m_sourceBadge, 0, Qt::AlignVCenter);
titleRow->addStretch(1);
auto *headerLeft = new QVBoxLayout;
@@ -111,7 +114,7 @@ ProviderDetailPane::ProviderDetailPane(QWidget *parent)
m_descriptionLabel->setWordWrap(true);
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_descriptionEdit = new QPlainTextEdit(this);
m_descriptionEdit->setMaximumHeight(60);
@@ -121,29 +124,12 @@ ProviderDetailPane::ProviderDetailPane(QWidget *parent)
identityGrid->setHorizontalSpacing(8);
identityGrid->setVerticalSpacing(4);
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->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->setEchoMode(QLineEdit::Password);
m_apiKeyEdit->setPlaceholderText(tr("Enter API key…"));
@@ -182,8 +168,7 @@ ProviderDetailPane::ProviderDetailPane(QWidget *parent)
emit apiKeySaveRequested(key);
m_apiKeyEdit->clear();
});
connect(m_apiKeyClearBtn, &QPushButton::clicked, this,
[this] { emit apiKeyClearRequested(); });
connect(m_apiKeyClearBtn, &QPushButton::clicked, this, [this] { emit apiKeyClearRequested(); });
m_keyHint = makeHintLabel(QString{}, this);
auto *keyRow = new QHBoxLayout;
@@ -194,15 +179,24 @@ ProviderDetailPane::ProviderDetailPane(QWidget *parent)
keyRow->addWidget(m_apiKeySaveBtn);
keyRow->addWidget(m_apiKeyClearBtn);
auto *credGrid = new QGridLayout;
credGrid->setContentsMargins(0, 0, 0, 0);
credGrid->setHorizontalSpacing(8);
credGrid->setVerticalSpacing(4);
FormBuilder credForm(credGrid);
credForm.row(tr("API key:"), keyRow);
credGrid->addWidget(m_legacyKeyBtn, credForm.currentRow(), 1, Qt::AlignLeft);
credGrid->addWidget(m_keyHint, credForm.currentRow() + 1, 1);
credSection->bodyLayout()->addLayout(credGrid);
auto *configGrid = new QGridLayout;
configGrid->setContentsMargins(0, 0, 0, 0);
configGrid->setHorizontalSpacing(8);
configGrid->setVerticalSpacing(4);
FormBuilder configForm(configGrid);
configForm.row(tr("API key:"), keyRow);
{
int row = configForm.currentRow();
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_launchEmptyHint = new QLabel(this);
@@ -218,12 +212,15 @@ ProviderDetailPane::ProviderDetailPane(QWidget *parent)
m_startBtn = new QPushButton(tr("Start"), this);
m_stopBtn = new QPushButton(tr("Stop"), this);
m_restartBtn = new QPushButton(tr("Restart"), this);
connect(m_startBtn, &QPushButton::clicked, this,
[this] { emit launchStartRequested(m_current.name); });
connect(m_stopBtn, &QPushButton::clicked, this,
[this] { emit launchStopRequested(m_current.name); });
connect(m_restartBtn, &QPushButton::clicked, this,
[this] { emit launchRestartRequested(m_current.name); });
connect(m_startBtn, &QPushButton::clicked, this, [this] {
emit launchStartRequested(m_current.name);
});
connect(m_stopBtn, &QPushButton::clicked, this, [this] {
emit launchStopRequested(m_current.name);
});
connect(m_restartBtn, &QPushButton::clicked, this, [this] {
emit launchRestartRequested(m_current.name);
});
auto *launchBtnRow = new QHBoxLayout;
launchBtnRow->setContentsMargins(0, 0, 0, 0);
launchBtnRow->setSpacing(6);
@@ -283,9 +280,8 @@ ProviderDetailPane::ProviderDetailPane(QWidget *parent)
root->addLayout(headerRow);
root->addWidget(headerSep);
root->addWidget(m_descriptionLabel);
root->addWidget(identitySection);
root->addWidget(endpointSection);
root->addWidget(credSection);
root->addWidget(m_identitySection);
root->addWidget(configSection);
root->addWidget(m_launchSection);
root->addWidget(m_rawToggle, 0, Qt::AlignLeft);
root->addWidget(m_rawToml);
@@ -302,6 +298,9 @@ void ProviderDetailPane::populate(const Providers::ProviderInstance &inst, bool
const bool needsKey = !inst.apiKeyRef.isEmpty();
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_descriptionLabel->setText(
@@ -323,8 +322,9 @@ void ProviderDetailPane::populate(const Providers::ProviderInstance &inst, bool
m_keyHint->setText(tr("This provider type does not use a key."));
} else if (hasStoredKey) {
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 "
"replace it, or Clear to erase it."));
m_keyHint->setText(
tr("A key is stored. Type a new key and press Save key to "
"replace it, or Clear to erase it."));
} else {
m_apiKeyEdit->setPlaceholderText(tr("Enter API key…"));
m_keyHint->setText(tr("No key stored yet. Type a key and press Save key."));
@@ -341,10 +341,6 @@ void ProviderDetailPane::populate(const Providers::ProviderInstance &inst, bool
m_legacyKeyBtn->setVisible(false);
}
m_samplePreview->setText(
QStringLiteral("# sample request line\nPOST %1/<agent endpoint>").arg(inst.url));
applyPreviewPalette();
m_deleteBtn->setEnabled(isUser);
m_dupBtn->setEnabled(true);
m_editBtn->setVisible(isUser);
@@ -360,6 +356,7 @@ void ProviderDetailPane::clear()
{
m_current = {};
m_nameLabel->setText(tr("Select a provider"));
m_sourceBadge->setVisible(false);
m_sourcePathLabel->clear();
m_descriptionLabel->clear();
m_nameEdit->clear();
@@ -373,7 +370,6 @@ void ProviderDetailPane::clear()
m_revealKeyBtn->setEnabled(false);
m_legacyKeyValue.clear();
m_legacyKeyBtn->setVisible(false);
m_samplePreview->clear();
m_rawToml->clear();
m_editBtn->setVisible(false);
m_dupBtn->setEnabled(false);
@@ -390,8 +386,9 @@ void ProviderDetailPane::refreshKeyStatus(bool hasStoredKey)
return;
if (hasStoredKey) {
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 "
"replace it, or Clear to erase it."));
m_keyHint->setText(
tr("A key is stored. Type a new key and press Save key to "
"replace it, or Clear to erase it."));
} else {
m_apiKeyEdit->setPlaceholderText(tr("Enter API key…"));
m_keyHint->setText(tr("No key stored yet. Type a key and press Save key."));
@@ -412,11 +409,11 @@ void ProviderDetailPane::setLaunchState(
m_launchTerminalToggle->setVisible(hasLaunch);
if (!hasLaunch) {
m_launchEmptyHint->setText(tr(
"No [launch] block. This provider is treated as external — "
"the plugin will not spawn or supervise any process. "
"Add a [launch] block to the TOML to have the plugin manage "
"a local server here."));
m_launchEmptyHint->setText(
tr("No [launch] block. This provider is treated as external — "
"the plugin will not spawn or supervise any process. "
"Add a [launch] block to the TOML to have the plugin manage "
"a local server here."));
m_launchCmdLabel->clear();
m_launchTerminal->clearContents();
return;
@@ -429,22 +426,31 @@ void ProviderDetailPane::setLaunchState(
Utils::creatorColor(Utils::Theme::PanelTextColorMid).name(),
tr("(detached — survives Qt Creator restart)"))
: QString();
m_launchCmdLabel->setText(
QStringLiteral("<b>%1</b> %2%3")
.arg(m_current.launch.command.toHtmlEscaped(),
m_current.launch.args.join(QLatin1Char(' ')).toHtmlEscaped(),
detachedNote));
m_launchCmdLabel->setText(QStringLiteral("<b>%1</b> %2%3")
.arg(
m_current.launch.command.toHtmlEscaped(),
m_current.launch.args.join(QLatin1Char(' ')).toHtmlEscaped(),
detachedNote));
QString statusText;
switch (st) {
case Providers::ProviderLauncher::Idle: statusText = tr("idle"); break;
case Providers::ProviderLauncher::Starting: 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::Idle:
statusText = tr("idle");
break;
case Providers::ProviderLauncher::Starting:
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:
statusText = lastError.isEmpty() ? tr("failed")
: tr("failed — %1").arg(lastError);
statusText = lastError.isEmpty() ? tr("failed") : tr("failed — %1").arg(lastError);
break;
}
m_launchStatusPill->setText(statusText);
@@ -473,8 +479,9 @@ void ProviderDetailPane::changeEvent(QEvent *event)
{
QWidget::changeEvent(event);
if (event->type() == QEvent::PaletteChange || event->type() == QEvent::StyleChange) {
applyPreviewPalette();
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)
{
m_editing = on;
m_identitySection->setVisible(on);
m_nameEdit->setReadOnly(!on);
m_descriptionEdit->setReadOnly(!on);
m_urlEdit->setReadOnly(!on);
@@ -510,15 +518,6 @@ Providers::ProviderInstance ProviderDetailPane::collectEdits() const
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()
{
if (!m_launchTerminal)