fix(web): render evidence card before quality summary
All checks were successful
CD Pipeline / tests (push) Successful in 5m57s
Code Review / ai-code-review (push) Successful in 11s
CD Pipeline / build-and-deploy (push) Successful in 3m57s
CD Pipeline / post-deploy-checks (push) Successful in 1m46s

This commit is contained in:
Your Name
2026-05-24 13:02:44 +08:00
parent 12c39a17a8
commit 54f227c597
3 changed files with 63 additions and 25 deletions

View File

@@ -237,6 +237,7 @@
"title": "AI Automation Evidence",
"claimReady": "Loop claim ready",
"claimBlocked": "Gaps remain",
"claimChecking": "Quality pending",
"loading": "Loading AI automation evidence...",
"empty": "No AI automation evidence is available yet.",
"missingApiBase": "NEXT_PUBLIC_API_URL is not set",
@@ -250,8 +251,10 @@
"mcpDetail": "{success} success / {failed} failed, latest {server}",
"autoRepair": "Auto repair",
"qualityDetail": "Average {score}, red {red}",
"qualityPending": "Quality summary is still calculating; other evidence is already shown",
"humanGap": "Human gap",
"humanGapDetail": "{gate} missing {count}",
"humanGapClear": "Quality summary has no top gap",
"modelRoute": "Model route",
"routeDetail": "{model}; current {selected}; {primary}={primaryStatus}; fallback {fallback}",
"routeReasonSeparator": "; ",

View File

@@ -238,6 +238,7 @@
"title": "AI 自動化證據鏈",
"claimReady": "可宣稱閉環",
"claimBlocked": "仍有缺口",
"claimChecking": "品質計算中",
"loading": "讀取 AI 自動化證據中...",
"empty": "尚無可呈現的 AI 自動化證據。",
"missingApiBase": "NEXT_PUBLIC_API_URL 未設定",
@@ -251,8 +252,10 @@
"mcpDetail": "成功 {success} / 失敗 {failed},最新 {server}",
"autoRepair": "自動修復",
"qualityDetail": "平均 {score},紅燈 {red}",
"qualityPending": "品質摘要計算中,其他證據已先顯示",
"humanGap": "人工缺口",
"humanGapDetail": "{gate} 缺 {count} 筆",
"humanGapClear": "品質摘要未列出主要缺口",
"modelRoute": "模型路由",
"routeDetail": "{model};目前 {selected}{primary}={primaryStatus};備援 {fallback}",
"routeReasonSeparator": "",

View File

@@ -248,30 +248,39 @@ export function AutomationEvidenceCard() {
async function load() {
try {
setError(null)
const [
quality,
coverage,
recurrence,
runs,
route,
] = await Promise.all([
fetchJson<AutomationQualitySummary>('/api/v1/platform/truth-chain/quality/summary?project_id=awoooi&hours=24&limit=200', controller.signal),
const [coverage, recurrence, runs, route] = await Promise.all([
fetchJson<DossierCoverageResponse>('/api/v1/platform/events/dossier/coverage?project_id=awoooi&limit=100', controller.signal),
fetchJson<EventRecurrenceResponse>('/api/v1/platform/events/dossier/recurrence?project_id=awoooi&limit=100', controller.signal),
fetchJson<RunsResponse>('/api/v1/platform/runs/list?project_id=awoooi&per_page=25', controller.signal),
fetchJson<AiRouteStatusResponse>('/api/v1/platform/ai-route-status?workload_type=deep_rca', controller.signal),
])
setSnapshot({
quality,
setSnapshot((current) => ({
quality: current?.quality ?? null,
coverage,
recurrence,
runs: Array.isArray(runs?.runs) ? runs.runs : Array.isArray(runs?.items) ? runs.items : [],
route,
})
}))
setLoading(false)
const quality = await fetchJson<AutomationQualitySummary>(
'/api/v1/platform/truth-chain/quality/summary?project_id=awoooi&hours=24&limit=200',
controller.signal
)
if (!controller.signal.aborted) {
setSnapshot((current) => ({
quality: quality ?? current?.quality ?? null,
coverage: current?.coverage ?? coverage,
recurrence: current?.recurrence ?? recurrence,
runs: current?.runs ?? (Array.isArray(runs?.runs) ? runs.runs : Array.isArray(runs?.items) ? runs.items : []),
route: current?.route ?? route,
}))
}
} catch (err) {
if (!controller.signal.aborted) {
setError(err instanceof Error ? err.message : t('loadFailed'))
setLoading(false)
}
} finally {
if (!controller.signal.aborted) setLoading(false)
@@ -304,6 +313,7 @@ export function AutomationEvidenceCard() {
)
const topGate = quality?.gate_failures?.[0]
const qualityLoaded = Boolean(quality)
const claimReady = Boolean(quality?.production_claim?.can_claim_full_auto_repair)
const route = snapshot?.route ?? null
const primaryProvider = route?.policy_order?.[0]?.provider_name ?? null
@@ -332,6 +342,7 @@ export function AutomationEvidenceCard() {
recurrence,
runEvidence,
topGate,
qualityLoaded,
claimReady,
selectedProvider,
fallback,
@@ -340,7 +351,13 @@ export function AutomationEvidenceCard() {
}
}, [snapshot, t])
const hasData = Boolean(snapshot?.quality || snapshot?.coverage || snapshot?.recurrence)
const hasData = Boolean(
snapshot?.quality ||
snapshot?.coverage ||
snapshot?.recurrence ||
snapshot?.route ||
(snapshot?.runs?.length ?? 0) > 0
)
return (
<div style={{
@@ -364,10 +381,21 @@ export function AutomationEvidenceCard() {
alignItems: 'center',
gap: 8,
}}>
<div style={{ width: 6, height: 6, borderRadius: '50%', background: derived.claimReady ? '#22C55E' : '#F59E0B', flexShrink: 0 }} />
<div style={{
width: 6,
height: 6,
borderRadius: '50%',
background: !derived.qualityLoaded ? '#9CA3AF' : derived.claimReady ? '#22C55E' : '#F59E0B',
flexShrink: 0,
}} />
{t('title')}
<span style={{ marginLeft: 'auto', fontSize: 10, color: derived.claimReady ? '#17602a' : '#8a5a08', fontWeight: 700 }}>
{derived.claimReady ? t('claimReady') : t('claimBlocked')}
<span style={{
marginLeft: 'auto',
fontSize: 10,
color: !derived.qualityLoaded ? '#77736a' : derived.claimReady ? '#17602a' : '#8a5a08',
fontWeight: 700,
}}>
{!derived.qualityLoaded ? t('claimChecking') : derived.claimReady ? t('claimReady') : t('claimBlocked')}
</span>
</div>
@@ -413,21 +441,25 @@ export function AutomationEvidenceCard() {
/>
<EvidenceMetric
label={t('autoRepair')}
value={`${derived.quality?.verified_auto_repair_total ?? 0}/${derived.quality?.evaluated_total ?? 0}`}
detail={t('qualityDetail', {
score: (derived.quality?.average_score ?? 0).toFixed(1),
red: derived.quality?.score_buckets?.red ?? 0,
})}
tone={derived.claimReady ? 'good' : 'warn'}
value={derived.quality ? `${derived.quality.verified_auto_repair_total}/${derived.quality.evaluated_total}` : '--'}
detail={derived.quality
? t('qualityDetail', {
score: derived.quality.average_score.toFixed(1),
red: derived.quality.score_buckets?.red ?? 0,
})
: t('qualityPending')}
tone={!derived.qualityLoaded ? 'neutral' : derived.claimReady ? 'good' : 'warn'}
icon={Activity}
/>
<EvidenceMetric
label={t('humanGap')}
value={derived.recurrence?.automation_gap_group_total ?? 0}
detail={t('humanGapDetail', {
gate: tQuality(gateLabelKey(derived.topGate?.gate) as never),
count: derived.topGate?.total ?? 0,
})}
detail={derived.topGate
? t('humanGapDetail', {
gate: tQuality(gateLabelKey(derived.topGate.gate) as never),
count: derived.topGate.total,
})
: derived.qualityLoaded ? t('humanGapClear') : t('qualityPending')}
tone={(derived.recurrence?.automation_gap_group_total ?? 0) > 0 ? 'warn' : 'good'}
icon={ShieldCheck}
/>