File size: 3,934 Bytes
9705b6c |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 |
import { Fragment } from 'react';
import type { TResPlugin } from 'librechat-data-provider';
import type { TMessageContent, 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('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-100">
<Error text={text} />
</div>
</Container>
);
};
// Display Message Component
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-70',
)}
>
{!isCreatedByUser ? (
<Markdown content={text} message={message} showCursor={showCursor} />
) : (
<>{text}</>
)}
</div>
</Container>
);
// Unfinished Message Component
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." />
);
// Content Component
const MessageContent = ({
text,
edit,
error,
unfinished,
isSubmitting,
isLast,
...props
}: TMessageContent) => {
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>();
// Function to get the next non-empty text index
const getNextNonEmptyTextIndex = (currentIndex: number) => {
for (let i = currentIndex + 1; i < splitText.length; i++) {
// Allow the last index to be last in case it has text
// this may need to change if I add back streaming
if (i === splitText.length - 1) {
return currentIndex;
}
if (splitText[i].trim() !== '' && !displayedIndices.has(i)) {
return i;
}
}
return currentIndex; // If no non-empty text is found, return the current index
};
return splitText.map((text, idx) => {
let currentText = text.trim();
let plugin: TResPlugin | null = null;
if (plugins) {
plugin = plugins[idx];
}
// If the current text is empty, get the next non-empty text index
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;
|