diff --git a/apps/web/messages/en.json b/apps/web/messages/en.json index 98839833..2f97935a 100644 --- a/apps/web/messages/en.json +++ b/apps/web/messages/en.json @@ -3311,6 +3311,23 @@ "approvals": "Runtime 批准" } }, + "technologyRadar": { + "title": "AI 技術雷達滾動監控", + "status": "滾動狀態", + "nearRealTime": "近即時監控", + "reviewQueue": "審核佇列", + "reviewQueueValue": "待審 {queue} / 高優先 {high} / 變更 {changed}", + "domainsTitle": "市場技術領域", + "rollingTitle": "日週月報與滾動更新控制", + "rolesTitle": "專業 Agent 分工", + "metrics": { + "progress": "雷達完成度", + "technologies": "監控技術", + "sources": "Primary Sources", + "failures": "來源失敗", + "policyHolds": "審核鎖" + } + }, "metrics": { "candidates": "候選數", "sources": "來源數", diff --git a/apps/web/messages/zh-TW.json b/apps/web/messages/zh-TW.json index 98839833..2f97935a 100644 --- a/apps/web/messages/zh-TW.json +++ b/apps/web/messages/zh-TW.json @@ -3311,6 +3311,23 @@ "approvals": "Runtime 批准" } }, + "technologyRadar": { + "title": "AI 技術雷達滾動監控", + "status": "滾動狀態", + "nearRealTime": "近即時監控", + "reviewQueue": "審核佇列", + "reviewQueueValue": "待審 {queue} / 高優先 {high} / 變更 {changed}", + "domainsTitle": "市場技術領域", + "rollingTitle": "日週月報與滾動更新控制", + "rolesTitle": "專業 Agent 分工", + "metrics": { + "progress": "雷達完成度", + "technologies": "監控技術", + "sources": "Primary Sources", + "failures": "來源失敗", + "policyHolds": "審核鎖" + } + }, "metrics": { "candidates": "候選數", "sources": "來源數", diff --git a/apps/web/src/app/[locale]/governance/tabs/agent-market-tab.tsx b/apps/web/src/app/[locale]/governance/tabs/agent-market-tab.tsx index 9605c16e..ecf358d2 100644 --- a/apps/web/src/app/[locale]/governance/tabs/agent-market-tab.tsx +++ b/apps/web/src/app/[locale]/governance/tabs/agent-market-tab.tsx @@ -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 } from '@/lib/api-client' +import { apiClient, type AgentMarketGovernanceSnapshot, type AiTechnologyRadarReadback } from '@/lib/api-client' // ============================================================================= // Helpers @@ -31,6 +31,23 @@ function formatDateTime(value: string): string { }) } +function formatPercent(value: number): string { + return `${value.toFixed(1)}%` +} + +const TECHNOLOGY_AREA_LABELS: Record = { + agent_frameworks: 'Agent Frameworks', + evaluation_and_observability: 'Eval / Observability', + mcp_and_a2a: 'MCP / A2A', + model_providers: 'Model Providers', + model_serving: 'Model Serving', + rag_and_vector: 'RAG / Vector', +} + +function formatTechnologyArea(value: string): string { + return TECHNOLOGY_AREA_LABELS[value] ?? value.replace(/_/g, ' ') +} + // ============================================================================= // Small UI // ============================================================================= @@ -176,6 +193,140 @@ function DetailRow({ label, children }: { label: string; children: React.ReactNo ) } +function RadarStat({ label, value, tone = 'neutral' }: { label: string; value: number | string; tone?: 'neutral' | 'ok' | 'warn' }) { + const color = tone === 'ok' ? '#22C55E' : tone === 'warn' ? '#F59E0B' : '#141413' + return ( +
+ + {label} + + + {value} + +
+ ) +} + +function TechnologyDomainCard({ domain }: { domain: AiTechnologyRadarReadback['technology_domains'][number] }) { + return ( +
+
+ + {formatTechnologyArea(domain.technology_area)} + + +
+
+ {domain.representative_technologies.map(technology => ( + + ))} +
+
+ + +
+
+ ) +} + +function RollingUpdateCard({ control }: { control: AiTechnologyRadarReadback['rolling_update_controls'][number] }) { + return ( +
+
+ + {control.cadence} + + +
+ + {control.agent_auto_action} + + + {control.output} + +
+ ) +} + +function ProfessionalAgentRoleCard({ role }: { role: AiTechnologyRadarReadback['professional_agent_roles'][number] }) { + return ( +
+
+ + {role.agent} + + +
+ + {role.professional_role} + + + {role.auto_scope} + + + {role.review_boundary} + +
+ ) +} + // ============================================================================= // Component // ============================================================================= @@ -183,14 +334,19 @@ function DetailRow({ label, children }: { label: string; children: React.ReactNo export function AgentMarketTab() { const t = useTranslations('governance.agentMarket') const [snapshot, setSnapshot] = useState(null) + const [technologyRadar, setTechnologyRadar] = useState(null) const [loading, setLoading] = useState(true) const [error, setError] = useState(false) const fetchSnapshot = () => { setLoading(true) - apiClient.getAgentMarketGovernanceSnapshot() - .then((data: AgentMarketGovernanceSnapshot) => { - setSnapshot(data) + Promise.all([ + apiClient.getAgentMarketGovernanceSnapshot(), + apiClient.getAiTechnologyRadarReadback(), + ]) + .then(([marketSnapshot, radarReadback]) => { + setSnapshot(marketSnapshot) + setTechnologyRadar(radarReadback) setError(false) }) .catch(() => setError(true)) @@ -215,7 +371,7 @@ export function AgentMarketTab() { ) } - if (error || !snapshot) { + if (error || !snapshot || !technologyRadar) { return (
@@ -250,6 +406,7 @@ export function AgentMarketTab() { } const summary = snapshot.summary + const radarSummary = technologyRadar.summary const allApprovals = summary.priority_upgrades_approved + summary.market_scorecard_updates_approved + @@ -259,6 +416,9 @@ export function AgentMarketTab() { summary.production_changes_approved + summary.shadow_or_canary_approved + summary.replacement_decisions_approved + const radarPolicyHolds = Object.entries(technologyRadar.policy) + .filter(([key, value]) => key !== 'read_only' && value === false) + .length const watchHealth = snapshot.market_watch_health const watchHealthHealthy = watchHealth.status === 'healthy' @@ -318,6 +478,115 @@ export function AgentMarketTab() { ]} /> + +
+
+
+ + + {t('technologyRadar.title')} + +
+
+ + +
+
+ +
+ + + + 0 ? 'warn' : 'ok'} /> + 0 ? 'warn' : 'ok'} /> +
+ +
+ + + + + {technologyRadar.report_contract.near_real_time} + + + {t('technologyRadar.reviewQueueValue', { + queue: radarSummary.review_queue_count, + high: radarSummary.high_priority_count, + changed: radarSummary.changed_technologies, + })} + +
+
+
+ + +
+
+ + + {t('technologyRadar.domainsTitle')} + +
+
+ {technologyRadar.technology_domains.map(domain => ( + + ))} +
+
+
+ + +
+
+ + + {t('technologyRadar.rollingTitle')} + +
+
+ {technologyRadar.rolling_update_controls.map(control => ( + + ))} +
+
+
+ + +
+
+ + + {t('technologyRadar.rolesTitle')} + +
+
+ {technologyRadar.professional_agent_roles.map(role => ( + + ))} +
+
+
+
{` @media (max-width: 900px) { .agent-market-kpi-grid, + .agent-market-radar-metrics-grid, + .agent-market-radar-contract-grid, + .agent-market-radar-domain-grid, + .agent-market-radar-rolling-grid, + .agent-market-radar-role-grid, .agent-market-health-grid, .agent-market-cadence-grid, .agent-market-decision-grid, diff --git a/apps/web/src/lib/api-client.ts b/apps/web/src/lib/api-client.ts index bdee5bac..fe97692f 100644 --- a/apps/web/src/lib/api-client.ts +++ b/apps/web/src/lib/api-client.ts @@ -314,6 +314,11 @@ export const apiClient = { return handleResponse(res) }, + async getAiTechnologyRadarReadback() { + const res = await fetch(`${API_BASE_URL}/agents/ai-technology-radar-readback`) + return handleResponse(res) + }, + async getAiAgentAutomationInventorySnapshot() { const res = await fetch(`${API_BASE_URL}/agents/automation-inventory-snapshot`) return handleResponse(res) @@ -1082,6 +1087,92 @@ export interface AgentMarketGovernanceSnapshot { forbidden_actions_without_new_approval: string[] } +// ========================================================================= +// AI Technology Radar Readback +// ========================================================================= + +export interface AiTechnologyRadarReadback { + schema_version: 'ai_technology_radar_readback_v1' + generated_at: string + source_scope: Record + summary: { + overall_completion_percent: number + ai_technology_radar_completion_percent: number + technology_count: number + technology_area_count: number + source_count: number + changed_technologies: number + review_queue_count: number + source_failures: number + high_priority_count: number + rolling_update_status: string + } + policy: { + read_only: true + raw_chat_history_synced: false + sdk_installation_approved: false + paid_api_calls_approved: false + production_routing_approved: false + telegram_send_approved: false + model_provider_switch_approved: false + host_write_approved: false + openclaw_replacement_approved: false + } + technology_area_counts: Record + technology_domains: Array<{ + technology_area: string + technology_count: number + high_priority_count: number + changed_count: number + representative_technologies: string[] + }> + high_priority_review_queue: Array> + professional_agent_roles: Array<{ + agent: string + professional_role: string + auto_scope: string + review_boundary: string + }> + rolling_update_controls: Array<{ + cadence: string + cadence_source: string + agent_auto_action: string + output: string + gate: string + }> + integration_candidates: Array<{ + technology_id: string + display_name: string + technology_area: string + integration_surface: string + awoooi_role: string + decision: string + changed: boolean + recommended_actions: string[] + }> + priority_workplan: Array<{ + order: number + priority: string + work_item: string + automation_mode: string + done_definition: string + }> + blocked_gates: string[] + report_contract: { + api_endpoint: string + frontend_target: string + schedule_enabled: boolean + schedule_workflow: string + schedule_cron_utc: string + near_real_time: string + daily: string + weekly: string + monthly: string + agent_auto_allowed_for: string[] + human_review_required_for: string[] + } +} + // ========================================================================= // AI Agent Automation Inventory Snapshot // =========================================================================