@assistant-ui/react
Version:
Open-source TypeScript/React library for building production-grade AI chat experiences
76 lines (75 loc) • 3.16 kB
JavaScript
"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