feat(governance): 新增 AI 技術雷達日週月報讀回
Some checks failed
Code Review / ai-code-review (push) Successful in 14s
CD Pipeline / tests (push) Successful in 1m39s
Ansible / Reboot Recovery Contract / validate (push) Has been cancelled
CD Pipeline / post-deploy-checks (push) Has been cancelled
CD Pipeline / build-and-deploy (push) Has been cancelled
Some checks failed
Code Review / ai-code-review (push) Successful in 14s
CD Pipeline / tests (push) Successful in 1m39s
Ansible / Reboot Recovery Contract / validate (push) Has been cancelled
CD Pipeline / post-deploy-checks (push) Has been cancelled
CD Pipeline / build-and-deploy (push) Has been cancelled
This commit is contained in:
@@ -14,7 +14,7 @@ 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 } from '@/lib/api-client'
|
||||
import { apiClient, type AgentMarketGovernanceSnapshot, type AiTechnologyRadarReadback, type AiTechnologyReportCadenceReadback } from '@/lib/api-client'
|
||||
|
||||
// =============================================================================
|
||||
// Helpers
|
||||
@@ -327,6 +327,109 @@ function ProfessionalAgentRoleCard({ role }: { role: AiTechnologyRadarReadback['
|
||||
)
|
||||
}
|
||||
|
||||
function ReportCadenceCard({ cadence }: { cadence: AiTechnologyReportCadenceReadback['report_cadences'][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' }}>
|
||||
<span style={{ fontFamily: 'Syne, sans-serif', fontSize: 13, fontWeight: 700, color: '#141413' }}>
|
||||
{cadence.cadence_label}
|
||||
</span>
|
||||
<CandidatePill value={cadence.telegram_delivery_status} muted />
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
|
||||
<CandidatePill value={cadence.owner_agent} />
|
||||
<CandidatePill value={cadence.reviewer_agent} muted />
|
||||
</div>
|
||||
<DetailRow label="REPORT PURPOSE">
|
||||
{cadence.report_purpose}
|
||||
</DetailRow>
|
||||
<DetailRow label="AI ANALYSIS">
|
||||
{cadence.ai_analysis_after_reading}
|
||||
</DetailRow>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function AgentWorkloadCard({ workload }: { workload: AiTechnologyReportCadenceReadback['agent_workload_reports'][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' }}>
|
||||
<span style={{ fontFamily: 'Syne, sans-serif', fontSize: 13, fontWeight: 700, color: '#141413' }}>
|
||||
{workload.agent}
|
||||
</span>
|
||||
<CandidatePill value={`${workload.work_unit_count} ${workload.work_unit_label}`} />
|
||||
</div>
|
||||
<DetailRow label="RESPONSIBILITY">
|
||||
{workload.professional_responsibility}
|
||||
</DetailRow>
|
||||
<DetailRow label="OUTPUT">
|
||||
{workload.latest_output}
|
||||
</DetailRow>
|
||||
<DetailRow label="NEXT">
|
||||
{workload.next_action}
|
||||
</DetailRow>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function PostReportAnalysisCard({ packet }: { packet: AiTechnologyReportCadenceReadback['post_report_analysis_packets'][number] }) {
|
||||
const tone = packet.risk_tier === 'high' ? '#F59E0B' : packet.risk_tier === 'medium' ? '#d97757' : '#22C55E'
|
||||
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' }}>
|
||||
<span style={{ fontFamily: 'Syne, sans-serif', fontSize: 13, fontWeight: 700, color: '#141413' }}>
|
||||
{packet.report_id}
|
||||
</span>
|
||||
<span style={{
|
||||
fontFamily: "'DM Mono', monospace",
|
||||
fontSize: 10,
|
||||
fontWeight: 700,
|
||||
color: tone,
|
||||
textTransform: 'uppercase',
|
||||
}}>
|
||||
{packet.risk_tier}
|
||||
</span>
|
||||
</div>
|
||||
<DetailRow label="FINDING">
|
||||
{packet.key_finding}
|
||||
</DetailRow>
|
||||
<DetailRow label="SOLUTION">
|
||||
{packet.proposed_solution}
|
||||
</DetailRow>
|
||||
<DetailRow label="BOUNDARY">
|
||||
{packet.execution_boundary}
|
||||
</DetailRow>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Component
|
||||
// =============================================================================
|
||||
@@ -335,6 +438,7 @@ export function AgentMarketTab() {
|
||||
const t = useTranslations('governance.agentMarket')
|
||||
const [snapshot, setSnapshot] = useState<AgentMarketGovernanceSnapshot | null>(null)
|
||||
const [technologyRadar, setTechnologyRadar] = useState<AiTechnologyRadarReadback | null>(null)
|
||||
const [technologyReportCadence, setTechnologyReportCadence] = useState<AiTechnologyReportCadenceReadback | null>(null)
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [error, setError] = useState(false)
|
||||
|
||||
@@ -343,10 +447,12 @@ export function AgentMarketTab() {
|
||||
Promise.all([
|
||||
apiClient.getAgentMarketGovernanceSnapshot(),
|
||||
apiClient.getAiTechnologyRadarReadback(),
|
||||
apiClient.getAiTechnologyReportCadenceReadback(),
|
||||
])
|
||||
.then(([marketSnapshot, radarReadback]) => {
|
||||
.then(([marketSnapshot, radarReadback, reportCadenceReadback]) => {
|
||||
setSnapshot(marketSnapshot)
|
||||
setTechnologyRadar(radarReadback)
|
||||
setTechnologyReportCadence(reportCadenceReadback)
|
||||
setError(false)
|
||||
})
|
||||
.catch(() => setError(true))
|
||||
@@ -371,7 +477,7 @@ export function AgentMarketTab() {
|
||||
)
|
||||
}
|
||||
|
||||
if (error || !snapshot || !technologyRadar) {
|
||||
if (error || !snapshot || !technologyRadar || !technologyReportCadence) {
|
||||
return (
|
||||
<div style={{ padding: 20 }}>
|
||||
<GlassCard variant="subtle" padding="lg">
|
||||
@@ -407,6 +513,7 @@ export function AgentMarketTab() {
|
||||
|
||||
const summary = snapshot.summary
|
||||
const radarSummary = technologyRadar.summary
|
||||
const reportSummary = technologyReportCadence.summary
|
||||
const allApprovals =
|
||||
summary.priority_upgrades_approved +
|
||||
summary.market_scorecard_updates_approved +
|
||||
@@ -527,6 +634,115 @@ 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 }}>
|
||||
<ListChecks size={15} style={{ color: '#d97757' }} />
|
||||
<span style={{ fontFamily: 'Syne, sans-serif', fontSize: 14, fontWeight: 700, color: '#141413' }}>
|
||||
{t('technologyReports.title')}
|
||||
</span>
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6, justifyContent: 'flex-end' }}>
|
||||
<CandidatePill value={technologyReportCadence.report_contract.api_endpoint} />
|
||||
<CandidatePill value={technologyReportCadence.telegram_report_bridge.integration_status} muted />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(6, minmax(0, 1fr))',
|
||||
gap: 10,
|
||||
}} className="agent-market-report-metrics-grid">
|
||||
<RadarStat label={t('technologyReports.metrics.reports')} value={`${reportSummary.report_ready_count}/${reportSummary.report_cadence_count}`} tone="ok" />
|
||||
<RadarStat label={t('technologyReports.metrics.charts')} value={reportSummary.chart_section_count} />
|
||||
<RadarStat label={t('technologyReports.metrics.agentStatus')} value={reportSummary.agent_status_report_count} />
|
||||
<RadarStat label={t('technologyReports.metrics.analysisPackets')} value={reportSummary.post_report_analysis_packet_count} />
|
||||
<RadarStat label={t('technologyReports.metrics.lowMedium')} value={reportSummary.low_medium_auto_action_proposal_count} tone="ok" />
|
||||
<RadarStat label={t('technologyReports.metrics.highRisk')} value={reportSummary.high_risk_owner_review_count} tone="warn" />
|
||||
</div>
|
||||
|
||||
<div style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(4, minmax(0, 1fr))',
|
||||
gap: 10,
|
||||
}} className="agent-market-report-gate-grid">
|
||||
<DetailRow label={t('technologyReports.status')}>
|
||||
<CandidatePill value={reportSummary.status} />
|
||||
</DetailRow>
|
||||
<DetailRow label={t('technologyReports.telegram')}>
|
||||
<CandidatePill value={`send=${technologyReportCadence.policy.telegram_send_enabled}`} muted />
|
||||
</DetailRow>
|
||||
<DetailRow label={t('technologyReports.receipt')}>
|
||||
<CandidatePill value={`receipt=${technologyReportCadence.policy.report_receipt_write_enabled}`} muted />
|
||||
</DetailRow>
|
||||
<DetailRow label={t('technologyReports.autoWrite')}>
|
||||
<CandidatePill value={`runtime=${technologyReportCadence.policy.low_medium_runtime_auto_write_enabled}`} muted />
|
||||
</DetailRow>
|
||||
</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 }}>
|
||||
<CalendarClock size={14} style={{ color: '#d97757' }} />
|
||||
<span style={{ fontFamily: 'Syne, sans-serif', fontSize: 13, fontWeight: 700, color: '#141413' }}>
|
||||
{t('technologyReports.cadencesTitle')}
|
||||
</span>
|
||||
</div>
|
||||
<div style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(3, minmax(0, 1fr))',
|
||||
gap: 10,
|
||||
}} className="agent-market-report-cadence-grid">
|
||||
{technologyReportCadence.report_cadences.map(cadence => (
|
||||
<ReportCadenceCard key={cadence.cadence} cadence={cadence} />
|
||||
))}
|
||||
</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.workloadTitle')}
|
||||
</span>
|
||||
</div>
|
||||
<div style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(2, minmax(0, 1fr))',
|
||||
gap: 10,
|
||||
}} className="agent-market-report-workload-grid">
|
||||
{technologyReportCadence.agent_workload_reports.map(workload => (
|
||||
<AgentWorkloadCard key={workload.agent} workload={workload} />
|
||||
))}
|
||||
</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 }}>
|
||||
<AlertTriangle size={14} style={{ color: '#F59E0B' }} />
|
||||
<span style={{ fontFamily: 'Syne, sans-serif', fontSize: 13, fontWeight: 700, color: '#141413' }}>
|
||||
{t('technologyReports.analysisTitle')}
|
||||
</span>
|
||||
</div>
|
||||
<div style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(3, minmax(0, 1fr))',
|
||||
gap: 10,
|
||||
}} className="agent-market-report-analysis-grid">
|
||||
{technologyReportCadence.post_report_analysis_packets.map(packet => (
|
||||
<PostReportAnalysisCard key={packet.report_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 }}>
|
||||
@@ -975,6 +1191,11 @@ export function AgentMarketTab() {
|
||||
.agent-market-radar-domain-grid,
|
||||
.agent-market-radar-rolling-grid,
|
||||
.agent-market-radar-role-grid,
|
||||
.agent-market-report-metrics-grid,
|
||||
.agent-market-report-gate-grid,
|
||||
.agent-market-report-cadence-grid,
|
||||
.agent-market-report-workload-grid,
|
||||
.agent-market-report-analysis-grid,
|
||||
.agent-market-health-grid,
|
||||
.agent-market-cadence-grid,
|
||||
.agent-market-decision-grid,
|
||||
|
||||
@@ -319,6 +319,11 @@ export const apiClient = {
|
||||
return handleResponse<AiTechnologyRadarReadback>(res)
|
||||
},
|
||||
|
||||
async getAiTechnologyReportCadenceReadback() {
|
||||
const res = await fetch(`${API_BASE_URL}/agents/ai-technology-report-cadence-readback`)
|
||||
return handleResponse<AiTechnologyReportCadenceReadback>(res)
|
||||
},
|
||||
|
||||
async getAiAgentAutomationInventorySnapshot() {
|
||||
const res = await fetch(`${API_BASE_URL}/agents/automation-inventory-snapshot`)
|
||||
return handleResponse<AiAgentAutomationInventorySnapshot>(res)
|
||||
@@ -1173,6 +1178,121 @@ export interface AiTechnologyRadarReadback {
|
||||
}
|
||||
}
|
||||
|
||||
export interface AiTechnologyReportCadenceReadback {
|
||||
schema_version: 'ai_technology_report_cadence_readback_v1'
|
||||
generated_at: string
|
||||
source_scope: Record<string, string>
|
||||
summary: {
|
||||
overall_completion_percent: number
|
||||
report_cadence_completion_percent: number
|
||||
report_cadence_count: number
|
||||
report_ready_count: number
|
||||
chart_section_count: number
|
||||
agent_status_report_count: number
|
||||
post_report_analysis_packet_count: number
|
||||
low_medium_auto_action_proposal_count: number
|
||||
high_risk_owner_review_count: number
|
||||
technology_count: number
|
||||
source_count: number
|
||||
source_failures: number
|
||||
telegram_send_enabled: false
|
||||
live_delivery_count_24h: 0
|
||||
report_receipt_write_count_24h: 0
|
||||
auto_optimization_write_count: 0
|
||||
policy_hold_count: number
|
||||
status: string
|
||||
}
|
||||
policy: {
|
||||
read_only: true
|
||||
raw_chat_history_synced: false
|
||||
raw_report_payload_display_allowed: false
|
||||
report_delivery_enabled: false
|
||||
telegram_send_enabled: false
|
||||
bot_api_call_enabled: false
|
||||
report_receipt_write_enabled: false
|
||||
ai_post_report_analysis_live_run_enabled: false
|
||||
low_medium_runtime_auto_write_enabled: false
|
||||
high_risk_owner_review_required: true
|
||||
sdk_installation_approved: false
|
||||
paid_api_calls_approved: false
|
||||
production_routing_approved: false
|
||||
model_provider_switch_approved: false
|
||||
host_write_approved: false
|
||||
openclaw_replacement_approved: false
|
||||
}
|
||||
report_cadences: Array<{
|
||||
cadence: 'daily' | 'weekly' | 'monthly'
|
||||
cadence_label: string
|
||||
owner_agent: string
|
||||
reviewer_agent: string
|
||||
report_purpose: string
|
||||
data_inputs: string[]
|
||||
metrics: Record<string, number>
|
||||
chart_types: string[]
|
||||
ai_analysis_after_reading: string
|
||||
low_medium_auto_actions: string[]
|
||||
high_risk_owner_review_actions: string[]
|
||||
telegram_delivery_status: string
|
||||
}>
|
||||
agent_workload_reports: Array<{
|
||||
agent: string
|
||||
professional_responsibility: string
|
||||
work_unit_count: number
|
||||
work_unit_label: string
|
||||
latest_output: string
|
||||
next_action: string
|
||||
}>
|
||||
chart_sections: Array<{
|
||||
chart_id: string
|
||||
title: string
|
||||
chart_type: string
|
||||
primary_metric: string
|
||||
value: number
|
||||
expected_report_signal: string
|
||||
}>
|
||||
post_report_analysis_packets: Array<{
|
||||
report_id: 'daily' | 'weekly' | 'monthly'
|
||||
risk_tier: 'low' | 'medium' | 'high'
|
||||
key_finding: string
|
||||
proposed_solution: string
|
||||
agent_decision: string
|
||||
execution_boundary: string
|
||||
}>
|
||||
risk_automation_policy: Array<{
|
||||
risk_tier: 'low' | 'medium' | 'high'
|
||||
agent_auto_scope: string
|
||||
blocked_without_approval: string
|
||||
reporting_mode: string
|
||||
}>
|
||||
telegram_report_bridge: {
|
||||
integration_status: string
|
||||
canonical_group_env: string
|
||||
telegram_send_enabled: false
|
||||
bot_api_call_enabled: false
|
||||
report_receipt_write_enabled: false
|
||||
draft_count: number
|
||||
drafts: Array<{
|
||||
draft_id: string
|
||||
cadence: 'daily' | 'weekly' | 'monthly'
|
||||
status: string
|
||||
required_gate: string
|
||||
}>
|
||||
delivery_note: string
|
||||
}
|
||||
blocked_gates: string[]
|
||||
next_allowed_actions: string[]
|
||||
forbidden_actions_without_new_approval: string[]
|
||||
report_contract: {
|
||||
api_endpoint: string
|
||||
frontend_target: string
|
||||
source_endpoint: string
|
||||
daily: string
|
||||
weekly: string
|
||||
monthly: string
|
||||
telegram: string
|
||||
}
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// AI Agent Automation Inventory Snapshot
|
||||
// =========================================================================
|
||||
|
||||
Reference in New Issue
Block a user