fix(desktop): unify log and network layouts

This commit is contained in:
apale7 2026-04-20 03:46:30 +08:00
parent 08343ad576
commit a82754eb40
4 changed files with 104 additions and 114 deletions

View File

@ -1643,27 +1643,18 @@ export default function App() {
) : null}
{activeTab === 'log' ? (
<section className="tab-layout trace-layout">
<article className="panel trace-list-panel">
<section className="tab-layout aliases-layout">
<article className="panel panel-fill list-column">
<div className="panel-header">
<div>
<h3>{t('log.title')}</h3>
<p className="subtle">
{logDetailOpen && selectedLogTrace
? `${t('log.detailTitle')}: #${selectedLogTrace.id}`
: traceStatus || t('log.subtitle')}
</p>
<p className="subtle">{traceStatus || t('log.subtitle')}</p>
</div>
<div className="toolbar toolbar-end">
<span className="subtle">{t('log.count', { count: requestTraces.length })}</span>
{logDetailOpen ? (
<button type="button" onClick={closeLogDetail}>
{t('actions.close')}
</button>
) : null}
<div className="list-header-actions">
<span className="subtle list-status-text">{t('log.count', { count: requestTraces.length })}</span>
</div>
</div>
<div className="trace-list">
<div className="scroll-list compact-list trace-scroll-list">
{requestTraces.length === 0 ? (
<article className="empty-card compact-empty">
<h4>{t('log.empty')}</h4>
@ -1671,28 +1662,50 @@ export default function App() {
</article>
) : null}
{requestTraces.map((trace) => (
<button
<article
key={trace.id}
type="button"
className={`trace-row ${logDetailOpen && selectedLogTrace?.id === trace.id ? 'active' : ''}`}
className={`resource-card ${logDetailOpen && selectedLogTrace?.id === trace.id ? 'active' : ''}`}
role="button"
tabIndex={0}
onClick={() => setSelectedLogTraceId(trace.id)}
onKeyDown={(event) => onResourceCardKeyDown(event, () => setSelectedLogTraceId(trace.id))}
>
<div className="trace-row-top">
<strong>{trace.alias || trace.rawModel || `#${trace.id}`}</strong>
<span className={`badge ${trace.success ? 'live' : 'idle'}`}>
<div className="resource-card-top">
<div className="resource-card-heading">
<div className="resource-card-titlewrap">
<strong className="resource-card-title">{trace.alias || trace.rawModel || `#${trace.id}`}</strong>
<code className="resource-card-code">#{trace.id}</code>
</div>
<p className="resource-card-subtitle trace-card-subtitle">
<span>{tracePrimaryText(trace)}</span>
<span>{formatDuration(trace.durationMs)}</span>
</p>
</div>
<div className="resource-card-side">
<span className={`badge status-badge ${trace.success ? 'live' : 'idle'}`}>
{trace.success ? t('log.success') : t('log.failed')}
</span>
</div>
<div className="trace-row-summary">
<span>{tracePrimaryText(trace)}</span>
<span>{formatDuration(trace.durationMs)}</span>
</div>
<div className="inline-pills">
<span className="pill">#{trace.id}</span>
<span className="pill">{formatDateTime(trace.startedAt)}</span>
{trace.failover ? <span className="pill trace-pill-warn">{t('log.failover')}</span> : null}
<div className="resource-card-meta">
<div className="resource-meta-item">
<span className="resource-meta-label">{t('log.startedAt')}</span>
<span className="resource-meta-value">{formatDateTime(trace.startedAt)}</span>
</div>
</button>
<div className="resource-meta-item">
<span className="resource-meta-label">{t('log.firstByte')}</span>
<span className="resource-meta-value">{formatDuration(trace.firstByteMs)}</span>
</div>
<div className="resource-meta-item">
<span className="resource-meta-label">{t('log.chainTitle')}</span>
<span className="resource-meta-value">
{trace.failover
? `${trace.attemptCount} · ${t('log.failover')}`
: `${trace.attemptCount}`}
</span>
</div>
</div>
</article>
))}
</div>
</article>
@ -1700,26 +1713,18 @@ export default function App() {
) : null}
{activeTab === 'network' ? (
<section className="tab-layout trace-layout">
<article className="panel trace-list-panel">
<section className="tab-layout aliases-layout">
<article className="panel panel-fill list-column">
<div className="panel-header">
<div>
<h3>{t('network.title')}</h3>
<p className="subtle">
{networkDetailOpen && selectedNetworkTrace
? `${t('network.detailTitle')}: #${selectedNetworkTrace.id}`
: traceStatus || t('network.subtitle')}
</p>
<p className="subtle">{traceStatus || t('network.subtitle')}</p>
</div>
<div className="toolbar toolbar-end">
{networkDetailOpen ? (
<button type="button" onClick={closeNetworkDetail}>
{t('actions.close')}
</button>
) : null}
<div className="list-header-actions">
<span className="subtle list-status-text">{t('network.count', { count: requestTraces.length })}</span>
</div>
</div>
<div className="trace-list">
<div className="scroll-list compact-list trace-scroll-list">
{requestTraces.length === 0 ? (
<article className="empty-card compact-empty">
<h4>{t('log.empty')}</h4>
@ -1727,21 +1732,44 @@ export default function App() {
</article>
) : null}
{requestTraces.map((trace) => (
<button
<article
key={trace.id}
type="button"
className={`trace-row ${networkDetailOpen && selectedNetworkTrace?.id === trace.id ? 'active' : ''}`}
className={`resource-card ${networkDetailOpen && selectedNetworkTrace?.id === trace.id ? 'active' : ''}`}
role="button"
tabIndex={0}
onClick={() => setSelectedNetworkTraceId(trace.id)}
onKeyDown={(event) => onResourceCardKeyDown(event, () => setSelectedNetworkTraceId(trace.id))}
>
<div className="trace-row-top">
<strong>#{trace.id}</strong>
<span className={`badge ${trace.success ? 'live' : 'idle'}`}>{trace.statusCode || '-'}</span>
<div className="resource-card-top">
<div className="resource-card-heading">
<div className="resource-card-titlewrap">
<strong className="resource-card-title">#{trace.id}</strong>
<code className="resource-card-code">{trace.finalProvider || trace.alias || trace.rawModel || '-'}</code>
</div>
<div className="trace-row-summary">
<p className="resource-card-subtitle trace-card-subtitle">
<span>{trace.finalUrl || tracePrimaryText(trace)}</span>
<span>{formatDuration(trace.firstByteMs)}</span>
</p>
</div>
</button>
<div className="resource-card-side">
<span className={`badge status-badge ${trace.success ? 'live' : 'idle'}`}>{trace.statusCode || '-'}</span>
</div>
</div>
<div className="resource-card-meta">
<div className="resource-meta-item">
<span className="resource-meta-label">{t('log.startedAt')}</span>
<span className="resource-meta-value">{formatDateTime(trace.startedAt)}</span>
</div>
<div className="resource-meta-item">
<span className="resource-meta-label">{t('network.totalTime')}</span>
<span className="resource-meta-value">{formatDuration(trace.durationMs)}</span>
</div>
<div className="resource-meta-item">
<span className="resource-meta-label">{t('log.chainTitle')}</span>
<span className="resource-meta-value">{trace.attemptCount}</span>
</div>
</div>
</article>
))}
</div>
</article>
@ -2571,7 +2599,7 @@ export default function App() {
<div className="chain-list">
{(selectedLogTrace.attempts || []).map((attempt) => (
<article className="chain-card" key={`${selectedLogTrace.id}-${attempt.attempt}-${attempt.provider}-${attempt.model}`}>
<div className="trace-row-top">
<div className="item-heading">
<strong>{t('log.attemptLabel', { attempt: attempt.attempt })}</strong>
<span className={`badge ${attempt.success ? 'live' : attempt.skipped ? 'outline' : 'idle'}`}>
{attempt.result || '-'}

View File

@ -271,6 +271,7 @@
"network": {
"title": "Network detail",
"subtitle": "Inspect URL, timing, status, headers, and params without exposing model IO content.",
"count": "{{count}} requests",
"detailTitle": "Network inspector",
"detailSubtitle": "Each attempt shows technical metadata captured around the upstream call.",
"url": "URL",

View File

@ -271,6 +271,7 @@
"network": {
"title": "网络详情",
"subtitle": "查看 URL、时延、状态码、请求头与参数同时避免暴露模型输入输出正文。",
"count": "共 {{count}} 条请求",
"detailTitle": "网络检查器",
"detailSubtitle": "每次尝试都会展示调用上游时采集到的技术元数据。",
"url": "URL",

View File

@ -503,15 +503,10 @@ dd {
}
.sync-layout,
.trace-layout,
.settings-layout {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.trace-layout {
grid-template-columns: minmax(300px, 0.78fr) minmax(0, 1.22fr);
}
.settings-layout {
grid-template-columns: minmax(0, 1.7fr) minmax(280px, 0.85fr);
align-items: start;
@ -532,45 +527,6 @@ dd {
grid-column: 1 / -1;
}
.trace-list-panel {
min-width: 0;
}
.trace-list {
display: grid;
gap: 0.85rem;
max-height: calc(100vh - 18rem);
overflow: auto;
padding-right: 0.2rem;
}
.trace-row {
display: grid;
gap: 0.6rem;
width: 100%;
padding: 0.9rem;
border-radius: 18px;
text-align: left;
background: var(--panel-muted);
}
.trace-row.active {
border-color: var(--accent);
box-shadow: var(--shadow-sm);
}
.trace-row-top,
.trace-row-summary {
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.75rem;
}
.trace-row-summary {
color: var(--text-muted);
font-size: 0.9rem;
}
.trace-hero {
display: grid;
@ -602,11 +558,6 @@ dd {
gap: 0.8rem;
}
.trace-pill-warn {
background: var(--warning-soft);
color: var(--warning);
}
.compact-header {
margin-bottom: 0;
}
@ -841,6 +792,12 @@ html[data-theme='dark'] button.ghost-danger:hover {
gap: 0.8rem;
}
.trace-scroll-list {
max-height: none;
padding-right: 0;
overflow: visible;
}
.resource-card {
display: grid;
gap: 0.88rem;
@ -979,6 +936,17 @@ html[data-theme='dark'] button.ghost-danger:hover {
line-height: 1.45;
}
.trace-card-subtitle {
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.75rem;
}
.trace-card-subtitle span:last-child {
flex-shrink: 0;
}
.detail-panel {
display: grid;
align-content: start;
@ -1564,7 +1532,6 @@ html[data-theme='dark'] .detail-empty-card {
.overview-layout,
.sync-layout,
.trace-layout,
.providers-layout,
.aliases-layout,
.stats-grid,
@ -1622,7 +1589,6 @@ html[data-theme='dark'] .detail-empty-card {
.nav-list,
.overview-layout,
.sync-layout,
.trace-layout,
.settings-layout,
.stats-grid,
.card-grid-list,
@ -1660,12 +1626,6 @@ html[data-theme='dark'] .detail-empty-card {
min-height: auto;
}
.trace-row-top,
.trace-row-summary {
flex-direction: column;
align-items: stretch;
}
.detail-backdrop {
align-items: stretch;
justify-content: stretch;