import React, { useCallback, useMemo, memo } from 'react'; import { useAtomValue } from 'jotai'; import { useRecoilValue } from 'recoil'; import { type TMessage } from 'librechat-data-provider'; import type { TMessageProps, TMessageIcon } from '~/common'; import MessageContent from '~/components/Chat/Messages/Content/MessageContent'; import { useLocalize, useMessageActions, useContentMetadata } from '~/hooks'; import PlaceholderRow from '~/components/Chat/Messages/ui/PlaceholderRow'; import SiblingSwitch from '~/components/Chat/Messages/SiblingSwitch'; import HoverButtons from '~/components/Chat/Messages/HoverButtons'; import MessageIcon from '~/components/Chat/Messages/MessageIcon'; import SubRow from '~/components/Chat/Messages/SubRow'; import { cn, getMessageAriaLabel } from '~/utils'; import { fontSizeAtom } from '~/store/fontSize'; import { MessageContext } from '~/Providers'; import store from '~/store'; type MessageRenderProps = { message?: TMessage; isSubmitting?: boolean; } & Pick< TMessageProps, 'currentEditId' | 'setCurrentEditId' | 'siblingIdx' | 'setSiblingIdx' | 'siblingCount' >; const MessageRender = memo(function MessageRender({ message: msg, siblingIdx, siblingCount, setSiblingIdx, currentEditId, setCurrentEditId, isSubmitting = false, }: MessageRenderProps) { const localize = useLocalize(); const { ask, edit, index, agent, assistant, enterEdit, conversation, messageLabel, handleFeedback, handleContinue, latestMessageId, copyToClipboard, regenerateMessage, latestMessageDepth, } = useMessageActions({ message: msg, currentEditId, setCurrentEditId, }); const fontSize = useAtomValue(fontSizeAtom); const maximizeChatSpace = useRecoilValue(store.maximizeChatSpace); const handleRegenerateMessage = useCallback(() => regenerateMessage(), [regenerateMessage]); const hasNoChildren = !(msg?.children?.length ?? 0); const isLast = useMemo( () => hasNoChildren && (msg?.depth === latestMessageDepth || msg?.depth === -1), [hasNoChildren, msg?.depth, latestMessageDepth], ); const isLatestMessage = msg?.messageId === latestMessageId; /** Only pass isSubmitting to the latest message to prevent unnecessary re-renders */ const effectiveIsSubmitting = isLatestMessage ? isSubmitting : false; const iconData: TMessageIcon = useMemo( () => ({ endpoint: msg?.endpoint ?? conversation?.endpoint, model: msg?.model ?? conversation?.model, iconURL: msg?.iconURL, modelLabel: messageLabel, isCreatedByUser: msg?.isCreatedByUser, }), [ messageLabel, conversation?.endpoint, conversation?.model, msg?.model, msg?.iconURL, msg?.endpoint, msg?.isCreatedByUser, ], ); const { hasParallelContent } = useContentMetadata(msg); const messageId = msg?.messageId ?? ''; const messageContextValue = useMemo( () => ({ messageId, isLatestMessage, isExpanded: false as const, isSubmitting: effectiveIsSubmitting, conversationId: conversation?.conversationId, }), [messageId, conversation?.conversationId, effectiveIsSubmitting, isLatestMessage], ); if (!msg) { return null; } const getChatWidthClass = () => { if (maximizeChatSpace) { return 'w-full max-w-full md:px-5 lg:px-1 xl:px-5'; } if (hasParallelContent) { return 'md:max-w-[58rem] xl:max-w-[70rem]'; } return 'md:max-w-[47rem] xl:max-w-[55rem]'; }; const baseClasses = { common: 'group mx-auto flex flex-1 gap-3 transition-all duration-300 transform-gpu ', chat: getChatWidthClass(), }; const conditionalClasses = { focus: 'focus:outline-none focus:ring-2 focus:ring-border-xheavy', }; return (
{!hasParallelContent && (
)}
{!hasParallelContent && (

{messageLabel}

)}
({}))} />
{hasNoChildren && effectiveIsSubmitting ? ( ) : ( )}
); }); MessageRender.displayName = 'MessageRender'; export default MessageRender;