| import React, { useMemo } from 'react'; |
| import { useAtomValue } from 'jotai'; |
| import { useRecoilValue } from 'recoil'; |
| import type { TMessageContentParts } from 'librechat-data-provider'; |
| import type { TMessageProps, TMessageIcon } from '~/common'; |
| import { useMessageHelpers, useLocalize, useAttachments } from '~/hooks'; |
| import MessageIcon from '~/components/Chat/Messages/MessageIcon'; |
| import ContentParts from './Content/ContentParts'; |
| import { fontSizeAtom } from '~/store/fontSize'; |
| import SiblingSwitch from './SiblingSwitch'; |
| import MultiMessage from './MultiMessage'; |
| import HoverButtons from './HoverButtons'; |
| import SubRow from './SubRow'; |
| import { cn } from '~/utils'; |
| import store from '~/store'; |
|
|
| export default function Message(props: TMessageProps) { |
| const localize = useLocalize(); |
| const { message, siblingIdx, siblingCount, setSiblingIdx, currentEditId, setCurrentEditId } = |
| props; |
| const { attachments, searchResults } = useAttachments({ |
| messageId: message?.messageId, |
| attachments: message?.attachments, |
| }); |
| const { |
| edit, |
| index, |
| agent, |
| isLast, |
| enterEdit, |
| assistant, |
| handleScroll, |
| conversation, |
| isSubmitting, |
| latestMessage, |
| handleContinue, |
| copyToClipboard, |
| regenerateMessage, |
| } = useMessageHelpers(props); |
|
|
| const fontSize = useAtomValue(fontSizeAtom); |
| const maximizeChatSpace = useRecoilValue(store.maximizeChatSpace); |
| const { children, messageId = null, isCreatedByUser } = message ?? {}; |
|
|
| const name = useMemo(() => { |
| let result = ''; |
| if (isCreatedByUser === true) { |
| result = localize('com_user_message'); |
| } else if (assistant) { |
| result = assistant.name ?? localize('com_ui_assistant'); |
| } else if (agent) { |
| result = agent.name ?? localize('com_ui_agent'); |
| } |
|
|
| return result; |
| }, [assistant, agent, isCreatedByUser, localize]); |
|
|
| const iconData: TMessageIcon = useMemo( |
| () => ({ |
| endpoint: message?.endpoint ?? conversation?.endpoint, |
| model: message?.model ?? conversation?.model, |
| iconURL: message?.iconURL ?? conversation?.iconURL, |
| modelLabel: name, |
| isCreatedByUser: message?.isCreatedByUser, |
| }), |
| [ |
| name, |
| conversation?.endpoint, |
| conversation?.iconURL, |
| conversation?.model, |
| message?.model, |
| message?.iconURL, |
| message?.endpoint, |
| message?.isCreatedByUser, |
| ], |
| ); |
|
|
| if (!message) { |
| return null; |
| } |
|
|
| const baseClasses = { |
| common: 'group mx-auto flex flex-1 gap-3 transition-all duration-300 transform-gpu', |
| chat: maximizeChatSpace |
| ? 'w-full max-w-full md:px-5 lg:px-1 xl:px-5' |
| : 'md:max-w-[47rem] xl:max-w-[55rem]', |
| }; |
|
|
| return ( |
| <> |
| <div |
| className="w-full border-0 bg-transparent dark:border-0 dark:bg-transparent" |
| onWheel={handleScroll} |
| onTouchMove={handleScroll} |
| > |
| <div className="m-auto justify-center p-4 py-2 md:gap-6"> |
| <div |
| id={messageId ?? ''} |
| aria-label={`message-${message.depth}-${messageId}`} |
| className={cn(baseClasses.common, baseClasses.chat, 'message-render')} |
| > |
| <div className="relative flex flex-shrink-0 flex-col items-center"> |
| <div className="flex h-6 w-6 items-center justify-center overflow-hidden rounded-full pt-0.5"> |
| <MessageIcon iconData={iconData} assistant={assistant} agent={agent} /> |
| </div> |
| </div> |
| <div |
| className={cn( |
| 'relative flex w-11/12 flex-col', |
| isCreatedByUser ? 'user-turn' : 'agent-turn', |
| )} |
| > |
| <h2 className={cn('select-none font-semibold text-text-primary', fontSize)}> |
| {name} |
| </h2> |
| <div className="flex flex-col gap-1"> |
| <div className="flex max-w-full flex-grow flex-col gap-0"> |
| <ContentParts |
| edit={edit} |
| isLast={isLast} |
| enterEdit={enterEdit} |
| siblingIdx={siblingIdx} |
| attachments={attachments} |
| isSubmitting={isSubmitting} |
| searchResults={searchResults} |
| messageId={message.messageId} |
| setSiblingIdx={setSiblingIdx} |
| isCreatedByUser={message.isCreatedByUser} |
| conversationId={conversation?.conversationId} |
| isLatestMessage={messageId === latestMessage?.messageId} |
| content={message.content as Array<TMessageContentParts | undefined>} |
| /> |
| </div> |
| {isLast && isSubmitting ? ( |
| <div className="mt-1 h-[27px] bg-transparent" /> |
| ) : ( |
| <SubRow classes="text-xs"> |
| <SiblingSwitch |
| siblingIdx={siblingIdx} |
| siblingCount={siblingCount} |
| setSiblingIdx={setSiblingIdx} |
| /> |
| <HoverButtons |
| index={index} |
| isEditing={edit} |
| message={message} |
| enterEdit={enterEdit} |
| isSubmitting={isSubmitting} |
| conversation={conversation ?? null} |
| regenerate={() => regenerateMessage()} |
| copyToClipboard={copyToClipboard} |
| handleContinue={handleContinue} |
| latestMessage={latestMessage} |
| isLast={isLast} |
| /> |
| </SubRow> |
| )} |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
| <MultiMessage |
| key={messageId} |
| messageId={messageId} |
| conversation={conversation} |
| messagesTree={children ?? []} |
| currentEditId={currentEditId} |
| setCurrentEditId={setCurrentEditId} |
| /> |
| </> |
| ); |
| } |
| |