diff --git a/apps/web/messages/en.json b/apps/web/messages/en.json index 4c22fe15e..b9da3fcbe 100644 --- a/apps/web/messages/en.json +++ b/apps/web/messages/en.json @@ -329,6 +329,12 @@ "quality": "品質閘門", "provider": "AI Provider", "km": "KM 健康" + }, + "swimlanes": { + "signals": "Signals / 收件", + "reasoning": "AI / MCP 判斷", + "execution": "PlayBook / Ansible", + "governance": "Approval / KM" } }, "homeProductMap": { diff --git a/apps/web/messages/zh-TW.json b/apps/web/messages/zh-TW.json index 4c22fe15e..b9da3fcbe 100644 --- a/apps/web/messages/zh-TW.json +++ b/apps/web/messages/zh-TW.json @@ -329,6 +329,12 @@ "quality": "品質閘門", "provider": "AI Provider", "km": "KM 健康" + }, + "swimlanes": { + "signals": "Signals / 收件", + "reasoning": "AI / MCP 判斷", + "execution": "PlayBook / Ansible", + "governance": "Approval / KM" } }, "homeProductMap": { diff --git a/apps/web/src/app/[locale]/page.tsx b/apps/web/src/app/[locale]/page.tsx index 64c872a26..387c46fdc 100644 --- a/apps/web/src/app/[locale]/page.tsx +++ b/apps/web/src/app/[locale]/page.tsx @@ -1879,6 +1879,55 @@ export default function Home({ params }: { params: { locale: string } }) { }), }, ] + const commandMapSwimlanes: Array<{ + key: string + label: string + summary: string + tone: HomepageWorkTone + stages: HomepageBlueprintStage[] + }> = [ + { + key: 'signals', + label: tDashboard('homeCommandMap.swimlanes.signals'), + summary: tDashboard('automationDiagrams.workspace.liveEvidence.signal.metric', { + sources: formatAutomationNumber(dossierCoverageSummary?.source_count, dossierCoverageLoaded), + refs: formatAutomationNumber(dossierCoverageSummary?.source_ref_total, dossierCoverageLoaded), + }), + tone: dossierCoverageLoaded ? 'live' : 'watching', + stages: automationFlowStages.filter(stage => stage.key === 'signal' || stage.key === 'intake'), + }, + { + key: 'reasoning', + label: tDashboard('homeCommandMap.swimlanes.reasoning'), + summary: tDashboard('automationDiagrams.workspace.values.aiRoute', { + lane: aiRouteLaneMode, + provider: aiRouteSelectedProvider, + }), + tone: hasAiRouteStatus ? 'live' : 'watching', + stages: automationFlowStages.filter(stage => stage.key === 'ai' || stage.key === 'mcp'), + }, + { + key: 'execution', + label: tDashboard('homeCommandMap.swimlanes.execution'), + summary: tDashboard('automationDiagrams.workspace.values.ansible', { + checkMode: formatAutomationNumber(executionBackend?.ansible_check_mode_total, automationQualityLoaded), + pending: formatAutomationNumber(executionBackend?.ansible_pending_check_mode_total, automationQualityLoaded), + blocker: ansibleRuntimeBlockerLabel, + }), + tone: ansibleRuntime?.can_run_check_mode ? 'live' : automationQualityAvailable ? 'blocked' : 'watching', + stages: automationFlowStages.filter(stage => stage.key === 'playbook' || stage.key === 'ansible'), + }, + { + key: 'governance', + label: tDashboard('homeCommandMap.swimlanes.governance'), + summary: tDashboard('automationDiagrams.workspace.liveEvidence.verify.metric', { + stale: formatAutomationNumber(kmStaleDisplayCount, kmGovernanceLoaded), + ratio: staleRatioLabel, + }), + tone: typeof kmStaleDisplayCount === 'number' && kmStaleDisplayCount > 0 ? 'progress' : hasKmStaleCandidates ? 'live' : 'watching', + stages: automationFlowStages.filter(stage => stage.key === 'approval' || stage.key === 'verify'), + }, + ] const productModuleRows: Array<{ key: string module: string @@ -2263,6 +2312,139 @@ export default function Home({ params }: { params: { locale: string } }) { })} +
+ {commandMapSwimlanes.map((lane) => { + const laneTone = automationWorkToneStyle[lane.tone] + return ( +
+
+
+ +
+ {lane.label} +
+
+
+ {lane.summary} +
+
+
+ {lane.stages.map((stage, index) => { + const stageTone = automationWorkToneStyle[stage.tone] + const isSelectedStage = stage.key === selectedBlueprintStage.key + return ( + setSelectedBlueprintStageKey(stage.key as HomepageBlueprintStageKey)} + aria-current={isSelectedStage ? 'step' : undefined} + style={{ + position: 'relative', + display: 'grid', + gridTemplateColumns: '22px minmax(0, 1fr)', + gap: 7, + alignItems: 'center', + minHeight: 34, + border: `0.5px solid ${isSelectedStage ? stageTone.color : '#e0ddd4'}`, + borderRadius: 7, + background: isSelectedStage ? stageTone.bg : '#fff', + color: 'inherit', + textDecoration: 'none', + padding: '5px 6px', + }} + > + {index > 0 && ( + + )} + + {automationFlowStages.findIndex(flowStage => flowStage.key === stage.key) + 1} + + + {stage.title} + + + ) + })} +
+
+ ) + })} +
+