| import { Fragment } from 'react'; |
| import { ViolationTypes } from 'librechat-data-provider'; |
| import type { TResPlugin } from 'librechat-data-provider'; |
| import type { TMessageContentProps, TText, TDisplayProps } from '~/common'; |
| import { useAuthContext } from '~/hooks'; |
| import { cn } from '~/utils'; |
| import EditMessage from './EditMessage'; |
| import Container from './Container'; |
| import Markdown from './Markdown'; |
| import Plugin from './Plugin'; |
| import Error from './Error'; |
|
|
| const ErrorMessage = ({ text }: TText) => { |
| const { logout } = useAuthContext(); |
|
|
| if (text.includes(ViolationTypes.BAN)) { |
| logout(); |
| return null; |
| } |
| return ( |
| <Container> |
| <div className="rounded-md border border-red-500 bg-red-500/10 px-3 py-2 text-sm text-gray-600 dark:text-gray-200"> |
| <Error text={text} /> |
| </div> |
| </Container> |
| ); |
| }; |
|
|
| |
| const DisplayMessage = ({ text, isCreatedByUser, message, showCursor }: TDisplayProps) => ( |
| <Container> |
| <div |
| className={cn( |
| 'markdown prose dark:prose-invert light w-full break-words', |
| isCreatedByUser ? 'whitespace-pre-wrap dark:text-gray-20' : 'dark:text-gray-100', |
| )} |
| > |
| {!isCreatedByUser ? ( |
| <Markdown content={text} message={message} showCursor={showCursor} /> |
| ) : ( |
| <>{text}</> |
| )} |
| </div> |
| </Container> |
| ); |
|
|
| |
| const UnfinishedMessage = () => ( |
| <ErrorMessage text="This is an unfinished message. The AI may still be generating a response, it was aborted, or a censor was triggered. Refresh or visit later to see more updates." /> |
| ); |
|
|
| |
| const MessageContent = ({ |
| text, |
| edit, |
| error, |
| unfinished, |
| isSubmitting, |
| isLast, |
| ...props |
| }: TMessageContentProps) => { |
| if (error) { |
| return <ErrorMessage text={text} />; |
| } else if (edit) { |
| return <EditMessage text={text} isSubmitting={isSubmitting} {...props} />; |
| } else { |
| const marker = ':::plugin:::\n'; |
| const splitText = text.split(marker); |
| const { message } = props; |
| const { plugins, messageId } = message; |
| const displayedIndices = new Set<number>(); |
| |
| const getNextNonEmptyTextIndex = (currentIndex: number) => { |
| for (let i = currentIndex + 1; i < splitText.length; i++) { |
| |
| |
| if (i === splitText.length - 1) { |
| return currentIndex; |
| } |
|
|
| if (splitText[i].trim() !== '' && !displayedIndices.has(i)) { |
| return i; |
| } |
| } |
| return currentIndex; |
| }; |
|
|
| return splitText.map((text, idx) => { |
| let currentText = text.trim(); |
| let plugin: TResPlugin | null = null; |
|
|
| if (plugins) { |
| plugin = plugins[idx]; |
| } |
|
|
| |
| const displayTextIndex = currentText === '' ? getNextNonEmptyTextIndex(idx) : idx; |
| currentText = splitText[displayTextIndex]; |
| const isLastIndex = displayTextIndex === splitText.length - 1; |
| const isEmpty = currentText.trim() === ''; |
| const showText = |
| (currentText && !isEmpty && !displayedIndices.has(displayTextIndex)) || |
| (isEmpty && isLastIndex); |
| displayedIndices.add(displayTextIndex); |
|
|
| return ( |
| <Fragment key={idx}> |
| {plugin && <Plugin key={`plugin-${messageId}-${idx}`} plugin={plugin} />} |
| {showText ? ( |
| <DisplayMessage |
| key={`display-${messageId}-${idx}`} |
| showCursor={isLastIndex && isLast} |
| text={currentText} |
| {...props} |
| /> |
| ) : null} |
| {!isSubmitting && unfinished && ( |
| <UnfinishedMessage key={`unfinished-${messageId}-${idx}`} /> |
| )} |
| </Fragment> |
| ); |
| }); |
| } |
| }; |
|
|
| export default MessageContent; |
|
|