fix(desktop): unify log and network layouts
This commit is contained in:
parent
08343ad576
commit
a82754eb40
@ -1643,27 +1643,18 @@ export default function App() {
|
|||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{activeTab === 'log' ? (
|
{activeTab === 'log' ? (
|
||||||
<section className="tab-layout trace-layout">
|
<section className="tab-layout aliases-layout">
|
||||||
<article className="panel trace-list-panel">
|
<article className="panel panel-fill list-column">
|
||||||
<div className="panel-header">
|
<div className="panel-header">
|
||||||
<div>
|
<div>
|
||||||
<h3>{t('log.title')}</h3>
|
<h3>{t('log.title')}</h3>
|
||||||
<p className="subtle">
|
<p className="subtle">{traceStatus || t('log.subtitle')}</p>
|
||||||
{logDetailOpen && selectedLogTrace
|
|
||||||
? `${t('log.detailTitle')}: #${selectedLogTrace.id}`
|
|
||||||
: traceStatus || t('log.subtitle')}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="toolbar toolbar-end">
|
<div className="list-header-actions">
|
||||||
<span className="subtle">{t('log.count', { count: requestTraces.length })}</span>
|
<span className="subtle list-status-text">{t('log.count', { count: requestTraces.length })}</span>
|
||||||
{logDetailOpen ? (
|
|
||||||
<button type="button" onClick={closeLogDetail}>
|
|
||||||
{t('actions.close')}
|
|
||||||
</button>
|
|
||||||
) : null}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="trace-list">
|
<div className="scroll-list compact-list trace-scroll-list">
|
||||||
{requestTraces.length === 0 ? (
|
{requestTraces.length === 0 ? (
|
||||||
<article className="empty-card compact-empty">
|
<article className="empty-card compact-empty">
|
||||||
<h4>{t('log.empty')}</h4>
|
<h4>{t('log.empty')}</h4>
|
||||||
@ -1671,28 +1662,50 @@ export default function App() {
|
|||||||
</article>
|
</article>
|
||||||
) : null}
|
) : null}
|
||||||
{requestTraces.map((trace) => (
|
{requestTraces.map((trace) => (
|
||||||
<button
|
<article
|
||||||
key={trace.id}
|
key={trace.id}
|
||||||
type="button"
|
className={`resource-card ${logDetailOpen && selectedLogTrace?.id === trace.id ? 'active' : ''}`}
|
||||||
className={`trace-row ${logDetailOpen && selectedLogTrace?.id === trace.id ? 'active' : ''}`}
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
onClick={() => setSelectedLogTraceId(trace.id)}
|
onClick={() => setSelectedLogTraceId(trace.id)}
|
||||||
|
onKeyDown={(event) => onResourceCardKeyDown(event, () => setSelectedLogTraceId(trace.id))}
|
||||||
>
|
>
|
||||||
<div className="trace-row-top">
|
<div className="resource-card-top">
|
||||||
<strong>{trace.alias || trace.rawModel || `#${trace.id}`}</strong>
|
<div className="resource-card-heading">
|
||||||
<span className={`badge ${trace.success ? 'live' : 'idle'}`}>
|
<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')}
|
{trace.success ? t('log.success') : t('log.failed')}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="trace-row-summary">
|
|
||||||
<span>{tracePrimaryText(trace)}</span>
|
|
||||||
<span>{formatDuration(trace.durationMs)}</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="inline-pills">
|
<div className="resource-card-meta">
|
||||||
<span className="pill">#{trace.id}</span>
|
<div className="resource-meta-item">
|
||||||
<span className="pill">{formatDateTime(trace.startedAt)}</span>
|
<span className="resource-meta-label">{t('log.startedAt')}</span>
|
||||||
{trace.failover ? <span className="pill trace-pill-warn">{t('log.failover')}</span> : null}
|
<span className="resource-meta-value">{formatDateTime(trace.startedAt)}</span>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
@ -1700,26 +1713,18 @@ export default function App() {
|
|||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{activeTab === 'network' ? (
|
{activeTab === 'network' ? (
|
||||||
<section className="tab-layout trace-layout">
|
<section className="tab-layout aliases-layout">
|
||||||
<article className="panel trace-list-panel">
|
<article className="panel panel-fill list-column">
|
||||||
<div className="panel-header">
|
<div className="panel-header">
|
||||||
<div>
|
<div>
|
||||||
<h3>{t('network.title')}</h3>
|
<h3>{t('network.title')}</h3>
|
||||||
<p className="subtle">
|
<p className="subtle">{traceStatus || t('network.subtitle')}</p>
|
||||||
{networkDetailOpen && selectedNetworkTrace
|
|
||||||
? `${t('network.detailTitle')}: #${selectedNetworkTrace.id}`
|
|
||||||
: traceStatus || t('network.subtitle')}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="toolbar toolbar-end">
|
<div className="list-header-actions">
|
||||||
{networkDetailOpen ? (
|
<span className="subtle list-status-text">{t('network.count', { count: requestTraces.length })}</span>
|
||||||
<button type="button" onClick={closeNetworkDetail}>
|
|
||||||
{t('actions.close')}
|
|
||||||
</button>
|
|
||||||
) : null}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="trace-list">
|
<div className="scroll-list compact-list trace-scroll-list">
|
||||||
{requestTraces.length === 0 ? (
|
{requestTraces.length === 0 ? (
|
||||||
<article className="empty-card compact-empty">
|
<article className="empty-card compact-empty">
|
||||||
<h4>{t('log.empty')}</h4>
|
<h4>{t('log.empty')}</h4>
|
||||||
@ -1727,21 +1732,44 @@ export default function App() {
|
|||||||
</article>
|
</article>
|
||||||
) : null}
|
) : null}
|
||||||
{requestTraces.map((trace) => (
|
{requestTraces.map((trace) => (
|
||||||
<button
|
<article
|
||||||
key={trace.id}
|
key={trace.id}
|
||||||
type="button"
|
className={`resource-card ${networkDetailOpen && selectedNetworkTrace?.id === trace.id ? 'active' : ''}`}
|
||||||
className={`trace-row ${networkDetailOpen && selectedNetworkTrace?.id === trace.id ? 'active' : ''}`}
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
onClick={() => setSelectedNetworkTraceId(trace.id)}
|
onClick={() => setSelectedNetworkTraceId(trace.id)}
|
||||||
|
onKeyDown={(event) => onResourceCardKeyDown(event, () => setSelectedNetworkTraceId(trace.id))}
|
||||||
>
|
>
|
||||||
<div className="trace-row-top">
|
<div className="resource-card-top">
|
||||||
<strong>#{trace.id}</strong>
|
<div className="resource-card-heading">
|
||||||
<span className={`badge ${trace.success ? 'live' : 'idle'}`}>{trace.statusCode || '-'}</span>
|
<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>
|
||||||
<div className="trace-row-summary">
|
<p className="resource-card-subtitle trace-card-subtitle">
|
||||||
<span>{trace.finalUrl || tracePrimaryText(trace)}</span>
|
<span>{trace.finalUrl || tracePrimaryText(trace)}</span>
|
||||||
<span>{formatDuration(trace.firstByteMs)}</span>
|
<span>{formatDuration(trace.firstByteMs)}</span>
|
||||||
|
</p>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
@ -2571,7 +2599,7 @@ export default function App() {
|
|||||||
<div className="chain-list">
|
<div className="chain-list">
|
||||||
{(selectedLogTrace.attempts || []).map((attempt) => (
|
{(selectedLogTrace.attempts || []).map((attempt) => (
|
||||||
<article className="chain-card" key={`${selectedLogTrace.id}-${attempt.attempt}-${attempt.provider}-${attempt.model}`}>
|
<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>
|
<strong>{t('log.attemptLabel', { attempt: attempt.attempt })}</strong>
|
||||||
<span className={`badge ${attempt.success ? 'live' : attempt.skipped ? 'outline' : 'idle'}`}>
|
<span className={`badge ${attempt.success ? 'live' : attempt.skipped ? 'outline' : 'idle'}`}>
|
||||||
{attempt.result || '-'}
|
{attempt.result || '-'}
|
||||||
|
|||||||
@ -271,6 +271,7 @@
|
|||||||
"network": {
|
"network": {
|
||||||
"title": "Network detail",
|
"title": "Network detail",
|
||||||
"subtitle": "Inspect URL, timing, status, headers, and params without exposing model IO content.",
|
"subtitle": "Inspect URL, timing, status, headers, and params without exposing model IO content.",
|
||||||
|
"count": "{{count}} requests",
|
||||||
"detailTitle": "Network inspector",
|
"detailTitle": "Network inspector",
|
||||||
"detailSubtitle": "Each attempt shows technical metadata captured around the upstream call.",
|
"detailSubtitle": "Each attempt shows technical metadata captured around the upstream call.",
|
||||||
"url": "URL",
|
"url": "URL",
|
||||||
|
|||||||
@ -271,6 +271,7 @@
|
|||||||
"network": {
|
"network": {
|
||||||
"title": "网络详情",
|
"title": "网络详情",
|
||||||
"subtitle": "查看 URL、时延、状态码、请求头与参数,同时避免暴露模型输入输出正文。",
|
"subtitle": "查看 URL、时延、状态码、请求头与参数,同时避免暴露模型输入输出正文。",
|
||||||
|
"count": "共 {{count}} 条请求",
|
||||||
"detailTitle": "网络检查器",
|
"detailTitle": "网络检查器",
|
||||||
"detailSubtitle": "每次尝试都会展示调用上游时采集到的技术元数据。",
|
"detailSubtitle": "每次尝试都会展示调用上游时采集到的技术元数据。",
|
||||||
"url": "URL",
|
"url": "URL",
|
||||||
|
|||||||
@ -503,15 +503,10 @@ dd {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.sync-layout,
|
.sync-layout,
|
||||||
.trace-layout,
|
|
||||||
.settings-layout {
|
.settings-layout {
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
}
|
}
|
||||||
|
|
||||||
.trace-layout {
|
|
||||||
grid-template-columns: minmax(300px, 0.78fr) minmax(0, 1.22fr);
|
|
||||||
}
|
|
||||||
|
|
||||||
.settings-layout {
|
.settings-layout {
|
||||||
grid-template-columns: minmax(0, 1.7fr) minmax(280px, 0.85fr);
|
grid-template-columns: minmax(0, 1.7fr) minmax(280px, 0.85fr);
|
||||||
align-items: start;
|
align-items: start;
|
||||||
@ -532,45 +527,6 @@ dd {
|
|||||||
grid-column: 1 / -1;
|
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 {
|
.trace-hero {
|
||||||
display: grid;
|
display: grid;
|
||||||
@ -602,11 +558,6 @@ dd {
|
|||||||
gap: 0.8rem;
|
gap: 0.8rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.trace-pill-warn {
|
|
||||||
background: var(--warning-soft);
|
|
||||||
color: var(--warning);
|
|
||||||
}
|
|
||||||
|
|
||||||
.compact-header {
|
.compact-header {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
@ -841,6 +792,12 @@ html[data-theme='dark'] button.ghost-danger:hover {
|
|||||||
gap: 0.8rem;
|
gap: 0.8rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.trace-scroll-list {
|
||||||
|
max-height: none;
|
||||||
|
padding-right: 0;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
.resource-card {
|
.resource-card {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 0.88rem;
|
gap: 0.88rem;
|
||||||
@ -979,6 +936,17 @@ html[data-theme='dark'] button.ghost-danger:hover {
|
|||||||
line-height: 1.45;
|
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 {
|
.detail-panel {
|
||||||
display: grid;
|
display: grid;
|
||||||
align-content: start;
|
align-content: start;
|
||||||
@ -1564,7 +1532,6 @@ html[data-theme='dark'] .detail-empty-card {
|
|||||||
|
|
||||||
.overview-layout,
|
.overview-layout,
|
||||||
.sync-layout,
|
.sync-layout,
|
||||||
.trace-layout,
|
|
||||||
.providers-layout,
|
.providers-layout,
|
||||||
.aliases-layout,
|
.aliases-layout,
|
||||||
.stats-grid,
|
.stats-grid,
|
||||||
@ -1622,7 +1589,6 @@ html[data-theme='dark'] .detail-empty-card {
|
|||||||
.nav-list,
|
.nav-list,
|
||||||
.overview-layout,
|
.overview-layout,
|
||||||
.sync-layout,
|
.sync-layout,
|
||||||
.trace-layout,
|
|
||||||
.settings-layout,
|
.settings-layout,
|
||||||
.stats-grid,
|
.stats-grid,
|
||||||
.card-grid-list,
|
.card-grid-list,
|
||||||
@ -1660,12 +1626,6 @@ html[data-theme='dark'] .detail-empty-card {
|
|||||||
min-height: auto;
|
min-height: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.trace-row-top,
|
|
||||||
.trace-row-summary {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: stretch;
|
|
||||||
}
|
|
||||||
|
|
||||||
.detail-backdrop {
|
.detail-backdrop {
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
justify-content: stretch;
|
justify-content: stretch;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user