diff --git a/apps/web/messages/en.json b/apps/web/messages/en.json index fbb8c885..09cb42d1 100644 --- a/apps/web/messages/en.json +++ b/apps/web/messages/en.json @@ -204,7 +204,20 @@ "viewAllReport": "View Full Report", "aiModelStatus": "AI Model Status", "loading": "Loading...", - "trendUp": "↑{pct}%" + "trendUp": "↑{pct}%", + "cotTitle": "Reasoning Timeline", + "cotNoEvents": "Waiting for reasoning data...", + "cotReasoning": "Reasoning", + "cotConfidence": "Confidence", + "cotProvider": "Model", + "cotLatency": "Latency", + "cotTools": "Tool Calls", + "cotClickHint": "Click an event to view reasoning details", + "byAnomalyTitle": "Anomaly Type Distribution Top 5", + "byAnomalyAutoRate": "Auto-Repair Rate", + "mttrTitle": "MTTR Overview", + "mttrUnit": "s", + "mttrNoData": "No MTTR data yet" }, "openclaw": { "name": "OpenClaw", diff --git a/apps/web/messages/zh-TW.json b/apps/web/messages/zh-TW.json index 7057996a..55784702 100644 --- a/apps/web/messages/zh-TW.json +++ b/apps/web/messages/zh-TW.json @@ -205,7 +205,20 @@ "viewAllReport": "查看完整報表", "aiModelStatus": "AI 模型狀態", "loading": "載入中...", - "trendUp": "↑{pct}%" + "trendUp": "↑{pct}%", + "cotTitle": "推理時間軸", + "cotNoEvents": "等待事件推理資料...", + "cotReasoning": "推理", + "cotConfidence": "信心", + "cotProvider": "模型", + "cotLatency": "耗時", + "cotTools": "工具呼叫", + "cotClickHint": "點擊事件查看推理細節", + "byAnomalyTitle": "異常類型分佈 Top 5", + "byAnomalyAutoRate": "自動修復率", + "mttrTitle": "MTTR 概覽", + "mttrUnit": "秒", + "mttrNoData": "尚無 MTTR 資料" }, "openclaw": { "name": "OpenClaw", diff --git a/apps/web/src/app/[locale]/page.tsx b/apps/web/src/app/[locale]/page.tsx index d84e9af5..35dc1db8 100644 --- a/apps/web/src/app/[locale]/page.tsx +++ b/apps/web/src/app/[locale]/page.tsx @@ -117,6 +117,7 @@ function ActivityStreamTab() { const t = useTranslations('dashboard') const [events, setEvents] = useState([]) const [connected, setConnected] = useState(false) + const [selectedIdx, setSelectedIdx] = useState(null) useEffect(() => { const es = new EventSource(`${API_BASE}/api/v1/dashboard/stream`) @@ -131,6 +132,9 @@ function ActivityStreamTab() { return () => es.close() }, []) + const selected = selectedIdx !== null ? events[selectedIdx] : null + const cot = selected?.data?.incident?.decision?.proposal_data ?? selected?.data?.proposal_data ?? null + return (
@@ -140,17 +144,83 @@ function ActivityStreamTab() {
{events.length === 0 ? (
{t('waitingEvents')}
- ) : events.map((ev, i) => ( -
- {ev._time} - - - {ev.type || 'EVENT'} - {ev.data?.overall_status && ` · ${t('statusLabel')}: ${ev.data.overall_status}`} - {ev.data?.hosts && ` · ${ev.data.hosts.length} ${t('hostsLabel')}`} - + ) : ( +
+ {/* Event list */} +
+ {events.map((ev, i) => ( +
setSelectedIdx(i === selectedIdx ? null : i)} + style={{ + display: 'flex', gap: 8, padding: '7px 8px', borderBottom: '0.5px solid #f0ede5', + fontSize: 12, cursor: 'pointer', borderRadius: 5, + background: i === selectedIdx ? 'rgba(74,144,217,0.06)' : 'transparent', + }} + > + {ev._time} + + + {ev.type || 'EVENT'} + {ev.data?.overall_status && ` · ${t('statusLabel')}: ${ev.data.overall_status}`} + {ev.data?.hosts && ` · ${ev.data.hosts.length} ${t('hostsLabel')}`} + {(ev.data?.incident?.decision?.proposal_data || ev.data?.proposal_data) && ( + COT + )} + +
+ ))} + {selectedIdx === null && events.length > 0 && ( +
{t('cotClickHint')}
+ )} +
+ + {/* Chain of Thought panel */} + {cot && ( +
+
{t('cotTitle')}
+ {/* Provider + confidence row */} +
+ {cot.provider && ( + + {t('cotProvider')}: {cot.provider} + + )} + {cot.confidence !== undefined && ( + = 0.8 ? 'rgba(34,197,94,0.1)' : 'rgba(245,158,11,0.1)', color: cot.confidence >= 0.8 ? '#22C55E' : '#F59E0B', borderRadius: 4, padding: '2px 8px', fontWeight: 600 }}> + {t('cotConfidence')}: {Math.round(cot.confidence * 100)}% + + )} + {cot.nemotron_latency_ms !== undefined && ( + + {t('cotLatency')}: {cot.nemotron_latency_ms}ms + + )} +
+ {/* Tools used */} + {Array.isArray(cot.nemotron_tools) && cot.nemotron_tools.length > 0 && ( +
+
{t('cotTools')}
+
+ {cot.nemotron_tools.map((tool: string, i: number) => ( + {tool} + ))} +
+
+ )} + {/* Reasoning */} + {cot.reasoning && ( +
+
{t('cotReasoning')}
+
+ {cot.reasoning} +
+
+ )} +
+ )}
- ))} + )}
) } @@ -216,7 +286,7 @@ function DispositionTab() { ))}
{/* 堆疊分佈條 */} -
+
{t('dispositionBreakdown')}
{s.total > 0 && <> @@ -227,6 +297,55 @@ function DispositionTab() { }
+ + {/* By Anomaly Top 5 */} + {Array.isArray(data?.by_anomaly) && data.by_anomaly.length > 0 && (() => { + const top5 = [...data.by_anomaly].sort((a: any, b: any) => b.count - a.count).slice(0, 5) + const maxCount = top5[0]?.count || 1 + return ( +
+
{t('byAnomalyTitle')}
+ {top5.map((item: any, i: number) => { + const autoRate = item.count > 0 ? Math.round((item.auto_repair ?? 0) / item.count * 100) : 0 + return ( +
+
+ {item.anomaly_type || item.type || '--'} + + {item.count} · {t('byAnomalyAutoRate', { pct: autoRate })} + +
+
+
= 80 ? '#22C55E' : autoRate >= 50 ? '#F59E0B' : '#cc2200', borderRadius: 3, transition: 'width 0.4s ease' }} /> +
+
+ ) + })} +
+ ) + })()} + + {/* MTTR */} + {(() => { + const mttr = data?.mttr + if (!mttr) return ( +
+
{t('mttrTitle')}
+
{t('mttrNoData')}
+
+ ) + const mttrVal = typeof mttr === 'number' ? mttr : mttr.avg_seconds ?? mttr.value ?? null + const mttrMin = mttrVal !== null ? (mttrVal / 60).toFixed(1) : '--' + return ( +
+
{t('mttrTitle')}
+
+ {mttrMin} + {t('mttrUnit')} +
+
+ ) + })()}
) }