UNPKG

@assistant-ui/react

Version:

TypeScript/React library for AI Chat

79 lines 3.59 kB
"use client"; import { jsx as _jsx } from "react/jsx-runtime"; import { Primitive } from "@radix-ui/react-primitive"; import { forwardRef, useCallback, } from "react"; import { useAssistantApi, useAssistantState } from "../../context/index.js"; import { useManagedRef } from "../../utils/hooks/useManagedRef.js"; import { useSizeHandle } from "../../utils/hooks/useSizeHandle.js"; import { useComposedRefs } from "@radix-ui/react-compose-refs"; import { useThreadViewport } from "../../context/react/ThreadViewportContext.js"; import { ThreadPrimitiveViewportSlack } from "../thread/ThreadViewportSlack.js"; const useIsHoveringRef = () => { const api = useAssistantApi(); const message = useAssistantState(() => api.message()); const callbackRef = useCallback((el) => { const handleMouseEnter = () => { message.setIsHovering(true); }; const handleMouseLeave = () => { message.setIsHovering(false); }; el.addEventListener("mouseenter", handleMouseEnter); el.addEventListener("mouseleave", handleMouseLeave); if (el.matches(":hover")) { // TODO this is needed for SSR to work, figure out why queueMicrotask(() => message.setIsHovering(true)); } return () => { el.removeEventListener("mouseenter", handleMouseEnter); el.removeEventListener("mouseleave", handleMouseLeave); message.setIsHovering(false); }; }, [message]); return useManagedRef(callbackRef); }; /** * Hook that registers the anchor user message as a content inset. * Only registers if: user message, at index messages.length-2, and last message is assistant. */ const useMessageViewportRef = () => { const turnAnchor = useThreadViewport((s) => s.turnAnchor); const registerUserHeight = useThreadViewport((s) => s.registerUserMessageHeight); // inset rules: // - the previous user message before the last assistant message registers its full height const shouldRegisterAsInset = useAssistantState(({ thread, message }) => turnAnchor === "top" && message.role === "user" && message.index === thread.messages.length - 2 && thread.messages.at(-1)?.role === "assistant"); const getHeight = useCallback((el) => el.offsetHeight, []); return useSizeHandle(shouldRegisterAsInset ? registerUserHeight : null, getHeight); }; /** * The root container component for a message. * * This component provides the foundational wrapper for message content and handles * hover state management for the message. It automatically tracks when the user * is hovering over the message, which can be used by child components like action bars. * * When `turnAnchor="top"` is set on the viewport, this component * registers itself as the scroll anchor if it's the last user message. * * @example * ```tsx * <MessagePrimitive.Root> * <MessagePrimitive.Content /> * <ActionBarPrimitive.Root> * <ActionBarPrimitive.Copy /> * <ActionBarPrimitive.Edit /> * </ActionBarPrimitive.Root> * </MessagePrimitive.Root> * ``` */ export const MessagePrimitiveRoot = forwardRef((props, forwardRef) => { const isHoveringRef = useIsHoveringRef(); const anchorUserMessageRef = useMessageViewportRef(); const ref = useComposedRefs(forwardRef, isHoveringRef, anchorUserMessageRef); return (_jsx(ThreadPrimitiveViewportSlack, { children: _jsx(Primitive.div, { ...props, ref: ref }) })); }); MessagePrimitiveRoot.displayName = "MessagePrimitive.Root"; //# sourceMappingURL=MessageRoot.js.map