feat(ui): optimize intelligent eval drawer UI
- Show estimated session progress in list (completed/estimated with running count) - Improve user input layout with scrollable sections for long text - Remove 50-item limit on activity feed, show all events - Enhance lifecycle steps and time distribution chart styling - Add pagination to decision log table (20/50/100 per page) - Unify font sizes across drawer tabs (12/13/14px scale)
This commit is contained in:
parent
d0487b54b4
commit
60981b634d
@ -100,7 +100,7 @@ export default function DecisionProcess({ evalId, onBack }: DecisionProcessProps
|
||||
dataIndex: 'cron_id',
|
||||
key: 'cron_id',
|
||||
width: 120,
|
||||
render: (val: string) => <code style={{ fontSize: 11 }}>{val.slice(0, 8)}</code>,
|
||||
render: (val: string) => <code style={{ fontSize: 12 }}>{val.slice(0, 8)}</code>,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
@ -157,8 +157,8 @@ export default function DecisionProcess({ evalId, onBack }: DecisionProcessProps
|
||||
{formatDateTime(log.created_at)}
|
||||
</span>
|
||||
</div>
|
||||
<div style={{ fontSize: 13 }}>{log.reason}</div>
|
||||
<div style={{ fontSize: 11, color: colors.textSecondary, marginTop: 4 }}>
|
||||
<div style={{ fontSize: 13, lineHeight: 1.5 }}>{log.reason}</div>
|
||||
<div style={{ fontSize: 12, color: colors.textSecondary, marginTop: 4 }}>
|
||||
Cron: <code>{log.cron_id.slice(0, 8)}</code>
|
||||
</div>
|
||||
</div>
|
||||
@ -174,7 +174,12 @@ export default function DecisionProcess({ evalId, onBack }: DecisionProcessProps
|
||||
loading={loading}
|
||||
dataSource={filteredLogs}
|
||||
columns={columns}
|
||||
pagination={false}
|
||||
pagination={{
|
||||
pageSize: 20,
|
||||
showSizeChanger: true,
|
||||
pageSizeOptions: [20, 50, 100],
|
||||
showTotal: (total) => `共 ${total} 条`,
|
||||
}}
|
||||
expandable={{
|
||||
expandedRowKeys: expandedLog ? [expandedLog] : [],
|
||||
expandIcon: () => null,
|
||||
|
||||
@ -89,26 +89,49 @@ export default function EvalOverview({ ev, targetName }: EvalOverviewProps) {
|
||||
</Card>
|
||||
|
||||
<Card size="small" title="用户输入" style={sectionCard}>
|
||||
<Descriptions column={1} size="small">
|
||||
<Descriptions.Item label="评估目标">{ev.goal || '—'}</Descriptions.Item>
|
||||
<Descriptions.Item label="考察意图">{ev.intent || '—'}</Descriptions.Item>
|
||||
<Descriptions.Item label="角色描述">
|
||||
<span style={{ whiteSpace: 'pre-wrap' }}>{ev.role_description || '—'}</span>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="种子集">
|
||||
{Object.keys(ev.seeds ?? {}).length === 0
|
||||
? '—'
|
||||
: (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
|
||||
<div>
|
||||
<div style={{ fontSize: 12, color: colors.textSecondary, marginBottom: 4 }}>评估目标</div>
|
||||
<div style={{
|
||||
fontSize: 13, padding: '8px 12px', background: colors.bgSubtle,
|
||||
borderRadius: 6, whiteSpace: 'pre-wrap', maxHeight: 120, overflowY: 'auto',
|
||||
}}>
|
||||
{ev.goal || '—'}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style={{ fontSize: 12, color: colors.textSecondary, marginBottom: 4 }}>考察意图</div>
|
||||
<div style={{
|
||||
fontSize: 13, padding: '8px 12px', background: colors.bgSubtle,
|
||||
borderRadius: 6, whiteSpace: 'pre-wrap', maxHeight: 100, overflowY: 'auto',
|
||||
}}>
|
||||
{ev.intent || '—'}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style={{ fontSize: 12, color: colors.textSecondary, marginBottom: 4 }}>角色描述</div>
|
||||
<div style={{
|
||||
fontSize: 13, padding: '8px 12px', background: colors.bgSubtle,
|
||||
borderRadius: 6, whiteSpace: 'pre-wrap', maxHeight: 100, overflowY: 'auto',
|
||||
}}>
|
||||
{ev.role_description || '—'}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style={{ fontSize: 12, color: colors.textSecondary, marginBottom: 4 }}>种子集</div>
|
||||
{Object.keys(ev.seeds ?? {}).length === 0 ? (
|
||||
<div style={{ fontSize: 13, padding: '8px 12px', background: colors.bgSubtle, borderRadius: 6 }}>—</div>
|
||||
) : (
|
||||
<pre style={{
|
||||
margin: 0, fontSize: 12, background: colors.bgSubtle,
|
||||
padding: 8, borderRadius: 6, overflowX: 'auto',
|
||||
}}
|
||||
>
|
||||
padding: '8px 12px', borderRadius: 6, overflowX: 'auto',
|
||||
maxHeight: 160, overflowY: 'auto',
|
||||
}}>
|
||||
{JSON.stringify(ev.seeds, null, 2)}
|
||||
</pre>
|
||||
)}
|
||||
</Descriptions.Item>
|
||||
</Descriptions>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</Col>
|
||||
|
||||
|
||||
@ -110,13 +110,13 @@ describe('ExecutionProcess', () => {
|
||||
await waitFor(() => expect(screen.getByText('评估已取消')).toBeInTheDocument())
|
||||
})
|
||||
|
||||
it('caps the activity feed at 50 entries and hints at the decision tab', async () => {
|
||||
it('shows all activity entries with total count', async () => {
|
||||
mockApi({}, 60)
|
||||
render(<ExecutionProcess ev={makeEval()} />)
|
||||
|
||||
await waitFor(() => expect(screen.getByText(/仅展示最近 50 条/)).toBeInTheDocument())
|
||||
await waitFor(() => expect(screen.getByText(/共 \d+ 条活动/)).toBeInTheDocument())
|
||||
expect(screen.getByText(/决策·执行会话:时段欠账 59/)).toBeInTheDocument()
|
||||
expect(screen.queryByText(/决策·执行会话:时段欠账 0/)).not.toBeInTheDocument()
|
||||
expect(screen.getByText(/决策·执行会话:时段欠账 0/)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('scopes task loading to the current evaluation', async () => {
|
||||
|
||||
@ -13,7 +13,6 @@ import { colors, statusColors } from '../../tokens'
|
||||
import { formatDateTime } from '../../utils/date'
|
||||
import { usePolling } from '../../hooks/usePolling'
|
||||
|
||||
const ACTIVITY_LIMIT = 50
|
||||
const ACTIVE_STATUSES = new Set(['planning', 'pending_approval', 'executing'])
|
||||
|
||||
const STAGE_STEPS = [
|
||||
@ -123,7 +122,7 @@ export default function ExecutionProcess({ ev }: { ev: IntelligentEval }) {
|
||||
const activities = useMemo(() => {
|
||||
const all = [...sessionEvents(ev), ...taskEvents(tasks), ...decisionEvents(logs)]
|
||||
all.sort((a, b) => (a.time < b.time ? 1 : -1))
|
||||
return all.slice(0, ACTIVITY_LIMIT)
|
||||
return all
|
||||
}, [ev, tasks, logs])
|
||||
|
||||
if (loading && progress == null) {
|
||||
@ -151,7 +150,10 @@ export default function ExecutionProcess({ ev }: { ev: IntelligentEval }) {
|
||||
seriesField: 'kind',
|
||||
group: true,
|
||||
height: Math.max(200, progress.slots.length * 72),
|
||||
color: [colors.textMuted, statusColors.running, statusColors.completed],
|
||||
color: [colors.textMuted, colors.primary, statusColors.completed],
|
||||
legend: { position: 'top' },
|
||||
xAxis: { label: { autoRotate: true } },
|
||||
yAxis: { min: 0 },
|
||||
}
|
||||
|
||||
return (
|
||||
@ -160,9 +162,10 @@ export default function ExecutionProcess({ ev }: { ev: IntelligentEval }) {
|
||||
<Steps
|
||||
current={stageIndex < 0 ? 0 : stageIndex}
|
||||
status={abnormal ? 'error' : progress.current_stage === 'done' ? 'finish' : 'process'}
|
||||
size="small"
|
||||
items={STAGE_STEPS.map((s) => ({
|
||||
title: s.title,
|
||||
status: undefined,
|
||||
description: s.key === progress.current_stage ? '当前阶段' : undefined,
|
||||
}))}
|
||||
/>
|
||||
{progress.blocker != null && (
|
||||
@ -201,14 +204,14 @@ export default function ExecutionProcess({ ev }: { ev: IntelligentEval }) {
|
||||
color: a.color,
|
||||
children: (
|
||||
<div>
|
||||
<div style={{ fontSize: 13 }}>{a.text}</div>
|
||||
<div style={{ fontSize: 11, color: colors.textSecondary }}>{formatDateTime(a.time)}</div>
|
||||
<div style={{ fontSize: 13, lineHeight: 1.5 }}>{a.text}</div>
|
||||
<div style={{ fontSize: 12, color: colors.textSecondary }}>{formatDateTime(a.time)}</div>
|
||||
</div>
|
||||
),
|
||||
}))}
|
||||
/>
|
||||
<div style={{ fontSize: 12, color: colors.textMuted, marginTop: 8 }}>
|
||||
{`仅展示最近 ${ACTIVITY_LIMIT} 条,完整决策历史见「决策过程」`}
|
||||
{`共 ${activities.length} 条活动,完整决策历史见「决策过程」`}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
@ -160,13 +160,18 @@ export default function IntelligentEvalsPage() {
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '会话进度', key: 'progress', width: 170,
|
||||
title: '会话进度', key: 'progress', width: 200,
|
||||
render: (_, ev) => {
|
||||
const pct = ev.session_count ? Math.round((ev.completed_sessions / ev.session_count) * 100) : 0
|
||||
const estimated = ev.plan?.estimated_sessions ?? ev.session_count
|
||||
const running = (ev.sessions ?? []).filter((s) => s.status === 'running').length
|
||||
const pct = estimated ? Math.round((ev.completed_sessions / estimated) * 100) : 0
|
||||
return (
|
||||
<Space size={6}>
|
||||
<Progress percent={pct} size="small" style={{ width: 90 }} />
|
||||
<span style={{ fontSize: 12, color: colors.textSecondary }}>{ev.completed_sessions}/{ev.session_count}</span>
|
||||
<span style={{ fontSize: 12, color: colors.textSecondary }}>
|
||||
{ev.completed_sessions}/{estimated}
|
||||
{running > 0 && <span style={{ color: statusColors.running, marginLeft: 4 }}>({running} 进行中)</span>}
|
||||
</span>
|
||||
</Space>
|
||||
)
|
||||
},
|
||||
|
||||
Loading…
Reference in New Issue
Block a user