mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-20 10:20:15 +01:00
* chore: enhance logging for latest message actions in message components
* fix: Extract previous convoId from latest text in message helpers and process hooks
- Updated `useMessageHelpers` and `useMessageProcess` to extract `convoId` from the previous text key for improved message handling.
- Refactored `getLengthAndLastTenChars` to `getLengthAndLastNChars` for better flexibility in character length retrieval.
- Introduced `getLatestContentForKey` function to streamline content extraction from messages.
* chore: Enhance logging for clearing latest messages in conversation hooks
* refactor: Update message key formatting for improved URL parameter handling
- Modified `getLatestContentForKey` to change the format from `${text}-${i}` to `${text}&i=${i}` for better URL parameter structure.
- Adjusted `getTextKey` to increase character length retrieval from 12 to 16 in `getLengthAndLastNChars` for enhanced text processing.
* refactor: Simplify convoId extraction and enhance message formatting
- Updated `useMessageHelpers` and `useMessageProcess` to extract `convoId` using a new format for improved clarity.
- Refactored `getLatestContentForKey` to streamline content formatting and ensure consistent use of `Constants.COMMON_DIVIDER` for better message structure.
- Removed redundant length and last character extraction logic from `getLengthAndLastNChars` for cleaner code.
* chore: linting
* chore: Simplify pre-commit hook by removing unnecessary lines
229 lines
7.7 KiB
TypeScript
229 lines
7.7 KiB
TypeScript
import React, { useCallback, useMemo, memo } from 'react';
|
|
import { useRecoilValue } from 'recoil';
|
|
import { type TMessage } from 'librechat-data-provider';
|
|
import type { TMessageProps, TMessageIcon } from '~/common';
|
|
import MessageContent from '~/components/Chat/Messages/Content/MessageContent';
|
|
import PlaceholderRow from '~/components/Chat/Messages/ui/PlaceholderRow';
|
|
import SiblingSwitch from '~/components/Chat/Messages/SiblingSwitch';
|
|
import HoverButtons from '~/components/Chat/Messages/HoverButtons';
|
|
import MessageIcon from '~/components/Chat/Messages/MessageIcon';
|
|
import { Plugin } from '~/components/Messages/Content';
|
|
import SubRow from '~/components/Chat/Messages/SubRow';
|
|
import { MessageContext } from '~/Providers';
|
|
import { useMessageActions } from '~/hooks';
|
|
import { cn, logger } from '~/utils';
|
|
import store from '~/store';
|
|
|
|
type MessageRenderProps = {
|
|
message?: TMessage;
|
|
isCard?: boolean;
|
|
isMultiMessage?: boolean;
|
|
isSubmittingFamily?: boolean;
|
|
} & Pick<
|
|
TMessageProps,
|
|
'currentEditId' | 'setCurrentEditId' | 'siblingIdx' | 'setSiblingIdx' | 'siblingCount'
|
|
>;
|
|
|
|
const MessageRender = memo(
|
|
({
|
|
message: msg,
|
|
isCard = false,
|
|
siblingIdx,
|
|
siblingCount,
|
|
setSiblingIdx,
|
|
currentEditId,
|
|
isMultiMessage = false,
|
|
setCurrentEditId,
|
|
isSubmittingFamily = false,
|
|
}: MessageRenderProps) => {
|
|
const {
|
|
ask,
|
|
edit,
|
|
index,
|
|
agent,
|
|
assistant,
|
|
enterEdit,
|
|
conversation,
|
|
messageLabel,
|
|
isSubmitting,
|
|
latestMessage,
|
|
handleContinue,
|
|
copyToClipboard,
|
|
setLatestMessage,
|
|
regenerateMessage,
|
|
handleFeedback,
|
|
} = useMessageActions({
|
|
message: msg,
|
|
currentEditId,
|
|
isMultiMessage,
|
|
setCurrentEditId,
|
|
});
|
|
const maximizeChatSpace = useRecoilValue(store.maximizeChatSpace);
|
|
const fontSize = useRecoilValue(store.fontSize);
|
|
|
|
const handleRegenerateMessage = useCallback(() => regenerateMessage(), [regenerateMessage]);
|
|
const hasNoChildren = !(msg?.children?.length ?? 0);
|
|
const isLast = useMemo(
|
|
() => hasNoChildren && (msg?.depth === latestMessage?.depth || msg?.depth === -1),
|
|
[hasNoChildren, msg?.depth, latestMessage?.depth],
|
|
);
|
|
const isLatestMessage = msg?.messageId === latestMessage?.messageId;
|
|
const showCardRender = isLast && !isSubmittingFamily && isCard;
|
|
const isLatestCard = isCard && !isSubmittingFamily && isLatestMessage;
|
|
|
|
/** Only pass isSubmitting to the latest message to prevent unnecessary re-renders */
|
|
const effectiveIsSubmitting = isLatestMessage ? isSubmitting : false;
|
|
|
|
const iconData: TMessageIcon = useMemo(
|
|
() => ({
|
|
endpoint: msg?.endpoint ?? conversation?.endpoint,
|
|
model: msg?.model ?? conversation?.model,
|
|
iconURL: msg?.iconURL,
|
|
modelLabel: messageLabel,
|
|
isCreatedByUser: msg?.isCreatedByUser,
|
|
}),
|
|
[
|
|
messageLabel,
|
|
conversation?.endpoint,
|
|
conversation?.model,
|
|
msg?.model,
|
|
msg?.iconURL,
|
|
msg?.endpoint,
|
|
msg?.isCreatedByUser,
|
|
],
|
|
);
|
|
|
|
const clickHandler = useMemo(
|
|
() =>
|
|
showCardRender && !isLatestMessage
|
|
? () => {
|
|
logger.log(
|
|
'latest_message',
|
|
`Message Card click: Setting ${msg?.messageId} as latest message`,
|
|
);
|
|
logger.dir(msg);
|
|
setLatestMessage(msg!);
|
|
}
|
|
: undefined,
|
|
[showCardRender, isLatestMessage, msg, setLatestMessage],
|
|
);
|
|
|
|
if (!msg) {
|
|
return null;
|
|
}
|
|
|
|
const baseClasses = {
|
|
common: 'group mx-auto flex flex-1 gap-3 transition-all duration-300 transform-gpu ',
|
|
card: 'relative w-full gap-1 rounded-lg border border-border-medium bg-surface-primary-alt p-2 md:w-1/2 md:gap-3 md:p-4',
|
|
chat: maximizeChatSpace
|
|
? 'w-full max-w-full md:px-5 lg:px-1 xl:px-5'
|
|
: 'md:max-w-[47rem] xl:max-w-[55rem]',
|
|
};
|
|
|
|
const conditionalClasses = {
|
|
latestCard: isLatestCard ? 'bg-surface-secondary' : '',
|
|
cardRender: showCardRender ? 'cursor-pointer transition-colors duration-300' : '',
|
|
focus: 'focus:outline-none focus:ring-2 focus:ring-border-xheavy',
|
|
};
|
|
|
|
return (
|
|
<div
|
|
id={msg.messageId}
|
|
aria-label={`message-${msg.depth}-${msg.messageId}`}
|
|
className={cn(
|
|
baseClasses.common,
|
|
isCard ? baseClasses.card : baseClasses.chat,
|
|
conditionalClasses.latestCard,
|
|
conditionalClasses.cardRender,
|
|
conditionalClasses.focus,
|
|
'message-render',
|
|
)}
|
|
onClick={clickHandler}
|
|
onKeyDown={(e) => {
|
|
if ((e.key === 'Enter' || e.key === ' ') && clickHandler) {
|
|
clickHandler();
|
|
}
|
|
}}
|
|
role={showCardRender ? 'button' : undefined}
|
|
tabIndex={showCardRender ? 0 : undefined}
|
|
>
|
|
{isLatestCard && (
|
|
<div className="absolute right-0 top-0 m-2 h-3 w-3 rounded-full bg-text-primary" />
|
|
)}
|
|
|
|
<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">
|
|
<MessageIcon iconData={iconData} assistant={assistant} agent={agent} />
|
|
</div>
|
|
</div>
|
|
|
|
<div
|
|
className={cn(
|
|
'relative flex w-11/12 flex-col',
|
|
msg.isCreatedByUser ? 'user-turn' : 'agent-turn',
|
|
)}
|
|
>
|
|
<h2 className={cn('select-none font-semibold', fontSize)}>{messageLabel}</h2>
|
|
|
|
<div className="flex flex-col gap-1">
|
|
<div className="flex max-w-full flex-grow flex-col gap-0">
|
|
<MessageContext.Provider
|
|
value={{
|
|
messageId: msg.messageId,
|
|
conversationId: conversation?.conversationId,
|
|
isExpanded: false,
|
|
isSubmitting: effectiveIsSubmitting,
|
|
isLatestMessage,
|
|
}}
|
|
>
|
|
{msg.plugin && <Plugin plugin={msg.plugin} />}
|
|
<MessageContent
|
|
ask={ask}
|
|
edit={edit}
|
|
isLast={isLast}
|
|
text={msg.text || ''}
|
|
message={msg}
|
|
enterEdit={enterEdit}
|
|
error={!!(msg.error ?? false)}
|
|
isSubmitting={effectiveIsSubmitting}
|
|
unfinished={msg.unfinished ?? false}
|
|
isCreatedByUser={msg.isCreatedByUser ?? true}
|
|
siblingIdx={siblingIdx ?? 0}
|
|
setSiblingIdx={setSiblingIdx ?? (() => ({}))}
|
|
/>
|
|
</MessageContext.Provider>
|
|
</div>
|
|
|
|
{hasNoChildren && (isSubmittingFamily === true || effectiveIsSubmitting) ? (
|
|
<PlaceholderRow isCard={isCard} />
|
|
) : (
|
|
<SubRow classes="text-xs">
|
|
<SiblingSwitch
|
|
siblingIdx={siblingIdx}
|
|
siblingCount={siblingCount}
|
|
setSiblingIdx={setSiblingIdx}
|
|
/>
|
|
<HoverButtons
|
|
index={index}
|
|
isEditing={edit}
|
|
message={msg}
|
|
enterEdit={enterEdit}
|
|
isSubmitting={isSubmitting}
|
|
conversation={conversation ?? null}
|
|
regenerate={handleRegenerateMessage}
|
|
copyToClipboard={copyToClipboard}
|
|
handleContinue={handleContinue}
|
|
latestMessage={latestMessage}
|
|
handleFeedback={handleFeedback}
|
|
isLast={isLast}
|
|
/>
|
|
</SubRow>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
},
|
|
);
|
|
|
|
export default MessageRender;
|