UNPKG

@assistant-ui/react

Version:

Open-source TypeScript/React library for building production-grade AI chat experiences

76 lines (75 loc) • 3.16 kB
"use client"; import { getThreadViewportAutoScroll } from "../../context/stores/ThreadViewport.js"; import { useThreadViewportStore } from "../../context/react/ThreadViewportContext.js"; import { useIsomorphicLayoutEffect } from "../../utils/useIsomorphicLayoutEffect.js"; import { useOnScrollToBottom } from "../../utils/hooks/useOnScrollToBottom.js"; import { useManagedRef } from "../../utils/hooks/useManagedRef.js"; import { useCallback, useEffect, useInsertionEffect, useRef } from "@assistant-ui/tap/react-shim"; import { useAui, useAuiEvent, useAuiState } from "@assistant-ui/store"; // src/primitives/thread/useThreadViewportAutoScroll.ts const useThreadViewportAutoScroll = ({ autoScroll, scrollToBottomOnRunStart = true, scrollToBottomOnInitialize = true, scrollToBottomOnThreadSwitch = true }) => { const aui = useAui(); const divRef = useRef(null); const hasMessages = useAuiState((s) => s.thread.messages.length > 0); const firstMessageId = useAuiState((s_0) => s_0.thread.messages[0]?.id); const threadId = useAuiState((s_1) => s_1.optional.threadListItem?.id); const isRunning = useAuiState((s_2) => s_2.thread.isRunning); const threadViewportStore = useThreadViewportStore(); const binding = getThreadViewportAutoScroll(threadViewportStore); const { controller } = binding; useInsertionEffect(() => { binding.getOptions = () => { const state = threadViewportStore.getState(); return { autoScroll: autoScroll ?? state.turnAnchor !== "top", holdFollow: isRunning && state.turnAnchor === "top" && state.element.viewport === divRef.current && state.element.anchor !== null, scrollToBottomOnInitialize, scrollToBottomOnRunStart: scrollToBottomOnRunStart && state.turnAnchor !== "top", scrollToBottomOnThreadSwitch }; }; }); const previousRef = useRef({ threadId, firstMessageId }); useIsomorphicLayoutEffect(() => { const previous = previousRef.current; previousRef.current = { threadId, firstMessageId }; if (previous.threadId !== threadId) return; const previousFirstMessageId = previous.firstMessageId; if (previousFirstMessageId === void 0 || previousFirstMessageId === firstMessageId) return; if (aui.thread.getState().messages.some((message, index) => index > 0 && message.id === previousFirstMessageId)) controller.messagesPrepended(previousFirstMessageId); }, [ aui, controller, firstMessageId, threadId ]); useIsomorphicLayoutEffect(() => { controller.setHasMessages(hasMessages); }, [ controller, hasMessages, scrollToBottomOnInitialize ]); useOnScrollToBottom(({ behavior }) => controller.scrollToBottom(behavior)); useAuiEvent("thread.runStart", () => controller.runStarted()); useEffect(() => { if (!aui.optional.threads) return; return aui.on("threads.selectionChanged", () => controller.threadSwitched()); }, [aui, controller]); return useManagedRef(useCallback((element) => { divRef.current = element; const detach = controller.attach(element); return () => { detach(); divRef.current = null; }; }, [controller])); }; export { useThreadViewportAutoScroll }; //# sourceMappingURL=useThreadViewportAutoScroll.js.map