feat(intelligent-eval): align detail/report drawer layouts with other modules
详情抽屉改两列布局(左静态上下文/右动态流程),会话进度加 Progress; 报告抽屉顶部加统计概览行与维度得分柱状图,移除总结卡内重复的分数展示。
This commit is contained in:
parent
22cc437e2d
commit
01f451c155
@ -1,6 +1,6 @@
|
|||||||
import { useCallback, useEffect, useState } from 'react'
|
import { useCallback, useEffect, useState } from 'react'
|
||||||
import {
|
import {
|
||||||
Alert, Button, Card, Descriptions, Empty, Input, Modal, Popconfirm, Space, Spin, Tag, message,
|
Alert, Button, Card, Col, Descriptions, Empty, Input, Modal, Popconfirm, Progress, Row, Space, Spin, Tag, message,
|
||||||
} from 'antd'
|
} from 'antd'
|
||||||
import { FileTextOutlined, StopOutlined } from '@ant-design/icons'
|
import { FileTextOutlined, StopOutlined } from '@ant-design/icons'
|
||||||
import { intelligentEvalsApi, type IntelligentEval, type IntelligentEvalSession } from '../../api'
|
import { intelligentEvalsApi, type IntelligentEval, type IntelligentEvalSession } from '../../api'
|
||||||
@ -125,111 +125,128 @@ export default function EvalDetail({ ev, targetName, onOpenReport, onChanged }:
|
|||||||
</Space>
|
</Space>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Card size="small" title="基本信息" style={sectionCard}>
|
<Row gutter={16}>
|
||||||
<Descriptions column={3} size="small">
|
<Col span={9}>
|
||||||
<Descriptions.Item label="评测对象">{targetName}</Descriptions.Item>
|
<Card size="small" title="基本信息" style={sectionCard}>
|
||||||
<Descriptions.Item label="时间窗口">{ev.time_window_hours} 小时</Descriptions.Item>
|
<Descriptions column={1} size="small">
|
||||||
<Descriptions.Item label="创建时间">{formatDateTime(ev.created_at)}</Descriptions.Item>
|
<Descriptions.Item label="评测对象">{targetName}</Descriptions.Item>
|
||||||
<Descriptions.Item label="开始时间">{formatDateTime(ev.started_at)}</Descriptions.Item>
|
<Descriptions.Item label="时间窗口">{ev.time_window_hours} 小时</Descriptions.Item>
|
||||||
<Descriptions.Item label="完成时间">{formatDateTime(ev.completed_at)}</Descriptions.Item>
|
<Descriptions.Item label="创建时间">{formatDateTime(ev.created_at)}</Descriptions.Item>
|
||||||
<Descriptions.Item label="会话进度">{ev.completed_sessions}/{ev.session_count}</Descriptions.Item>
|
<Descriptions.Item label="开始时间">{formatDateTime(ev.started_at)}</Descriptions.Item>
|
||||||
</Descriptions>
|
<Descriptions.Item label="完成时间">{formatDateTime(ev.completed_at)}</Descriptions.Item>
|
||||||
</Card>
|
<Descriptions.Item label="会话进度">{ev.completed_sessions}/{ev.session_count}</Descriptions.Item>
|
||||||
|
</Descriptions>
|
||||||
<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
|
|
||||||
? '—'
|
|
||||||
: (
|
|
||||||
<pre style={{
|
|
||||||
margin: 0, fontSize: 12, background: colors.bgSubtle,
|
|
||||||
padding: 8, borderRadius: 6, overflowX: 'auto',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{JSON.stringify(ev.seeds, null, 2)}
|
|
||||||
</pre>
|
|
||||||
)}
|
|
||||||
</Descriptions.Item>
|
|
||||||
</Descriptions>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
{ev.status === 'planning' && (
|
|
||||||
ev.plan_feedback ? (
|
|
||||||
<Alert
|
|
||||||
style={sectionCard}
|
|
||||||
type="warning"
|
|
||||||
showIcon
|
|
||||||
message="计划已打回,OpenClaw 正在根据反馈重新规划"
|
|
||||||
description={ev.plan_feedback}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<Card size="small" style={sectionCard}>
|
|
||||||
<Spin size="small" />
|
|
||||||
<span style={{ marginLeft: 10, color: colors.textSecondary }}>
|
|
||||||
OpenClaw 正在规划,产出粗计划后会自动进入待审批…
|
|
||||||
</span>
|
|
||||||
</Card>
|
</Card>
|
||||||
)
|
|
||||||
)}
|
|
||||||
|
|
||||||
{(ev.status === 'pending_approval' || ev.status === 'executing' || ev.status === 'completed') && (
|
<Card size="small" title="用户输入" style={sectionCard}>
|
||||||
<Card
|
<Descriptions column={1} size="small">
|
||||||
size="small"
|
<Descriptions.Item label="评估目标">{ev.goal || '—'}</Descriptions.Item>
|
||||||
title="粗计划"
|
<Descriptions.Item label="考察意图">{ev.intent || '—'}</Descriptions.Item>
|
||||||
style={sectionCard}
|
<Descriptions.Item label="角色描述">
|
||||||
extra={ev.status === 'pending_approval' && (
|
<span style={{ whiteSpace: 'pre-wrap' }}>{ev.role_description || '—'}</span>
|
||||||
<Space>
|
</Descriptions.Item>
|
||||||
<Button size="small" danger onClick={() => setRejectOpen(true)}>打回</Button>
|
<Descriptions.Item label="种子集">
|
||||||
<Button size="small" type="primary" loading={busy} onClick={approve}>批准并执行</Button>
|
{Object.keys(ev.seeds ?? {}).length === 0
|
||||||
</Space>
|
? '—'
|
||||||
)}
|
: (
|
||||||
>
|
<pre style={{
|
||||||
<PlanView ev={ev} />
|
margin: 0, fontSize: 12, background: colors.bgSubtle,
|
||||||
</Card>
|
padding: 8, borderRadius: 6, overflowX: 'auto',
|
||||||
)}
|
}}
|
||||||
|
>
|
||||||
|
{JSON.stringify(ev.seeds, null, 2)}
|
||||||
|
</pre>
|
||||||
|
)}
|
||||||
|
</Descriptions.Item>
|
||||||
|
</Descriptions>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
|
||||||
{showSessions && (
|
<Col span={15}>
|
||||||
<Card size="small" title={`会话进度(${ev.completed_sessions}/${ev.session_count})`} style={sectionCard}>
|
{ev.status === 'planning' && (
|
||||||
{sessions === null && <Spin size="small" />}
|
ev.plan_feedback ? (
|
||||||
{sessions !== null && sessions.length === 0 && (
|
<Alert
|
||||||
<div style={{ fontSize: 13, color: colors.textSecondary }}>
|
style={sectionCard}
|
||||||
等待 OpenClaw 按粗计划的时间分布创建会话…
|
type="warning"
|
||||||
</div>
|
showIcon
|
||||||
)}
|
message="计划已打回,OpenClaw 正在根据反馈重新规划"
|
||||||
{sessions?.map((s) => {
|
description={ev.plan_feedback}
|
||||||
const sMeta = SESSION_STATUS[s.status]
|
/>
|
||||||
return (
|
) : (
|
||||||
<div
|
<Card size="small" style={sectionCard}>
|
||||||
key={s.id}
|
<Spin size="small" />
|
||||||
style={{
|
<span style={{ marginLeft: 10, color: colors.textSecondary }}>
|
||||||
display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap',
|
OpenClaw 正在规划,产出粗计划后会自动进入待审批…
|
||||||
padding: '6px 0', borderBottom: `1px solid ${colors.border}`, fontSize: 13,
|
</span>
|
||||||
}}
|
</Card>
|
||||||
>
|
|
||||||
<b style={{ color: colors.text }}>{personaLabel(s.persona)}</b>
|
|
||||||
<Tag color={sMeta.color} style={{ margin: 0 }}>{sMeta.label}</Tag>
|
|
||||||
{s.dimension && <Tag style={{ margin: 0 }}>{s.dimension}</Tag>}
|
|
||||||
<span style={{ color: colors.textSecondary, flex: 1, minWidth: 120 }}>{s.goal}</span>
|
|
||||||
<span style={{ color: colors.textMuted }}>{s.turn_count} 轮</span>
|
|
||||||
{s.created_at && (
|
|
||||||
<span style={{ 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>
|
|
||||||
)}
|
{(ev.status === 'pending_approval' || ev.status === 'executing' || ev.status === 'completed') && (
|
||||||
|
<Card
|
||||||
|
size="small"
|
||||||
|
title="粗计划"
|
||||||
|
style={sectionCard}
|
||||||
|
extra={ev.status === 'pending_approval' && (
|
||||||
|
<Space>
|
||||||
|
<Button size="small" danger onClick={() => setRejectOpen(true)}>打回</Button>
|
||||||
|
<Button size="small" type="primary" loading={busy} onClick={approve}>批准并执行</Button>
|
||||||
|
</Space>
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<PlanView ev={ev} />
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{showSessions && (
|
||||||
|
<Card
|
||||||
|
size="small"
|
||||||
|
title={`会话进度(${ev.completed_sessions}/${ev.session_count})`}
|
||||||
|
style={sectionCard}
|
||||||
|
extra={(
|
||||||
|
<Progress
|
||||||
|
percent={ev.session_count ? Math.round((ev.completed_sessions / ev.session_count) * 100) : 0}
|
||||||
|
size="small"
|
||||||
|
style={{ width: 140 }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{sessions === null && <Spin size="small" />}
|
||||||
|
{sessions !== null && 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: '6px 0', borderBottom: `1px solid ${colors.border}`, fontSize: 13,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<b style={{ color: colors.text }}>{personaLabel(s.persona)}</b>
|
||||||
|
<Tag color={sMeta.color} style={{ margin: 0 }}>{sMeta.label}</Tag>
|
||||||
|
{s.dimension && <Tag style={{ margin: 0 }}>{s.dimension}</Tag>}
|
||||||
|
<span style={{ color: colors.textSecondary, flex: 1, minWidth: 120 }}>{s.goal}</span>
|
||||||
|
<span style={{ color: colors.textMuted }}>{s.turn_count} 轮</span>
|
||||||
|
{s.created_at && (
|
||||||
|
<span style={{ 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>
|
||||||
|
</Row>
|
||||||
|
|
||||||
<Modal
|
<Modal
|
||||||
title="打回计划"
|
title="打回计划"
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import {
|
import {
|
||||||
Button, Card, Collapse, Empty, Space, Spin, Statistic, Tag, message,
|
Button, Card, Col, Collapse, Empty, Row, Space, Spin, Statistic, Tag, message,
|
||||||
} from 'antd'
|
} from 'antd'
|
||||||
import { ArrowLeftOutlined, DownloadOutlined } from '@ant-design/icons'
|
import { ArrowLeftOutlined, DownloadOutlined } from '@ant-design/icons'
|
||||||
|
import { Bar } from '@ant-design/charts'
|
||||||
import ChatBubble from '../ChatBubble'
|
import ChatBubble from '../ChatBubble'
|
||||||
import {
|
import {
|
||||||
intelligentEvalsApi,
|
intelligentEvalsApi,
|
||||||
@ -143,6 +144,23 @@ export default function EvalReport({ ev, onBack }: EvalReportProps) {
|
|||||||
const findings = [...(report?.findings ?? [])]
|
const findings = [...(report?.findings ?? [])]
|
||||||
.sort((a, b) => severityOf(a.severity).order - severityOf(b.severity).order)
|
.sort((a, b) => severityOf(a.severity).order - severityOf(b.severity).order)
|
||||||
const scores = Object.entries(report?.scores ?? {})
|
const scores = Object.entries(report?.scores ?? {})
|
||||||
|
const overall = scores.length > 0
|
||||||
|
? (scores.reduce((sum, [, v]) => sum + Number(v), 0) / scores.length).toFixed(1)
|
||||||
|
: null
|
||||||
|
const sevCounts = findings.reduce((acc, f) => {
|
||||||
|
acc[f.severity] = (acc[f.severity] ?? 0) + 1
|
||||||
|
return acc
|
||||||
|
}, {} as Record<string, number>)
|
||||||
|
|
||||||
|
const scoreData = scores.map(([dimension, score]) => ({ dimension, score: Number(score) }))
|
||||||
|
const scoreConfig = {
|
||||||
|
data: scoreData,
|
||||||
|
xField: 'dimension',
|
||||||
|
yField: 'score',
|
||||||
|
coordinate: { transform: [{ type: 'transpose' }] },
|
||||||
|
height: Math.max(160, scoreData.length * 44),
|
||||||
|
color: colors.primary,
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ height: '100%', overflowY: 'auto', padding: '0 16px 16px' }}>
|
<div style={{ height: '100%', overflowY: 'auto', padding: '0 16px 16px' }}>
|
||||||
@ -167,17 +185,36 @@ export default function EvalReport({ ev, onBack }: EvalReportProps) {
|
|||||||
|
|
||||||
{report && (
|
{report && (
|
||||||
<>
|
<>
|
||||||
|
<Row gutter={[16, 16]} style={{ marginBottom: 16 }}>
|
||||||
|
<Col span={6}>
|
||||||
|
<Card size="small"><Statistic title="综合评分" value={overall ?? '—'} /></Card>
|
||||||
|
</Col>
|
||||||
|
<Col span={6}>
|
||||||
|
<Card size="small">
|
||||||
|
<Statistic title="问题发现" value={findings.length} />
|
||||||
|
<div style={{ fontSize: 12, color: colors.textMuted }}>
|
||||||
|
高 {sevCounts.high ?? 0} · 中 {sevCounts.medium ?? 0} · 低 {sevCounts.low ?? 0}
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
<Col span={6}>
|
||||||
|
<Card size="small"><Statistic title="亮点" value={report.highlights.length} /></Card>
|
||||||
|
</Col>
|
||||||
|
<Col span={6}>
|
||||||
|
<Card size="small"><Statistic title="会话数" value={ev.session_count} /></Card>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
{scoreData.length > 0 && (
|
||||||
|
<Card size="small" title="维度得分" style={sectionCard}>
|
||||||
|
<Bar {...scoreConfig} />
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
|
||||||
<Card size="small" title="总结" style={sectionCard}>
|
<Card size="small" title="总结" style={sectionCard}>
|
||||||
<div style={{ whiteSpace: 'pre-wrap', fontSize: 13, lineHeight: 1.8 }}>
|
<div style={{ whiteSpace: 'pre-wrap', fontSize: 13, lineHeight: 1.8 }}>
|
||||||
{report.summary}
|
{report.summary}
|
||||||
</div>
|
</div>
|
||||||
{scores.length > 0 && (
|
|
||||||
<div style={{ display: 'flex', gap: 32, marginTop: 12, flexWrap: 'wrap' }}>
|
|
||||||
{scores.map(([dimension, score]) => (
|
|
||||||
<Statistic key={dimension} title={dimension} value={score} />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Card size="small" title={`问题发现(${findings.length})`} style={sectionCard}>
|
<Card size="small" title={`问题发现(${findings.length})`} style={sectionCard}>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user