feat(governance): 新增 Agent 報告狀態總覽
This commit is contained in:
@@ -52,6 +52,7 @@ import {
|
||||
type AiAgentOwnerApprovedResultCaptureDryRunSnapshot,
|
||||
type AiAgentOwnerApprovedResultCaptureReadbackSnapshot,
|
||||
type AiAgentReportAutomationReviewSnapshot,
|
||||
type AiAgentReportStatusBoardSnapshot,
|
||||
type AiAgentReportRuntimeDryRunSnapshot,
|
||||
type AiAgentReportRuntimeFixtureReadbackSnapshot,
|
||||
type AiAgentReportRuntimeReadinessSnapshot,
|
||||
@@ -421,6 +422,7 @@ export function AutomationInventoryTab() {
|
||||
const [postWriteVerifierPackage, setPostWriteVerifierPackage] = useState<AiAgentPostWriteVerifierPackageSnapshot | null>(null)
|
||||
const [runtimeVerifierEvidenceReview, setRuntimeVerifierEvidenceReview] = useState<AiAgentRuntimeVerifierEvidenceReviewSnapshot | null>(null)
|
||||
const [reportAutomationReview, setReportAutomationReview] = useState<AiAgentReportAutomationReviewSnapshot | null>(null)
|
||||
const [reportStatusBoard, setReportStatusBoard] = useState<AiAgentReportStatusBoardSnapshot | null>(null)
|
||||
const [reportRuntimeReadiness, setReportRuntimeReadiness] = useState<AiAgentReportRuntimeReadinessSnapshot | null>(null)
|
||||
const [reportRuntimeDryRun, setReportRuntimeDryRun] = useState<AiAgentReportRuntimeDryRunSnapshot | null>(null)
|
||||
const [reportRuntimeFixtureReadback, setReportRuntimeFixtureReadback] = useState<AiAgentReportRuntimeFixtureReadbackSnapshot | null>(null)
|
||||
@@ -465,6 +467,7 @@ export function AutomationInventoryTab() {
|
||||
apiClient.getAiAgentPostWriteVerifierPackage(),
|
||||
apiClient.getAiAgentRuntimeVerifierEvidenceReview(),
|
||||
apiClient.getAiAgentReportAutomationReview(),
|
||||
apiClient.getAiAgentReportStatusBoard(),
|
||||
apiClient.getAiAgentReportRuntimeReadiness(),
|
||||
apiClient.getAiAgentReportRuntimeDryRun(),
|
||||
apiClient.getAiAgentReportRuntimeFixtureReadback(),
|
||||
@@ -508,6 +511,7 @@ export function AutomationInventoryTab() {
|
||||
postWriteVerifierPackageResult,
|
||||
runtimeVerifierEvidenceReviewResult,
|
||||
reportAutomationReviewResult,
|
||||
reportStatusBoardResult,
|
||||
reportRuntimeReadinessResult,
|
||||
reportRuntimeDryRunResult,
|
||||
reportRuntimeFixtureReadbackResult,
|
||||
@@ -548,6 +552,7 @@ export function AutomationInventoryTab() {
|
||||
setPostWriteVerifierPackage(postWriteVerifierPackageResult.status === 'fulfilled' ? postWriteVerifierPackageResult.value : null)
|
||||
setRuntimeVerifierEvidenceReview(runtimeVerifierEvidenceReviewResult.status === 'fulfilled' ? runtimeVerifierEvidenceReviewResult.value : null)
|
||||
setReportAutomationReview(reportAutomationReviewResult.status === 'fulfilled' ? reportAutomationReviewResult.value : null)
|
||||
setReportStatusBoard(reportStatusBoardResult.status === 'fulfilled' ? reportStatusBoardResult.value : null)
|
||||
setReportRuntimeReadiness(reportRuntimeReadinessResult.status === 'fulfilled' ? reportRuntimeReadinessResult.value : null)
|
||||
setReportRuntimeDryRun(reportRuntimeDryRunResult.status === 'fulfilled' ? reportRuntimeDryRunResult.value : null)
|
||||
setReportRuntimeFixtureReadback(reportRuntimeFixtureReadbackResult.status === 'fulfilled' ? reportRuntimeFixtureReadbackResult.value : null)
|
||||
@@ -586,6 +591,7 @@ export function AutomationInventoryTab() {
|
||||
postWriteVerifierPackageResult,
|
||||
runtimeVerifierEvidenceReviewResult,
|
||||
reportAutomationReviewResult,
|
||||
reportStatusBoardResult,
|
||||
reportRuntimeReadinessResult,
|
||||
reportRuntimeDryRunResult,
|
||||
reportRuntimeFixtureReadbackResult,
|
||||
@@ -989,6 +995,11 @@ export function AutomationInventoryTab() {
|
||||
return reportAutomationReview.report_charts.slice(0, 4)
|
||||
}, [reportAutomationReview])
|
||||
|
||||
const visibleReportStatusCharts = useMemo(() => {
|
||||
if (!reportStatusBoard) return []
|
||||
return reportStatusBoard.visible_charts.slice(0, 3)
|
||||
}, [reportStatusBoard])
|
||||
|
||||
const visibleReportRecommendations = useMemo(() => {
|
||||
if (!reportAutomationReview) return []
|
||||
const priority = { critical: 0, high: 0, medium: 1, low: 2 } as Record<string, number>
|
||||
@@ -1692,7 +1703,7 @@ export function AutomationInventoryTab() {
|
||||
)
|
||||
}
|
||||
|
||||
if (error || !snapshot || !backlog || !backupTargets || !backupReadiness || !backupPolicy || !offsiteEscrow || !giteaHealth || !observabilityMatrix || !providerRouteMatrix || !deploymentLayout || !proactiveOperations || !interactionLearningProof || !liveReadModelGate || !redisDryRunGate || !learningWritebackPackage || !telegramReceiptPackage || !ownerApprovedLearningDryRun || !runtimeWriteGateReview || !postWriteVerifierPackage || !runtimeVerifierEvidenceReview || !reportAutomationReview || !reportRuntimeReadiness || !reportRuntimeDryRun || !reportRuntimeFixtureReadback || !runtimeWorkerShadowGate || !operationPermissionModel || !candidateOperationDryRunEvidence || !taskResultAuditTrail || !matchedPlaybookLearningGap || !criticReviewerResultCapture || !ownerApprovedResultCaptureDryRun || !ownerApprovedResultCaptureReadback || !reportTruthActionabilityReview || !ownerDryRunPackage || !hostStatefulInventory || !serviceHealthGapMatrix || !serviceHealthNotificationPolicy) {
|
||||
if (error || !snapshot || !backlog || !backupTargets || !backupReadiness || !backupPolicy || !offsiteEscrow || !giteaHealth || !observabilityMatrix || !providerRouteMatrix || !deploymentLayout || !proactiveOperations || !interactionLearningProof || !liveReadModelGate || !redisDryRunGate || !learningWritebackPackage || !telegramReceiptPackage || !ownerApprovedLearningDryRun || !runtimeWriteGateReview || !postWriteVerifierPackage || !runtimeVerifierEvidenceReview || !reportAutomationReview || !reportStatusBoard || !reportRuntimeReadiness || !reportRuntimeDryRun || !reportRuntimeFixtureReadback || !runtimeWorkerShadowGate || !operationPermissionModel || !candidateOperationDryRunEvidence || !taskResultAuditTrail || !matchedPlaybookLearningGap || !criticReviewerResultCapture || !ownerApprovedResultCaptureDryRun || !ownerApprovedResultCaptureReadback || !reportTruthActionabilityReview || !ownerDryRunPackage || !hostStatefulInventory || !serviceHealthGapMatrix || !serviceHealthNotificationPolicy) {
|
||||
return (
|
||||
<div style={{ padding: 20 }}>
|
||||
<GlassCard variant="subtle" padding="lg">
|
||||
@@ -1842,6 +1853,15 @@ export function AutomationInventoryTab() {
|
||||
const reportAutoEnabled = reportAutomationReview.rollups.current_auto_execution_enabled_count
|
||||
const reportLiveDelivery = reportAutomationReview.rollups.live_report_delivery_count
|
||||
const reportLiveOptimization = reportAutomationReview.rollups.live_auto_optimization_count
|
||||
const reportStatusOverall = reportStatusBoard.program_status.overall_completion_percent
|
||||
const reportStatusCards = reportStatusBoard.rollups.report_card_count
|
||||
const reportStatusAgents = reportStatusBoard.rollups.agent_status_count
|
||||
const reportStatusCharts = reportStatusBoard.rollups.visible_chart_count
|
||||
const reportStatusWorkload = reportStatusBoard.rollups.workload_unit_total
|
||||
const reportStatusDone = reportStatusBoard.rollups.workload_done_total
|
||||
const reportStatusWaitingApproval = reportStatusBoard.rollups.workload_waiting_approval_total
|
||||
const reportStatusLiveDelivery = reportStatusBoard.rollups.live_delivery_count
|
||||
const reportStatusLiveOptimization = reportStatusBoard.rollups.live_auto_optimization_count
|
||||
const reportRuntimeOverall = reportRuntimeReadiness.program_status.overall_completion_percent
|
||||
const reportRuntimeLanes = reportRuntimeReadiness.rollups.runtime_lane_count
|
||||
const reportRuntimeReady = reportRuntimeReadiness.rollups.ready_contract_count
|
||||
@@ -2780,6 +2800,179 @@ export function AutomationInventoryTab() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ padding: 12, border: '0.5px solid #c7d2fe', borderRadius: 7, background: '#f8faff', display: 'flex', flexDirection: 'column', gap: 12, minWidth: 0 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 10, flexWrap: 'wrap' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 7, minWidth: 0 }}>
|
||||
<ShieldCheck size={14} style={{ color: '#4f46e5' }} />
|
||||
<span style={{ fontFamily: 'Syne, sans-serif', fontSize: 13, fontWeight: 700, color: '#141413' }}>
|
||||
{t('reportStatusBoard.title')}
|
||||
</span>
|
||||
</div>
|
||||
<Chip
|
||||
value={t('reportStatusBoard.source', {
|
||||
generated: formatDateTime(reportStatusBoard.generated_at),
|
||||
current: reportStatusBoard.program_status.current_task_id,
|
||||
next: reportStatusBoard.program_status.next_task_id,
|
||||
})}
|
||||
muted
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(128px, 1fr))', gap: 10 }} className="automation-inventory-live-read-kpi-grid">
|
||||
<MetricCard label={t('reportStatusBoard.metrics.overall')} value={`${reportStatusOverall}%`} tone="ok" icon={<Gauge size={16} />} />
|
||||
<MetricCard label={t('reportStatusBoard.metrics.reports')} value={reportStatusCards} tone="ok" icon={<FileText size={16} />} />
|
||||
<MetricCard label={t('reportStatusBoard.metrics.agents')} value={reportStatusAgents} tone="ok" icon={<Boxes size={16} />} />
|
||||
<MetricCard label={t('reportStatusBoard.metrics.charts')} value={reportStatusCharts} tone="ok" icon={<Layers3 size={16} />} />
|
||||
<MetricCard label={t('reportStatusBoard.metrics.workload')} value={reportStatusWorkload} tone="warn" icon={<Target size={16} />} />
|
||||
<MetricCard label={t('reportStatusBoard.metrics.done')} value={reportStatusDone} tone="ok" icon={<ShieldCheck size={16} />} />
|
||||
<MetricCard label={t('reportStatusBoard.metrics.waitingApproval')} value={reportStatusWaitingApproval} tone={reportStatusWaitingApproval > 0 ? 'danger' : 'ok'} icon={<Lock size={16} />} />
|
||||
<MetricCard label={t('reportStatusBoard.metrics.liveDelivery')} value={reportStatusLiveDelivery} tone={reportStatusLiveDelivery === 0 ? 'warn' : 'ok'} icon={<BellRing size={16} />} />
|
||||
<MetricCard label={t('reportStatusBoard.metrics.liveOptimization')} value={reportStatusLiveOptimization} tone={reportStatusLiveOptimization === 0 ? 'warn' : 'ok'} icon={<PackageCheck size={16} />} />
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'minmax(0, 1fr) minmax(0, 1fr)', gap: 12 }} className="automation-inventory-live-read-grid">
|
||||
<div style={{ padding: 11, border: '0.5px solid #dde3ff', borderRadius: 7, background: '#fff', display: 'flex', flexDirection: 'column', gap: 8, minWidth: 0 }}>
|
||||
<SmallLabel>{t('reportStatusBoard.truthTitle')}</SmallLabel>
|
||||
<span style={{ fontFamily: "'DM Mono', monospace", fontSize: 10, color: '#62636b', lineHeight: 1.5, overflowWrap: 'anywhere' }}>
|
||||
{reportStatusBoard.report_completion_truth.truth_note}
|
||||
</span>
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
|
||||
<Chip value={t('reportStatusBoard.flags.daily', { value: String(reportStatusBoard.report_completion_truth.daily_report_visible) })} />
|
||||
<Chip value={t('reportStatusBoard.flags.weekly', { value: String(reportStatusBoard.report_completion_truth.weekly_report_visible) })} />
|
||||
<Chip value={t('reportStatusBoard.flags.monthly', { value: String(reportStatusBoard.report_completion_truth.monthly_report_visible) })} />
|
||||
<Chip value={t('reportStatusBoard.flags.perAgent', { value: String(reportStatusBoard.report_completion_truth.per_agent_status_visible) })} />
|
||||
<Chip value={t('reportStatusBoard.flags.telegram', { value: String(reportStatusBoard.report_completion_truth.telegram_digest_draft_visible) })} muted />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ padding: 11, border: '0.5px solid #dde3ff', borderRadius: 7, background: '#fff', display: 'flex', flexDirection: 'column', gap: 8, minWidth: 0 }}>
|
||||
<SmallLabel>{t('reportStatusBoard.boundaryTitle')}</SmallLabel>
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
|
||||
<Chip value={t('reportStatusBoard.flags.scheduler', { value: String(reportStatusBoard.activation_boundaries.scheduler_enabled) })} muted />
|
||||
<Chip value={t('reportStatusBoard.flags.queue', { value: String(reportStatusBoard.activation_boundaries.gateway_queue_write_enabled) })} muted />
|
||||
<Chip value={t('reportStatusBoard.flags.send', { value: String(reportStatusBoard.activation_boundaries.telegram_send_enabled) })} muted />
|
||||
<Chip value={t('reportStatusBoard.flags.analysis', { value: String(reportStatusBoard.activation_boundaries.ai_analysis_run_enabled) })} muted />
|
||||
<Chip value={t('reportStatusBoard.flags.optimization', { value: String(reportStatusBoard.activation_boundaries.production_optimization_write_enabled) })} muted />
|
||||
<Chip value={t('reportStatusBoard.flags.highApproval', { value: String(reportStatusBoard.activation_boundaries.high_risk_requires_human_approval) })} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(210px, 1fr))', gap: 10 }} className="automation-inventory-live-read-card-grid">
|
||||
{reportStatusBoard.report_status_cards.map(card => (
|
||||
<div key={card.cadence_id} style={{ padding: 10, border: '0.5px solid #dde3ff', borderRadius: 7, background: '#fff', display: 'flex', flexDirection: 'column', gap: 8, minWidth: 0 }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', gap: 8, alignItems: 'center', minWidth: 0 }}>
|
||||
<span style={{ fontFamily: 'Syne, sans-serif', fontSize: 12, fontWeight: 700, color: '#141413', overflowWrap: 'anywhere' }}>
|
||||
{card.display_name}
|
||||
</span>
|
||||
<Chip value={redisDryRunValueLabel('agents', card.owner_agent)} muted />
|
||||
</div>
|
||||
<MiniBar
|
||||
label={t('reportStatusBoard.labels.completion')}
|
||||
value={card.completion_percent}
|
||||
detail={t('reportStatusBoard.labels.reportDetail', {
|
||||
sections: card.sections_count,
|
||||
charts: card.chart_count,
|
||||
work: card.work_units_total,
|
||||
live: card.live_delivery_count,
|
||||
})}
|
||||
tone="ok"
|
||||
/>
|
||||
<span style={{ fontFamily: "'DM Mono', monospace", fontSize: 10, color: '#62636b', lineHeight: 1.45, overflowWrap: 'anywhere' }}>
|
||||
{card.next_gate}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(220px, 1fr))', gap: 10 }} className="automation-inventory-live-read-card-grid">
|
||||
{reportStatusBoard.agent_status_reports.map(agent => {
|
||||
const progress = agent.work_units_total > 0 ? Math.round((agent.work_units_done / agent.work_units_total) * 100) : 0
|
||||
return (
|
||||
<div key={agent.agent_id} style={{ padding: 10, border: '0.5px solid #dde3ff', borderRadius: 7, background: '#fff', display: 'flex', flexDirection: 'column', gap: 8, minWidth: 0 }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', gap: 8, alignItems: 'center', minWidth: 0 }}>
|
||||
<span style={{ fontFamily: 'Syne, sans-serif', fontSize: 12, fontWeight: 700, color: '#141413', overflowWrap: 'anywhere' }}>
|
||||
{agent.display_name}
|
||||
</span>
|
||||
<Chip value={agent.current_state} muted />
|
||||
</div>
|
||||
<span style={{ fontFamily: "'DM Mono', monospace", fontSize: 10, color: '#4f5565', lineHeight: 1.45, overflowWrap: 'anywhere' }}>
|
||||
{agent.primary_role}
|
||||
</span>
|
||||
<MiniBar
|
||||
label={t('reportStatusBoard.labels.workDone')}
|
||||
value={progress}
|
||||
detail={t('reportStatusBoard.labels.agentDetail', {
|
||||
done: agent.work_units_done,
|
||||
total: agent.work_units_total,
|
||||
approval: agent.work_units_waiting_approval,
|
||||
live: agent.live_runtime_work_units_24h,
|
||||
})}
|
||||
tone={progress >= 85 ? 'ok' : 'warn'}
|
||||
/>
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
|
||||
<Chip value={t('reportStatusBoard.labels.sections', { count: agent.report_sections_owned })} muted />
|
||||
<Chip value={t('reportStatusBoard.labels.recommendations', { count: agent.analysis_recommendations_owned })} muted />
|
||||
<Chip value={agent.communication_state} muted />
|
||||
<Chip value={agent.learning_state} muted />
|
||||
<Chip value={agent.telegram_policy} muted />
|
||||
</div>
|
||||
<span style={{ fontFamily: "'DM Mono', monospace", fontSize: 10, color: '#62636b', lineHeight: 1.45, overflowWrap: 'anywhere' }}>
|
||||
{agent.status_note}
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(220px, 1fr))', gap: 10 }} className="automation-inventory-live-read-card-grid">
|
||||
{visibleReportStatusCharts.map(chart => {
|
||||
const maxValue = Math.max(...chart.series.map(item => item.value), 1)
|
||||
return (
|
||||
<div key={chart.chart_id} style={{ padding: 10, border: '0.5px solid #dde3ff', borderRadius: 7, background: '#fff', display: 'flex', flexDirection: 'column', gap: 8, minWidth: 0 }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', gap: 8, alignItems: 'center' }}>
|
||||
<span style={{ fontFamily: 'Syne, sans-serif', fontSize: 12, fontWeight: 700, color: '#141413' }}>
|
||||
{chart.display_name}
|
||||
</span>
|
||||
<Chip value={chart.chart_type} muted />
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 7 }}>
|
||||
{chart.series.map(item => (
|
||||
<div key={`${chart.chart_id}-${item.label}`} style={{ display: 'grid', gridTemplateColumns: '88px minmax(0, 1fr) 36px', gap: 8, alignItems: 'center', minWidth: 0 }}>
|
||||
<span style={{ fontFamily: "'DM Mono', monospace", fontSize: 10, color: '#605f59', overflowWrap: 'anywhere' }}>
|
||||
{item.label}
|
||||
</span>
|
||||
<div style={{ height: 7, borderRadius: 999, background: '#e7e3d8', overflow: 'hidden' }}>
|
||||
<div style={{ height: '100%', width: `${Math.max(4, Math.round((item.value / maxValue) * 100))}%`, borderRadius: 999, background: toneColor(item.tone) }} />
|
||||
</div>
|
||||
<span style={{ fontFamily: "'DM Mono', monospace", fontSize: 10, fontWeight: 700, color: toneColor(item.tone), textAlign: 'right' }}>
|
||||
{item.value}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(220px, 1fr))', gap: 10 }} className="automation-inventory-live-read-card-grid">
|
||||
{reportStatusBoard.operator_answer_cards.map(answer => (
|
||||
<div key={answer.answer_id} style={{ padding: 10, border: '0.5px solid #dde3ff', borderRadius: 7, background: '#fff', display: 'flex', flexDirection: 'column', gap: 7, minWidth: 0 }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', gap: 8, alignItems: 'center', minWidth: 0 }}>
|
||||
<span style={{ fontFamily: 'Syne, sans-serif', fontSize: 12, fontWeight: 700, color: '#141413', overflowWrap: 'anywhere' }}>
|
||||
{answer.question}
|
||||
</span>
|
||||
<Chip value={t(`reportStatusBoard.answerStatuses.${answer.status}` as never)} muted={answer.status !== 'complete'} />
|
||||
</div>
|
||||
<span style={{ fontFamily: "'DM Mono', monospace", fontSize: 10, color: '#62636b', lineHeight: 1.45, overflowWrap: 'anywhere' }}>
|
||||
{answer.answer}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ padding: 12, border: '0.5px solid #b9d0dd', borderRadius: 7, background: '#f8fcff', display: 'flex', flexDirection: 'column', gap: 12, minWidth: 0 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 10, flexWrap: 'wrap' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 7, minWidth: 0 }}>
|
||||
|
||||
Reference in New Issue
Block a user