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:
sinohqb 2026-08-20 17:44:05 +08:00
parent d0487b54b4
commit 60981b634d
5 changed files with 73 additions and 37 deletions

View File

@ -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,

View File

@ -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>

View File

@ -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 () => {

View File

@ -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>
</>
)}

View File

@ -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>
)
},