"use client"; import { useSSE } from "@/lib/use-sse"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { ScrollArea } from "@/components/ui/scroll-area"; import type { ActivityEvent } from "@/lib/types"; const green = { bg: "#22c55e", shadow: "0 0 8px rgba(34, 197, 94, 0.4)" }; const blue = { bg: "#3b82f6", shadow: "0 0 8px rgba(59, 130, 246, 0.4)" }; const amber = { bg: "#f59e0b", shadow: "0 0 8px rgba(245, 158, 11, 0.4)" }; const red = { bg: "#ef4444", shadow: "0 0 8px rgba(239, 68, 68, 0.4)" }; const purple = { bg: "#a855f7", shadow: "0 0 8px rgba(168, 85, 247, 0.4)" }; const typeColors: Record = { stack_healthy: green, backup_result: green, drift_clean: green, cron_complete: green, disk_scan_complete: green, email_classified: blue, email_classifying: blue, email_cached: blue, start: blue, receipt_extracted: amber, container_restarted: amber, restart_analysis: amber, container_unhealthy: red, drift_found: red, disk_warning: red, error: red, changelog_generated: purple, changelog_commits: purple, pr_reviewed: purple, }; const defaultDot = { bg: "#6b7280", shadow: "none" }; function formatTime(ts: string) { try { return new Date(ts).toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit", }); } catch { return ts; } } function eventMessage(event: ActivityEvent): string { switch (event.type) { case "email_classified": return `Classified as ${event.category ?? "?"} ${event.label ? `- ${event.label}` : ""}`.trim(); case "email_classifying": return `[${event.progress ?? "?"}] Classifying: ${event.subject ?? "?"}`; case "email_cached": return `Cached: ${event.subject ?? "?"} -> ${event.category ?? "?"}`; case "receipt_extracted": return `Receipt: ${event.vendor ?? "?"} $${event.amount ?? "?"}`; case "container_restarted": return `Restarted ${event.container} on ${event.endpoint}`; case "container_unhealthy": return event.container ? `Unhealthy: ${event.container} on ${event.endpoint}` : event.raw ?? "Unhealthy container detected"; case "backup_result": return `Backup: ${event.status ?? "?"}`; case "drift_clean": return "Config drift check: all clean"; case "drift_found": return `Config drift: ${event.drifts ?? "?"} drifts in ${event.services ?? "?"} services`; case "cron_complete": return "Automation run completed"; case "stack_healthy": return "All containers healthy"; case "disk_warning": return `Disk warning: ${event.days} days remaining`; case "disk_scan_complete": return `Disk scan: ${event.count} filesystems checked`; case "pr_reviewed": return `AI reviewed PR #${event.pr}`; case "changelog_generated": return `Changelog: ${event.commits} commits`; case "changelog_commits": return `${event.count} new commits found`; case "restart_analysis": return `LLM says ${event.decision} for ${event.container}`; default: // Strip timestamp prefix from raw log line for cleaner display if (typeof event.raw === "string") { return event.raw.replace(/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2},?\d*\s+\S+\s+/, ""); } return `${event.type} from ${event.source}`; } } export function ActivityFeed() { const events = useSSE("/api/activity"); return ( Activity Feed LIVE {events.length === 0 && (

Waiting for events...

)}
{events.map((event, i) => (

{eventMessage(event)}

{formatTime(event.timestamp)} · {event.source}

))}
); }