diff --git a/apps/web/messages/en.json b/apps/web/messages/en.json index 6f3a52bf..9965d5de 100644 --- a/apps/web/messages/en.json +++ b/apps/web/messages/en.json @@ -1706,6 +1706,33 @@ "ready": "In Sync", "loading": "Loading", "degraded": "Degraded", + "sourceFlow": { + "title": "Source Flow and Work Progress", + "subtitle": "Reads recent Channel Event recurrence data so the overview shows source persistence, run linkage, work items, and source correlation state.", + "sourceEvents": "{count} source events", + "unavailable": "Unavailable", + "loadFailed": "Unable to load the source flow overview. Check the Work Chain or Run Monitor recurrence API.", + "empty": "No source event data is available yet.", + "metrics": { + "linkedRuns": "Run Linkage", + "linkedRunsDetail": "Unlinked events: {unlinked}", + "openWork": "Open Work", + "openWorkDetail": "No repair {gap} / manual gates {manual} / failed repairs {failed}", + "sourceDecision": "Source Decision", + "sourceDecisionNone": "No Review", + "sourceDecisionDetail": "Recorded reviews: {recorded}", + "latest": "Latest Event", + "latestDetail": "{groups} recurrence groups" + }, + "progress": { + "linked": "Source to Run Coverage", + "linkedDetail": "Whether source events can be traced back to Run / Incident", + "work": "Work Item Cleanup", + "workDetail": "Whether recurrence groups still have open work", + "decision": "Source Match Decision", + "decisionDetail": "Whether source review / apply has a decision record" + } + }, "quality": { "title": "Automation Quality", "subtitle": "Whether recent alerts actually reached AI auto-repair, verification, and learning writeback in the last 24 hours.", diff --git a/apps/web/messages/zh-TW.json b/apps/web/messages/zh-TW.json index 5f344641..a8672b56 100644 --- a/apps/web/messages/zh-TW.json +++ b/apps/web/messages/zh-TW.json @@ -1707,6 +1707,33 @@ "ready": "同步中", "loading": "讀取中", "degraded": "降級", + "sourceFlow": { + "title": "來源流程與工作進度", + "subtitle": "從 Channel Event recurrence 讀取最近來源事件,讓首頁直接呈現來源落庫、Run 連結、工作項與 source correlation 狀態。", + "sourceEvents": "來源事件 {count}", + "unavailable": "無法讀取", + "loadFailed": "無法讀取來源流程總覽。請回工作鏈路或 Run 監控檢查 recurrence API。", + "empty": "尚無來源事件資料。", + "metrics": { + "linkedRuns": "Run 連結", + "linkedRunsDetail": "未連結事件:{unlinked}", + "openWork": "待處理工作", + "openWorkDetail": "無修復 {gap} / 人工閘門 {manual} / 修復失敗 {failed}", + "sourceDecision": "來源決策", + "sourceDecisionNone": "無待審", + "sourceDecisionDetail": "已記錄審核:{recorded}", + "latest": "最新事件", + "latestDetail": "共 {groups} 個 recurrence group" + }, + "progress": { + "linked": "來源到 Run 覆蓋", + "linkedDetail": "來源事件是否已能回到 Run / Incident", + "work": "工作項清理", + "workDetail": "recurrence group 是否仍有待處理項", + "decision": "來源配對決策", + "decisionDetail": "source review / apply 是否已有決策紀錄" + } + }, "quality": { "title": "自動化品質", "subtitle": "最近 24 小時告警是否真正走到 AI 自動修復、驗證與學習回寫。", diff --git a/apps/web/src/app/[locale]/awooop/page.tsx b/apps/web/src/app/[locale]/awooop/page.tsx index 7b0c15d7..dccfdb29 100644 --- a/apps/web/src/app/[locale]/awooop/page.tsx +++ b/apps/web/src/app/[locale]/awooop/page.tsx @@ -84,6 +84,27 @@ type AutomationQualitySummary = { }; }; +type SourceFlowSummary = { + source_event_total: number; + recurrence_group_total: number; + linked_run_total: number; + unlinked_event_total: number; + open_work_item_group_total?: number; + manual_gate_group_total?: number; + automation_gap_group_total?: number; + failed_repair_group_total?: number; + source_correlation_review_group_total?: number; + source_correlation_decision_recorded_group_total?: number; + source_correlation_applied_group_total?: number; + latest_received_at?: string | null; +}; + +type SourceFlowResponse = { + project_id: string; + limit: number; + summary: SourceFlowSummary; +}; + const API_BASE = process.env.NEXT_PUBLIC_API_URL ?? ""; const emptySnapshot: Snapshot = { @@ -304,6 +325,171 @@ function QualityMetric({ ); } +function percentValue(numerator: number, denominator: number): number { + if (denominator <= 0) return 0; + return Math.max(0, Math.min(100, (numerator / denominator) * 100)); +} + +function ProgressRow({ + label, + value, + detail, + percent, + tone, +}: { + label: string; + value: string; + detail: string; + percent: number; + tone: "good" | "warn" | "neutral"; +}) { + return ( +
{label}
+{detail}
+{t("subtitle")}
+