// 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 =
'
';
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 +=
`
` +
`
${renderFailurePatterns(data.patterns || [])}
`;
html +=
`
` +
`
${renderPatrolFindings(data.patrol || [])}
`;
html +=
`
` +
`
${renderDecisionLog(data.decisions || [])}
`;
html +=
`
` +
`
${renderFailureHeatmap(data.heatmap)}
`;
html +=
`
` +
`
${renderGlobalLearnings(data.globalLearnings || [])}
`;
html +=
`
`;
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 =
'';
});
}
}
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 +=
``;
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 '';
let html = "";
for (const f of findings) {
const severity = (f.severity || "low").toLowerCase();
html +=
`` +
`
${escapeHtml(f.description || f.message || "")}
` +
(f.file ? `
${escapeHtml(f.file)}
` : "") +
"
";
}
return html;
}
function renderDecisionLog(decisions: Decision[]): string {
if (!decisions.length)
return '';
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(content)}
`;
}
html += "
";
return html;
}
function renderFailureHeatmap(data: HeatmapData | null): string {
if (!data?.heatmap)
return '';
const heatmap = data.heatmap;
const stages = Object.keys(heatmap);
if (stages.length === 0)
return '';
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 '';
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() {},
};