From 9116ff7bf6da40eadf1ddec5c4ce155f1ef093c2 Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 4 Jun 2026 10:26:59 +0800 Subject: [PATCH] fix(web): surface ai route action summaries --- apps/web/messages/en.json | 18 ++++ apps/web/messages/zh-TW.json | 18 ++++ .../web/src/app/[locale]/awooop/runs/page.tsx | 97 ++++++++++++++----- .../app/[locale]/awooop/work-items/page.tsx | 37 ++++++- docs/LOGBOOK.md | 34 +++++++ 5 files changed, 181 insertions(+), 23 deletions(-) diff --git a/apps/web/messages/en.json b/apps/web/messages/en.json index 24d24d04..5e8c8e86 100644 --- a/apps/web/messages/en.json +++ b/apps/web/messages/en.json @@ -3084,12 +3084,24 @@ "recurrenceSourceApplied": "來源配對已套用:{count}", "recurrenceEmpty": "近期重複告警尚無待處理工作項", "aiRouteRepairWorkItem": "AI route:{lane};目前 {selected};目標 {target};阻塞 {blockers} 項", + "aiRouteActions": { + "monitor": "持續監控即可", + "repair_skipped_primary_lane": "修復被跳過的 Primary lane", + "restore_ollama_lanes": "恢復 Ollama lanes,避免只剩雲端", + "inspect_ai_router": "檢查 AI Router / provider 狀態", + "unknown": "待確認下一步" + }, "aiRouteRepairWorkItemId": "Work item:{id}", "aiRouteRepairSkipped": "已跳過:{skipped}", "aiRouteRepairOwner": "Owner:{owner};主責 Agent:{lead}", "aiRouteRepairPlaybook": "PlayBook:{playbook};步驟 {steps}", "aiRouteRepairSafety": "可安全自動修復:{safe}", + "aiRouteRepairSummary": "AI route 目前由 {selected} 承接;下一步:{action};需人工介入:{human}", "aiRouteRepairUnavailable": "AI route repair evidence 尚未回傳", + "humanRequired": { + "yes": "是", + "no": "否" + }, "driftFingerprint": "Config Drift:{state};12h 內 {count} 次", "driftFingerprintUnavailable": "Config Drift fingerprint state API 尚未回應", "driftFingerprintId": "Fingerprint:{fingerprint};Report:{report}", @@ -4263,6 +4275,12 @@ "inspect_ai_router": "需檢查 AI Router / provider 狀態", "unknown": "待確認下一步" }, + "summary": { + "primaryTitle": "目前由 {provider} 承接,AI lane 正常", + "primaryDetail": "後續備援順序:{standby}。Gemini 只在 Ollama lanes 都不可用後接手;目前下一步是持續監控與保留 fallback 證據。", + "fallbackTitle": "目前由 {provider} 接手,AI lane 已降級", + "fallbackDetail": "已跳過:{skipped}。下一步:{action};需確認是否已有 Work Item、PlayBook 與人工 gate。" + }, "degradedSummary": "目前由 {active} 接手;已跳過 {skipped};下一步:{action}", "repairEvidence": { "title": "最新修復診斷證據", diff --git a/apps/web/messages/zh-TW.json b/apps/web/messages/zh-TW.json index 24d24d04..5e8c8e86 100644 --- a/apps/web/messages/zh-TW.json +++ b/apps/web/messages/zh-TW.json @@ -3084,12 +3084,24 @@ "recurrenceSourceApplied": "來源配對已套用:{count}", "recurrenceEmpty": "近期重複告警尚無待處理工作項", "aiRouteRepairWorkItem": "AI route:{lane};目前 {selected};目標 {target};阻塞 {blockers} 項", + "aiRouteActions": { + "monitor": "持續監控即可", + "repair_skipped_primary_lane": "修復被跳過的 Primary lane", + "restore_ollama_lanes": "恢復 Ollama lanes,避免只剩雲端", + "inspect_ai_router": "檢查 AI Router / provider 狀態", + "unknown": "待確認下一步" + }, "aiRouteRepairWorkItemId": "Work item:{id}", "aiRouteRepairSkipped": "已跳過:{skipped}", "aiRouteRepairOwner": "Owner:{owner};主責 Agent:{lead}", "aiRouteRepairPlaybook": "PlayBook:{playbook};步驟 {steps}", "aiRouteRepairSafety": "可安全自動修復:{safe}", + "aiRouteRepairSummary": "AI route 目前由 {selected} 承接;下一步:{action};需人工介入:{human}", "aiRouteRepairUnavailable": "AI route repair evidence 尚未回傳", + "humanRequired": { + "yes": "是", + "no": "否" + }, "driftFingerprint": "Config Drift:{state};12h 內 {count} 次", "driftFingerprintUnavailable": "Config Drift fingerprint state API 尚未回應", "driftFingerprintId": "Fingerprint:{fingerprint};Report:{report}", @@ -4263,6 +4275,12 @@ "inspect_ai_router": "需檢查 AI Router / provider 狀態", "unknown": "待確認下一步" }, + "summary": { + "primaryTitle": "目前由 {provider} 承接,AI lane 正常", + "primaryDetail": "後續備援順序:{standby}。Gemini 只在 Ollama lanes 都不可用後接手;目前下一步是持續監控與保留 fallback 證據。", + "fallbackTitle": "目前由 {provider} 接手,AI lane 已降級", + "fallbackDetail": "已跳過:{skipped}。下一步:{action};需確認是否已有 Work Item、PlayBook 與人工 gate。" + }, "degradedSummary": "目前由 {active} 接手;已跳過 {skipped};下一步:{action}", "repairEvidence": { "title": "最新修復診斷證據", diff --git a/apps/web/src/app/[locale]/awooop/runs/page.tsx b/apps/web/src/app/[locale]/awooop/runs/page.tsx index 5f0e578c..d9f9f77d 100644 --- a/apps/web/src/app/[locale]/awooop/runs/page.tsx +++ b/apps/web/src/app/[locale]/awooop/runs/page.tsx @@ -3437,6 +3437,29 @@ function aiRouteOperatorActionLabelKey(action?: string | null) { return "operatorActions.unknown"; } +function aiRouteSummaryTone(mode?: string | null) { + if (mode === "primary") { + return "border-[#b8d8bd] bg-[#f0faf2] text-[#17602a]"; + } + if (mode === "cloud_fallback" || mode === "unavailable") { + return "border-[#e1aaa2] bg-[#fff3f1] text-[#9f2f25]"; + } + return "border-[#d9b36f] bg-[#fff7e8] text-[#6d4707]"; +} + +function aiRouteStandbySummary(status: AiRouteStatusResponse) { + const standby = status.policy_order + .filter((item) => item.provider_name !== status.selected_provider) + .map((item) => item.provider_name) + .slice(0, 3) + .join(" -> "); + const skipped = (status.skipped_lanes ?? []) + .map((lane) => lane.provider_name) + .filter(Boolean) + .join(" -> "); + return { standby: standby || "--", skipped: skipped || "--" }; +} + const AI_ROUTE_REPAIR_BLOCKER_KEYS = new Set([ "gcloud_compute_instances_get_missing", "gcloud_compute_instances_list_missing", @@ -3499,6 +3522,7 @@ function AiRouteStatusPanel({ const repairBlockers = repairEvidence?.access_blockers?.filter(Boolean).slice(0, 4) ?? []; const repairProbes = Object.entries(repairEvidence?.live_probe ?? {}).slice(0, 6); const skippedLanes = status?.skipped_lanes ?? []; + const routeSummary = status ? aiRouteStandbySummary(status) : { standby: "--", skipped: "--" }; const skippedProviderSet = new Set( skippedLanes .map((lane) => lane.provider_name) @@ -3551,6 +3575,27 @@ function AiRouteStatusPanel({ ) : ( <> +
+

+ {laneMode === "primary" + ? t("summary.primaryTitle", { provider: selectedProvider ?? "--" }) + : t("summary.fallbackTitle", { provider: selectedProvider ?? "--" })} +

+

+ {laneMode === "primary" + ? t("summary.primaryDetail", { + standby: routeSummary.standby, + }) + : t("summary.fallbackDetail", { + skipped: routeSummary.skipped, + action: t(operatorActionKey as never), + })} +

+
+ {laneMode && laneMode !== "primary" && (