style(ui): unify subpage tabs and tighten usage layout

Move agent detection into the client list, simplify usage filters and pagination, and route interface text through the shared type scale. Subpage navigation now uses underlined tabs instead of segmented controls.
This commit is contained in:
lzt404 committed 2026-10-04 17:31:52 +08:00
1 parent 5aff4d8a58
commit 6dc8650e68
10 files changed
+216 -118

No files matched your search

+19 -14
View File
@@ -2019,28 +2019,33 @@ export function AgentsPage({ embedded = false, onConfigurationApplied }: AgentsP
return (
<section className={`page management-page agents-page${embedded ? ' agents-page-embedded' : ''}`}>
<header className="management-header">
<div className={embedded ? 'agent-embedded-header-copy' : undefined}>
{embedded ? (
<>
<header className="management-header">
<div className="agent-embedded-header-copy">
<h1>{t('agents.embedded.title')}</h1>
<p>{t('agents.embedded.subtitle')}</p>
</>
) : null}
</div>
<div className="agent-header-actions">
{detectionError ? (
<MessageNotice message={detectionError} onDismiss={() => setDetectionError('')} />
) : null}
<button type="button" className="secondary-button compact-button" onClick={() => void refresh()} disabled={loading || busy}>
<RefreshCw size={16} className={loading ? 'spin' : ''} />
{t('agents.redetect')}
</button>
</div>
</header>
) : null}
<div className="agent-workbench">
<aside className="panel agent-client-list" aria-label={t('agents.localClients')}>
<div className="agent-client-list-heading">
<strong>{t('agents.localClients')}</strong>
<button
type="button"
className="icon-button quiet"
onClick={() => void refresh()}
disabled={loading || busy}
title={t('agents.redetect')}
aria-label={t('agents.redetect')}
>
<RefreshCw size={15} className={loading ? 'spin' : ''} aria-hidden="true" />
</button>
</div>
{detectionError ? (
<MessageNotice message={detectionError} onDismiss={() => setDetectionError('')} />
) : null}
<div className="agent-list-items">
{agentDefinitions.map((agent) => {
const status = statuses.find((item) => item.id === agent.id);
+6 -6
View File
@@ -844,11 +844,11 @@ export function EasyModePage({
<div className="simple-mode-choice-title">
<strong>{t("easyMode.oauth.title")}</strong>
{totalLoggedInOAuth > 0 ? (
<span className="state-pill success" style={{ fontSize: "12px" }}>
<span className="state-pill success">
{t("easyMode.oauth.accountsLoggedIn", { count: totalLoggedInOAuth })}
</span>
) : (
<span className="state-pill neutral" style={{ fontSize: "12px" }}>{t("easyMode.oauth.recommended")}</span>
<span className="state-pill neutral">{t("easyMode.oauth.recommended")}</span>
)}
</div>
</div>
@@ -867,7 +867,7 @@ export function EasyModePage({
<div className="simple-mode-choice-title">
<strong>{t("easyMode.api.title")}</strong>
{totalApiProviders > 0 ? (
<span className="state-pill success" style={{ fontSize: "12px" }}>
<span className="state-pill success">
{t("easyMode.api.platformsConnected", { count: totalApiProviders })}
</span>
) : null}
@@ -906,12 +906,12 @@ export function EasyModePage({
<div className="simple-mode-provider-card-foot">
{loggedIn ? (
<span className="state-pill success" style={{ fontSize: "12px" }}>
<span className="state-pill success">
<Check size={12} style={{ marginRight: 4 }} />
{t("easyMode.oauth.loggedIn")}
</span>
) : (
<span className="state-pill neutral" style={{ fontSize: "12px" }}>{t("easyMode.status.notLoggedIn")}</span>
<span className="state-pill neutral">{t("easyMode.status.notLoggedIn")}</span>
)}
<button
@@ -1126,7 +1126,7 @@ export function EasyModePage({
<button
type="button"
className="primary-button"
style={{ minHeight: 42, padding: "0 22px", fontSize: "15px" }}
style={{ minHeight: 42, padding: "0 22px" }}
disabled={!hasConnectedSource}
onClick={() => {
setActiveStep(2);
+8 -6
View File
@@ -662,7 +662,7 @@ export function UsageRecordsPage() {
<div className="usage-filter-row">
<div className="usage-filter-group">
<label className="usage-filter-item">
<span className="usage-filter-label">{t('usage.filter.timeRange')}</span>
<span className="sr-only">{t('usage.filter.timeRange')}</span>
<select
value={range}
onChange={(event) => {
@@ -682,7 +682,7 @@ export function UsageRecordsPage() {
</label>
<label className="usage-filter-item">
<span className="usage-filter-label">{t('usage.filter.model')}</span>
<span className="sr-only">{t('usage.filter.model')}</span>
<select
value={model}
onChange={(event) => changeFilter(setModel, event.currentTarget.value)}
@@ -698,7 +698,7 @@ export function UsageRecordsPage() {
</label>
<label className="usage-filter-item">
<span className="usage-filter-label">{t('usage.column.provider')}</span>
<span className="sr-only">{t('usage.column.provider')}</span>
<select
value={provider}
onChange={(event) => changeFilter(setProvider, event.currentTarget.value)}
@@ -714,7 +714,7 @@ export function UsageRecordsPage() {
</label>
<label className="usage-filter-item">
<span className="usage-filter-label">{t('usage.filter.source')}</span>
<span className="sr-only">{t('usage.filter.source')}</span>
<select
value={source}
onChange={(event) => changeFilter(setSource, event.currentTarget.value)}
@@ -730,7 +730,7 @@ export function UsageRecordsPage() {
</label>
<label className="usage-filter-item">
<span className="usage-filter-label">{t('apiAccess.field.key')}</span>
<span className="sr-only">{t('apiAccess.field.key')}</span>
<select
value={apiKeyHash}
onChange={(event) => changeFilter(setApiKeyHash, event.currentTarget.value)}
@@ -746,7 +746,7 @@ export function UsageRecordsPage() {
</label>
<label className="usage-filter-item">
<span className="usage-filter-label">{t('usage.filter.result')}</span>
<span className="sr-only">{t('usage.filter.result')}</span>
<select
value={result}
onChange={(event) => changeFilter(setResult, event.currentTarget.value)}
@@ -2204,6 +2204,7 @@ function EventsView({
</div>
<div className="usage-events-summary-right">
<div className="usage-pagination-controls">
<select
className="usage-page-size-select"
value={pageSize}
@@ -2239,6 +2240,7 @@ function EventsView({
<ChevronRight size={14} />
</button>
</div>
</div>
<button
type="button"
+58 -45
View File
@@ -1728,7 +1728,7 @@ textarea:focus-visible {
.client-api-card-heading strong {
color: var(--theme-2d2a26);
font-size: 13.5px;
font-size: var(--font-size-label);
font-weight: var(--font-weight-semibold);
line-height: 1.2;
}
@@ -1736,7 +1736,7 @@ textarea:focus-visible {
.client-api-card-heading .client-api-format-tag,
.client-api-card-heading span {
color: var(--theme-7a746c);
font-size: 11px;
font-size: var(--font-size-caption);
line-height: 1.2;
white-space: nowrap;
}
@@ -1778,7 +1778,7 @@ textarea:focus-visible {
border-radius: 6px;
background: var(--bg-secondary);
color: var(--theme-2d2a26);
font-size: 12.5px;
font-size: var(--font-size-meta);
font-family: var(--font-mono);
overflow: hidden;
text-overflow: ellipsis;
@@ -6051,31 +6051,18 @@ textarea:focus-visible {
}
.agents-page {
grid-template-rows: minmax(54px, auto) auto;
grid-template-rows: minmax(0, 1fr);
gap: 14px;
}
.agents-page-embedded {
grid-template-rows: minmax(48px, auto) minmax(0, 1fr);
}
.agents-page-embedded .agent-client-list {
min-height: 0;
}
.agent-header-actions {
display: flex;
min-width: 0;
align-items: center;
justify-content: flex-end;
gap: 10px;
}
.agent-header-actions .agent-inline-message {
max-width: 360px;
text-align: right;
}
.agent-header-actions .compact-button {
flex: none;
}
.agent-inline-message {
display: block;
min-width: 0;
@@ -6096,11 +6083,37 @@ textarea:focus-visible {
.agent-client-list {
position: relative;
display: block;
display: flex;
flex-direction: column;
overflow: hidden;
gap: 10px;
padding: 14px;
}
.agent-client-list-heading {
display: flex;
flex: none;
align-items: center;
justify-content: space-between;
gap: 10px;
min-height: 32px;
}
.agent-client-list-heading strong {
min-width: 0;
overflow: hidden;
color: var(--theme-2d2a26);
font-size: var(--font-size-section);
font-weight: var(--font-weight-semibold);
text-overflow: ellipsis;
white-space: nowrap;
}
.agent-client-list-heading .icon-button {
width: 30px;
height: 30px;
}
.agent-list-items button > span {
display: grid;
width: 100%;
@@ -6128,8 +6141,8 @@ textarea:focus-visible {
}
.agent-list-items {
position: absolute;
inset: 14px;
position: relative;
flex: 1 1 auto;
display: grid;
align-content: start;
grid-auto-rows: max-content;
@@ -8413,8 +8426,6 @@ textarea:focus-visible {
align-items: center;
justify-content: center;
gap: 6px;
min-width: 96px;
height: 32px;
padding: 0 14px;
border: 0;
border-radius: 6px;
@@ -8467,8 +8478,8 @@ textarea:focus-visible {
display: flex;
flex-direction: column;
gap: 4px;
min-width: 120px;
flex: 1 1 130px;
min-width: 0;
flex: 1 1 0;
}
.usage-filter-label {
@@ -8504,6 +8515,7 @@ textarea:focus-visible {
.usage-filter-reset-btn {
display: inline-flex;
align-items: center;
align-self: flex-end;
gap: 5px;
height: 34px;
padding: 0 12px;
@@ -8613,7 +8625,7 @@ textarea:focus-visible {
.usage-stat-card-value {
color: var(--theme-2d2a26);
font-size: 20px;
font-size: var(--font-size-dialog-title);
font-weight: var(--font-weight-semibold);
font-variant-numeric: tabular-nums;
line-height: 1.2;
@@ -9128,7 +9140,7 @@ textarea:focus-visible {
background: var(--theme-fffdf8);
color: var(--theme-8a847c);
font-size: var(--font-size-caption);
flex-wrap: wrap;
flex-wrap: nowrap;
}
.usage-events-summary-left {
@@ -9165,9 +9177,17 @@ textarea:focus-visible {
align-items: center;
gap: 8px;
flex-wrap: wrap;
justify-content: flex-end;
min-width: 0;
}
.usage-pagination-controls {
display: inline-flex;
align-items: center;
gap: 8px;
flex: none;
}
.usage-col-hint {
font-size: var(--font-size-caption);
color: var(--text-quaternary);
@@ -9728,6 +9748,7 @@ body.table-col-resizing * {
.usage-events-summary {
gap: 8px;
padding: 8px 12px;
flex-wrap: wrap;
}
.usage-events-summary-left,
@@ -11448,17 +11469,6 @@ body.table-col-resizing * {
flex-direction: column;
}
.agent-header-actions {
width: 100%;
align-items: stretch;
flex-direction: column;
}
.agent-header-actions .agent-inline-message {
max-width: none;
text-align: left;
}
.agent-subpage-tabs {
width: 100%;
grid-auto-columns: minmax(104px, 1fr);
@@ -11534,14 +11544,17 @@ body.table-col-resizing * {
.usage-tabs {
width: 100%;
max-width: 100%;
display: flex;
flex-wrap: wrap;
flex-wrap: nowrap;
overflow-x: auto;
scrollbar-width: none;
}
.usage-tabs button {
flex: 1 1 45%;
min-width: 0;
padding: 0 7px;
flex: 0 0 auto;
min-width: max-content;
padding: 0 12px;
}
.usage-topbar-actions {
+9 -7
View File
@@ -2191,7 +2191,6 @@
}
.agents-page-embedded .agent-list-items {
inset: 16px;
min-height: 0;
max-height: none;
overflow-y: auto;
@@ -2985,13 +2984,16 @@
.usage-tabs {
display: flex;
width: 100%;
flex-wrap: wrap;
max-width: 100%;
flex-wrap: nowrap;
overflow-x: auto;
scrollbar-width: none;
}
.usage-tabs button {
flex: 1 1 45%;
min-width: 0;
padding-inline: 7px;
flex: 0 0 auto;
min-width: max-content;
padding-inline: 12px;
}
.usage-topbar-actions {
@@ -3453,7 +3455,7 @@
.app-shell:has(.agents-page:not(.agents-page-embedded)) .agents-page:not(.agents-page-embedded) {
min-height: 0;
flex: 1 1 auto;
grid-template-rows: minmax(54px, auto) minmax(0, 1fr);
grid-template-rows: minmax(0, 1fr);
overflow: hidden;
}
@@ -3522,7 +3524,7 @@
.app-shell:has(.agents-page:not(.agents-page-embedded)) .agents-page:not(.agents-page-embedded) {
flex: none;
grid-template-rows: auto auto;
grid-template-rows: auto;
overflow: visible;
}
+112 -36
View File
@@ -124,7 +124,7 @@ body, html, #root {
.sidebar-theme-selector button {
border-radius: 6px;
font-size: 12px;
font-size: var(--font-size-caption);
font-weight: 500;
color: var(--text-tertiary);
transition: all 0.15s ease;
@@ -188,13 +188,13 @@ body, html, #root {
.control-panel .panel-detail-row dt {
color: var(--text-secondary);
font-size: 13.5px;
font-size: var(--font-size-label);
}
.control-panel .panel-detail-row dd {
color: var(--text-primary);
font-weight: 500;
font-size: 13.5px;
font-size: var(--font-size-label);
}
.control-panel .panel-detail-row dd code,
@@ -204,7 +204,7 @@ body, html, #root {
border-radius: 6px;
padding: 2px 8px;
font-family: var(--font-mono);
font-size: 13px;
font-size: var(--font-size-meta);
}
/* Home Page API Access Panel */
@@ -314,44 +314,60 @@ body, html, #root {
.home-quick-info strong {
color: var(--text-primary);
font-size: 14.5px;
font-size: var(--font-size-label);
font-weight: 600;
}
.home-quick-info p {
margin: 0;
color: var(--text-tertiary);
font-size: 12px;
font-size: var(--font-size-caption);
line-height: 1.45;
white-space: normal;
overflow-wrap: anywhere;
}
/* 6. Tabs & Segmented Controls (Unified Modern Style) */
/* 6. Subpage navigation */
.agent-subpage-tabs,
.oauth-subpage-tabs,
.config-subpage-tabs,
.usage-tabs {
border: 1px solid var(--border-color);
border-radius: 8px;
background: var(--bg-hover);
padding: 3px;
display: inline-flex;
gap: 2px;
align-items: stretch;
gap: 4px;
width: fit-content;
max-width: 100%;
padding: 0;
border: 0;
border-bottom: 1px solid var(--border-color);
border-radius: 0;
background: transparent;
box-shadow: none;
}
.agent-subpage-tabs button,
.oauth-subpage-tabs button,
.config-subpage-tabs button,
.usage-tabs button {
border-radius: 6px;
border: none;
.usage-tabs button,
.agent-subpage-tabs button:hover,
.oauth-subpage-tabs button:hover,
.config-subpage-tabs button:hover,
.usage-tabs button:hover,
.agent-subpage-tabs button.active,
.oauth-subpage-tabs button.active,
.config-subpage-tabs button.active,
.usage-tabs button.active {
min-width: 0;
height: 38px;
padding: 0 14px;
border: 0;
border-bottom: 2px solid transparent;
border-radius: 0;
background: transparent;
box-shadow: none;
color: var(--text-tertiary);
font-weight: 500;
font-size: 13.5px;
padding: 6px 14px;
transition: all 0.15s ease;
font-size: var(--font-size-label);
font-weight: var(--font-weight-medium);
}
.agent-subpage-tabs button:hover,
@@ -365,10 +381,10 @@ body, html, #root {
.oauth-subpage-tabs button.active,
.config-subpage-tabs button.active,
.usage-tabs button.active {
background: var(--bg-primary);
margin-bottom: -1px;
border-bottom-color: var(--ui-accent);
color: var(--text-primary);
box-shadow: 0 1px 2px rgba(15, 23, 42, 0.08);
font-weight: 600;
font-weight: var(--font-weight-semibold);
}
/* API access toolbar */
@@ -500,7 +516,7 @@ body, html, #root {
.oauth-hint {
color: var(--text-tertiary);
font-size: 12.5px;
font-size: var(--font-size-meta);
line-height: 1.5;
}
@@ -596,34 +612,82 @@ body, html, #root {
box-shadow: none;
}
.usage-records-page .usage-filter-row,
.usage-records-page .usage-filter-group {
align-items: center;
gap: 8px;
}
.usage-records-page .usage-filter-item {
min-width: 0;
flex: 1 1 150px;
flex-direction: row;
align-items: center;
gap: 0;
min-width: 132px;
max-width: 210px;
height: 36px;
padding: 0 8px;
border: 1px solid var(--border-color);
border-radius: 10px;
background: var(--bg-primary);
}
.usage-records-page .usage-filter-label {
padding-left: 12px;
flex: none;
padding: 0 8px 0 0;
border-right: 1px solid var(--border-color);
margin-right: 2px;
color: var(--text-tertiary);
font-size: var(--font-size-caption);
line-height: 20px;
}
.usage-records-page .usage-filter-item select,
.usage-records-page .usage-filter-item input {
width: 100%;
min-width: 0;
height: 34px;
border: 1px solid var(--border-color);
border-radius: 8px;
background: var(--bg-primary);
padding: 0 18px 0 4px;
border: 0;
border-radius: 0;
background-color: transparent;
box-shadow: none;
color: var(--text-primary);
}
.usage-records-page .usage-filter-item select:hover,
.usage-records-page .usage-filter-item input:hover {
.usage-records-page .usage-filter-item:hover {
border-color: var(--border-hover);
}
.usage-records-page .usage-filter-item select:focus,
.usage-records-page .usage-filter-item input:focus {
.usage-records-page .usage-filter-item:focus-within {
border-color: var(--ui-accent);
box-shadow: 0 0 0 3px var(--ui-focus-ring);
}
.usage-records-page .usage-filter-item select:hover,
.usage-records-page .usage-filter-item input:hover,
.usage-records-page .usage-filter-item select:focus,
.usage-records-page .usage-filter-item input:focus {
border-color: transparent;
box-shadow: none;
}
.usage-records-page .usage-filter-reset-btn {
align-self: center;
height: 36px;
border-style: solid;
border-color: var(--border-color);
border-radius: 10px;
background: var(--bg-primary);
color: var(--text-secondary);
}
.usage-records-page .usage-filter-reset-btn:hover {
border-color: var(--border-hover);
background: var(--bg-hover);
color: var(--text-primary);
}
.usage-records-page .usage-section-heading strong {
color: #1d1d1f;
font-weight: 650;
@@ -670,14 +734,14 @@ body, html, #root {
.usage-stat-card-label {
color: var(--text-secondary);
font-size: 12px;
font-size: var(--font-size-caption);
font-weight: 590;
letter-spacing: -0.01em;
}
.usage-stat-card-value {
color: var(--usage-tone);
font-size: 30px;
font-size: var(--font-size-display);
font-weight: 650;
letter-spacing: -0.045em;
line-height: 1.02;
@@ -718,7 +782,7 @@ body, html, #root {
border-radius: 999px;
background: #f2f2f7;
color: #3a3a3c;
font-size: 11px;
font-size: var(--font-size-caption);
font-weight: 590;
line-height: 1.45;
}
@@ -757,7 +821,7 @@ body, html, #root {
.metric-card strong {
color: var(--text-primary);
font-size: 26px;
font-size: var(--font-size-display);
font-weight: 700;
}
@@ -1161,3 +1225,15 @@ body, html, #root {
background: rgba(15, 118, 110, 0.2);
color: #2dd4bf;
}
:root[data-theme='dark'] .usage-records-page .usage-filter-item {
background: var(--bg-primary);
border-color: var(--border-color);
}
:root[data-theme='dark'] .usage-records-page .usage-filter-item select,
:root[data-theme='dark'] .usage-records-page .usage-filter-item input {
background: transparent;
border-color: transparent;
color: var(--text-primary);
}
+1 -1
View File
@@ -32,7 +32,7 @@ const desktopViewports = [
});
await page.waitForFunction(() => window.fixtureCalls.some(call => call.cmd === 'get_agent_config_statuses')
&& document.querySelector('.agent-config-panel')?.getBoundingClientRect().height > 0
&& !document.querySelector('.agent-header-actions button')?.disabled);
&& !document.querySelector('.agent-client-list-heading button')?.disabled);
await page.evaluate(() => document.fonts.ready);
};
+1 -1
View File
@@ -16,7 +16,7 @@ const path = require('node:path');
const open = async query => {
await page.goto('http://localhost:1421/tests/fixtures/agent-backups.html?reset-selections&' + query);
await page.waitForFunction(() => {
const refresh = document.querySelector('.agent-header-actions button');
const refresh = document.querySelector('.agent-client-list-heading button');
return refresh && !refresh.disabled && document.querySelector('.agent-model-trigger, .agent-desktop-models')
&& window.fixtureCalls.some(call => call.cmd === 'get_agent_models');
});
+1 -1
View File
@@ -28,7 +28,7 @@ const base = 'http://127.0.0.1:1421';
await page.goto(`${base}/tests/fixtures/agent-backups.html?reset-selections&client=codex&${query}`, { waitUntil: 'domcontentloaded' });
await update().waitFor();
await page.waitForFunction(() => {
const button = document.querySelector('.agents-page .management-header button');
const button = document.querySelector('.agent-client-list-heading button');
return window.fixtureCalls.some(call => call.cmd === 'get_agent_models') && button && !button.disabled;
});
};
+1 -1
View File
@@ -18,7 +18,7 @@ const assert = require('node:assert/strict');
assert.equal(await page.locator('[role=tab][aria-selected=true]').count(), 1);
};
const ready = () => page.waitForFunction(() => {
const refresh = document.querySelector('.agent-header-actions button');
const refresh = document.querySelector('.agent-client-list-heading button');
return refresh && !refresh.disabled && window.fixtureCalls.some(call => call.cmd === 'get_agent_models');
});
const open = async query => {