mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-16 16:30: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 { useMessageProcess, useMessageActions } from '~/hooks';
|
||||
import type { TMessage } from 'librechat-data-provider';
|
||||
import React from 'react';
|
||||
import { useMessageProcess } from '~/hooks';
|
||||
import type { TMessageProps } from '~/common';
|
||||
import Icon from '~/components/Chat/Messages/MessageIcon';
|
||||
import { Plugin } from '~/components/Messages/Content';
|
||||
import MessageContent from './Content/MessageContent';
|
||||
import SiblingSwitch from './SiblingSwitch';
|
||||
import MessageRender from './ui/MessageRender';
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import MultiMessage from './MultiMessage';
|
||||
import HoverButtons from './HoverButtons';
|
||||
import SubRow from './SubRow';
|
||||
import { cn } from '~/utils';
|
||||
|
||||
const MessageContainer = React.memo(
|
||||
({ 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) {
|
||||
const {
|
||||
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();
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
startupConfig &&
|
||||
conversationId === 'new' &&
|
||||
endpointsQuery.data &&
|
||||
modelsQuery.data &&
|
||||
!modelsQuery.data?.initial &&
|
||||
!hasSetConversation.current
|
||||
) {
|
||||
const shouldSetConvo =
|
||||
startupConfig && !hasSetConversation.current && !modelsQuery.data?.initial;
|
||||
/* Early exit if startupConfig is not loaded and conversation is already set and only initial models have loaded */
|
||||
if (!shouldSetConvo) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (conversationId === 'new' && endpointsQuery.data && modelsQuery.data) {
|
||||
const spec = getDefaultModelSpec(startupConfig.modelSpecs?.list);
|
||||
|
||||
newConversation({
|
||||
|
|
@ -63,14 +63,7 @@ export default function ChatRoute() {
|
|||
});
|
||||
|
||||
hasSetConversation.current = true;
|
||||
} else if (
|
||||
startupConfig &&
|
||||
initialConvoQuery.data &&
|
||||
endpointsQuery.data &&
|
||||
modelsQuery.data &&
|
||||
!modelsQuery.data?.initial &&
|
||||
!hasSetConversation.current
|
||||
) {
|
||||
} else if (initialConvoQuery.data && endpointsQuery.data && modelsQuery.data) {
|
||||
newConversation({
|
||||
template: initialConvoQuery.data,
|
||||
/* this is necessary to load all existing settings */
|
||||
|
|
@ -80,9 +73,6 @@ export default function ChatRoute() {
|
|||
});
|
||||
hasSetConversation.current = true;
|
||||
} else if (
|
||||
startupConfig &&
|
||||
!hasSetConversation.current &&
|
||||
!modelsQuery.data?.initial &&
|
||||
conversationId === 'new' &&
|
||||
assistantListMap[EModelEndpoint.assistants] &&
|
||||
assistantListMap[EModelEndpoint.azureAssistants]
|
||||
|
|
@ -103,9 +93,6 @@ export default function ChatRoute() {
|
|||
});
|
||||
hasSetConversation.current = true;
|
||||
} else if (
|
||||
startupConfig &&
|
||||
!hasSetConversation.current &&
|
||||
!modelsQuery.data?.initial &&
|
||||
assistantListMap[EModelEndpoint.assistants] &&
|
||||
assistantListMap[EModelEndpoint.azureAssistants]
|
||||
) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue