mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-18 09:20:15 +01:00
🔧 refactor: Reduce Complexity of Initial Load Effect & Message Styling (#3213)
* move shared conditions and early bail to reduce cognitive complexity, improve readability * refactor: make condition as close to the original as possible * chore: adjust comment in chat route * style: fix original styling of non-multi messages * refactor: separate messagerender logic from 'Message' --------- Co-authored-by: RehaS <beratson@gmail.com>
This commit is contained in:
parent
791b0139bc
commit
ed5ee1f86f
4 changed files with 179 additions and 181 deletions
|
|
@ -1,16 +1,9 @@
|
||||||
import React, { useCallback, useMemo } from 'react';
|
import React from 'react';
|
||||||
import { useMessageProcess, useMessageActions } from '~/hooks';
|
import { useMessageProcess } from '~/hooks';
|
||||||
import type { TMessage } from 'librechat-data-provider';
|
|
||||||
import type { TMessageProps } from '~/common';
|
import type { TMessageProps } from '~/common';
|
||||||
import Icon from '~/components/Chat/Messages/MessageIcon';
|
import MessageRender from './ui/MessageRender';
|
||||||
import { Plugin } from '~/components/Messages/Content';
|
|
||||||
import MessageContent from './Content/MessageContent';
|
|
||||||
import SiblingSwitch from './SiblingSwitch';
|
|
||||||
// eslint-disable-next-line import/no-cycle
|
// eslint-disable-next-line import/no-cycle
|
||||||
import MultiMessage from './MultiMessage';
|
import MultiMessage from './MultiMessage';
|
||||||
import HoverButtons from './HoverButtons';
|
|
||||||
import SubRow from './SubRow';
|
|
||||||
import { cn } from '~/utils';
|
|
||||||
|
|
||||||
const MessageContainer = React.memo(
|
const MessageContainer = React.memo(
|
||||||
({ handleScroll, children }: { handleScroll: () => void; children: React.ReactNode }) => {
|
({ handleScroll, children }: { handleScroll: () => void; children: React.ReactNode }) => {
|
||||||
|
|
@ -26,155 +19,6 @@ const MessageContainer = React.memo(
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const PlaceholderRow = React.memo(({ isLast, isCard }: { isLast: boolean; isCard?: boolean }) => {
|
|
||||||
if (!isCard) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (!isLast) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return <div className="mt-1 h-[27px] bg-transparent" />;
|
|
||||||
});
|
|
||||||
|
|
||||||
type MessageRenderProps = {
|
|
||||||
message?: TMessage;
|
|
||||||
isCard?: boolean;
|
|
||||||
isMultiMessage?: boolean;
|
|
||||||
isSubmittingFamily?: boolean;
|
|
||||||
} & Pick<
|
|
||||||
TMessageProps,
|
|
||||||
'currentEditId' | 'setCurrentEditId' | 'siblingIdx' | 'setSiblingIdx' | 'siblingCount'
|
|
||||||
>;
|
|
||||||
|
|
||||||
const MessageRender = React.memo(
|
|
||||||
({
|
|
||||||
isCard,
|
|
||||||
siblingIdx,
|
|
||||||
siblingCount,
|
|
||||||
message: msg,
|
|
||||||
setSiblingIdx,
|
|
||||||
currentEditId,
|
|
||||||
isMultiMessage,
|
|
||||||
setCurrentEditId,
|
|
||||||
isSubmittingFamily,
|
|
||||||
}: MessageRenderProps) => {
|
|
||||||
const {
|
|
||||||
ask,
|
|
||||||
edit,
|
|
||||||
index,
|
|
||||||
assistant,
|
|
||||||
enterEdit,
|
|
||||||
conversation,
|
|
||||||
messageLabel,
|
|
||||||
isSubmitting,
|
|
||||||
latestMessage,
|
|
||||||
handleContinue,
|
|
||||||
copyToClipboard,
|
|
||||||
setLatestMessage,
|
|
||||||
regenerateMessage,
|
|
||||||
} = useMessageActions({
|
|
||||||
message: msg,
|
|
||||||
currentEditId,
|
|
||||||
isMultiMessage,
|
|
||||||
setCurrentEditId,
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleRegenerateMessage = useCallback(() => regenerateMessage(), [regenerateMessage]);
|
|
||||||
const { isCreatedByUser, error, unfinished } = msg ?? {};
|
|
||||||
const isLast = useMemo(
|
|
||||||
() => !msg?.children?.length && (msg?.depth === latestMessage?.depth || msg?.depth === -1),
|
|
||||||
[msg?.children, msg?.depth, latestMessage?.depth],
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!msg) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const isLatest = isCard && !isSubmittingFamily && msg.messageId === latestMessage?.messageId;
|
|
||||||
const clickHandler =
|
|
||||||
isLast && isCard && !isSubmittingFamily && msg.messageId !== latestMessage?.messageId
|
|
||||||
? () => setLatestMessage(msg)
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
'final-completion group mx-auto flex flex-1 gap-3 text-base',
|
|
||||||
isCard
|
|
||||||
? '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'
|
|
||||||
: 'md:max-w-3xl lg:max-w-[40rem] xl:max-w-[48rem]',
|
|
||||||
isLatest ? 'bg-surface-secondary' : '',
|
|
||||||
isLast && !isSubmittingFamily && isCard
|
|
||||||
? 'cursor-pointer transition-colors duration-300'
|
|
||||||
: '',
|
|
||||||
)}
|
|
||||||
onClick={clickHandler}
|
|
||||||
>
|
|
||||||
{isLatest && (
|
|
||||||
<div className="absolute right-0 top-0 m-2 h-3 w-3 rounded-full bg-text-primary"></div>
|
|
||||||
)}
|
|
||||||
<div className="relative flex flex-shrink-0 flex-col items-end">
|
|
||||||
<div>
|
|
||||||
<div className="pt-0.5">
|
|
||||||
<div className="flex h-6 w-6 items-center justify-center overflow-hidden rounded-full">
|
|
||||||
<Icon message={msg} conversation={conversation} assistant={assistant} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className={cn('relative flex w-11/12 flex-col', msg?.isCreatedByUser ? '' : 'agent-turn')}
|
|
||||||
>
|
|
||||||
<div className="select-none font-semibold">{messageLabel}</div>
|
|
||||||
<div className="flex-col gap-1 md:gap-3">
|
|
||||||
<div className="flex max-w-full flex-grow flex-col gap-0">
|
|
||||||
{msg?.plugin && <Plugin plugin={msg?.plugin} />}
|
|
||||||
<MessageContent
|
|
||||||
ask={ask}
|
|
||||||
edit={edit}
|
|
||||||
isLast={isLast}
|
|
||||||
text={msg.text ?? ''}
|
|
||||||
message={msg}
|
|
||||||
enterEdit={enterEdit}
|
|
||||||
error={!!error}
|
|
||||||
isSubmitting={isSubmitting}
|
|
||||||
unfinished={unfinished ?? false}
|
|
||||||
isCreatedByUser={isCreatedByUser ?? true}
|
|
||||||
siblingIdx={siblingIdx ?? 0}
|
|
||||||
setSiblingIdx={setSiblingIdx ?? (() => ({}))}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{!msg?.children?.length && (isSubmittingFamily || isSubmitting) ? (
|
|
||||||
<PlaceholderRow isLast={isLast} 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}
|
|
||||||
isLast={isLast}
|
|
||||||
/>
|
|
||||||
</SubRow>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
export default function Message(props: TMessageProps) {
|
export default function Message(props: TMessageProps) {
|
||||||
const {
|
const {
|
||||||
showSibling,
|
showSibling,
|
||||||
|
|
|
||||||
154
client/src/components/Chat/Messages/ui/MessageRender.tsx
Normal file
154
client/src/components/Chat/Messages/ui/MessageRender.tsx
Normal file
|
|
@ -0,0 +1,154 @@
|
||||||
|
import React, { useCallback, useMemo } from 'react';
|
||||||
|
import { useMessageActions } from '~/hooks';
|
||||||
|
import type { TMessage } from 'librechat-data-provider';
|
||||||
|
import type { TMessageProps } 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 Icon from '~/components/Chat/Messages/MessageIcon';
|
||||||
|
import { Plugin } from '~/components/Messages/Content';
|
||||||
|
import SubRow from '~/components/Chat/Messages/SubRow';
|
||||||
|
import { cn } from '~/utils';
|
||||||
|
|
||||||
|
type MessageRenderProps = {
|
||||||
|
message?: TMessage;
|
||||||
|
isCard?: boolean;
|
||||||
|
isMultiMessage?: boolean;
|
||||||
|
isSubmittingFamily?: boolean;
|
||||||
|
} & Pick<
|
||||||
|
TMessageProps,
|
||||||
|
'currentEditId' | 'setCurrentEditId' | 'siblingIdx' | 'setSiblingIdx' | 'siblingCount'
|
||||||
|
>;
|
||||||
|
|
||||||
|
const MessageRender = React.memo(
|
||||||
|
({
|
||||||
|
isCard,
|
||||||
|
siblingIdx,
|
||||||
|
siblingCount,
|
||||||
|
message: msg,
|
||||||
|
setSiblingIdx,
|
||||||
|
currentEditId,
|
||||||
|
isMultiMessage,
|
||||||
|
setCurrentEditId,
|
||||||
|
isSubmittingFamily,
|
||||||
|
}: MessageRenderProps) => {
|
||||||
|
const {
|
||||||
|
ask,
|
||||||
|
edit,
|
||||||
|
index,
|
||||||
|
assistant,
|
||||||
|
enterEdit,
|
||||||
|
conversation,
|
||||||
|
messageLabel,
|
||||||
|
isSubmitting,
|
||||||
|
latestMessage,
|
||||||
|
handleContinue,
|
||||||
|
copyToClipboard,
|
||||||
|
setLatestMessage,
|
||||||
|
regenerateMessage,
|
||||||
|
} = useMessageActions({
|
||||||
|
message: msg,
|
||||||
|
currentEditId,
|
||||||
|
isMultiMessage,
|
||||||
|
setCurrentEditId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleRegenerateMessage = useCallback(() => regenerateMessage(), [regenerateMessage]);
|
||||||
|
const { isCreatedByUser, error, unfinished } = msg ?? {};
|
||||||
|
const isLast = useMemo(
|
||||||
|
() => !msg?.children?.length && (msg?.depth === latestMessage?.depth || msg?.depth === -1),
|
||||||
|
[msg?.children, msg?.depth, latestMessage?.depth],
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!msg) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isLatestCard =
|
||||||
|
isCard && !isSubmittingFamily && msg.messageId === latestMessage?.messageId;
|
||||||
|
const clickHandler =
|
||||||
|
isLast && isCard && !isSubmittingFamily && msg.messageId !== latestMessage?.messageId
|
||||||
|
? () => setLatestMessage(msg)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
'final-completion group mx-auto flex flex-1 gap-3 text-base',
|
||||||
|
isCard
|
||||||
|
? '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'
|
||||||
|
: 'md:max-w-3xl md:px-5 lg:max-w-[40rem] lg:px-1 xl:max-w-[48rem] xl:px-5',
|
||||||
|
isLatestCard ? 'bg-surface-secondary' : '',
|
||||||
|
isLast && !isSubmittingFamily && isCard
|
||||||
|
? 'cursor-pointer transition-colors duration-300'
|
||||||
|
: '',
|
||||||
|
)}
|
||||||
|
onClick={clickHandler}
|
||||||
|
>
|
||||||
|
{isLatestCard && (
|
||||||
|
<div className="absolute right-0 top-0 m-2 h-3 w-3 rounded-full bg-text-primary"></div>
|
||||||
|
)}
|
||||||
|
<div className="relative flex flex-shrink-0 flex-col items-end">
|
||||||
|
<div>
|
||||||
|
<div className="pt-0.5">
|
||||||
|
<div className="flex h-6 w-6 items-center justify-center overflow-hidden rounded-full">
|
||||||
|
<Icon message={msg} conversation={conversation} assistant={assistant} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={cn('relative flex w-11/12 flex-col', msg?.isCreatedByUser ? '' : 'agent-turn')}
|
||||||
|
>
|
||||||
|
<div className="select-none font-semibold">{messageLabel}</div>
|
||||||
|
<div className="flex-col gap-1 md:gap-3">
|
||||||
|
<div className="flex max-w-full flex-grow flex-col gap-0">
|
||||||
|
{msg?.plugin && <Plugin plugin={msg?.plugin} />}
|
||||||
|
<MessageContent
|
||||||
|
ask={ask}
|
||||||
|
edit={edit}
|
||||||
|
isLast={isLast}
|
||||||
|
text={msg.text ?? ''}
|
||||||
|
message={msg}
|
||||||
|
enterEdit={enterEdit}
|
||||||
|
error={!!error}
|
||||||
|
isSubmitting={isSubmitting}
|
||||||
|
unfinished={unfinished ?? false}
|
||||||
|
isCreatedByUser={isCreatedByUser ?? true}
|
||||||
|
siblingIdx={siblingIdx ?? 0}
|
||||||
|
setSiblingIdx={setSiblingIdx ?? (() => ({}))}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{!msg?.children?.length && (isSubmittingFamily || isSubmitting) ? (
|
||||||
|
<PlaceholderRow isLast={isLast} 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}
|
||||||
|
isLast={isLast}
|
||||||
|
/>
|
||||||
|
</SubRow>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export default MessageRender;
|
||||||
13
client/src/components/Chat/Messages/ui/PlaceholderRow.tsx
Normal file
13
client/src/components/Chat/Messages/ui/PlaceholderRow.tsx
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { memo } from 'react';
|
||||||
|
|
||||||
|
const PlaceholderRow = memo(({ isLast, isCard }: { isLast: boolean; isCard?: boolean }) => {
|
||||||
|
if (!isCard) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (!isLast) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return <div className="mt-1 h-[27px] bg-transparent" />;
|
||||||
|
});
|
||||||
|
|
||||||
|
export default PlaceholderRow;
|
||||||
|
|
@ -38,14 +38,14 @@ export default function ChatRoute() {
|
||||||
const assistantListMap = useAssistantListMap();
|
const assistantListMap = useAssistantListMap();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
const shouldSetConvo =
|
||||||
startupConfig &&
|
startupConfig && !hasSetConversation.current && !modelsQuery.data?.initial;
|
||||||
conversationId === 'new' &&
|
/* Early exit if startupConfig is not loaded and conversation is already set and only initial models have loaded */
|
||||||
endpointsQuery.data &&
|
if (!shouldSetConvo) {
|
||||||
modelsQuery.data &&
|
return;
|
||||||
!modelsQuery.data?.initial &&
|
}
|
||||||
!hasSetConversation.current
|
|
||||||
) {
|
if (conversationId === 'new' && endpointsQuery.data && modelsQuery.data) {
|
||||||
const spec = getDefaultModelSpec(startupConfig.modelSpecs?.list);
|
const spec = getDefaultModelSpec(startupConfig.modelSpecs?.list);
|
||||||
|
|
||||||
newConversation({
|
newConversation({
|
||||||
|
|
@ -63,14 +63,7 @@ export default function ChatRoute() {
|
||||||
});
|
});
|
||||||
|
|
||||||
hasSetConversation.current = true;
|
hasSetConversation.current = true;
|
||||||
} else if (
|
} else if (initialConvoQuery.data && endpointsQuery.data && modelsQuery.data) {
|
||||||
startupConfig &&
|
|
||||||
initialConvoQuery.data &&
|
|
||||||
endpointsQuery.data &&
|
|
||||||
modelsQuery.data &&
|
|
||||||
!modelsQuery.data?.initial &&
|
|
||||||
!hasSetConversation.current
|
|
||||||
) {
|
|
||||||
newConversation({
|
newConversation({
|
||||||
template: initialConvoQuery.data,
|
template: initialConvoQuery.data,
|
||||||
/* this is necessary to load all existing settings */
|
/* this is necessary to load all existing settings */
|
||||||
|
|
@ -80,9 +73,6 @@ export default function ChatRoute() {
|
||||||
});
|
});
|
||||||
hasSetConversation.current = true;
|
hasSetConversation.current = true;
|
||||||
} else if (
|
} else if (
|
||||||
startupConfig &&
|
|
||||||
!hasSetConversation.current &&
|
|
||||||
!modelsQuery.data?.initial &&
|
|
||||||
conversationId === 'new' &&
|
conversationId === 'new' &&
|
||||||
assistantListMap[EModelEndpoint.assistants] &&
|
assistantListMap[EModelEndpoint.assistants] &&
|
||||||
assistantListMap[EModelEndpoint.azureAssistants]
|
assistantListMap[EModelEndpoint.azureAssistants]
|
||||||
|
|
@ -103,9 +93,6 @@ export default function ChatRoute() {
|
||||||
});
|
});
|
||||||
hasSetConversation.current = true;
|
hasSetConversation.current = true;
|
||||||
} else if (
|
} else if (
|
||||||
startupConfig &&
|
|
||||||
!hasSetConversation.current &&
|
|
||||||
!modelsQuery.data?.initial &&
|
|
||||||
assistantListMap[EModelEndpoint.assistants] &&
|
assistantListMap[EModelEndpoint.assistants] &&
|
||||||
assistantListMap[EModelEndpoint.azureAssistants]
|
assistantListMap[EModelEndpoint.azureAssistants]
|
||||||
) {
|
) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue