import { memo, useMemo, useState } from 'react'; import { useRecoilValue, useRecoilState } from 'recoil'; import { ContentTypes } from 'librechat-data-provider'; import type { TMessageContentParts, TAttachment, Agents } from 'librechat-data-provider'; import { ThinkingButton } from '~/components/Artifacts/Thinking'; import EditTextPart from './Parts/EditTextPart'; import useLocalize from '~/hooks/useLocalize'; import { mapAttachments } from '~/utils/map'; import { MessageContext } from '~/Providers'; import store from '~/store'; import Part from './Part'; type ContentPartsProps = { content: Array | undefined; messageId: string; conversationId?: string | null; attachments?: TAttachment[]; isCreatedByUser: boolean; isLast: boolean; isSubmitting: boolean; edit?: boolean; enterEdit?: (cancel?: boolean) => void | null | undefined; siblingIdx?: number; setSiblingIdx?: | ((value: number) => void | React.Dispatch>) | null | undefined; }; const ContentParts = memo( ({ content, messageId, conversationId, attachments, isCreatedByUser, isLast, isSubmitting, edit, enterEdit, siblingIdx, setSiblingIdx, }: ContentPartsProps) => { const localize = useLocalize(); const [showThinking, setShowThinking] = useRecoilState(store.showThinking); const [isExpanded, setIsExpanded] = useState(showThinking); const messageAttachmentsMap = useRecoilValue(store.messageAttachmentsMap); const attachmentMap = useMemo( () => mapAttachments(attachments ?? messageAttachmentsMap[messageId] ?? []), [attachments, messageAttachmentsMap, messageId], ); const hasReasoningParts = useMemo(() => { const hasThinkPart = content?.some((part) => part?.type === ContentTypes.THINK) ?? false; const allThinkPartsHaveContent = content?.every((part) => { if (part?.type !== ContentTypes.THINK) { return true; } if (typeof part.think === 'string') { const cleanedContent = part.think.replace(/<\/?think>/g, '').trim(); return cleanedContent.length > 0; } return false; }) ?? false; return hasThinkPart && allThinkPartsHaveContent; }, [content]); if (!content) { return null; } if (edit === true && enterEdit && setSiblingIdx) { return ( <> {content.map((part, idx) => { if (part?.type !== ContentTypes.TEXT || typeof part.text !== 'string') { return null; } return ( ); })} ); } return ( <> {hasReasoningParts && (
setIsExpanded((prev) => { const val = !prev; setShowThinking(val); return val; }) } label={ isSubmitting && isLast ? localize('com_ui_thinking') : localize('com_ui_thoughts') } />
)} {content .filter((part) => part) .map((part, idx) => { const toolCallId = (part?.[ContentTypes.TOOL_CALL] as Agents.ToolCall | undefined)?.id ?? ''; const attachments = attachmentMap[toolCallId]; return ( ); })} ); }, ); export default ContentParts;