fix(frontend): report overall NaN + header card styling
All checks were successful
CI / test (push) Successful in 4m1s
All checks were successful
CI / test (push) Successful in 4m1s
- NaN root cause: report.scores is nested { overall, dimensions: {...} };
the old code treated every value as a number, so Number({object}) → NaN.
Now: dimScores from scores.dimensions, overall prefers scores.overall.
- Header summary cards restyled: icon + colored big number + responsive
(xs=12 md=6), colored score (>0.8 green / 0.6-0.8 amber / else red).
This commit is contained in:
parent
0c1e4578c9
commit
6309b6abca
@ -1,8 +1,11 @@
|
|||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import {
|
import {
|
||||||
Button, Card, Col, Collapse, Empty, Row, Space, Spin, Statistic, Tag, message,
|
Button, Card, Col, Collapse, Empty, Row, Space, Spin, Tag, message,
|
||||||
} from 'antd'
|
} from 'antd'
|
||||||
import { ArrowLeftOutlined, DownloadOutlined } from '@ant-design/icons'
|
import {
|
||||||
|
ArrowLeftOutlined, BulbOutlined, DownloadOutlined, MessageOutlined,
|
||||||
|
StarOutlined, WarningOutlined,
|
||||||
|
} from '@ant-design/icons'
|
||||||
import { Bar } from '@ant-design/charts'
|
import { Bar } from '@ant-design/charts'
|
||||||
import ChatBubble from '../ChatBubble'
|
import ChatBubble from '../ChatBubble'
|
||||||
import {
|
import {
|
||||||
@ -125,16 +128,28 @@ 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 ?? {})
|
// report.scores 是嵌套结构:{ overall: number, dimensions: { 维度: 分 } }
|
||||||
const overall = scores.length > 0
|
// 只把 dimensions 的值当作数字维度,overall 单独取,避免 Number(对象) 产生 NaN。
|
||||||
? (scores.reduce((sum, [, v]) => sum + Number(v), 0) / scores.length).toFixed(1)
|
const rawScores = report?.scores ?? {}
|
||||||
: null
|
const dimScores = Object.entries(rawScores.dimensions ?? {}).filter(
|
||||||
|
([, v]) => typeof v === 'number',
|
||||||
|
)
|
||||||
|
const rawOverall = rawScores.overall
|
||||||
|
const overall = typeof rawOverall === 'number'
|
||||||
|
? rawOverall.toFixed(1)
|
||||||
|
: dimScores.length > 0
|
||||||
|
? (dimScores.reduce((sum, [, v]) => sum + Number(v), 0) / dimScores.length).toFixed(1)
|
||||||
|
: null
|
||||||
|
const overallNum = overall != null ? Number(overall) : null
|
||||||
|
const overallColor = overallNum == null ? colors.textMuted
|
||||||
|
: overallNum >= 0.8 ? '#52c41a'
|
||||||
|
: overallNum >= 0.6 ? colors.warning : '#ff4d4f'
|
||||||
const sevCounts = findings.reduce((acc, f) => {
|
const sevCounts = findings.reduce((acc, f) => {
|
||||||
acc[f.severity] = (acc[f.severity] ?? 0) + 1
|
acc[f.severity] = (acc[f.severity] ?? 0) + 1
|
||||||
return acc
|
return acc
|
||||||
}, {} as Record<string, number>)
|
}, {} as Record<string, number>)
|
||||||
|
|
||||||
const scoreData = scores.map(([dimension, score]) => ({ dimension, score: Number(score) }))
|
const scoreData = dimScores.map(([dimension, score]) => ({ dimension, score: Number(score) }))
|
||||||
const scoreConfig = {
|
const scoreConfig = {
|
||||||
data: scoreData,
|
data: scoreData,
|
||||||
xField: 'dimension',
|
xField: 'dimension',
|
||||||
@ -144,6 +159,19 @@ export default function EvalReport({ ev, onBack }: EvalReportProps) {
|
|||||||
color: colors.primary,
|
color: colors.primary,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const summaryCard: React.CSSProperties = {
|
||||||
|
display: 'flex', alignItems: 'center', gap: 10,
|
||||||
|
border: `1px solid ${colors.border}`,
|
||||||
|
borderRadius: 10, padding: '14px 16px', background: colors.bgContainer,
|
||||||
|
boxShadow: '0 1px 3px rgba(0,0,0,0.06)',
|
||||||
|
height: '100%',
|
||||||
|
}
|
||||||
|
const summaryIcon = (color: string): React.CSSProperties => ({
|
||||||
|
width: 42, height: 42, borderRadius: 10, flexShrink: 0,
|
||||||
|
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||||
|
background: `${color}1a`, color, fontSize: 20,
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ height: '100%', overflowY: 'auto', padding: 16 }}>
|
<div style={{ height: '100%', overflowY: 'auto', padding: 16 }}>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 12 }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 12 }}>
|
||||||
@ -167,23 +195,53 @@ export default function EvalReport({ ev, onBack }: EvalReportProps) {
|
|||||||
|
|
||||||
{report && (
|
{report && (
|
||||||
<>
|
<>
|
||||||
<Row gutter={[16, 16]} style={{ marginBottom: 16 }}>
|
<Row gutter={[12, 12]} style={{ marginBottom: 16 }}>
|
||||||
<Col span={6}>
|
<Col xs={12} md={6}>
|
||||||
<Card size="small"><Statistic title="综合评分" value={overall ?? '—'} /></Card>
|
<div style={summaryCard}>
|
||||||
</Col>
|
<div style={summaryIcon(overallColor)}><StarOutlined /></div>
|
||||||
<Col span={6}>
|
<div>
|
||||||
<Card size="small">
|
<div style={{ fontSize: 12, color: colors.textMuted }}>综合评分</div>
|
||||||
<Statistic title="问题发现" value={findings.length} />
|
<div style={{ fontSize: 26, fontWeight: 700, color: overallColor, lineHeight: 1.2 }}>
|
||||||
<div style={{ fontSize: 12, color: colors.textMuted }}>
|
{overall ?? '—'}
|
||||||
高 {sevCounts.high ?? 0} · 中 {sevCounts.medium ?? 0} · 低 {sevCounts.low ?? 0}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</div>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={6}>
|
<Col xs={12} md={6}>
|
||||||
<Card size="small"><Statistic title="亮点" value={report.highlights.length} /></Card>
|
<div style={summaryCard}>
|
||||||
|
<div style={summaryIcon('#ff4d4f')}><WarningOutlined /></div>
|
||||||
|
<div>
|
||||||
|
<div style={{ fontSize: 12, color: colors.textMuted }}>问题发现</div>
|
||||||
|
<div style={{ fontSize: 26, fontWeight: 700, color: colors.text, lineHeight: 1.2 }}>
|
||||||
|
{findings.length}
|
||||||
|
</div>
|
||||||
|
<div style={{ fontSize: 12, color: colors.textMuted }}>
|
||||||
|
高 {sevCounts.high ?? 0} · 中 {sevCounts.medium ?? 0} · 低 {sevCounts.low ?? 0}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={6}>
|
<Col xs={12} md={6}>
|
||||||
<Card size="small"><Statistic title="会话数" value={ev.session_count} /></Card>
|
<div style={summaryCard}>
|
||||||
|
<div style={summaryIcon(colors.warning)}><BulbOutlined /></div>
|
||||||
|
<div>
|
||||||
|
<div style={{ fontSize: 12, color: colors.textMuted }}>亮点</div>
|
||||||
|
<div style={{ fontSize: 26, fontWeight: 700, color: colors.text, lineHeight: 1.2 }}>
|
||||||
|
{report.highlights.length}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
<Col xs={12} md={6}>
|
||||||
|
<div style={summaryCard}>
|
||||||
|
<div style={summaryIcon(colors.primary)}><MessageOutlined /></div>
|
||||||
|
<div>
|
||||||
|
<div style={{ fontSize: 12, color: colors.textMuted }}>会话数</div>
|
||||||
|
<div style={{ fontSize: 26, fontWeight: 700, color: colors.text, lineHeight: 1.2 }}>
|
||||||
|
{ev.session_count}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user