diff --git a/package.json b/package.json index 07aa894b..d2821d64 100644 --- a/package.json +++ b/package.json @@ -93,6 +93,7 @@ "@tanstack/react-pacer": "^0.21.1", "@tanstack/react-query": "^5.96.1", "@tanstack/react-query-devtools": "^5.96.1", + "@tanstack/react-virtual": "^3.13.23", "@tiptap/core": "3.22.3", "@tiptap/extension-code-block": "3.22.3", "@tiptap/extension-highlight": "3.22.3", diff --git a/src/components/assistant-ui/thread.tsx b/src/components/assistant-ui/thread.tsx index cead0150..e2efadc8 100644 --- a/src/components/assistant-ui/thread.tsx +++ b/src/components/assistant-ui/thread.tsx @@ -39,8 +39,9 @@ import { useMessagePartText, useAuiState, } from "@assistant-ui/react"; +import { useVirtualizer } from "@tanstack/react-virtual"; -import type { FC } from "react"; +import type { FC, RefObject } from "react"; import { createContext, useContext } from "react"; import { useEffect, useRef, useState, useMemo, useCallback } from "react"; @@ -137,13 +138,7 @@ export const Thread: FC = ({ items = [] }) => { - +
@@ -153,6 +148,53 @@ export const Thread: FC = ({ items = [] }) => { ); }; +interface VirtualizedMessagesProps { + scrollRef: RefObject; +} + +const VirtualizedMessages: FC = ({ scrollRef }) => { + const messageCount = useAuiState((s) => s.thread.messages.length); + + const virtualizer = useVirtualizer({ + count: messageCount, + getScrollElement: () => scrollRef.current, + estimateSize: () => 350, + overscan: 5, + }); + + if (messageCount === 0) return null; + + return ( +
+ {virtualizer.getVirtualItems().map((virtualRow) => ( +
+ +
+ ))} +
+ ); +}; + const ThreadLoadingSkeleton: FC = () => { return (
= ({ items }) => { null, ); const isThreadEmpty = useAuiState(({ thread }) => thread?.isEmpty ?? true); - const composerText = useAuiState( - (s) => (s as { composer?: { text?: string } })?.composer?.text ?? "", + const hasComposerText = useAuiState( + (s) => + Boolean( + ((s as { composer?: { text?: string } })?.composer?.text ?? "").trim(), + ), ); - const hasComposerText = Boolean(composerText?.trim()); const handleDirectFill = useCallback( (fill: string) => { @@ -1429,6 +1473,12 @@ const EditComposer: FC = () => { ); }; +const MESSAGE_COMPONENTS = { + UserMessage, + EditComposer, + AssistantMessage, +}; + const BranchPicker: FC = ({ className, ...rest