feat(ui): restructure overview tab to 3-row layout
- Row 1: basic info (span=8) + plan (span=16) with virtual users in 2-column grid and time distribution as a table - Row 2: user input in one row (goal/intent/role/seeds), seeds area enlarged with no height cap for full JSON display - Row 3: session progress full-width with progress bar in header
This commit is contained in:
parent
63e96c2bf2
commit
d626dd8c3f
@ -1,5 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
Alert, Card, Col, Descriptions, Empty, Progress, Row, Space, Spin, Tag,
|
Alert, Card, Col, Descriptions, Empty, Progress, Row, Space, Spin, Table, Tag,
|
||||||
} from 'antd'
|
} from 'antd'
|
||||||
import type { IntelligentEval } from '../../api'
|
import type { IntelligentEval } from '../../api'
|
||||||
import { colors } from '../../tokens'
|
import { colors } from '../../tokens'
|
||||||
@ -14,12 +14,33 @@ function personaLabel(persona: Record<string, unknown>): string {
|
|||||||
return JSON.stringify(persona)
|
return JSON.stringify(persona)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function FieldBlock({ label, content }: { label: string; content: string }) {
|
||||||
|
return (
|
||||||
|
<div style={{ height: '100%' }}>
|
||||||
|
<div style={{ fontSize: 12, color: colors.textSecondary, marginBottom: 4 }}>{label}</div>
|
||||||
|
<div style={{
|
||||||
|
fontSize: 13, lineHeight: 1.6, padding: '8px 12px', background: colors.bgSubtle,
|
||||||
|
borderRadius: 6, whiteSpace: 'pre-wrap', height: 'calc(100% - 22px)',
|
||||||
|
}}>
|
||||||
|
{content || '—'}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
function PlanView({ ev }: { ev: IntelligentEval }) {
|
function PlanView({ ev }: { ev: IntelligentEval }) {
|
||||||
const plan = ev.plan
|
const plan = ev.plan
|
||||||
if (!plan) return <Empty description="暂无粗计划" image={Empty.PRESENTED_IMAGE_SIMPLE} />
|
if (!plan) return <Empty description="暂无粗计划" image={Empty.PRESENTED_IMAGE_SIMPLE} />
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Descriptions column={3} size="small" bordered style={{ marginBottom: 12 }} labelStyle={{ fontSize: 12 }} contentStyle={{ fontSize: 13 }}>
|
<Descriptions
|
||||||
|
column={3}
|
||||||
|
size="small"
|
||||||
|
bordered
|
||||||
|
style={{ marginBottom: 12 }}
|
||||||
|
labelStyle={{ fontSize: 12 }}
|
||||||
|
contentStyle={{ fontSize: 13 }}
|
||||||
|
>
|
||||||
<Descriptions.Item label="预估会话数">{plan.estimated_sessions}</Descriptions.Item>
|
<Descriptions.Item label="预估会话数">{plan.estimated_sessions}</Descriptions.Item>
|
||||||
<Descriptions.Item label="单会话最大轮数">{plan.budget?.max_turns_per_session ?? '—'}</Descriptions.Item>
|
<Descriptions.Item label="单会话最大轮数">{plan.budget?.max_turns_per_session ?? '—'}</Descriptions.Item>
|
||||||
<Descriptions.Item label="总轮数上限">{plan.budget?.total_max_turns ?? '—'}</Descriptions.Item>
|
<Descriptions.Item label="总轮数上限">{plan.budget?.total_max_turns ?? '—'}</Descriptions.Item>
|
||||||
@ -31,25 +52,38 @@ function PlanView({ ev }: { ev: IntelligentEval }) {
|
|||||||
</Space>
|
</Space>
|
||||||
|
|
||||||
<div style={{ fontSize: 13, fontWeight: 500, marginBottom: 6 }}>虚拟用户</div>
|
<div style={{ fontSize: 13, fontWeight: 500, marginBottom: 6 }}>虚拟用户</div>
|
||||||
<Space direction="vertical" size={4} style={{ width: '100%', marginBottom: 12 }}>
|
<Row gutter={[12, 6]} style={{ marginBottom: 12 }}>
|
||||||
{(plan.virtual_users ?? []).map((u, i) => (
|
{(plan.virtual_users ?? []).map((u, i) => (
|
||||||
<div key={i} style={{ fontSize: 13, color: colors.textSecondary }}>
|
<Col span={12} key={i}>
|
||||||
<b style={{ color: colors.text }}>{personaLabel(u.persona)}</b> · 目标:{u.goal}
|
<div style={{
|
||||||
</div>
|
fontSize: 13, color: colors.textSecondary, lineHeight: 1.5,
|
||||||
|
padding: '6px 10px', background: colors.bgSubtle, borderRadius: 6,
|
||||||
|
}}>
|
||||||
|
<b style={{ color: colors.text }}>{personaLabel(u.persona)}</b> · {u.goal}
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
))}
|
))}
|
||||||
</Space>
|
</Row>
|
||||||
|
|
||||||
<div style={{ fontSize: 13, fontWeight: 500, marginBottom: 6 }}>时间分布</div>
|
<div style={{ fontSize: 13, fontWeight: 500, marginBottom: 6 }}>时间分布</div>
|
||||||
<Space direction="vertical" size={4} style={{ width: '100%', marginBottom: 12 }}>
|
<Table
|
||||||
{(plan.time_distribution ?? []).map((t, i) => (
|
size="small"
|
||||||
<div key={i} style={{ fontSize: 13, color: colors.textSecondary }}>
|
rowKey={(t) => t.time_slot}
|
||||||
<Tag style={{ margin: 0, fontSize: 12 }}>{t.time_slot}</Tag> {t.sessions} 个会话 · {t.scenario}
|
dataSource={plan.time_distribution ?? []}
|
||||||
</div>
|
pagination={false}
|
||||||
))}
|
style={{ marginBottom: 12 }}
|
||||||
</Space>
|
columns={[
|
||||||
|
{
|
||||||
|
title: '时段', dataIndex: 'time_slot', width: 100,
|
||||||
|
render: (v: string) => <Tag style={{ margin: 0, fontSize: 12 }}>{v}</Tag>,
|
||||||
|
},
|
||||||
|
{ title: '会话数', dataIndex: 'sessions', width: 70 },
|
||||||
|
{ title: '场景说明', dataIndex: 'scenario', render: (v: string) => <span style={{ fontSize: 12, color: colors.textSecondary }}>{v}</span> },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
|
||||||
{plan.completion_criteria && (
|
{plan.completion_criteria && (
|
||||||
<div style={{ fontSize: 13, color: colors.textSecondary }}>
|
<div style={{ fontSize: 13, color: colors.textSecondary, lineHeight: 1.6 }}>
|
||||||
<b style={{ color: colors.text }}>完成标准:</b>{plan.completion_criteria}
|
<b style={{ color: colors.text }}>完成标准:</b>{plan.completion_criteria}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@ -63,19 +97,22 @@ interface EvalOverviewProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 评估详情"概览"子视图(纯展示):基本信息、用户输入、粗计划、会话进度。
|
* 评估详情"概览"子视图(纯展示):核心信息、用户输入、会话进度。
|
||||||
* 审批/取消等动作由评估列表页的详情抽屉头部统一管理。
|
* 审批/取消等动作由评估列表页的详情抽屉头部统一管理。
|
||||||
*/
|
*/
|
||||||
export default function EvalOverview({ ev, targetName }: EvalOverviewProps) {
|
export default function EvalOverview({ ev, targetName }: EvalOverviewProps) {
|
||||||
const meta = EVAL_STATUS[ev.status] ?? { label: ev.status, color: 'default' }
|
const meta = EVAL_STATUS[ev.status] ?? { label: ev.status, color: 'default' }
|
||||||
const showSessions = ev.status === 'executing' || ev.status === 'completed'
|
const showSessions = ev.status === 'executing' || ev.status === 'completed'
|
||||||
const sessions = showSessions ? ev.sessions ?? [] : []
|
const sessions = showSessions ? ev.sessions ?? [] : []
|
||||||
|
const estimated = ev.plan?.estimated_sessions ?? ev.session_count
|
||||||
|
const hasSeeds = Object.keys(ev.seeds ?? {}).length > 0
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
|
||||||
|
{/* 第一行:核心信息(基本信息 + 粗计划) */}
|
||||||
<Row gutter={16}>
|
<Row gutter={16}>
|
||||||
<Col span={8}>
|
<Col span={8}>
|
||||||
<Card size="small" title="基本信息" style={sectionCard}>
|
<Card size="small" title="基本信息" style={{ ...sectionCard, height: '100%' }}>
|
||||||
<Descriptions column={1} size="small" labelStyle={{ fontSize: 12, color: colors.textSecondary }} contentStyle={{ fontSize: 13 }}>
|
<Descriptions column={1} size="small" labelStyle={{ fontSize: 12, color: colors.textSecondary }} contentStyle={{ fontSize: 13 }}>
|
||||||
<Descriptions.Item label="评测对象">{targetName}</Descriptions.Item>
|
<Descriptions.Item label="评测对象">{targetName}</Descriptions.Item>
|
||||||
<Descriptions.Item label="状态">
|
<Descriptions.Item label="状态">
|
||||||
@ -85,7 +122,7 @@ export default function EvalOverview({ ev, targetName }: EvalOverviewProps) {
|
|||||||
<Descriptions.Item label="创建时间">{formatDateTime(ev.created_at)}</Descriptions.Item>
|
<Descriptions.Item label="创建时间">{formatDateTime(ev.created_at)}</Descriptions.Item>
|
||||||
<Descriptions.Item label="开始时间">{formatDateTime(ev.started_at)}</Descriptions.Item>
|
<Descriptions.Item label="开始时间">{formatDateTime(ev.started_at)}</Descriptions.Item>
|
||||||
<Descriptions.Item label="完成时间">{formatDateTime(ev.completed_at)}</Descriptions.Item>
|
<Descriptions.Item label="完成时间">{formatDateTime(ev.completed_at)}</Descriptions.Item>
|
||||||
<Descriptions.Item label="会话进度">{ev.completed_sessions}/{ev.plan?.estimated_sessions ?? ev.session_count}</Descriptions.Item>
|
<Descriptions.Item label="会话进度">{ev.completed_sessions}/{estimated}</Descriptions.Item>
|
||||||
</Descriptions>
|
</Descriptions>
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
@ -115,103 +152,81 @@ export default function EvalOverview({ ev, targetName }: EvalOverviewProps) {
|
|||||||
<PlanView ev={ev} />
|
<PlanView ev={ev} />
|
||||||
</Card>
|
</Card>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{showSessions && (
|
|
||||||
<Card
|
|
||||||
size="small"
|
|
||||||
title={`会话进度(${ev.completed_sessions}/${ev.plan?.estimated_sessions ?? ev.session_count})`}
|
|
||||||
style={sectionCard}
|
|
||||||
extra={(
|
|
||||||
<Progress
|
|
||||||
percent={(ev.plan?.estimated_sessions ?? ev.session_count) ? Math.round((ev.completed_sessions / (ev.plan?.estimated_sessions ?? ev.session_count)) * 100) : 0}
|
|
||||||
size="small"
|
|
||||||
style={{ width: 140 }}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{sessions.length === 0 && (
|
|
||||||
<div style={{ fontSize: 13, color: colors.textSecondary }}>
|
|
||||||
等待 OpenClaw 按粗计划的时间分布创建会话…
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{sessions.map((s) => {
|
|
||||||
const sMeta = SESSION_STATUS[s.status]
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={s.id}
|
|
||||||
style={{
|
|
||||||
display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap',
|
|
||||||
padding: '8px 0', borderBottom: `1px solid ${colors.border}`,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<b style={{ fontSize: 13, color: colors.text }}>{personaLabel(s.persona)}</b>
|
|
||||||
<Tag color={sMeta.color} style={{ margin: 0, fontSize: 12 }}>{sMeta.label}</Tag>
|
|
||||||
{s.dimension && <Tag style={{ margin: 0, fontSize: 12 }}>{s.dimension}</Tag>}
|
|
||||||
<span style={{ fontSize: 13, color: colors.textSecondary, flex: 1, minWidth: 120 }}>{s.goal}</span>
|
|
||||||
<span style={{ fontSize: 12, color: colors.textMuted }}>{s.turn_count} 轮</span>
|
|
||||||
{s.created_at && (
|
|
||||||
<span style={{ fontSize: 12, color: colors.textMuted }}>{shortDateTime(s.created_at)}</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
{ev.status === 'executing' && (
|
|
||||||
<div style={{ fontSize: 12, color: colors.textMuted, marginTop: 8 }}>
|
|
||||||
会话由 OpenClaw 按粗计划的时间分布自唤醒创建,本页每 5 秒自动刷新
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Card>
|
|
||||||
)}
|
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
|
{/* 第二行:用户输入(种子集独立放大区) */}
|
||||||
<Card size="small" title="用户输入">
|
<Card size="small" title="用户输入">
|
||||||
<Row gutter={16}>
|
<Row gutter={16}>
|
||||||
|
<Col span={6}><FieldBlock label="评估目标" content={ev.goal} /></Col>
|
||||||
|
<Col span={6}><FieldBlock label="考察意图" content={ev.intent} /></Col>
|
||||||
|
<Col span={4}><FieldBlock label="角色描述" content={ev.role_description} /></Col>
|
||||||
<Col span={8}>
|
<Col span={8}>
|
||||||
<div style={{ marginBottom: 12 }}>
|
|
||||||
<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 style={{ marginBottom: 12 }}>
|
|
||||||
<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>
|
|
||||||
</Col>
|
|
||||||
<Col span={16}>
|
|
||||||
<div style={{ fontSize: 12, color: colors.textSecondary, marginBottom: 4 }}>种子集</div>
|
<div style={{ fontSize: 12, color: colors.textSecondary, marginBottom: 4 }}>种子集</div>
|
||||||
{Object.keys(ev.seeds ?? {}).length === 0 ? (
|
{hasSeeds ? (
|
||||||
<div style={{ fontSize: 13, padding: '8px 12px', background: colors.bgSubtle, borderRadius: 6 }}>—</div>
|
|
||||||
) : (
|
|
||||||
<pre style={{
|
<pre style={{
|
||||||
margin: 0, fontSize: 12, background: colors.bgSubtle,
|
margin: 0, fontSize: 12, lineHeight: 1.6, background: colors.bgSubtle,
|
||||||
padding: '12px 16px', borderRadius: 6, overflowX: 'auto',
|
padding: '12px 16px', borderRadius: 6, overflowX: 'auto',
|
||||||
maxHeight: 300, overflowY: 'auto', lineHeight: 1.6,
|
|
||||||
}}>
|
}}>
|
||||||
{JSON.stringify(ev.seeds, null, 2)}
|
{JSON.stringify(ev.seeds, null, 2)}
|
||||||
</pre>
|
</pre>
|
||||||
|
) : (
|
||||||
|
<div style={{
|
||||||
|
fontSize: 13, padding: '8px 12px', background: colors.bgSubtle, borderRadius: 6,
|
||||||
|
}}>
|
||||||
|
—
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
{/* 第三行:会话进度(全宽) */}
|
||||||
|
{showSessions && (
|
||||||
|
<Card
|
||||||
|
size="small"
|
||||||
|
title={`会话进度(${ev.completed_sessions}/${estimated})`}
|
||||||
|
extra={(
|
||||||
|
<Progress
|
||||||
|
percent={estimated ? Math.round((ev.completed_sessions / estimated) * 100) : 0}
|
||||||
|
size="small"
|
||||||
|
style={{ width: 160 }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{sessions.length === 0 && (
|
||||||
|
<div style={{ fontSize: 13, color: colors.textSecondary }}>
|
||||||
|
等待 OpenClaw 按粗计划的时间分布创建会话…
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{sessions.map((s) => {
|
||||||
|
const sMeta = SESSION_STATUS[s.status]
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={s.id}
|
||||||
|
style={{
|
||||||
|
display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap',
|
||||||
|
padding: '8px 0', borderBottom: `1px solid ${colors.border}`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<b style={{ fontSize: 13, color: colors.text, minWidth: 80 }}>{personaLabel(s.persona)}</b>
|
||||||
|
<Tag color={sMeta.color} style={{ margin: 0, fontSize: 12 }}>{sMeta.label}</Tag>
|
||||||
|
{s.dimension && <Tag style={{ margin: 0, fontSize: 12 }}>{s.dimension}</Tag>}
|
||||||
|
<span style={{ fontSize: 13, color: colors.textSecondary, flex: 1, minWidth: 120 }}>{s.goal}</span>
|
||||||
|
<span style={{ fontSize: 12, color: colors.textMuted }}>{s.turn_count} 轮</span>
|
||||||
|
{s.created_at && (
|
||||||
|
<span style={{ fontSize: 12, color: colors.textMuted }}>{shortDateTime(s.created_at)}</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
{ev.status === 'executing' && (
|
||||||
|
<div style={{ fontSize: 12, color: colors.textMuted, marginTop: 8 }}>
|
||||||
|
会话由 OpenClaw 按粗计划的时间分布自唤醒创建,本页每 5 秒自动刷新
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user