"use client"; import { useEffect, useState, useCallback } from "react"; import { motion, AnimatePresence } from "framer-motion"; import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { useSSE } from "@/hooks/use-sse"; interface FeedItem { id: string; type: "task" | "online" | "offline"; lobsterName: string; city?: string; country?: string; summary?: string; durationMs?: number; timestamp: number; } export function LobsterFeed() { const [items, setItems] = useState([]); const handleEvent = useCallback((event: { type: string; data: Record }) => { if (event.type === "task" || event.type === "online" || event.type === "offline") { const newItem: FeedItem = { id: `${Date.now()}-${Math.random().toString(36).slice(2)}`, type: event.type as FeedItem["type"], lobsterName: (event.data.lobsterName as string) ?? "Unknown", city: event.data.city as string | undefined, country: event.data.country as string | undefined, summary: event.data.summary as string | undefined, durationMs: event.data.durationMs as number | undefined, timestamp: Date.now(), }; setItems((prev) => [newItem, ...prev].slice(0, 50)); } }, []); useSSE({ url: "/api/v1/stream", onEvent: handleEvent, }); // Load initial recent tasks useEffect(() => { const fetchRecent = async () => { try { const res = await fetch("/api/v1/lobsters?limit=10"); if (res.ok) { const data = await res.json(); const feedItems: FeedItem[] = (data.lobsters ?? []) .filter((l: Record) => l.lastTask) .map((l: Record) => { const task = l.lastTask as Record; return { id: `init-${l.id}`, type: "task" as const, lobsterName: l.name as string, city: l.city as string, country: l.country as string, summary: task.summary as string, durationMs: task.durationMs as number, timestamp: new Date(task.timestamp as string).getTime(), }; }); setItems(feedItems); } } catch { // will populate via SSE } }; fetchRecent(); }, []); const formatDuration = (ms?: number) => { if (!ms) return ""; if (ms < 1000) return `${ms}ms`; return `${(ms / 1000).toFixed(1)}s`; }; const getIcon = (type: string) => { switch (type) { case "task": return "⚡"; case "online": return "🟢"; case "offline": return "⭕"; default: return "🦞"; } }; return ( Live Feed {items.length === 0 ? (

Waiting for lobster activity...

) : ( items.map((item) => (
{getIcon(item.type)}
{item.lobsterName} {item.city && ( {item.city}, {item.country} )}
{item.summary && (

{item.summary}

)}
{item.durationMs && ( {formatDuration(item.durationMs)} )} {new Date(item.timestamp).toLocaleTimeString()}
)) )}
); }