import { useRecoilValue } from 'recoil'; import { ArrowIcon } from '@librechat/client'; import { useCallback, useMemo, memo } from 'react'; import type { TMessage, TMessageContentParts, TConversationCosts } from 'librechat-data-provider'; import type { TMessageProps, TMessageIcon } from '~/common'; import ContentParts from '~/components/Chat/Messages/Content/ContentParts'; import PlaceholderRow from '~/components/Chat/Messages/ui/PlaceholderRow'; import { useAttachments, useMessageActions, useLocalize } from '~/hooks'; 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, logger } from '~/utils'; import store from '~/store'; type ContentRenderProps = { message?: TMessage; isCard?: boolean; isMultiMessage?: boolean; isSubmittingFamily?: boolean; costs?: TConversationCosts; } & Pick< TMessageProps, 'currentEditId' | 'setCurrentEditId' | 'siblingIdx' | 'setSiblingIdx' | 'siblingCount' >; const ContentRender = memo( ({ message: msg, isCard = false, siblingIdx, siblingCount, setSiblingIdx, currentEditId, isMultiMessage = false, setCurrentEditId, isSubmittingFamily = false, costs, }: ContentRenderProps) => { const localize = useLocalize(); const { attachments, searchResults } = useAttachments({ messageId: msg?.messageId, attachments: msg?.attachments, }); const { edit, index, agent, assistant, enterEdit, conversation, messageLabel, isSubmitting, latestMessage, handleContinue, copyToClipboard, setLatestMessage, regenerateMessage, handleFeedback, } = useMessageActions({ message: msg, searchResults, currentEditId, isMultiMessage, setCurrentEditId, }); const maximizeChatSpace = useRecoilValue(store.maximizeChatSpace); const fontSize = useRecoilValue(store.fontSize); const showCostTracking = useRecoilValue(store.showCostTracking); const perMessageCost = useMemo(() => { if (!showCostTracking || !costs || !costs.perMessage || !msg?.messageId) { return null; } return costs.perMessage.find((p) => p.messageId === msg.messageId) ?? null; }, [showCostTracking, costs, msg?.messageId]); const handleRegenerateMessage = useCallback(() => regenerateMessage(), [regenerateMessage]); const isLast = useMemo( () => !(msg?.children?.length ?? 0) && (msg?.depth === latestMessage?.depth || msg?.depth === -1), [msg?.children, msg?.depth, latestMessage?.depth], ); const isLatestMessage = msg?.messageId === latestMessage?.messageId; const showCardRender = isLast && !isSubmittingFamily && isCard; const isLatestCard = isCard && !isSubmittingFamily && isLatestMessage; 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 clickHandler = useMemo( () => showCardRender && !isLatestMessage ? () => { logger.log(`Message Card click: Setting ${msg?.messageId} as latest message`); logger.dir(msg); setLatestMessage(msg!); } : undefined, [showCardRender, isLatestMessage, msg, setLatestMessage], ); if (!msg) { return null; } const baseClasses = { common: 'group mx-auto flex flex-1 gap-3 transition-all duration-300 transform-gpu ', card: 'relative w-full gap-1 rounded-lg border border-border-medium bg-surface-primary-alt p-2 md:w-1/2 md:gap-3 md:p-4', chat: maximizeChatSpace ? 'w-full max-w-full md:px-5 lg:px-1 xl:px-5' : 'md:max-w-[47rem] xl:max-w-[55rem]', }; const conditionalClasses = { latestCard: isLatestCard ? 'bg-surface-secondary' : '', cardRender: showCardRender ? 'cursor-pointer transition-colors duration-300' : '', focus: 'focus:outline-none focus:ring-2 focus:ring-border-xheavy', }; return (