// Insights tab - failure patterns, patrol findings, decision log, failure heatmap import { store } from "../core/state"; import { escapeHtml, formatTime } from "../core/helpers"; import { icon } from "../design/icons"; import * as api from "../core/api"; import type { FleetState, View, InsightsData, FailurePattern, Decision, PatrolFinding, HeatmapData, } from "../types/api"; function fetchInsightsData(): void { const cache = store.get("insightsCache"); if (cache) { renderInsightsTab(cache); return; } const panel = document.getElementById("panel-insights"); if (panel) panel.innerHTML = '

Loading insights...

'; const results: InsightsData = { patterns: null, decisions: null, patrol: null, heatmap: null, globalLearnings: null, }; let pending = 5; function checkDone() { pending--; if (pending <= 0) { store.set("insightsCache", results); renderInsightsTab(results); } } api .fetchPatterns() .then((d) => { results.patterns = d.patterns || []; }) .catch(() => { results.patterns = []; }) .then(checkDone); api .fetchDecisions() .then((d) => { results.decisions = d.decisions || []; }) .catch(() => { results.decisions = []; }) .then(checkDone); api .fetchPatrol() .then((d) => { results.patrol = d.findings || []; }) .catch(() => { results.patrol = []; }) .then(checkDone); api .fetchHeatmap() .then((d) => { results.heatmap = d; }) .catch(() => { results.heatmap = null; }) .then(checkDone); api .fetchGlobalLearnings() .then((d) => { results.globalLearnings = d.learnings || []; }) .catch(() => { results.globalLearnings = []; }) .then(checkDone); } function renderInsightsTab(data: InsightsData): void { const panel = document.getElementById("panel-insights"); if (!panel) return; let html = '
'; html += `

${icon("lightbulb", 18)} Failure Patterns

` + `
${renderFailurePatterns(data.patterns || [])}
`; html += `

${icon("shield-alert", 18)} Patrol Findings

` + `
${renderPatrolFindings(data.patrol || [])}
`; html += `

${icon("git-branch", 18)} Decision Log

` + `
${renderDecisionLog(data.decisions || [])}
`; html += `

${icon("bar-chart-3", 18)} Failure Heatmap

` + `
${renderFailureHeatmap(data.heatmap)}
`; html += `

${icon("brain", 18)} Global Learnings

` + `
${renderGlobalLearnings(data.globalLearnings || [])}
`; html += `

${icon("clipboard-list", 18)} Audit Log

` + `

Loading...

`; html += "
"; panel.innerHTML = html; // Load audit log asynchronously const auditContainer = document.getElementById("audit-log-content"); if (auditContainer) { api .fetchAuditLog() .then((data) => { const entries = data.entries || []; if (entries.length === 0) { auditContainer.innerHTML = '

No audit entries. Human interventions will appear here.

'; return; } let html2 = '
'; for (const e of entries.slice(0, 50)) { const ts = e.ts ? formatTime(String(e.ts)) : ""; const action = String(e.action || "unknown"); const issue = e.issue ? ` #${e.issue}` : ""; const details = Object.entries(e) .filter(([k]) => !["ts", "ts_epoch", "action", "issue"].includes(k)) .map(([k, v]) => `${k}: ${String(v)}`) .join(", "); html2 += `
${ts}` + `${escapeHtml(action)}${issue}` + (details ? `${escapeHtml(details)}` : "") + "
"; } html2 += "
"; auditContainer.innerHTML = html2; }) .catch(() => { auditContainer.innerHTML = '

Could not load audit log

'; }); } } function renderFailurePatterns(patterns: FailurePattern[]): string { if (!patterns.length) return '

No failure patterns recorded

'; const sorted = [...patterns].sort( (a, b) => (b.frequency || b.count || 0) - (a.frequency || a.count || 0), ); let html = ""; for (const p of sorted) { const freq = p.frequency || p.count || 0; html += `
` + `${escapeHtml(p.description || p.pattern || "")}` + `${freq}x
`; if (p.root_cause) html += `
Root cause: ${escapeHtml(p.root_cause)}
`; if (p.fix || p.suggested_fix) html += `
Fix: ${escapeHtml(p.fix || p.suggested_fix || "")}
`; html += "
"; } return html; } function renderPatrolFindings(findings: PatrolFinding[]): string { if (!findings.length) return '

No patrol findings

'; let html = ""; for (const f of findings) { const severity = (f.severity || "low").toLowerCase(); html += `
` + `${escapeHtml(severity.toUpperCase())}` + `${escapeHtml(f.type || f.category || "")}
` + `
${escapeHtml(f.description || f.message || "")}
` + (f.file ? `
${escapeHtml(f.file)}
` : "") + "
"; } return html; } function renderDecisionLog(decisions: Decision[]): string { if (!decisions.length) return '

No decisions logged

'; let html = '
'; for (const d of decisions) { html += `
` + `${formatTime(d.timestamp || d.ts)}` + `${escapeHtml(d.action || d.decision || "")}` + `${escapeHtml(d.outcome || d.result || "")}` + (d.issue ? `#${d.issue}` : "") + "
"; } html += "
"; return html; } function renderGlobalLearnings( learnings: Array>, ): string { if (!learnings.length) return '

No global learnings yet. Agents accumulate learnings across pipelines.

'; let html = '
'; for (const l of learnings) { const category = String(l.category || l.type || "general"); const content = String(l.content || l.description || l.learning || ""); const source = l.source ? `${escapeHtml(String(l.source))}` : ""; const ts = l.timestamp || l.ts; const time = ts ? `${formatTime(String(ts))}` : ""; html += `
` + `
${escapeHtml(category)}${source}${time}
` + `
${escapeHtml(content)}
`; } html += "
"; return html; } function renderFailureHeatmap(data: HeatmapData | null): string { if (!data?.heatmap) return '

No heatmap data

'; const heatmap = data.heatmap; const stages = Object.keys(heatmap); if (stages.length === 0) return '

No heatmap data

'; const daysSet = new Set(); for (const stage of stages) { for (const day of Object.keys(heatmap[stage])) daysSet.add(day); } const days = Array.from(daysSet).sort(); if (days.length === 0) return '

No heatmap data

'; let maxCount = 0; for (const stage of stages) { for (const day of days) { const count = heatmap[stage]?.[day] || 0; if (count > maxCount) maxCount = count; } } if (maxCount === 0) maxCount = 1; let html = `
`; html += '
'; for (const d of days) { const parts = d.split("-"); const label = parts.length >= 3 ? parts[1] + "/" + parts[2] : d; html += `
${escapeHtml(label)}
`; } for (const s of stages) { html += `
${escapeHtml(s)}
`; for (const d of days) { const count = heatmap[s]?.[d] || 0; const intensity = count / maxCount; const bgColor = count === 0 ? "transparent" : `rgba(244, 63, 94, ${(0.2 + intensity * 0.8).toFixed(2)})`; html += `
${count > 0 ? count : ""}
`; } } html += "
"; return html; } export const insightsView: View = { init() { fetchInsightsData(); }, render(_data: FleetState) { const cache = store.get("insightsCache"); if (cache) renderInsightsTab(cache); }, destroy() {}, };