import { motion } from "framer-motion"; import { useEffect, useMemo, useRef } from "react"; import type { MessageRecord } from "../../../shared/api/client"; import { cardItem, cardStagger } from "../../../shared/hooks/useFramerStagger"; import { ScrollArea, EmptyStateMascot } from "../../../shared/ui"; import { MessageCard, MessageCardSkeleton } from "./MessageCard"; export interface MessageFeedProps { messages: MessageRecord[]; onReanalyze: (id: string) => Promise; emptyText?: string; loading?: boolean; onLoadMore?: () => void; hasMore?: boolean; loadingMore?: boolean; } /** Messages from the same user within 5 minutes are visually grouped. */ const GROUP_WINDOW_MS = 5 * 60 * 1000; interface MessageGroup { messages: MessageRecord[]; } function groupMessages(messages: MessageRecord[]): MessageGroup[] { const groups: MessageGroup[] = []; for (const msg of messages) { const lastGroup = groups[groups.length - 1]; if ( lastGroup && lastGroup.messages[0].user_id === msg.user_id && lastGroup.messages[lastGroup.messages.length - 1].created_at - msg.created_at < GROUP_WINDOW_MS ) { lastGroup.messages.push(msg); } else { groups.push({ messages: [msg] }); } } return groups; } export function MessageFeed({ messages, onReanalyze, emptyText: _emptyText, loading, onLoadMore, hasMore, loadingMore, }: MessageFeedProps) { // IntersectionObserver for infinite scroll — fires when sentinel becomes visible const sentinelRef = useRef(null); useEffect(() => { if (!onLoadMore || !hasMore) return; const el = sentinelRef.current; if (!el) return; const observer = new IntersectionObserver( (entries) => { if (entries[0]?.isIntersecting) onLoadMore(); }, { rootMargin: "400px" }, ); observer.observe(el); return () => observer.disconnect(); }, [onLoadMore, hasMore]); const groupedMessages = useMemo(() => groupMessages(messages), [messages]); if (loading) { return (
{[1, 2, 3, 4, 5].map((i) => ( ))}
); } if (messages.length === 0) { return ; } return ( {groupedMessages.map((group) => ( ))} {/* Infinite-scroll sentinel */} {hasMore && (
{loadingMore ? ( ) : (
)}
)} ); }