feat(ui): single-column overview layout with end-of-content marker
- Overview: full-width stacked cards (basic info as two aligned rows, plan, user input with seeds in fixed-height right column, sessions) - Basic info labels fixed-width right-aligned for vertical alignment - Add "已显示全部内容" end marker and increase drawer tab bottom padding (16px → 24px) so content no longer looks cut off
This commit is contained in:
parent
d626dd8c3f
commit
61d8b8b4b8
@ -132,7 +132,7 @@ export default function ConfigSnapshots({ evalId, onBack }: ConfigSnapshotsProps
|
|||||||
|
|
||||||
if (selectedSnapshot) {
|
if (selectedSnapshot) {
|
||||||
return (
|
return (
|
||||||
<div style={{ height: '100%', overflowY: 'auto', padding: 16 }}>
|
<div style={{ height: '100%', overflowY: 'auto', padding: '16px 16px 24px' }}>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 16 }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 16 }}>
|
||||||
<Button icon={<ArrowLeftOutlined />} onClick={() => setSelectedSnapshot(null)}>返回</Button>
|
<Button icon={<ArrowLeftOutlined />} onClick={() => setSelectedSnapshot(null)}>返回</Button>
|
||||||
<span style={{ fontSize: 16, fontWeight: 600 }}>快照详情</span>
|
<span style={{ fontSize: 16, fontWeight: 600 }}>快照详情</span>
|
||||||
@ -182,7 +182,7 @@ export default function ConfigSnapshots({ evalId, onBack }: ConfigSnapshotsProps
|
|||||||
|
|
||||||
if (compareMode && comparison) {
|
if (compareMode && comparison) {
|
||||||
return (
|
return (
|
||||||
<div style={{ height: '100%', overflowY: 'auto', padding: 16 }}>
|
<div style={{ height: '100%', overflowY: 'auto', padding: '16px 16px 24px' }}>
|
||||||
<SectionHeader title="快照对比" onBack={() => setCompareMode(false)} />
|
<SectionHeader title="快照对比" onBack={() => setCompareMode(false)} />
|
||||||
|
|
||||||
<Card size="small" title="对比信息" style={{ marginBottom: 16 }}>
|
<Card size="small" title="对比信息" style={{ marginBottom: 16 }}>
|
||||||
@ -238,7 +238,7 @@ export default function ConfigSnapshots({ evalId, onBack }: ConfigSnapshotsProps
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ height: '100%', overflowY: 'auto', padding: 16 }}>
|
<div style={{ height: '100%', overflowY: 'auto', padding: '16px 16px 24px' }}>
|
||||||
<SectionHeader
|
<SectionHeader
|
||||||
title="配置历史"
|
title="配置历史"
|
||||||
onBack={onBack}
|
onBack={onBack}
|
||||||
|
|||||||
@ -115,7 +115,7 @@ export default function DecisionProcess({ evalId, onBack }: DecisionProcessProps
|
|||||||
]
|
]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ height: '100%', overflowY: 'auto', padding: 16 }}>
|
<div style={{ height: '100%', overflowY: 'auto', padding: '16px 16px 24px' }}>
|
||||||
<SectionHeader
|
<SectionHeader
|
||||||
title="决策过程"
|
title="决策过程"
|
||||||
onBack={onBack}
|
onBack={onBack}
|
||||||
|
|||||||
@ -1,26 +1,37 @@
|
|||||||
import {
|
import {
|
||||||
Alert, Card, Col, Descriptions, Empty, Progress, Row, Space, Spin, Table, Tag,
|
Alert, Card, Empty, Progress, Row, Col, 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'
|
||||||
import { formatDateTime, shortDateTime } from '../../utils/date'
|
import { formatDateTime, shortDateTime } from '../../utils/date'
|
||||||
import { EVAL_STATUS, SESSION_STATUS } from './status'
|
import { EVAL_STATUS, SESSION_STATUS } from './status'
|
||||||
|
|
||||||
const sectionCard: React.CSSProperties = { marginBottom: 16 }
|
|
||||||
|
|
||||||
function personaLabel(persona: Record<string, unknown>): string {
|
function personaLabel(persona: Record<string, unknown>): string {
|
||||||
if (typeof persona.name === 'string' && persona.name) return persona.name
|
if (typeof persona.name === 'string' && persona.name) return persona.name
|
||||||
if (typeof persona.background === 'string' && persona.background) return persona.background
|
if (typeof persona.background === 'string' && persona.background) return persona.background
|
||||||
return JSON.stringify(persona)
|
return JSON.stringify(persona)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function InfoRow({ items }: { items: [string, React.ReactNode][] }) {
|
||||||
|
return (
|
||||||
|
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '8px 32px', padding: '4px 0' }}>
|
||||||
|
{items.map(([k, v]) => (
|
||||||
|
<div key={k} style={{ display: 'flex', gap: 8, fontSize: 13, alignItems: 'center' }}>
|
||||||
|
<span style={{ color: colors.textSecondary, fontSize: 12, width: 60, textAlign: 'right', flexShrink: 0 }}>{k}</span>
|
||||||
|
<span>{v}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
function FieldBlock({ label, content }: { label: string; content: string }) {
|
function FieldBlock({ label, content }: { label: string; content: string }) {
|
||||||
return (
|
return (
|
||||||
<div style={{ height: '100%' }}>
|
<div>
|
||||||
<div style={{ fontSize: 12, color: colors.textSecondary, marginBottom: 4 }}>{label}</div>
|
<div style={{ fontSize: 12, color: colors.textSecondary, marginBottom: 4 }}>{label}</div>
|
||||||
<div style={{
|
<div style={{
|
||||||
fontSize: 13, lineHeight: 1.6, padding: '8px 12px', background: colors.bgSubtle,
|
fontSize: 13, lineHeight: 1.6, padding: '8px 12px', background: colors.bgSubtle,
|
||||||
borderRadius: 6, whiteSpace: 'pre-wrap', height: 'calc(100% - 22px)',
|
borderRadius: 6, whiteSpace: 'pre-wrap',
|
||||||
}}>
|
}}>
|
||||||
{content || '—'}
|
{content || '—'}
|
||||||
</div>
|
</div>
|
||||||
@ -33,18 +44,20 @@ function PlanView({ ev }: { ev: IntelligentEval }) {
|
|||||||
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
|
<Row gutter={12} style={{ marginBottom: 12 }}>
|
||||||
column={3}
|
{[
|
||||||
size="small"
|
['预估会话数', plan.estimated_sessions],
|
||||||
bordered
|
['单会话最大轮数', plan.budget?.max_turns_per_session ?? '—'],
|
||||||
style={{ marginBottom: 12 }}
|
['总轮数上限', plan.budget?.total_max_turns ?? '—'],
|
||||||
labelStyle={{ fontSize: 12 }}
|
].map(([k, v]) => (
|
||||||
contentStyle={{ fontSize: 13 }}
|
<Col span={8} key={k as string}>
|
||||||
>
|
<div style={{ background: colors.bgSubtle, borderRadius: 6, padding: '8px 12px' }}>
|
||||||
<Descriptions.Item label="预估会话数">{plan.estimated_sessions}</Descriptions.Item>
|
<div style={{ fontSize: 12, color: colors.textSecondary, marginBottom: 2 }}>{k}</div>
|
||||||
<Descriptions.Item label="单会话最大轮数">{plan.budget?.max_turns_per_session ?? '—'}</Descriptions.Item>
|
<div style={{ fontSize: 15, fontWeight: 600 }}>{v}</div>
|
||||||
<Descriptions.Item label="总轮数上限">{plan.budget?.total_max_turns ?? '—'}</Descriptions.Item>
|
</div>
|
||||||
</Descriptions>
|
</Col>
|
||||||
|
))}
|
||||||
|
</Row>
|
||||||
|
|
||||||
<div style={{ fontSize: 13, fontWeight: 500, marginBottom: 6 }}>评测维度</div>
|
<div style={{ fontSize: 13, fontWeight: 500, marginBottom: 6 }}>评测维度</div>
|
||||||
<Space size={6} wrap style={{ marginBottom: 12 }}>
|
<Space size={6} wrap style={{ marginBottom: 12 }}>
|
||||||
@ -97,7 +110,8 @@ interface EvalOverviewProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 评估详情"概览"子视图(纯展示):核心信息、用户输入、会话进度。
|
* 评估详情"概览"子视图(纯展示,单列布局):
|
||||||
|
* 基本信息 → 粗计划 → 用户输入(种子集独立列)→ 会话进度。
|
||||||
* 审批/取消等动作由评估列表页的详情抽屉头部统一管理。
|
* 审批/取消等动作由评估列表页的详情抽屉头部统一管理。
|
||||||
*/
|
*/
|
||||||
export default function EvalOverview({ ev, targetName }: EvalOverviewProps) {
|
export default function EvalOverview({ ev, targetName }: EvalOverviewProps) {
|
||||||
@ -109,79 +123,76 @@ export default function EvalOverview({ ev, targetName }: EvalOverviewProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
|
||||||
{/* 第一行:核心信息(基本信息 + 粗计划) */}
|
{/* 基本信息:两行横向描述,label 定宽右对齐 */}
|
||||||
<Row gutter={16}>
|
<Card size="small" title="基本信息">
|
||||||
<Col span={8}>
|
<InfoRow items={[
|
||||||
<Card size="small" title="基本信息" style={{ ...sectionCard, height: '100%' }}>
|
['评测对象', targetName],
|
||||||
<Descriptions column={1} size="small" labelStyle={{ fontSize: 12, color: colors.textSecondary }} contentStyle={{ fontSize: 13 }}>
|
['状态', <Tag key="s" color={meta.color}>{meta.label}</Tag>],
|
||||||
<Descriptions.Item label="评测对象">{targetName}</Descriptions.Item>
|
['时间窗口', `${ev.time_window_hours} 小时`],
|
||||||
<Descriptions.Item label="状态">
|
['会话进度', `${ev.completed_sessions}/${estimated}`],
|
||||||
<Tag color={meta.color}>{meta.label}</Tag>
|
]} />
|
||||||
</Descriptions.Item>
|
<div style={{ borderTop: `1px dashed ${colors.border}`, marginTop: 4, paddingTop: 8 }}>
|
||||||
<Descriptions.Item label="时间窗口">{ev.time_window_hours} 小时</Descriptions.Item>
|
<InfoRow items={[
|
||||||
<Descriptions.Item label="创建时间">{formatDateTime(ev.created_at)}</Descriptions.Item>
|
['创建时间', formatDateTime(ev.created_at)],
|
||||||
<Descriptions.Item label="开始时间">{formatDateTime(ev.started_at)}</Descriptions.Item>
|
['开始时间', formatDateTime(ev.started_at)],
|
||||||
<Descriptions.Item label="完成时间">{formatDateTime(ev.completed_at)}</Descriptions.Item>
|
['完成时间', formatDateTime(ev.completed_at)],
|
||||||
<Descriptions.Item label="会话进度">{ev.completed_sessions}/{estimated}</Descriptions.Item>
|
]} />
|
||||||
</Descriptions>
|
</div>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* 粗计划 */}
|
||||||
|
{ev.status === 'planning' && (
|
||||||
|
ev.plan_feedback ? (
|
||||||
|
<Alert
|
||||||
|
type="warning"
|
||||||
|
showIcon
|
||||||
|
message="计划已打回,OpenClaw 正在根据反馈重新规划"
|
||||||
|
description={ev.plan_feedback}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Card size="small">
|
||||||
|
<Spin size="small" />
|
||||||
|
<span style={{ marginLeft: 10, fontSize: 13, color: colors.textSecondary }}>
|
||||||
|
OpenClaw 正在规划,产出粗计划后会自动进入待审批…
|
||||||
|
</span>
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
)
|
||||||
|
)}
|
||||||
|
|
||||||
<Col span={16}>
|
{(ev.status === 'pending_approval' || ev.status === 'executing' || ev.status === 'completed') && (
|
||||||
{ev.status === 'planning' && (
|
<Card size="small" title="粗计划">
|
||||||
ev.plan_feedback ? (
|
<PlanView ev={ev} />
|
||||||
<Alert
|
</Card>
|
||||||
style={sectionCard}
|
)}
|
||||||
type="warning"
|
|
||||||
showIcon
|
|
||||||
message="计划已打回,OpenClaw 正在根据反馈重新规划"
|
|
||||||
description={ev.plan_feedback}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<Card size="small" style={sectionCard}>
|
|
||||||
<Spin size="small" />
|
|
||||||
<span style={{ marginLeft: 10, fontSize: 13, color: colors.textSecondary }}>
|
|
||||||
OpenClaw 正在规划,产出粗计划后会自动进入待审批…
|
|
||||||
</span>
|
|
||||||
</Card>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
|
|
||||||
{(ev.status === 'pending_approval' || ev.status === 'executing' || ev.status === 'completed') && (
|
{/* 用户输入:左列目标/意图/角色堆叠,右列种子集固定高度 */}
|
||||||
<Card size="small" title="粗计划" style={sectionCard}>
|
|
||||||
<PlanView ev={ev} />
|
|
||||||
</Card>
|
|
||||||
)}
|
|
||||||
</Col>
|
|
||||||
</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={9}>
|
||||||
<Col span={6}><FieldBlock label="考察意图" content={ev.intent} /></Col>
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
|
||||||
<Col span={4}><FieldBlock label="角色描述" content={ev.role_description} /></Col>
|
<FieldBlock label="评估目标" content={ev.goal} />
|
||||||
<Col span={8}>
|
<FieldBlock label="考察意图" content={ev.intent} />
|
||||||
|
<FieldBlock label="角色描述" content={ev.role_description} />
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
<Col span={15}>
|
||||||
<div style={{ fontSize: 12, color: colors.textSecondary, marginBottom: 4 }}>种子集</div>
|
<div style={{ fontSize: 12, color: colors.textSecondary, marginBottom: 4 }}>种子集</div>
|
||||||
{hasSeeds ? (
|
{hasSeeds ? (
|
||||||
<pre style={{
|
<pre style={{
|
||||||
margin: 0, fontSize: 12, lineHeight: 1.6, 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',
|
||||||
|
height: 340, overflowY: 'auto',
|
||||||
}}>
|
}}>
|
||||||
{JSON.stringify(ev.seeds, null, 2)}
|
{JSON.stringify(ev.seeds, null, 2)}
|
||||||
</pre>
|
</pre>
|
||||||
) : (
|
) : (
|
||||||
<div style={{
|
<div style={{ fontSize: 13, padding: '8px 12px', background: colors.bgSubtle, borderRadius: 6 }}>—</div>
|
||||||
fontSize: 13, padding: '8px 12px', background: colors.bgSubtle, borderRadius: 6,
|
|
||||||
}}>
|
|
||||||
—
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* 第三行:会话进度(全宽) */}
|
{/* 会话进度(全宽) */}
|
||||||
{showSessions && (
|
{showSessions && (
|
||||||
<Card
|
<Card
|
||||||
size="small"
|
size="small"
|
||||||
@ -227,6 +238,16 @@ export default function EvalOverview({ ev, targetName }: EvalOverviewProps) {
|
|||||||
)}
|
)}
|
||||||
</Card>
|
</Card>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* 收尾标识:明确内容已到底,避免"看起来没显示完" */}
|
||||||
|
<div style={{
|
||||||
|
display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 12,
|
||||||
|
fontSize: 12, color: colors.textMuted, padding: '4px 0 8px',
|
||||||
|
}}>
|
||||||
|
<span style={{ height: 1, width: 80, background: colors.border }} />
|
||||||
|
已显示全部内容
|
||||||
|
<span style={{ height: 1, width: 80, background: colors.border }} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -168,7 +168,7 @@ export default function EvalReport({ ev, onBack }: EvalReportProps) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ height: '100%', overflowY: 'auto', padding: 16 }}>
|
<div style={{ height: '100%', overflowY: 'auto', padding: '16px 16px 24px' }}>
|
||||||
<SectionHeader
|
<SectionHeader
|
||||||
title={`${ev.name} · 评估报告`}
|
title={`${ev.name} · 评估报告`}
|
||||||
onBack={onBack}
|
onBack={onBack}
|
||||||
|
|||||||
@ -157,7 +157,7 @@ export default function ExecutionProcess({ ev }: { ev: IntelligentEval }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ height: '100%', overflowY: 'auto', padding: 16 }}>
|
<div style={{ height: '100%', overflowY: 'auto', padding: '16px 16px 24px' }}>
|
||||||
<Card size="small" title="生命周期" style={{ marginBottom: 16 }}>
|
<Card size="small" title="生命周期" style={{ marginBottom: 16 }}>
|
||||||
<Steps
|
<Steps
|
||||||
current={stageIndex < 0 ? 0 : stageIndex}
|
current={stageIndex < 0 ? 0 : stageIndex}
|
||||||
|
|||||||
@ -197,7 +197,7 @@ export default function IntelligentEvalsPage() {
|
|||||||
key: 'overview',
|
key: 'overview',
|
||||||
label: '概览',
|
label: '概览',
|
||||||
children: (
|
children: (
|
||||||
<div style={{ height: '100%', overflowY: 'auto', padding: '0 16px 16px' }}>
|
<div style={{ height: '100%', overflowY: 'auto', padding: '0 16px 24px' }}>
|
||||||
<EvalOverview ev={selected} targetName={targetName(selected.target_id)} />
|
<EvalOverview ev={selected} targetName={targetName(selected.target_id)} />
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user