"use client" import { useCallback } from "react" import type { ReactNode } from "react" import { useVirtualizer } from "@tanstack/react-virtual" import { useStickToBottomContext } from "use-stick-to-bottom" import { MessageThreadContent, type MessageThreadContentProps, } from "@/components/ai-elements/message-thread" import { cn } from "@/lib/utils" interface VirtualizedMessageThreadProps { items: T[] getItemKey: (item: T, index: number) => string renderItem: (item: T, index: number) => ReactNode emptyState?: ReactNode estimateSize?: number overscan?: number className?: string contentClassName?: string contentProps?: Omit } export function VirtualizedMessageThread({ items, getItemKey, renderItem, emptyState, estimateSize = 160, overscan = 8, className, contentClassName, contentProps, }: VirtualizedMessageThreadProps) { const { scrollRef } = useStickToBottomContext() // eslint-disable-next-line react-hooks/incompatible-library const virtualizer = useVirtualizer({ count: items.length, getScrollElement: () => scrollRef.current, estimateSize: () => estimateSize, overscan, useAnimationFrameWithResizeObserver: true, isScrollingResetDelay: 100, paddingStart: 16, paddingEnd: 16, gap: 32, getItemKey: (index) => { const item = items[index] return item ? getItemKey(item, index) : index }, }) const renderVirtualRow = useCallback( (virtualItem: ReturnType[number]) => { const item = items[virtualItem.index] if (!item) return null return (
{renderItem(item, virtualItem.index)}
) }, [className, items, renderItem, virtualizer] ) return ( {items.length === 0 ? ( (emptyState ?? null) ) : (
{virtualizer.getVirtualItems().map(renderVirtualRow)}
)}
) }