feat(governance): 顯示 Telegram 報告實發批准包
This commit is contained in:
@@ -3337,6 +3337,8 @@
|
||||
"cadencesTitle": "報告節奏與 AI 閱讀後分析",
|
||||
"workloadTitle": "每個 Agent 工作狀態報告",
|
||||
"analysisTitle": "報告後 AI 解法與風險邊界",
|
||||
"deliveryTitle": "Telegram 報告實發批准包",
|
||||
"deliveryGuardTitle": "路由鎖 / 無發送回執保護",
|
||||
"metrics": {
|
||||
"reports": "報告完成",
|
||||
"charts": "圖表區塊",
|
||||
@@ -3344,6 +3346,20 @@
|
||||
"analysisPackets": "分析包",
|
||||
"lowMedium": "低中風險",
|
||||
"highRisk": "高風險"
|
||||
},
|
||||
"deliveryMetrics": {
|
||||
"packets": "批准包",
|
||||
"routes": "路由鎖",
|
||||
"redaction": "遮蔽檢查",
|
||||
"receipts": "Dry-run 回執",
|
||||
"liveSend": "實發次數"
|
||||
},
|
||||
"deliveryTruth": {
|
||||
"scheduler": "排程",
|
||||
"queue": "Gateway 佇列",
|
||||
"botApi": "Bot API",
|
||||
"receiptWrite": "回執寫入",
|
||||
"note": "真相註記"
|
||||
}
|
||||
},
|
||||
"metrics": {
|
||||
|
||||
@@ -3337,6 +3337,8 @@
|
||||
"cadencesTitle": "報告節奏與 AI 閱讀後分析",
|
||||
"workloadTitle": "每個 Agent 工作狀態報告",
|
||||
"analysisTitle": "報告後 AI 解法與風險邊界",
|
||||
"deliveryTitle": "Telegram 報告實發批准包",
|
||||
"deliveryGuardTitle": "路由鎖 / 無發送回執保護",
|
||||
"metrics": {
|
||||
"reports": "報告完成",
|
||||
"charts": "圖表區塊",
|
||||
@@ -3344,6 +3346,20 @@
|
||||
"analysisPackets": "分析包",
|
||||
"lowMedium": "低中風險",
|
||||
"highRisk": "高風險"
|
||||
},
|
||||
"deliveryMetrics": {
|
||||
"packets": "批准包",
|
||||
"routes": "路由鎖",
|
||||
"redaction": "遮蔽檢查",
|
||||
"receipts": "Dry-run 回執",
|
||||
"liveSend": "實發次數"
|
||||
},
|
||||
"deliveryTruth": {
|
||||
"scheduler": "排程",
|
||||
"queue": "Gateway 佇列",
|
||||
"botApi": "Bot API",
|
||||
"receiptWrite": "回執寫入",
|
||||
"note": "真相註記"
|
||||
}
|
||||
},
|
||||
"metrics": {
|
||||
|
||||
@@ -14,7 +14,13 @@ import { useTranslations } from 'next-intl'
|
||||
import { GlassCard } from '@/components/ui/glass-card'
|
||||
import { StatusOrb } from '@/components/ui/status-orb'
|
||||
import { AgentActivityConstellation } from '@/components/governance/agent-activity-constellation'
|
||||
import { apiClient, type AgentMarketGovernanceSnapshot, type AiTechnologyRadarReadback, type AiTechnologyReportCadenceReadback } from '@/lib/api-client'
|
||||
import {
|
||||
apiClient,
|
||||
type AgentMarketGovernanceSnapshot,
|
||||
type AiAgentReportLiveDeliveryApprovalPackageSnapshot,
|
||||
type AiTechnologyRadarReadback,
|
||||
type AiTechnologyReportCadenceReadback,
|
||||
} from '@/lib/api-client'
|
||||
|
||||
// =============================================================================
|
||||
// Helpers
|
||||
@@ -432,6 +438,132 @@ function PostReportAnalysisCard({ packet }: { packet: AiTechnologyReportCadenceR
|
||||
)
|
||||
}
|
||||
|
||||
function DeliveryApprovalPacketCard({ packet }: { packet: AiAgentReportLiveDeliveryApprovalPackageSnapshot['delivery_approval_packets'][number] }) {
|
||||
const tone = packet.risk_tier === 'critical' ? '#DC2626' : packet.risk_tier === 'high' ? '#F59E0B' : '#d97757'
|
||||
return (
|
||||
<div style={{
|
||||
minWidth: 0,
|
||||
padding: 12,
|
||||
border: '0.5px solid #e0ddd4',
|
||||
borderRadius: 7,
|
||||
background: '#fff',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 9,
|
||||
}}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', gap: 10, alignItems: 'flex-start', minWidth: 0 }}>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 6, minWidth: 0 }}>
|
||||
<span style={{ fontFamily: 'Syne, sans-serif', fontSize: 13, fontWeight: 700, color: '#141413' }}>
|
||||
{packet.display_name}
|
||||
</span>
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6, minWidth: 0 }}>
|
||||
<CandidatePill value={packet.cadence} />
|
||||
<CandidatePill value={packet.owner_agent} muted />
|
||||
<CandidatePill value={packet.status} muted={packet.status !== 'blocked_by_policy'} />
|
||||
</div>
|
||||
</div>
|
||||
<span style={{
|
||||
fontFamily: "'DM Mono', monospace",
|
||||
fontSize: 10,
|
||||
fontWeight: 700,
|
||||
color: tone,
|
||||
textTransform: 'uppercase',
|
||||
whiteSpace: 'nowrap',
|
||||
}}>
|
||||
{packet.risk_tier}
|
||||
</span>
|
||||
</div>
|
||||
<DetailRow label="必填欄位">
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6, minWidth: 0 }}>
|
||||
{packet.required_approval_fields.map(field => (
|
||||
<CandidatePill key={field} value={field} muted />
|
||||
))}
|
||||
</div>
|
||||
</DetailRow>
|
||||
<DetailRow label="阻擋動作">
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6, minWidth: 0 }}>
|
||||
{packet.blocked_runtime_actions.map(action => (
|
||||
<CandidatePill key={action} value={action} muted />
|
||||
))}
|
||||
</div>
|
||||
</DetailRow>
|
||||
<DetailRow label="操作指引">
|
||||
{packet.operator_guidance}
|
||||
</DetailRow>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function DeliveryGateCard({ gate }: { gate: AiAgentReportLiveDeliveryApprovalPackageSnapshot['route_lock_gates'][number] }) {
|
||||
return (
|
||||
<div style={{
|
||||
minWidth: 0,
|
||||
padding: 12,
|
||||
border: '0.5px solid #e0ddd4',
|
||||
borderRadius: 7,
|
||||
background: '#fff',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 9,
|
||||
}}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', gap: 10, alignItems: 'center', minWidth: 0 }}>
|
||||
<span style={{ fontFamily: 'Syne, sans-serif', fontSize: 13, fontWeight: 700, color: '#141413' }}>
|
||||
{gate.display_name}
|
||||
</span>
|
||||
<CandidatePill value={gate.status} muted={gate.status !== 'blocked_by_policy'} />
|
||||
</div>
|
||||
<DetailRow label="阻擋路由">
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6, minWidth: 0 }}>
|
||||
{gate.blocked_routes.map(route => (
|
||||
<CandidatePill key={route} value={route} muted />
|
||||
))}
|
||||
</div>
|
||||
</DetailRow>
|
||||
<DetailRow label="實發旗標">
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6, minWidth: 0 }}>
|
||||
<CandidatePill value={`send=${gate.telegram_send_enabled}`} muted />
|
||||
<CandidatePill value={`bot=${gate.bot_api_call_enabled}`} muted />
|
||||
<CandidatePill value={`queue=${gate.gateway_queue_write_enabled}`} muted />
|
||||
</div>
|
||||
</DetailRow>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function DryRunReceiptCard({ receipt }: { receipt: AiAgentReportLiveDeliveryApprovalPackageSnapshot['dry_run_delivery_receipts'][number] }) {
|
||||
return (
|
||||
<div style={{
|
||||
minWidth: 0,
|
||||
padding: 12,
|
||||
border: '0.5px solid #e0ddd4',
|
||||
borderRadius: 7,
|
||||
background: '#fff',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 9,
|
||||
}}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', gap: 10, alignItems: 'center', minWidth: 0 }}>
|
||||
<span style={{ fontFamily: 'Syne, sans-serif', fontSize: 13, fontWeight: 700, color: '#141413' }}>
|
||||
{receipt.display_name}
|
||||
</span>
|
||||
<CandidatePill value={receipt.status} muted={receipt.status !== 'blocked_by_policy'} />
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6, minWidth: 0 }}>
|
||||
<CandidatePill value={receipt.cadence} />
|
||||
<CandidatePill value={`live_send=${receipt.live_send_count}`} muted />
|
||||
<CandidatePill value={`receipt_write=${receipt.receipt_write_allowed}`} muted />
|
||||
</div>
|
||||
<DetailRow label="必填欄位">
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6, minWidth: 0 }}>
|
||||
{receipt.required_fields.map(field => (
|
||||
<CandidatePill key={field} value={field} muted />
|
||||
))}
|
||||
</div>
|
||||
</DetailRow>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Component
|
||||
// =============================================================================
|
||||
@@ -441,6 +573,7 @@ export function AgentMarketTab() {
|
||||
const [snapshot, setSnapshot] = useState<AgentMarketGovernanceSnapshot | null>(null)
|
||||
const [technologyRadar, setTechnologyRadar] = useState<AiTechnologyRadarReadback | null>(null)
|
||||
const [technologyReportCadence, setTechnologyReportCadence] = useState<AiTechnologyReportCadenceReadback | null>(null)
|
||||
const [reportDeliveryApproval, setReportDeliveryApproval] = useState<AiAgentReportLiveDeliveryApprovalPackageSnapshot | null>(null)
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [error, setError] = useState(false)
|
||||
|
||||
@@ -450,11 +583,13 @@ export function AgentMarketTab() {
|
||||
apiClient.getAgentMarketGovernanceSnapshot(),
|
||||
apiClient.getAiTechnologyRadarReadback(),
|
||||
apiClient.getAiTechnologyReportCadenceReadback(),
|
||||
apiClient.getAiAgentReportLiveDeliveryApprovalPackage(),
|
||||
])
|
||||
.then(([marketSnapshot, radarReadback, reportCadenceReadback]) => {
|
||||
.then(([marketSnapshot, radarReadback, reportCadenceReadback, deliveryApprovalPackage]) => {
|
||||
setSnapshot(marketSnapshot)
|
||||
setTechnologyRadar(radarReadback)
|
||||
setTechnologyReportCadence(reportCadenceReadback)
|
||||
setReportDeliveryApproval(deliveryApprovalPackage)
|
||||
setError(false)
|
||||
})
|
||||
.catch(() => setError(true))
|
||||
@@ -479,7 +614,7 @@ export function AgentMarketTab() {
|
||||
)
|
||||
}
|
||||
|
||||
if (error || !snapshot || !technologyRadar || !technologyReportCadence) {
|
||||
if (error || !snapshot || !technologyRadar || !technologyReportCadence || !reportDeliveryApproval) {
|
||||
return (
|
||||
<div style={{ padding: 20 }}>
|
||||
<GlassCard variant="subtle" padding="lg">
|
||||
@@ -516,6 +651,7 @@ export function AgentMarketTab() {
|
||||
const summary = snapshot.summary
|
||||
const radarSummary = technologyRadar.summary
|
||||
const reportSummary = technologyReportCadence.summary
|
||||
const deliveryRollups = reportDeliveryApproval.rollups
|
||||
const allApprovals =
|
||||
summary.priority_upgrades_approved +
|
||||
summary.market_scorecard_updates_approved +
|
||||
@@ -636,6 +772,97 @@ export function AgentMarketTab() {
|
||||
</div>
|
||||
</GlassCard>
|
||||
|
||||
<GlassCard variant="subtle" padding="md">
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 14, minWidth: 0 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12, flexWrap: 'wrap' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 9, minWidth: 0 }}>
|
||||
<Lock size={15} style={{ color: '#F59E0B' }} />
|
||||
<span style={{ fontFamily: 'Syne, sans-serif', fontSize: 14, fontWeight: 700, color: '#141413' }}>
|
||||
{t('technologyReports.deliveryTitle')}
|
||||
</span>
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6, justifyContent: 'flex-end', minWidth: 0, maxWidth: '100%' }}>
|
||||
<CandidatePill value={reportDeliveryApproval.program_status.current_task_id} />
|
||||
<CandidatePill value={reportDeliveryApproval.program_status.runtime_authority} muted />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(5, minmax(0, 1fr))',
|
||||
gap: 10,
|
||||
}} className="agent-market-delivery-metrics-grid">
|
||||
<RadarStat label={t('technologyReports.deliveryMetrics.packets')} value={deliveryRollups.delivery_approval_packet_count} />
|
||||
<RadarStat label={t('technologyReports.deliveryMetrics.routes')} value={deliveryRollups.route_lock_gate_count} />
|
||||
<RadarStat label={t('technologyReports.deliveryMetrics.redaction')} value={deliveryRollups.payload_redaction_check_count} tone="ok" />
|
||||
<RadarStat label={t('technologyReports.deliveryMetrics.receipts')} value={deliveryRollups.dry_run_delivery_receipt_count} />
|
||||
<RadarStat label={t('technologyReports.deliveryMetrics.liveSend')} value={deliveryRollups.telegram_send_count} tone="ok" />
|
||||
</div>
|
||||
|
||||
<div style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(4, minmax(0, 1fr))',
|
||||
gap: 10,
|
||||
}} className="agent-market-delivery-gate-grid">
|
||||
<DetailRow label={t('technologyReports.deliveryTruth.scheduler')}>
|
||||
<CandidatePill value={String(reportDeliveryApproval.delivery_approval_truth.scheduler_enabled)} muted />
|
||||
</DetailRow>
|
||||
<DetailRow label={t('technologyReports.deliveryTruth.queue')}>
|
||||
<CandidatePill value={String(reportDeliveryApproval.delivery_approval_truth.gateway_queue_write_enabled)} muted />
|
||||
</DetailRow>
|
||||
<DetailRow label={t('technologyReports.deliveryTruth.botApi')}>
|
||||
<CandidatePill value={String(reportDeliveryApproval.delivery_approval_truth.bot_api_call_enabled)} muted />
|
||||
</DetailRow>
|
||||
<DetailRow label={t('technologyReports.deliveryTruth.receiptWrite')}>
|
||||
<CandidatePill value={String(reportDeliveryApproval.delivery_approval_truth.report_receipt_write_enabled)} muted />
|
||||
</DetailRow>
|
||||
</div>
|
||||
|
||||
<DetailRow label={t('technologyReports.deliveryTruth.note')}>
|
||||
{reportDeliveryApproval.delivery_approval_truth.truth_note}
|
||||
</DetailRow>
|
||||
|
||||
<div style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(2, minmax(0, 1fr))',
|
||||
gap: 10,
|
||||
}} className="agent-market-delivery-packet-grid">
|
||||
{reportDeliveryApproval.delivery_approval_packets.map(packet => (
|
||||
<DeliveryApprovalPacketCard key={packet.packet_id} packet={packet} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</GlassCard>
|
||||
|
||||
<GlassCard variant="subtle" padding="md">
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 13, minWidth: 0 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 7 }}>
|
||||
<ShieldCheck size={14} style={{ color: '#d97757' }} />
|
||||
<span style={{ fontFamily: 'Syne, sans-serif', fontSize: 13, fontWeight: 700, color: '#141413' }}>
|
||||
{t('technologyReports.deliveryGuardTitle')}
|
||||
</span>
|
||||
</div>
|
||||
<div style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(2, minmax(0, 1fr))',
|
||||
gap: 10,
|
||||
}} className="agent-market-delivery-guard-grid">
|
||||
{reportDeliveryApproval.route_lock_gates.map(gate => (
|
||||
<DeliveryGateCard key={gate.gate_id} gate={gate} />
|
||||
))}
|
||||
</div>
|
||||
<div style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(2, minmax(0, 1fr))',
|
||||
gap: 10,
|
||||
}} className="agent-market-delivery-receipt-grid">
|
||||
{reportDeliveryApproval.dry_run_delivery_receipts.map(receipt => (
|
||||
<DryRunReceiptCard key={receipt.receipt_id} receipt={receipt} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</GlassCard>
|
||||
|
||||
<GlassCard variant="subtle" padding="md">
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 14, minWidth: 0 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12, flexWrap: 'wrap' }}>
|
||||
@@ -1198,6 +1425,11 @@ export function AgentMarketTab() {
|
||||
.agent-market-report-cadence-grid,
|
||||
.agent-market-report-workload-grid,
|
||||
.agent-market-report-analysis-grid,
|
||||
.agent-market-delivery-metrics-grid,
|
||||
.agent-market-delivery-gate-grid,
|
||||
.agent-market-delivery-packet-grid,
|
||||
.agent-market-delivery-guard-grid,
|
||||
.agent-market-delivery-receipt-grid,
|
||||
.agent-market-health-grid,
|
||||
.agent-market-cadence-grid,
|
||||
.agent-market-decision-grid,
|
||||
|
||||
Reference in New Issue
Block a user