refactor: AgentMonitor 改用 MUI Table 布局标签-值对
This commit is contained in:
@@ -1,9 +1,11 @@
|
|||||||
/**
|
/**
|
||||||
* AgentMonitor — Agent 状态指示器
|
* AgentMonitor — Agent 状态指示器
|
||||||
|
*
|
||||||
|
* 完全使用 MUI 组件,Table 布局标签-值对。
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Box, Typography, Stack } from '@mui/material';
|
import { Box, Typography, Stack, Table, TableBody, TableRow, TableCell, Chip } from '@mui/material';
|
||||||
import { Activity, Cpu, Clock } from 'lucide-react';
|
import { Activity, Cpu } from 'lucide-react';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useAgentStore, type AgentStatus } from '@renderer/stores/agent-store';
|
import { useAgentStore, type AgentStatus } from '@renderer/stores/agent-store';
|
||||||
import { AGENT_STATUS_COLORS, AGENT_STATUS_LABELS, PROVIDER_LABELS } from '@renderer/lib/constants';
|
import { AGENT_STATUS_COLORS, AGENT_STATUS_LABELS, PROVIDER_LABELS } from '@renderer/lib/constants';
|
||||||
@@ -20,7 +22,6 @@ export function AgentMonitor(): React.JSX.Element {
|
|||||||
const traceSteps = useAgentStore((s) => s.traceSteps);
|
const traceSteps = useAgentStore((s) => s.traceSteps);
|
||||||
const setMaxIterations = useAgentStore((s) => s.setMaxIterations);
|
const setMaxIterations = useAgentStore((s) => s.setMaxIterations);
|
||||||
|
|
||||||
// 启动时从数据库读取 maxIterations
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (window.metona?.config?.get) {
|
if (window.metona?.config?.get) {
|
||||||
window.metona.config.get('agent.maxIterations').then((v) => {
|
window.metona.config.get('agent.maxIterations').then((v) => {
|
||||||
@@ -29,47 +30,68 @@ export function AgentMonitor(): React.JSX.Element {
|
|||||||
}
|
}
|
||||||
}, [setMaxIterations]);
|
}, [setMaxIterations]);
|
||||||
|
|
||||||
// 监听配置变更
|
|
||||||
useEffect(() => {
|
|
||||||
const interval = setInterval(() => {
|
|
||||||
if (window.metona?.config?.get) {
|
|
||||||
window.metona.config.get('agent.maxIterations').then((v) => {
|
|
||||||
if (typeof v === 'number' && v > 0) setMaxIterations(v);
|
|
||||||
}).catch(() => {});
|
|
||||||
}
|
|
||||||
}, 3000);
|
|
||||||
return () => clearInterval(interval);
|
|
||||||
}, [setMaxIterations]);
|
|
||||||
const StatusIcon = STATUS_ICONS[agentStatus];
|
const StatusIcon = STATUS_ICONS[agentStatus];
|
||||||
const statusColor = AGENT_STATUS_COLORS[agentStatus];
|
const statusColor = AGENT_STATUS_COLORS[agentStatus];
|
||||||
const statusLabel = AGENT_STATUS_LABELS[agentStatus];
|
const statusLabel = AGENT_STATUS_LABELS[agentStatus];
|
||||||
const totalDuration = traceSteps.reduce((sum, s) => sum + (s.completedAt ? s.completedAt - s.startedAt : 0), 0);
|
const totalDuration = traceSteps.reduce((sum, s) => sum + (s.completedAt ? s.completedAt - s.startedAt : 0), 0);
|
||||||
|
|
||||||
const InfoRow = ({ icon: Icon, label, value }: { icon: typeof Activity; label: string; value: string }) => (
|
|
||||||
<Stack direction="row" alignItems="center" sx={{ gap: 1 }}>
|
|
||||||
<Stack direction="row" spacing={0.75} alignItems="center" sx={{ color: 'text.secondary', flexShrink: 0, minWidth: 60 }}>
|
|
||||||
<Icon size={10} /><Typography variant="caption">{label}</Typography>
|
|
||||||
</Stack>
|
|
||||||
<Typography variant="caption" sx={{ fontFamily: 'monospace', color: 'text.primary', flex: 1, textAlign: 'right', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{value}</Typography>
|
|
||||||
</Stack>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ mt: 2, pt: 2, borderTop: 1, borderColor: 'divider', flexShrink: 0 }}>
|
<Box sx={{ mt: 2, pt: 2, borderTop: 1, borderColor: 'divider', flexShrink: 0 }}>
|
||||||
<Stack direction="row" spacing={1} alignItems="center" sx={{ mb: 1.5 }}>
|
<Stack direction="row" spacing={1} alignItems="center" sx={{ mb: 1.5 }}>
|
||||||
<Cpu size={14} style={{ color: '#818cf8' }} />
|
<Cpu size={14} style={{ color: '#818cf8' }} />
|
||||||
<Typography variant="caption" sx={{ fontWeight: 600, textTransform: 'uppercase', letterSpacing: 1, color: 'text.secondary' }}>Agent 状态</Typography>
|
<Typography variant="caption" sx={{ fontWeight: 600, textTransform: 'uppercase', letterSpacing: 1, color: 'text.secondary' }}>
|
||||||
</Stack>
|
Agent 状态
|
||||||
<Stack direction="row" spacing={1} alignItems="center" sx={{ px: 1.5, py: 1, borderRadius: 1.5, mb: 1, bgcolor: 'background.default' }}>
|
</Typography>
|
||||||
<StatusIcon size={14} style={{ color: statusColor, animation: (agentStatus === 'thinking' || agentStatus === 'executing') ? 'pulse 2s infinite' : 'none' }} />
|
|
||||||
<Typography variant="caption" sx={{ fontWeight: 500, color: statusColor }}>{statusLabel}</Typography>
|
|
||||||
</Stack>
|
|
||||||
<Stack spacing={0.75}>
|
|
||||||
<InfoRow icon={Activity} label="Provider" value={PROVIDER_LABELS[provider] ?? provider} />
|
|
||||||
<InfoRow icon={Cpu} label="模型" value={model} />
|
|
||||||
<InfoRow icon={Activity} label="迭代" value={`${currentIteration} / ${maxIterations}`} />
|
|
||||||
{totalDuration > 0 && <InfoRow icon={Clock} label="总耗时" value={formatDuration(totalDuration)} />}
|
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
|
{/* 状态指示 */}
|
||||||
|
<Box sx={{ px: 1.5, py: 1, borderRadius: 1.5, mb: 1.5, bgcolor: 'background.default', border: '1px solid', borderColor: 'divider' }}>
|
||||||
|
<Stack direction="row" spacing={1} alignItems="center">
|
||||||
|
<StatusIcon
|
||||||
|
size={14}
|
||||||
|
style={{
|
||||||
|
color: statusColor,
|
||||||
|
animation: (agentStatus === 'thinking' || agentStatus === 'executing') ? 'pulse 2s infinite' : 'none',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Typography variant="caption" sx={{ fontWeight: 500, color: statusColor }}>{statusLabel}</Typography>
|
||||||
|
{(agentStatus === 'thinking' || agentStatus === 'executing') && (
|
||||||
|
<Box sx={{ width: 6, height: 6, borderRadius: '50%', bgcolor: statusColor, animation: 'pulse 1.5s infinite', ml: 'auto' }} />
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* 详情表格 */}
|
||||||
|
<Table size="small" sx={{ '& .MuiTableCell-root': { border: 0, py: 0.5, px: 0.5 } }}>
|
||||||
|
<TableBody>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell sx={{ color: 'text.secondary', fontSize: 11, width: '40%' }}>Provider</TableCell>
|
||||||
|
<TableCell sx={{ fontFamily: 'monospace', fontSize: 11, color: 'text.primary', textAlign: 'right', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', maxWidth: 0 }}>
|
||||||
|
{PROVIDER_LABELS[provider] ?? provider}
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell sx={{ color: 'text.secondary', fontSize: 11 }}>模型</TableCell>
|
||||||
|
<TableCell sx={{ fontFamily: 'monospace', fontSize: 11, color: 'text.primary', textAlign: 'right', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', maxWidth: 0 }}>
|
||||||
|
{model}
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell sx={{ color: 'text.secondary', fontSize: 11 }}>迭代</TableCell>
|
||||||
|
<TableCell sx={{ fontFamily: 'monospace', fontSize: 11, color: 'text.secondary', textAlign: 'right' }}>
|
||||||
|
{currentIteration} / {maxIterations}
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
{totalDuration > 0 && (
|
||||||
|
<TableRow>
|
||||||
|
<TableCell sx={{ color: 'text.secondary', fontSize: 11 }}>总耗时</TableCell>
|
||||||
|
<TableCell sx={{ fontFamily: 'monospace', fontSize: 11, color: 'text.secondary', textAlign: 'right' }}>
|
||||||
|
{formatDuration(totalDuration)}
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
)}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user