apps/chat/src/components/Chat/ChatMessage/ChatMessageContent/ChatMessageContent.tsx (151 lines of code) (raw):

import { IconUser } from '@tabler/icons-react'; import { MouseEvent, RefObject, useRef } from 'react'; import classNames from 'classnames'; import { isSmallScreen } from '@/src/utils/app/mobile'; import { Conversation } from '@/src/types/chat'; import { useAppSelector } from '@/src/store/hooks'; import { ModelsSelectors } from '@/src/store/models/models.reducers'; import { SettingsSelectors } from '@/src/store/settings/settings.reducers'; import { UISelectors } from '@/src/store/ui/ui.reducers'; import { AssistantMessage } from '@/src/components/Chat/ChatMessage/ChatMessageContent/AssistantMessage'; import { UserMessage } from '@/src/components/Chat/ChatMessage/ChatMessageContent/UserMessage'; import { ModelIcon } from '@/src/components/Chatbar/ModelIcon'; import { LikeState, Message, Role } from '@epam/ai-dial-shared'; export interface Props { message: Message; messageIndex: number; conversation: Conversation; isLikesEnabled: boolean; isEditing: boolean; isLastMessage: boolean; toggleEditing: (value: boolean) => void; isEditingTemplates: boolean; toggleEditingTemplates: (value: boolean) => void; messageCopied?: boolean; editDisabled?: boolean; onRegenerate?: () => void; onEdit?: (editedMessage: Message, index: number) => void; onCopy?: () => void; onLike?: (likeStatus: LikeState) => void; onDelete?: () => void; onClick?: ( e: MouseEvent<HTMLDivElement>, messageRef: RefObject<HTMLDivElement>, ) => void; withButtons?: boolean; } const OVERLAY_ICON_SIZE = 18; const MOBILE_ICON_SIZE = 20; const DEFAULT_ICON_SIZE = 28; export function ChatMessageContent({ messageIndex, isLastMessage, message, conversation, onEdit, editDisabled, onLike, isLikesEnabled, onDelete, onClick, messageCopied, onCopy, isEditing, toggleEditing, isEditingTemplates, toggleEditingTemplates, withButtons, onRegenerate, }: Props) { const modelsMap = useAppSelector(ModelsSelectors.selectModelsMap); const isChatFullWidth = useAppSelector(UISelectors.selectIsChatFullWidth); const isOverlay = useAppSelector(SettingsSelectors.selectIsOverlay); const messageRef = useRef<HTMLDivElement>(null); const isAssistant = message.role === Role.Assistant; const isShowResponseLoader: boolean = !!conversation.isMessageStreaming && isLastMessage; const isUser = message.role === Role.User; const chatIconSize = isOverlay ? OVERLAY_ICON_SIZE : isSmallScreen() ? MOBILE_ICON_SIZE : DEFAULT_ICON_SIZE; const isMobileOrOverlay = isSmallScreen() || isOverlay; return ( <div ref={messageRef} className={classNames( 'group h-full border-b border-secondary md:px-4 xl:px-8', isAssistant && 'bg-layer-2', )} style={{ overflowWrap: 'anywhere' }} data-qa="chat-message" onClick={(e) => { if (!conversation.isMessageStreaming) { onClick?.(e, messageRef); } }} > <div className={classNames( 'm-auto flex h-full md:gap-6 md:py-6 lg:px-0', !isChatFullWidth && 'md:max-w-2xl xl:max-w-3xl', isMobileOrOverlay ? 'p-3' : 'p-4', )} > <div className="font-bold" data-qa="message-icon"> <div className={classNames( 'flex justify-center', isMobileOrOverlay ? 'mr-2.5' : 'mx-2.5', )} > {isAssistant ? ( <ModelIcon entityId={message.model?.id ?? conversation.model.id} entity={ (message.model?.id && modelsMap[message.model?.id]) || undefined } animate={isShowResponseLoader} size={chatIconSize} /> ) : ( <IconUser size={chatIconSize} /> )} </div> </div> <div className="mt-[-2px] w-full min-w-0 shrink" data-qa="message-content" > {isUser ? ( <UserMessage message={message} conversation={conversation} messageIndex={messageIndex} isEditing={isEditing} isEditingTemplates={isEditingTemplates} toggleEditing={toggleEditing} toggleEditingTemplates={toggleEditingTemplates} withButtons={withButtons} editDisabled={editDisabled} onEdit={onEdit} onDelete={onDelete} /> ) : ( <AssistantMessage message={message} conversation={conversation} isLastMessage={isLastMessage} isLikesEnabled={isLikesEnabled} withButtons={withButtons} messageCopied={messageCopied} onCopy={onCopy} onLike={onLike} onRegenerate={onRegenerate} /> )} </div> </div> </div> ); }