refactor: 详情面板布局优化 — 固定高度 + 空值占位
- DetailPanel: TraceViewer flex:1 撑满, TokenUsage/AgentMonitor flexShrink:0 固定 - TraceViewer: 空内容时「等待 Agent 活动」居中撑满剩余空间 - TokenUsage: 始终展示完整结构(无会话时显示 -/0 而非隐藏) - AgentMonitor: 固定 flexShrink:0 防止被挤压
This commit is contained in:
@@ -55,7 +55,7 @@ export function AgentMonitor(): React.JSX.Element {
|
||||
);
|
||||
|
||||
return (
|
||||
<Box sx={{ mt: 2, pt: 2, borderTop: 1, borderColor: 'divider' }}>
|
||||
<Box sx={{ mt: 2, pt: 2, borderTop: 1, borderColor: 'divider', flexShrink: 0 }}>
|
||||
<Stack direction="row" spacing={1} alignItems="center" sx={{ mb: 1.5 }}>
|
||||
<Cpu size={14} style={{ color: '#818cf8' }} />
|
||||
<Typography variant="caption" sx={{ fontWeight: 600, textTransform: 'uppercase', letterSpacing: 1, color: 'text.secondary' }}>Agent 状态</Typography>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* DetailPanel — 右侧详情面板
|
||||
*
|
||||
* 始终显示,包含 TraceViewer、TokenUsage、AgentMonitor。
|
||||
* 布局策略:TraceViewer flex:1 撑满,TokenUsage/AgentMonitor flexShrink:0 固定。
|
||||
*/
|
||||
|
||||
import { Box } from '@mui/material';
|
||||
@@ -12,8 +12,14 @@ import { LAYOUT } from '@renderer/lib/constants';
|
||||
|
||||
export function DetailPanel(): React.JSX.Element {
|
||||
return (
|
||||
<Box component="aside" sx={{ flexShrink: 0, display: 'flex', flexDirection: 'column', overflow: 'hidden', bgcolor: 'background.paper', borderLeft: 1, borderColor: 'divider', width: LAYOUT.DETAIL_WIDTH }}>
|
||||
<Box sx={{ p: 1.5, flex: 1, overflow: 'hidden', display: 'flex', flexDirection: 'column' }}>
|
||||
<Box
|
||||
component="aside"
|
||||
sx={{
|
||||
flexShrink: 0, display: 'flex', flexDirection: 'column', overflow: 'hidden',
|
||||
bgcolor: 'background.paper', borderLeft: 1, borderColor: 'divider', width: LAYOUT.DETAIL_WIDTH,
|
||||
}}
|
||||
>
|
||||
<Box sx={{ p: 1.5, flex: 1, display: 'flex', flexDirection: 'column', overflow: 'hidden', minHeight: 0 }}>
|
||||
<TraceViewer />
|
||||
<TokenUsage />
|
||||
<AgentMonitor />
|
||||
|
||||
@@ -1,51 +1,45 @@
|
||||
/**
|
||||
* TokenUsage — Token 统计图表
|
||||
* TokenUsage — Token 统计面板
|
||||
*
|
||||
* 进度条可视化 + 数值显示。
|
||||
* 始终展示完整结构,无会话时显示零值。
|
||||
* 固定在详情面板中部,高度由内容自然决定。
|
||||
*/
|
||||
|
||||
import { Box, Typography, Stack, LinearProgress } from '@mui/material';
|
||||
import { Box, Typography, Stack } from '@mui/material';
|
||||
import { Zap } from 'lucide-react';
|
||||
import { useAgentStore } from '@renderer/stores/agent-store';
|
||||
import { formatTokens } from '@renderer/lib/formatters';
|
||||
import { cn } from '@renderer/lib/cn';
|
||||
|
||||
export function TokenUsage(): React.JSX.Element {
|
||||
const tokenUsage = useAgentStore((s) => s.tokenUsage);
|
||||
const maxIterations = useAgentStore((s) => s.maxIterations);
|
||||
const currentIteration = useAgentStore((s) => s.currentIteration);
|
||||
|
||||
if (tokenUsage.totalTokens === 0) {
|
||||
return (
|
||||
<Box sx={{ mt: 2, pt: 2, borderTop: 1, borderColor: 'divider' }}>
|
||||
<Stack direction="row" spacing={1} alignItems="center" sx={{ mb: 1.5 }}>
|
||||
<Zap size={14} style={{ color: '#fbbf24' }} />
|
||||
<Typography variant="caption" sx={{ fontWeight: 600, textTransform: 'uppercase', letterSpacing: 1, color: 'text.secondary' }}>Token 用量</Typography>
|
||||
</Stack>
|
||||
<Typography variant="caption" sx={{ textAlign: 'center', py: 1, display: 'block', color: 'text.disabled' }}>等待 Agent 活动...</Typography>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
const maxTokens = 128_000;
|
||||
const usagePercent = Math.min((tokenUsage.totalTokens / maxTokens) * 100, 100);
|
||||
const inputPercent = tokenUsage.totalTokens > 0 ? (tokenUsage.inputTokens / tokenUsage.totalTokens) * 100 : 0;
|
||||
const usagePercent = tokenUsage.totalTokens > 0
|
||||
? Math.min((tokenUsage.totalTokens / maxTokens) * 100, 100)
|
||||
: 0;
|
||||
const inputPercent = tokenUsage.totalTokens > 0
|
||||
? (tokenUsage.inputTokens / tokenUsage.totalTokens) * 100
|
||||
: 0;
|
||||
|
||||
return (
|
||||
<Box sx={{ mt: 2, pt: 2, borderTop: 1, borderColor: 'divider' }}>
|
||||
<Box sx={{ mt: 2, pt: 2, borderTop: 1, borderColor: 'divider', flexShrink: 0 }}>
|
||||
{/* 标题 */}
|
||||
<Stack direction="row" spacing={1} alignItems="center" sx={{ mb: 1.5 }}>
|
||||
<Zap size={14} style={{ color: '#fbbf24' }} />
|
||||
<Typography variant="caption" sx={{ fontWeight: 600, textTransform: 'uppercase', letterSpacing: 1, color: 'text.secondary' }}>Token 用量</Typography>
|
||||
<Typography variant="caption" sx={{ fontWeight: 600, textTransform: 'uppercase', letterSpacing: 1, color: 'text.secondary' }}>
|
||||
Token 用量
|
||||
</Typography>
|
||||
</Stack>
|
||||
|
||||
{/* 进度条 */}
|
||||
<Box sx={{ width: '100%', height: 6, borderRadius: 3, overflow: 'hidden', mb: 2, bgcolor: 'action.hover', display: 'flex' }}>
|
||||
<Box sx={{ height: '100%', width: `${inputPercent}%`, bgcolor: '#22d3ee', transition: 'width 300ms', borderRadius: '3px 0 0 3px' }} />
|
||||
<Box sx={{ height: '100%', width: `${100 - inputPercent}%`, bgcolor: '#a855f7', transition: 'width 300ms', borderRadius: '0 3px 3px 0' }} />
|
||||
<Box sx={{ height: '100%', width: `${Math.max(100 - inputPercent, 0)}%`, bgcolor: '#a855f7', transition: 'width 300ms', borderRadius: '0 3px 3px 0' }} />
|
||||
</Box>
|
||||
|
||||
{/* 数值网格 */}
|
||||
{/* 数值卡片 */}
|
||||
<Box sx={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 1.5, mb: 1.5 }}>
|
||||
<StatCard label="输入" value={formatTokens(tokenUsage.inputTokens)} color="#22d3ee" />
|
||||
<StatCard label="输出" value={formatTokens(tokenUsage.outputTokens)} color="#a855f7" />
|
||||
@@ -53,9 +47,9 @@ export function TokenUsage(): React.JSX.Element {
|
||||
|
||||
{/* 底部汇总 */}
|
||||
<Stack spacing={0.5}>
|
||||
<SummaryRow label="总计" value={formatTokens(tokenUsage.totalTokens)} highlight />
|
||||
<SummaryRow label="上下文" value={`${usagePercent.toFixed(1)}%`}
|
||||
color={usagePercent > 80 ? 'error.main' : usagePercent > 60 ? 'warning.main' : 'text.secondary'} />
|
||||
<SummaryRow label="总计" value={tokenUsage.totalTokens > 0 ? formatTokens(tokenUsage.totalTokens) : '-'} highlight />
|
||||
<SummaryRow label="上下文" value={tokenUsage.totalTokens > 0 ? `${usagePercent.toFixed(1)}%` : '-'}
|
||||
color={usagePercent > 80 ? 'error.main' : usagePercent > 60 ? 'warning.main' : undefined} />
|
||||
<SummaryRow label="迭代" value={`${currentIteration} / ${maxIterations}`} />
|
||||
</Stack>
|
||||
</Box>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
/**
|
||||
* TraceViewer — Trace 时间轴查看器
|
||||
* TraceViewer — ReAct 迭代追踪时间轴
|
||||
*
|
||||
* flex:1 撑满详情面板剩余空间,内容超出时内部滚动。
|
||||
*/
|
||||
|
||||
import { Box, Typography, Stack } from '@mui/material';
|
||||
@@ -12,20 +14,28 @@ export function TraceViewer(): React.JSX.Element {
|
||||
const agentStatus = useAgentStore((s) => s.agentStatus);
|
||||
|
||||
return (
|
||||
<Box sx={{ flexShrink: 0, display: 'flex', flexDirection: 'column' }}>
|
||||
<Stack direction="row" spacing={1} alignItems="center" sx={{ mb: 1.5 }}>
|
||||
<Box sx={{ flex: 1, display: 'flex', flexDirection: 'column', minHeight: 0, overflow: 'hidden' }}>
|
||||
<Stack direction="row" spacing={1} alignItems="center" sx={{ mb: 1.5, flexShrink: 0 }}>
|
||||
<Activity size={14} style={{ color: '#818cf8' }} />
|
||||
<Typography variant="caption" sx={{ fontWeight: 600, textTransform: 'uppercase', letterSpacing: 1, color: 'text.secondary' }}>
|
||||
Trace Viewer
|
||||
</Typography>
|
||||
</Stack>
|
||||
|
||||
<Box sx={{ overflowY: 'auto', display: 'flex', flexDirection: 'column', gap: 0.5, maxHeight: 200 }}>
|
||||
<Box sx={{ flex: 1, overflowY: 'auto', display: 'flex', flexDirection: 'column', gap: 0.5, minHeight: 0 }}>
|
||||
{traceSteps.length === 0 ? (
|
||||
<Typography variant="caption" sx={{ textAlign: 'center', py: 2, color: 'text.secondary' }}>等待 Agent 活动...</Typography>
|
||||
) : traceSteps.map((step, i) => (
|
||||
<TraceStep key={`${step.iteration}-${step.state}-${i}`} step={step} isCurrent={i === traceSteps.length - 1 && agentStatus !== 'idle'} />
|
||||
))}
|
||||
<Typography variant="caption" sx={{ textAlign: 'center', py: 2, color: 'text.disabled', flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||||
等待 Agent 活动...
|
||||
</Typography>
|
||||
) : (
|
||||
traceSteps.map((step, i) => (
|
||||
<TraceStep
|
||||
key={`${step.iteration}-${step.state}-${i}`}
|
||||
step={step}
|
||||
isCurrent={i === traceSteps.length - 1 && agentStatus !== 'idle'}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user