@assistant-ui/react
Version:
TypeScript/React library for AI Chat
79 lines • 3.59 kB
JavaScript
"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