import { useMemo } from "react" import { Inbox } from "lucide-react" import { useDashboard } from "@/lib/dashboard" import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table" import { Badge } from "@/components/ui/badge" // deterministic label → hue (matches the spirit of the backend's md5 colouring) function hueFor(label: string): number { let h = 0 for (let i = 0; i < label.length; i++) h = (h * 31 + label.charCodeAt(i)) % 360 return h } function StatusBadge({ status }: { status: string }) { const variant = status.includes("fail") || status.includes("missing") ? "danger" : status.includes("posted") ? "info" : status.includes("block") ? "warning" : "live" return {status} } export function ActivityPanel() { const { result } = useDashboard() const detections = result?.detections ?? [] const events = result?.events ?? [] const maxT = useMemo( () => Math.max( 0.001, ...detections.map((d) => d.timestamp_sec), ...events.map((e) => e.timestamp_sec), ), [detections, events], ) const labels = useMemo( () => Array.from(new Set(detections.map((d) => d.label))), [detections], ) if (!result) { return (

Run detection to populate the activity timeline.

) } return (
{/* timeline track */}
0.0s detection timeline {maxT.toFixed(1)}s
{/* gridlines */} {[0.25, 0.5, 0.75].map((f) => (
))} {/* detection ticks */} {detections.map((d, i) => ( ))} {/* event markers */} {events.map((e, i) => (
))}
{/* legend */}
{labels.map((l) => (
{l}
))}
{/* tables */} Actions {events.length} Detections {detections.length} {events.length ? ( Time Rule Action Status Frame {events.map((e, i) => ( {e.timestamp_sec.toFixed(1)}s {e.rule} {e.action} {e.frame_index} ))}
) : (

No actions fired this run.

)}
Frame Label Conf. Time {detections.slice(0, 200).map((d, i) => ( {d.frame_index} {d.label} {d.confidence.toFixed(2)} {d.timestamp_sec.toFixed(1)}s ))}
) }