diff --git a/client/src/components/Chat/Messages/Content/Container.tsx b/client/src/components/Chat/Messages/Content/Container.tsx
index cbd085e308..ecc40d6cd1 100644
--- a/client/src/components/Chat/Messages/Content/Container.tsx
+++ b/client/src/components/Chat/Messages/Content/Container.tsx
@@ -1,12 +1,12 @@
import { TMessage } from 'librechat-data-provider';
import Files from './Files';
-const Container = ({ children, message }: { children: React.ReactNode; message: TMessage }) => (
+const Container = ({ children, message }: { children: React.ReactNode; message?: TMessage }) => (
- {message.isCreatedByUser && }
+ {message?.isCreatedByUser === true && }
{children}
);
diff --git a/client/src/components/Chat/Messages/Content/ContentParts.tsx b/client/src/components/Chat/Messages/Content/ContentParts.tsx
index 3227bce072..d73377241e 100644
--- a/client/src/components/Chat/Messages/Content/ContentParts.tsx
+++ b/client/src/components/Chat/Messages/Content/ContentParts.tsx
@@ -1,51 +1,34 @@
-import { Suspense } from 'react';
+import { memo } from 'react';
import type { TMessageContentParts } from 'librechat-data-provider';
-import { UnfinishedMessage } from './MessageContent';
-import { DelayedRender } from '~/components/ui';
import Part from './Part';
-const ContentParts = ({
- error,
- unfinished,
- isSubmitting,
- isLast,
- content,
- ...props
-}: // eslint-disable-next-line @typescript-eslint/no-explicit-any
-any) => {
- if (error) {
- // return ;
- } else {
- const { message } = props;
- const { messageId } = message;
+type ContentPartsProps = {
+ content: Array;
+ messageId: string;
+ isCreatedByUser: boolean;
+ isLast: boolean;
+ isSubmitting: boolean;
+};
+const ContentParts = memo(
+ ({ content, messageId, isCreatedByUser, isLast, isSubmitting }: ContentPartsProps) => {
return (
<>
{content
- .filter((part: TMessageContentParts | undefined) => part)
- .map((part: TMessageContentParts | undefined, idx: number) => {
- const showCursor = idx === content.length - 1 && isLast;
- return (
-
- );
- })}
- {/* Temporarily remove this */}
- {/* {!isSubmitting && unfinished && (
-
-
-
-
-
- )} */}
+ .filter((part) => part)
+ .map((part, idx) => (
+
+ ))}
>
);
- }
-};
+ },
+);
export default ContentParts;
diff --git a/client/src/components/Chat/Messages/Content/Files.tsx b/client/src/components/Chat/Messages/Content/Files.tsx
index beff81b58b..09801d92c1 100644
--- a/client/src/components/Chat/Messages/Content/Files.tsx
+++ b/client/src/components/Chat/Messages/Content/Files.tsx
@@ -3,7 +3,7 @@ import type { TFile, TMessage } from 'librechat-data-provider';
import FileContainer from '~/components/Chat/Input/Files/FileContainer';
import Image from './Image';
-const Files = ({ message }: { message: TMessage }) => {
+const Files = ({ message }: { message?: TMessage }) => {
const imageFiles = useMemo(() => {
return message?.files?.filter((file) => file.type?.startsWith('image/')) || [];
}, [message?.files]);
@@ -20,7 +20,7 @@ const Files = ({ message }: { message: TMessage }) => {
imageFiles.map((file) => (
) => {
+}: Pick & {
+ message?: TMessage;
+}) => {
const localize = useLocalize();
if (text === 'Error connecting to server, try refreshing the page.') {
console.log('error message', message);
diff --git a/client/src/components/Chat/Messages/Content/Part.tsx b/client/src/components/Chat/Messages/Content/Part.tsx
index 7fcc9dbe4a..457e79b9d1 100644
--- a/client/src/components/Chat/Messages/Content/Part.tsx
+++ b/client/src/components/Chat/Messages/Content/Part.tsx
@@ -4,81 +4,43 @@ import {
imageGenTools,
isImageVisionTool,
} from 'librechat-data-provider';
-import { useMemo } from 'react';
-import type { TMessageContentParts, TMessage } from 'librechat-data-provider';
-import type { TDisplayProps } from '~/common';
+import { memo } from 'react';
+import type { TMessageContentParts } from 'librechat-data-provider';
import { ErrorMessage } from './MessageContent';
-import { useChatContext } from '~/Providers';
import RetrievalCall from './RetrievalCall';
import CodeAnalyze from './CodeAnalyze';
import Container from './Container';
import ToolCall from './ToolCall';
-import Markdown from './Markdown';
import ImageGen from './ImageGen';
-import { cn } from '~/utils';
+import Text from './Parts/Text';
import Image from './Image';
-// Display Message Component
-const DisplayMessage = ({ text, isCreatedByUser = false, message, showCursor }: TDisplayProps) => {
- const { isSubmitting, latestMessage } = useChatContext();
- const showCursorState = useMemo(
- () => showCursor === true && isSubmitting,
- [showCursor, isSubmitting],
- );
- const isLatestMessage = useMemo(
- () => message.messageId === latestMessage?.messageId,
- [message.messageId, latestMessage?.messageId],
- );
-
- // Note: for testing purposes
- // isSubmitting && isLatestMessage && logger.log('message_stream', { text, isCreatedByUser, isSubmitting, showCursorState });
-
- return (
-
- {!isCreatedByUser ? (
-
- ) : (
- <>{text}>
- )}
-
- );
-};
-
-export default function Part({
- part,
- showCursor,
- isSubmitting,
- message,
-}: {
- part: TMessageContentParts | undefined;
+type PartProps = {
+ part?: TMessageContentParts;
isSubmitting: boolean;
showCursor: boolean;
- message: TMessage;
-}) {
+ messageId: string;
+ isCreatedByUser: boolean;
+};
+
+const Part = memo(({ part, isSubmitting, showCursor, messageId, isCreatedByUser }: PartProps) => {
if (!part) {
return null;
}
if (part.type === ContentTypes.ERROR) {
- return ;
+ return ;
} else if (part.type === ContentTypes.TEXT) {
const text = typeof part.text === 'string' ? part.text : part.text.value;
if (typeof text !== 'string') {
return null;
}
return (
-
-
+
@@ -132,11 +94,11 @@ export default function Part({
if (isImageVisionTool(toolCall)) {
if (isSubmitting && showCursor) {
return (
-
-
+
@@ -174,4 +136,6 @@ export default function Part({
}
return null;
-}
+});
+
+export default Part;
diff --git a/client/src/components/Chat/Messages/Content/Parts/Text.tsx b/client/src/components/Chat/Messages/Content/Parts/Text.tsx
new file mode 100644
index 0000000000..7d0b386c81
--- /dev/null
+++ b/client/src/components/Chat/Messages/Content/Parts/Text.tsx
@@ -0,0 +1,39 @@
+import { memo, useMemo } from 'react';
+import { useChatContext } from '~/Providers';
+import Markdown from '~/components/Chat/Messages/Content/Markdown';
+import { cn } from '~/utils';
+
+type TextPartProps = {
+ text: string;
+ isCreatedByUser: boolean;
+ messageId: string;
+ showCursor: boolean;
+};
+
+const TextPart = memo(({ text, isCreatedByUser, messageId, showCursor }: TextPartProps) => {
+ const { isSubmitting, latestMessage } = useChatContext();
+ const showCursorState = useMemo(() => showCursor && isSubmitting, [showCursor, isSubmitting]);
+ const isLatestMessage = useMemo(
+ () => messageId === latestMessage?.messageId,
+ [messageId, latestMessage?.messageId],
+ );
+
+ return (
+
+ {!isCreatedByUser ? (
+
+ ) : (
+ <>{text}>
+ )}
+
+ );
+});
+
+export default TextPart;
diff --git a/client/src/components/Chat/Messages/MessageParts.tsx b/client/src/components/Chat/Messages/MessageParts.tsx
index 37794c44f5..52a2dbe2dd 100644
--- a/client/src/components/Chat/Messages/MessageParts.tsx
+++ b/client/src/components/Chat/Messages/MessageParts.tsx
@@ -1,4 +1,5 @@
import { useRecoilValue } from 'recoil';
+import type { TMessageContentParts } from 'librechat-data-provider';
import type { TMessageProps } from '~/common';
import Icon from '~/components/Chat/Messages/MessageIcon';
import { useMessageHelpers, useLocalize } from '~/hooks';
@@ -17,7 +18,6 @@ export default function Message(props: TMessageProps) {
props;
const {
- ask,
edit,
index,
agent,
@@ -33,7 +33,7 @@ export default function Message(props: TMessageProps) {
regenerateMessage,
} = useMessageHelpers(props);
const fontSize = useRecoilValue(store.fontSize);
- const { content, children, messageId = null, isCreatedByUser, error, unfinished } = message ?? {};
+ const { children, messageId = null, isCreatedByUser } = message ?? {};
if (!message) {
return null;
@@ -82,24 +82,11 @@ export default function Message(props: TMessageProps) {
}
+ messageId={message.messageId}
+ isCreatedByUser={message.isCreatedByUser}
isLast={isLast}
- content={content ?? []}
- message={message}
- messageId={messageId}
- enterEdit={enterEdit}
- error={!!(error ?? false)}
isSubmitting={isSubmitting}
- unfinished={unfinished ?? false}
- isCreatedByUser={isCreatedByUser ?? true}
- siblingIdx={siblingIdx ?? 0}
- setSiblingIdx={
- setSiblingIdx ??
- (() => {
- return;
- })
- }
/>
diff --git a/client/src/components/Chat/Messages/MultiMessage.tsx b/client/src/components/Chat/Messages/MultiMessage.tsx
index 0e3204f392..eb2d2e9257 100644
--- a/client/src/components/Chat/Messages/MultiMessage.tsx
+++ b/client/src/components/Chat/Messages/MultiMessage.tsx
@@ -1,10 +1,14 @@
import { useRecoilState } from 'recoil';
import { useEffect, useCallback } from 'react';
+import { isAssistantsEndpoint } from 'librechat-data-provider';
+import type { TMessage } from 'librechat-data-provider';
import type { TMessageProps } from '~/common';
// eslint-disable-next-line import/no-cycle
-import Message from './Message';
+import MessageContent from '~/components/Messages/MessageContent';
// eslint-disable-next-line import/no-cycle
import MessageParts from './MessageParts';
+// eslint-disable-next-line import/no-cycle
+import Message from './Message';
import store from '~/store';
export default function MultiMessage({
@@ -30,22 +34,22 @@ export default function MultiMessage({
}, [messagesTree?.length]);
useEffect(() => {
- if (messagesTree?.length && siblingIdx >= messagesTree?.length) {
+ if (messagesTree?.length && siblingIdx >= messagesTree.length) {
setSiblingIdx(0);
}
}, [siblingIdx, messagesTree?.length, setSiblingIdx]);
- if (!(messagesTree && messagesTree?.length)) {
+ if (!(messagesTree && messagesTree.length)) {
return null;
}
- const message = messagesTree[messagesTree.length - siblingIdx - 1];
+ const message = messagesTree[messagesTree.length - siblingIdx - 1] as TMessage | undefined;
if (!message) {
return null;
}
- if (message.content) {
+ if (isAssistantsEndpoint(message.endpoint) && message.content) {
return (
);
+ } else if (message.content) {
+ return (
+
+ );
}
return (
diff --git a/client/src/components/Messages/ContentRender.tsx b/client/src/components/Messages/ContentRender.tsx
new file mode 100644
index 0000000000..d3f58942b5
--- /dev/null
+++ b/client/src/components/Messages/ContentRender.tsx
@@ -0,0 +1,164 @@
+import { useRecoilValue } from 'recoil';
+import { useCallback, useMemo, memo } from 'react';
+import type { TMessage, TMessageContentParts } from 'librechat-data-provider';
+import type { TMessageProps } from '~/common';
+import ContentParts from '~/components/Chat/Messages/Content/ContentParts';
+import PlaceholderRow from '~/components/Chat/Messages/ui/PlaceholderRow';
+import SiblingSwitch from '~/components/Chat/Messages/SiblingSwitch';
+import HoverButtons from '~/components/Chat/Messages/HoverButtons';
+import Icon from '~/components/Chat/Messages/MessageIcon';
+import SubRow from '~/components/Chat/Messages/SubRow';
+import { useMessageActions } from '~/hooks';
+import { cn, logger } from '~/utils';
+import store from '~/store';
+
+type ContentRenderProps = {
+ message?: TMessage;
+ isCard?: boolean;
+ isMultiMessage?: boolean;
+ isSubmittingFamily?: boolean;
+} & Pick<
+ TMessageProps,
+ 'currentEditId' | 'setCurrentEditId' | 'siblingIdx' | 'setSiblingIdx' | 'siblingCount'
+>;
+
+const ContentRender = memo(
+ ({
+ isCard,
+ siblingIdx,
+ siblingCount,
+ message: msg,
+ setSiblingIdx,
+ currentEditId,
+ isMultiMessage,
+ setCurrentEditId,
+ isSubmittingFamily,
+ }: ContentRenderProps) => {
+ const {
+ ask,
+ edit,
+ index,
+ assistant,
+ enterEdit,
+ conversation,
+ messageLabel,
+ isSubmitting,
+ latestMessage,
+ handleContinue,
+ copyToClipboard,
+ setLatestMessage,
+ regenerateMessage,
+ } = useMessageActions({
+ message: msg,
+ currentEditId,
+ isMultiMessage,
+ setCurrentEditId,
+ });
+
+ const fontSize = useRecoilValue(store.fontSize);
+ const handleRegenerateMessage = useCallback(() => regenerateMessage(), [regenerateMessage]);
+ // const { isCreatedByUser, error, unfinished } = msg ?? {};
+ const isLast = useMemo(
+ () =>
+ !(msg?.children?.length ?? 0) && (msg?.depth === latestMessage?.depth || msg?.depth === -1),
+ [msg?.children, msg?.depth, latestMessage?.depth],
+ );
+
+ if (!msg) {
+ return null;
+ }
+
+ const isLatestMessage = msg.messageId === latestMessage?.messageId;
+ const showCardRender = isLast && !(isSubmittingFamily === true) && isCard === true;
+ const isLatestCard = isCard === true && !(isSubmittingFamily === true) && isLatestMessage;
+ const clickHandler =
+ showCardRender && !isLatestMessage
+ ? () => {
+ logger.log(`Message Card click: Setting ${msg.messageId} as latest message`);
+ logger.dir(msg);
+ setLatestMessage(msg);
+ }
+ : undefined;
+
+ return (
+ {
+ if ((e.key === 'Enter' || e.key === ' ') && clickHandler) {
+ clickHandler();
+ }
+ }}
+ role={showCardRender ? 'button' : undefined}
+ tabIndex={showCardRender ? 0 : undefined}
+ >
+ {isLatestCard === true && (
+
+ )}
+
+
+
{messageLabel}
+
+
+ }
+ messageId={msg.messageId}
+ isCreatedByUser={msg.isCreatedByUser}
+ isLast={isLast}
+ isSubmitting={isSubmitting}
+ />
+
+
+ {!(msg.children?.length ?? 0) && (isSubmittingFamily === true || isSubmitting) ? (
+
+ ) : (
+
+
+
+
+ )}
+
+
+ );
+ },
+);
+
+export default ContentRender;
diff --git a/client/src/components/Messages/MessageContent.tsx b/client/src/components/Messages/MessageContent.tsx
new file mode 100644
index 0000000000..e472b1e8dd
--- /dev/null
+++ b/client/src/components/Messages/MessageContent.tsx
@@ -0,0 +1,82 @@
+import React from 'react';
+import { useMessageProcess } from '~/hooks';
+import type { TMessageProps } from '~/common';
+// eslint-disable-next-line import/no-cycle
+import MultiMessage from '~/components/Chat/Messages/MultiMessage';
+import ContentRender from './ContentRender';
+
+const MessageContainer = React.memo(
+ ({
+ handleScroll,
+ children,
+ }: {
+ handleScroll: (event?: unknown) => void;
+ children: React.ReactNode;
+ }) => {
+ return (
+
+ {children}
+
+ );
+ },
+);
+
+export default function MessageContent(props: TMessageProps) {
+ const {
+ showSibling,
+ conversation,
+ handleScroll,
+ siblingMessage,
+ latestMultiMessage,
+ isSubmittingFamily,
+ } = useMessageProcess({ message: props.message });
+ const { message, currentEditId, setCurrentEditId } = props;
+
+ if (!message || typeof message !== 'object') {
+ return null;
+ }
+
+ const { children, messageId = null } = message;
+
+ return (
+ <>
+
+ {showSibling ? (
+
+ ) : (
+
+
+
+ )}
+
+
+ >
+ );
+}