mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-17 08:50:15 +01:00
⚡️ refactor: Optimize Rendering Performance for Icons, Conversations (#5234)
* refactor: HoverButtons and Fork components to use explicit props * refactor: improve typing for Fork Component * fix: memoize SpecIcon to avoid unnecessary re-renders * feat: introduce URLIcon component and update SpecIcon for improved icon handling * WIP: optimizing icons * refactor: simplify modelLabel assignment in Message components * refactor: memoize ConvoOptions component to optimize rendering performance
This commit is contained in:
parent
687ab32bd3
commit
0f95604a67
19 changed files with 206 additions and 171 deletions
|
|
@ -307,6 +307,12 @@ export type TMessageProps = {
|
||||||
setSiblingIdx?: ((value: number) => void | React.Dispatch<React.SetStateAction<number>>) | null;
|
setSiblingIdx?: ((value: number) => void | React.Dispatch<React.SetStateAction<number>>) | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TMessageIcon = { endpoint?: string | null; isCreatedByUser?: boolean } & Pick<
|
||||||
|
t.TConversation,
|
||||||
|
'modelLabel'
|
||||||
|
> &
|
||||||
|
Pick<t.TMessage, 'model' | 'iconURL'>;
|
||||||
|
|
||||||
export type TInitialProps = {
|
export type TInitialProps = {
|
||||||
text: string;
|
text: string;
|
||||||
edit: boolean;
|
edit: boolean;
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { memo } from 'react';
|
||||||
import { EModelEndpoint, KnownEndpoints } from 'librechat-data-provider';
|
import { EModelEndpoint, KnownEndpoints } from 'librechat-data-provider';
|
||||||
import { CustomMinimalIcon } from '~/components/svg';
|
import { CustomMinimalIcon } from '~/components/svg';
|
||||||
import { IconContext } from '~/common';
|
import { IconContext } from '~/common';
|
||||||
|
|
@ -53,7 +54,7 @@ const getKnownClass = ({
|
||||||
return cn(match, defaultClass);
|
return cn(match, defaultClass);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function UnknownIcon({
|
function UnknownIcon({
|
||||||
className = '',
|
className = '',
|
||||||
endpoint: _endpoint,
|
endpoint: _endpoint,
|
||||||
iconURL = '',
|
iconURL = '',
|
||||||
|
|
@ -93,3 +94,5 @@ export default function UnknownIcon({
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default memo(UnknownIcon);
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
import React from 'react';
|
import React, { memo } from 'react';
|
||||||
import type { TModelSpec, TEndpointsConfig } from 'librechat-data-provider';
|
import type { TModelSpec, TEndpointsConfig } from 'librechat-data-provider';
|
||||||
import type { IconMapProps } from '~/common';
|
import type { IconMapProps } from '~/common';
|
||||||
import { getModelSpecIconURL, getIconKey, getEndpointField } from '~/utils';
|
import { getModelSpecIconURL, getIconKey, getEndpointField } from '~/utils';
|
||||||
import { icons } from '~/components/Chat/Menus/Endpoints/Icons';
|
import { icons } from '~/components/Chat/Menus/Endpoints/Icons';
|
||||||
|
import { URLIcon } from '~/components/Endpoints/URLIcon';
|
||||||
|
|
||||||
interface SpecIconProps {
|
interface SpecIconProps {
|
||||||
currentSpec: TModelSpec;
|
currentSpec: TModelSpec;
|
||||||
|
|
@ -16,24 +17,12 @@ const SpecIcon: React.FC<SpecIconProps> = ({ currentSpec, endpointsConfig }) =>
|
||||||
const iconKey = getIconKey({ endpoint, endpointsConfig, endpointIconURL });
|
const iconKey = getIconKey({ endpoint, endpointsConfig, endpointIconURL });
|
||||||
let Icon: (props: IconMapProps) => React.JSX.Element;
|
let Icon: (props: IconMapProps) => React.JSX.Element;
|
||||||
|
|
||||||
if (!iconURL?.includes('http')) {
|
if (!iconURL.includes('http')) {
|
||||||
Icon = icons[iconKey] ?? icons.unknown;
|
Icon = icons[iconKey] ?? icons.unknown;
|
||||||
|
} else if (iconURL) {
|
||||||
|
return <URLIcon iconURL={iconURL} altName={currentSpec.name} />;
|
||||||
} else {
|
} else {
|
||||||
Icon = iconURL
|
Icon = icons[endpoint ?? ''] ?? icons.unknown;
|
||||||
? () => (
|
|
||||||
<div
|
|
||||||
className="icon-xl mr-1 shrink-0 overflow-hidden rounded-full "
|
|
||||||
style={{ width: '20', height: '20' }}
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
src={iconURL}
|
|
||||||
alt={currentSpec.name}
|
|
||||||
style={{ width: '100%', height: '100%' }}
|
|
||||||
className="object-cover"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
: icons[endpoint ?? ''] ?? icons.unknown;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -42,9 +31,9 @@ const SpecIcon: React.FC<SpecIconProps> = ({ currentSpec, endpointsConfig }) =>
|
||||||
endpoint={endpoint}
|
endpoint={endpoint}
|
||||||
context="menu-item"
|
context="menu-item"
|
||||||
iconURL={endpointIconURL}
|
iconURL={endpointIconURL}
|
||||||
className="icon-lg mr-1 shrink-0 dark:text-white"
|
className="icon-lg mr-1 shrink-0 text-text-primary"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default SpecIcon;
|
export default memo(SpecIcon);
|
||||||
|
|
|
||||||
|
|
@ -50,9 +50,13 @@ export default function HoverButtons({
|
||||||
} = useGenerationsByLatest({
|
} = useGenerationsByLatest({
|
||||||
isEditing,
|
isEditing,
|
||||||
isSubmitting,
|
isSubmitting,
|
||||||
message,
|
error: message.error,
|
||||||
endpoint: endpoint ?? '',
|
endpoint: endpoint ?? '',
|
||||||
latestMessage,
|
messageId: message.messageId,
|
||||||
|
searchResult: message.searchResult,
|
||||||
|
finish_reason: message.finish_reason,
|
||||||
|
isCreatedByUser: message.isCreatedByUser,
|
||||||
|
latestMessageId: latestMessage?.messageId,
|
||||||
});
|
});
|
||||||
if (!conversation) {
|
if (!conversation) {
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -146,7 +150,7 @@ export default function HoverButtons({
|
||||||
messageId={message.messageId}
|
messageId={message.messageId}
|
||||||
conversationId={conversation.conversationId}
|
conversationId={conversation.conversationId}
|
||||||
forkingSupported={forkingSupported}
|
forkingSupported={forkingSupported}
|
||||||
latestMessage={latestMessage}
|
latestMessageId={latestMessage?.messageId}
|
||||||
/>
|
/>
|
||||||
{continueSupported === true ? (
|
{continueSupported === true ? (
|
||||||
<button
|
<button
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,38 @@
|
||||||
import React, { useMemo, memo } from 'react';
|
import React, { useMemo, memo } from 'react';
|
||||||
import { useGetEndpointsQuery } from 'librechat-data-provider/react-query';
|
import { useGetEndpointsQuery } from 'librechat-data-provider/react-query';
|
||||||
import type { Assistant, Agent, TMessage } from 'librechat-data-provider';
|
import type { Assistant, Agent } from 'librechat-data-provider';
|
||||||
|
import type { TMessageIcon } from '~/common';
|
||||||
|
import { getEndpointField, getIconEndpoint, logger } from '~/utils';
|
||||||
import ConvoIconURL from '~/components/Endpoints/ConvoIconURL';
|
import ConvoIconURL from '~/components/Endpoints/ConvoIconURL';
|
||||||
import { getEndpointField, getIconEndpoint } from '~/utils';
|
|
||||||
import Icon from '~/components/Endpoints/Icon';
|
import Icon from '~/components/Endpoints/Icon';
|
||||||
|
|
||||||
const MessageIcon = memo(
|
const MessageIcon = memo(
|
||||||
(props: {
|
({
|
||||||
iconData?: TMessage & { modelLabel?: string };
|
iconData,
|
||||||
|
assistant,
|
||||||
|
agent,
|
||||||
|
}: {
|
||||||
|
iconData?: TMessageIcon;
|
||||||
assistant?: Assistant;
|
assistant?: Assistant;
|
||||||
agent?: Agent;
|
agent?: Agent;
|
||||||
}) => {
|
}) => {
|
||||||
|
logger.log('icon_data', iconData, assistant, agent);
|
||||||
const { data: endpointsConfig } = useGetEndpointsQuery();
|
const { data: endpointsConfig } = useGetEndpointsQuery();
|
||||||
const { iconData, assistant, agent } = props;
|
|
||||||
|
|
||||||
|
const agentName = useMemo(() => agent?.name ?? '', [agent]);
|
||||||
|
const agentAvatar = useMemo(() => agent?.avatar?.filepath ?? '', [agent]);
|
||||||
const assistantName = useMemo(() => assistant?.name ?? '', [assistant]);
|
const assistantName = useMemo(() => assistant?.name ?? '', [assistant]);
|
||||||
const assistantAvatar = useMemo(() => assistant?.metadata?.avatar ?? '', [assistant]);
|
const assistantAvatar = useMemo(() => assistant?.metadata?.avatar ?? '', [assistant]);
|
||||||
const agentName = useMemo(() => props.agent?.name ?? '', [props.agent]);
|
|
||||||
const agentAvatar = useMemo(() => props.agent?.avatar?.filepath ?? '', [props.agent]);
|
|
||||||
|
|
||||||
let avatarURL = '';
|
const avatarURL = useMemo(() => {
|
||||||
|
let result = '';
|
||||||
if (assistant) {
|
if (assistant) {
|
||||||
avatarURL = assistantAvatar;
|
result = assistantAvatar;
|
||||||
} else if (agent) {
|
} else if (agent) {
|
||||||
avatarURL = agentAvatar;
|
result = agentAvatar;
|
||||||
}
|
}
|
||||||
|
return result;
|
||||||
|
}, [assistant, agent, assistantAvatar, agentAvatar]);
|
||||||
|
|
||||||
const iconURL = iconData?.iconURL;
|
const iconURL = iconData?.iconURL;
|
||||||
const endpoint = useMemo(
|
const endpoint = useMemo(
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
import type { TMessage, TMessageContentParts } from 'librechat-data-provider';
|
import type { TMessageContentParts } from 'librechat-data-provider';
|
||||||
import type { TMessageProps } from '~/common';
|
import type { TMessageProps, TMessageIcon } from '~/common';
|
||||||
import MessageIcon from '~/components/Chat/Messages/MessageIcon';
|
import MessageIcon from '~/components/Chat/Messages/MessageIcon';
|
||||||
import { useMessageHelpers, useLocalize } from '~/hooks';
|
import { useMessageHelpers, useLocalize } from '~/hooks';
|
||||||
import ContentParts from './Content/ContentParts';
|
import ContentParts from './Content/ContentParts';
|
||||||
|
|
@ -35,19 +35,29 @@ export default function Message(props: TMessageProps) {
|
||||||
} = useMessageHelpers(props);
|
} = useMessageHelpers(props);
|
||||||
const fontSize = useRecoilValue(store.fontSize);
|
const fontSize = useRecoilValue(store.fontSize);
|
||||||
const { children, messageId = null, isCreatedByUser } = message ?? {};
|
const { children, messageId = null, isCreatedByUser } = message ?? {};
|
||||||
|
const name = useMemo(() => {
|
||||||
|
let result = '';
|
||||||
|
if (isCreatedByUser === true) {
|
||||||
|
result = localize('com_user_message');
|
||||||
|
} else if (assistant) {
|
||||||
|
result = assistant.name ?? localize('com_ui_assistant');
|
||||||
|
} else if (agent) {
|
||||||
|
result = agent.name ?? localize('com_ui_agent');
|
||||||
|
}
|
||||||
|
|
||||||
const iconData = useMemo(
|
return result;
|
||||||
() =>
|
}, [assistant, agent, isCreatedByUser, localize]);
|
||||||
({
|
|
||||||
endpoint: message?.endpoint ?? conversation?.endpoint,
|
const iconData: TMessageIcon = useMemo(
|
||||||
model: message?.model ?? conversation?.model,
|
() => ({
|
||||||
iconURL: message?.iconURL ?? conversation?.iconURL,
|
endpoint: message?.endpoint ?? conversation?.endpoint,
|
||||||
modelLabel: conversation?.chatGptLabel ?? conversation?.modelLabel,
|
model: message?.model ?? conversation?.model,
|
||||||
isCreatedByUser: message?.isCreatedByUser,
|
iconURL: message?.iconURL ?? conversation?.iconURL,
|
||||||
} as TMessage & { modelLabel?: string }),
|
modelLabel: name,
|
||||||
|
isCreatedByUser: message?.isCreatedByUser,
|
||||||
|
}),
|
||||||
[
|
[
|
||||||
conversation?.chatGptLabel,
|
name,
|
||||||
conversation?.modelLabel,
|
|
||||||
conversation?.endpoint,
|
conversation?.endpoint,
|
||||||
conversation?.iconURL,
|
conversation?.iconURL,
|
||||||
conversation?.model,
|
conversation?.model,
|
||||||
|
|
@ -61,16 +71,6 @@ export default function Message(props: TMessageProps) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
let name = '';
|
|
||||||
|
|
||||||
if (isCreatedByUser === true) {
|
|
||||||
name = localize('com_user_message');
|
|
||||||
} else if (assistant) {
|
|
||||||
name = assistant.name ?? localize('com_ui_assistant');
|
|
||||||
} else if (agent) {
|
|
||||||
name = agent.name ?? localize('com_ui_agent');
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
import { useAuthContext, useLocalize } from '~/hooks';
|
import { useAuthContext, useLocalize } from '~/hooks';
|
||||||
import type { TMessage } from 'librechat-data-provider';
|
import type { TMessageProps, TMessageIcon } from '~/common';
|
||||||
import type { TMessageProps } from '~/common';
|
|
||||||
import MinimalHoverButtons from '~/components/Chat/Messages/MinimalHoverButtons';
|
import MinimalHoverButtons from '~/components/Chat/Messages/MinimalHoverButtons';
|
||||||
import Icon from '~/components/Chat/Messages/MessageIcon';
|
import Icon from '~/components/Chat/Messages/MessageIcon';
|
||||||
import SearchContent from './Content/SearchContent';
|
import SearchContent from './Content/SearchContent';
|
||||||
|
|
@ -17,14 +16,13 @@ export default function Message({ message }: Pick<TMessageProps, 'message'>) {
|
||||||
const { user } = useAuthContext();
|
const { user } = useAuthContext();
|
||||||
const localize = useLocalize();
|
const localize = useLocalize();
|
||||||
|
|
||||||
const iconData = useMemo(
|
const iconData: TMessageIcon = useMemo(
|
||||||
() =>
|
() => ({
|
||||||
({
|
endpoint: message?.endpoint,
|
||||||
endpoint: message?.endpoint,
|
model: message?.model,
|
||||||
model: message?.model,
|
iconURL: message?.iconURL ?? '',
|
||||||
iconURL: message?.iconURL ?? '',
|
isCreatedByUser: message?.isCreatedByUser,
|
||||||
isCreatedByUser: message?.isCreatedByUser,
|
}),
|
||||||
} as TMessage & { modelLabel?: string }),
|
|
||||||
[message?.model, message?.iconURL, message?.endpoint, message?.isCreatedByUser],
|
[message?.model, message?.iconURL, message?.endpoint, message?.isCreatedByUser],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
import { useCallback, useMemo, memo } from 'react';
|
import { useCallback, useMemo, memo } from 'react';
|
||||||
import type { TMessage } from 'librechat-data-provider';
|
import type { TMessage } from 'librechat-data-provider';
|
||||||
import type { TMessageProps } from '~/common';
|
import type { TMessageProps, TMessageIcon } from '~/common';
|
||||||
import MessageContent from '~/components/Chat/Messages/Content/MessageContent';
|
import MessageContent from '~/components/Chat/Messages/Content/MessageContent';
|
||||||
import PlaceholderRow from '~/components/Chat/Messages/ui/PlaceholderRow';
|
import PlaceholderRow from '~/components/Chat/Messages/ui/PlaceholderRow';
|
||||||
import SiblingSwitch from '~/components/Chat/Messages/SiblingSwitch';
|
import SiblingSwitch from '~/components/Chat/Messages/SiblingSwitch';
|
||||||
|
|
@ -66,18 +66,16 @@ const MessageRender = memo(
|
||||||
[hasNoChildren, msg?.depth, latestMessage?.depth],
|
[hasNoChildren, msg?.depth, latestMessage?.depth],
|
||||||
);
|
);
|
||||||
|
|
||||||
const iconData = useMemo(
|
const iconData: TMessageIcon = useMemo(
|
||||||
() =>
|
() => ({
|
||||||
({
|
endpoint: msg?.endpoint ?? conversation?.endpoint,
|
||||||
endpoint: msg?.endpoint ?? conversation?.endpoint,
|
model: msg?.model ?? conversation?.model,
|
||||||
model: msg?.model ?? conversation?.model,
|
iconURL: msg?.iconURL ?? conversation?.iconURL,
|
||||||
iconURL: msg?.iconURL ?? conversation?.iconURL,
|
modelLabel: messageLabel,
|
||||||
modelLabel: conversation?.chatGptLabel ?? conversation?.modelLabel,
|
isCreatedByUser: msg?.isCreatedByUser,
|
||||||
isCreatedByUser: msg?.isCreatedByUser,
|
}),
|
||||||
} as TMessage & { modelLabel?: string }),
|
|
||||||
[
|
[
|
||||||
conversation?.chatGptLabel,
|
messageLabel,
|
||||||
conversation?.modelLabel,
|
|
||||||
conversation?.endpoint,
|
conversation?.endpoint,
|
||||||
conversation?.iconURL,
|
conversation?.iconURL,
|
||||||
conversation?.model,
|
conversation?.model,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { useState, useId, useRef } from 'react';
|
import { useState, useId, useRef, memo } from 'react';
|
||||||
import * as Menu from '@ariakit/react/menu';
|
import * as Menu from '@ariakit/react/menu';
|
||||||
import { Ellipsis, Share2, Copy, Archive, Pen, Trash } from 'lucide-react';
|
import { Ellipsis, Share2, Copy, Archive, Pen, Trash } from 'lucide-react';
|
||||||
import { useGetStartupConfig } from 'librechat-data-provider/react-query';
|
import { useGetStartupConfig } from 'librechat-data-provider/react-query';
|
||||||
|
|
@ -12,7 +12,7 @@ import DeleteButton from './DeleteButton';
|
||||||
import ShareButton from './ShareButton';
|
import ShareButton from './ShareButton';
|
||||||
import { cn } from '~/utils';
|
import { cn } from '~/utils';
|
||||||
|
|
||||||
export default function ConvoOptions({
|
function ConvoOptions({
|
||||||
conversationId,
|
conversationId,
|
||||||
title,
|
title,
|
||||||
retainView,
|
retainView,
|
||||||
|
|
@ -161,3 +161,5 @@ export default function ConvoOptions({
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default memo(ConvoOptions);
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { useState, useRef } from 'react';
|
||||||
import { useRecoilState } from 'recoil';
|
import { useRecoilState } from 'recoil';
|
||||||
import { GitFork, InfoIcon } from 'lucide-react';
|
import { GitFork, InfoIcon } from 'lucide-react';
|
||||||
import * as Popover from '@radix-ui/react-popover';
|
import * as Popover from '@radix-ui/react-popover';
|
||||||
import { ForkOptions, TMessage } from 'librechat-data-provider';
|
import { ForkOptions } from 'librechat-data-provider';
|
||||||
import { GitCommit, GitBranchPlus, ListTree } from 'lucide-react';
|
import { GitCommit, GitBranchPlus, ListTree } from 'lucide-react';
|
||||||
import {
|
import {
|
||||||
Checkbox,
|
Checkbox,
|
||||||
|
|
@ -26,9 +26,9 @@ interface PopoverButtonProps {
|
||||||
setActiveSetting: React.Dispatch<React.SetStateAction<string>>;
|
setActiveSetting: React.Dispatch<React.SetStateAction<string>>;
|
||||||
sideOffset?: number;
|
sideOffset?: number;
|
||||||
timeoutRef: React.MutableRefObject<NodeJS.Timeout | null>;
|
timeoutRef: React.MutableRefObject<NodeJS.Timeout | null>;
|
||||||
hoverInfo?: React.ReactNode;
|
hoverInfo?: React.ReactNode | string;
|
||||||
hoverTitle?: React.ReactNode;
|
hoverTitle?: React.ReactNode | string;
|
||||||
hoverDescription?: React.ReactNode;
|
hoverDescription?: React.ReactNode | string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const optionLabels = {
|
const optionLabels = {
|
||||||
|
|
@ -73,7 +73,9 @@ const PopoverButton: React.FC<PopoverButtonProps> = ({
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</Popover.Close>
|
</Popover.Close>
|
||||||
{(hoverInfo || hoverTitle || hoverDescription) && (
|
{((hoverInfo != null && hoverInfo !== '') ||
|
||||||
|
(hoverTitle != null && hoverTitle !== '') ||
|
||||||
|
(hoverDescription != null && hoverDescription !== '')) && (
|
||||||
<HoverCardPortal>
|
<HoverCardPortal>
|
||||||
<HoverCardContent
|
<HoverCardContent
|
||||||
side="right"
|
side="right"
|
||||||
|
|
@ -82,9 +84,11 @@ const PopoverButton: React.FC<PopoverButtonProps> = ({
|
||||||
>
|
>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<p className="flex flex-col gap-2 text-sm text-gray-600 dark:text-gray-300">
|
<p className="flex flex-col gap-2 text-sm text-gray-600 dark:text-gray-300">
|
||||||
{hoverInfo && hoverInfo}
|
{hoverInfo != null && hoverInfo !== '' && hoverInfo}
|
||||||
{hoverTitle && <span className="flex flex-wrap gap-1 font-bold">{hoverTitle}</span>}
|
{hoverTitle != null && hoverTitle !== '' && (
|
||||||
{hoverDescription && hoverDescription}
|
<span className="flex flex-wrap gap-1 font-bold">{hoverTitle}</span>
|
||||||
|
)}
|
||||||
|
{hoverDescription != null && hoverDescription !== '' && hoverDescription}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</HoverCardContent>
|
</HoverCardContent>
|
||||||
|
|
@ -95,17 +99,17 @@ const PopoverButton: React.FC<PopoverButtonProps> = ({
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function Fork({
|
export default function Fork({
|
||||||
isLast,
|
isLast = false,
|
||||||
messageId,
|
messageId,
|
||||||
conversationId,
|
conversationId: _convoId,
|
||||||
forkingSupported,
|
forkingSupported = false,
|
||||||
latestMessage,
|
latestMessageId,
|
||||||
}: {
|
}: {
|
||||||
isLast?: boolean;
|
isLast?: boolean;
|
||||||
messageId: string;
|
messageId: string;
|
||||||
conversationId: string | null;
|
conversationId: string | null;
|
||||||
forkingSupported?: boolean;
|
forkingSupported?: boolean;
|
||||||
latestMessage: TMessage | null;
|
latestMessageId?: string;
|
||||||
}) {
|
}) {
|
||||||
const localize = useLocalize();
|
const localize = useLocalize();
|
||||||
const { index } = useChatContext();
|
const { index } = useChatContext();
|
||||||
|
|
@ -119,13 +123,11 @@ export default function Fork({
|
||||||
const [rememberGlobal, setRememberGlobal] = useRecoilState(store.rememberDefaultFork);
|
const [rememberGlobal, setRememberGlobal] = useRecoilState(store.rememberDefaultFork);
|
||||||
const forkConvo = useForkConvoMutation({
|
const forkConvo = useForkConvoMutation({
|
||||||
onSuccess: (data) => {
|
onSuccess: (data) => {
|
||||||
if (data) {
|
navigateToConvo(data.conversation);
|
||||||
navigateToConvo(data.conversation);
|
showToast({
|
||||||
showToast({
|
message: localize('com_ui_fork_success'),
|
||||||
message: localize('com_ui_fork_success'),
|
status: 'success',
|
||||||
status: 'success',
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
onMutate: () => {
|
onMutate: () => {
|
||||||
showToast({
|
showToast({
|
||||||
|
|
@ -141,6 +143,7 @@ export default function Fork({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const conversationId = _convoId ?? '';
|
||||||
if (!forkingSupported || !conversationId || !messageId) {
|
if (!forkingSupported || !conversationId || !messageId) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
@ -156,7 +159,7 @@ export default function Fork({
|
||||||
conversationId,
|
conversationId,
|
||||||
option,
|
option,
|
||||||
splitAtTarget,
|
splitAtTarget,
|
||||||
latestMessageId: latestMessage?.messageId,
|
latestMessageId,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -177,7 +180,7 @@ export default function Fork({
|
||||||
splitAtTarget,
|
splitAtTarget,
|
||||||
conversationId,
|
conversationId,
|
||||||
option: forkSetting,
|
option: forkSetting,
|
||||||
latestMessageId: latestMessage?.messageId,
|
latestMessageId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
import React, { memo } from 'react';
|
import { memo, useMemo } from 'react';
|
||||||
import type { IconMapProps } from '~/common';
|
import type { IconMapProps } from '~/common';
|
||||||
import { icons } from '~/components/Chat/Menus/Endpoints/Icons';
|
import { icons } from '~/components/Chat/Menus/Endpoints/Icons';
|
||||||
|
import { URLIcon } from '~/components/Endpoints/URLIcon';
|
||||||
|
|
||||||
interface ConvoIconURLProps {
|
interface ConvoIconURLProps {
|
||||||
iconURL?: string;
|
iconURL?: string;
|
||||||
modelLabel?: string;
|
modelLabel?: string | null;
|
||||||
endpointIconURL?: string;
|
endpointIconURL?: string;
|
||||||
assistantName?: string;
|
assistantName?: string;
|
||||||
agentName?: string;
|
agentName?: string;
|
||||||
|
|
@ -38,33 +39,26 @@ const ConvoIconURL: React.FC<ConvoIconURLProps> = ({
|
||||||
agentName,
|
agentName,
|
||||||
context,
|
context,
|
||||||
}) => {
|
}) => {
|
||||||
let Icon: (
|
const Icon: (
|
||||||
props: IconMapProps & {
|
props: IconMapProps & {
|
||||||
context?: string;
|
context?: string;
|
||||||
iconURL?: string;
|
iconURL?: string;
|
||||||
},
|
},
|
||||||
) => React.JSX.Element;
|
) => React.JSX.Element = useMemo(() => icons[iconURL] ?? icons.unknown, [iconURL]);
|
||||||
|
const isURL = useMemo(
|
||||||
const isURL = !!(iconURL && (iconURL.includes('http') || iconURL.startsWith('/images/')));
|
() => !!(iconURL && (iconURL.includes('http') || iconURL.startsWith('/images/'))),
|
||||||
|
[iconURL],
|
||||||
if (!isURL) {
|
);
|
||||||
Icon = icons[iconURL] ?? icons.unknown;
|
if (isURL) {
|
||||||
} else {
|
return (
|
||||||
Icon = () => (
|
<URLIcon
|
||||||
<div
|
iconURL={iconURL}
|
||||||
|
altName={modelLabel}
|
||||||
className={classMap[context ?? 'default'] ?? classMap.default}
|
className={classMap[context ?? 'default'] ?? classMap.default}
|
||||||
style={styleMap[context ?? 'default'] ?? styleMap.default}
|
containerStyle={styleMap[context ?? 'default'] ?? styleMap.default}
|
||||||
>
|
imageStyle={styleImageMap[context ?? 'default'] ?? styleImageMap.default}
|
||||||
<img
|
/>
|
||||||
src={iconURL}
|
|
||||||
alt={modelLabel}
|
|
||||||
style={styleImageMap[context ?? 'default'] ?? styleImageMap.default}
|
|
||||||
className="object-cover"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return <Icon context={context} />;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { EModelEndpoint, isAssistantsEndpoint, alternateName } from 'librechat-data-provider';
|
import { memo } from 'react';
|
||||||
import UnknownIcon from '~/components/Chat/Menus/Endpoints/UnknownIcon';
|
|
||||||
import { Feather } from 'lucide-react';
|
import { Feather } from 'lucide-react';
|
||||||
|
import { EModelEndpoint, isAssistantsEndpoint, alternateName } from 'librechat-data-provider';
|
||||||
import {
|
import {
|
||||||
Plugin,
|
Plugin,
|
||||||
GPTIcon,
|
GPTIcon,
|
||||||
|
|
@ -13,10 +13,16 @@ import {
|
||||||
AzureMinimalIcon,
|
AzureMinimalIcon,
|
||||||
CustomMinimalIcon,
|
CustomMinimalIcon,
|
||||||
} from '~/components/svg';
|
} from '~/components/svg';
|
||||||
|
import UnknownIcon from '~/components/Chat/Menus/Endpoints/UnknownIcon';
|
||||||
import { IconProps } from '~/common';
|
import { IconProps } from '~/common';
|
||||||
import { cn } from '~/utils';
|
import { cn } from '~/utils';
|
||||||
|
|
||||||
|
type EndpointIcon = {
|
||||||
|
icon: React.ReactNode | React.JSX.Element;
|
||||||
|
bg?: string;
|
||||||
|
name?: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
function getOpenAIColor(_model: string | null | undefined) {
|
function getOpenAIColor(_model: string | null | undefined) {
|
||||||
const model = _model?.toLowerCase() ?? '';
|
const model = _model?.toLowerCase() ?? '';
|
||||||
if (model && /\bo1\b/i.test(model)) {
|
if (model && /\bo1\b/i.test(model)) {
|
||||||
|
|
@ -116,7 +122,9 @@ const MessageEndpointIcon: React.FC<IconProps> = (props) => {
|
||||||
name: endpoint,
|
name: endpoint,
|
||||||
};
|
};
|
||||||
|
|
||||||
const endpointIcons = {
|
const endpointIcons: {
|
||||||
|
[key: string]: EndpointIcon | undefined;
|
||||||
|
} = {
|
||||||
[EModelEndpoint.assistants]: assistantsIcon,
|
[EModelEndpoint.assistants]: assistantsIcon,
|
||||||
[EModelEndpoint.agents]: agentsIcon,
|
[EModelEndpoint.agents]: agentsIcon,
|
||||||
[EModelEndpoint.azureAssistants]: assistantsIcon,
|
[EModelEndpoint.azureAssistants]: assistantsIcon,
|
||||||
|
|
@ -189,7 +197,9 @@ const MessageEndpointIcon: React.FC<IconProps> = (props) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
let { icon, bg, name } =
|
let { icon, bg, name } =
|
||||||
endpoint && endpointIcons[endpoint] ? endpointIcons[endpoint] : endpointIcons.default;
|
endpoint != null && endpoint && endpointIcons[endpoint]
|
||||||
|
? endpointIcons[endpoint] ?? {}
|
||||||
|
: (endpointIcons.default as EndpointIcon);
|
||||||
|
|
||||||
if (iconURL && endpointIcons[iconURL]) {
|
if (iconURL && endpointIcons[iconURL]) {
|
||||||
({ icon, bg, name } = endpointIcons[iconURL]);
|
({ icon, bg, name } = endpointIcons[iconURL]);
|
||||||
|
|
@ -201,9 +211,9 @@ const MessageEndpointIcon: React.FC<IconProps> = (props) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
title={name}
|
title={name ?? ''}
|
||||||
style={{
|
style={{
|
||||||
background: bg || 'transparent',
|
background: bg != null ? bg || 'transparent' : 'transparent',
|
||||||
width: size,
|
width: size,
|
||||||
height: size,
|
height: size,
|
||||||
}}
|
}}
|
||||||
|
|
@ -222,4 +232,4 @@ const MessageEndpointIcon: React.FC<IconProps> = (props) => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default MessageEndpointIcon;
|
export default memo(MessageEndpointIcon);
|
||||||
|
|
|
||||||
21
client/src/components/Endpoints/URLIcon.tsx
Normal file
21
client/src/components/Endpoints/URLIcon.tsx
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
import React, { memo } from 'react';
|
||||||
|
|
||||||
|
export const URLIcon = memo(
|
||||||
|
({
|
||||||
|
iconURL,
|
||||||
|
altName,
|
||||||
|
containerStyle = { width: '20', height: '20' },
|
||||||
|
imageStyle = { width: '100%', height: '100%' },
|
||||||
|
className = 'icon-xl mr-1 shrink-0 overflow-hidden rounded-full',
|
||||||
|
}: {
|
||||||
|
iconURL: string;
|
||||||
|
altName?: string | null;
|
||||||
|
className?: string;
|
||||||
|
containerStyle?: React.CSSProperties;
|
||||||
|
imageStyle?: React.CSSProperties;
|
||||||
|
}) => (
|
||||||
|
<div className={className} style={containerStyle}>
|
||||||
|
<img src={iconURL} alt={altName ?? ''} style={imageStyle} className="object-cover" />
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
import { useCallback, useMemo, memo } from 'react';
|
import { useCallback, useMemo, memo } from 'react';
|
||||||
import type { TMessage, TMessageContentParts } from 'librechat-data-provider';
|
import type { TMessage, TMessageContentParts } from 'librechat-data-provider';
|
||||||
import type { TMessageProps } from '~/common';
|
import type { TMessageProps, TMessageIcon } from '~/common';
|
||||||
import ContentParts from '~/components/Chat/Messages/Content/ContentParts';
|
import ContentParts from '~/components/Chat/Messages/Content/ContentParts';
|
||||||
import PlaceholderRow from '~/components/Chat/Messages/ui/PlaceholderRow';
|
import PlaceholderRow from '~/components/Chat/Messages/ui/PlaceholderRow';
|
||||||
import SiblingSwitch from '~/components/Chat/Messages/SiblingSwitch';
|
import SiblingSwitch from '~/components/Chat/Messages/SiblingSwitch';
|
||||||
|
|
@ -65,18 +65,16 @@ const ContentRender = memo(
|
||||||
[msg?.children, msg?.depth, latestMessage?.depth],
|
[msg?.children, msg?.depth, latestMessage?.depth],
|
||||||
);
|
);
|
||||||
|
|
||||||
const iconData = useMemo(
|
const iconData: TMessageIcon = useMemo(
|
||||||
() =>
|
() => ({
|
||||||
({
|
endpoint: msg?.endpoint ?? conversation?.endpoint,
|
||||||
endpoint: msg?.endpoint ?? conversation?.endpoint,
|
model: msg?.model ?? conversation?.model,
|
||||||
model: msg?.model ?? conversation?.model,
|
iconURL: msg?.iconURL ?? conversation?.iconURL,
|
||||||
iconURL: msg?.iconURL ?? conversation?.iconURL,
|
modelLabel: messageLabel,
|
||||||
modelLabel: conversation?.chatGptLabel ?? conversation?.modelLabel,
|
isCreatedByUser: msg?.isCreatedByUser,
|
||||||
isCreatedByUser: msg?.isCreatedByUser,
|
}),
|
||||||
} as TMessage & { modelLabel?: string }),
|
|
||||||
[
|
[
|
||||||
conversation?.chatGptLabel,
|
messageLabel,
|
||||||
conversation?.modelLabel,
|
|
||||||
conversation?.endpoint,
|
conversation?.endpoint,
|
||||||
conversation?.iconURL,
|
conversation?.iconURL,
|
||||||
conversation?.model,
|
conversation?.model,
|
||||||
|
|
|
||||||
|
|
@ -220,6 +220,7 @@ export default function useChatFunctions({
|
||||||
isCreatedByUser: false,
|
isCreatedByUser: false,
|
||||||
isEdited: isEditOrContinue,
|
isEdited: isEditOrContinue,
|
||||||
iconURL: convo.iconURL,
|
iconURL: convo.iconURL,
|
||||||
|
model: convo.model,
|
||||||
error: false,
|
error: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -254,6 +255,7 @@ export default function useChatFunctions({
|
||||||
currentMessages = currentMessages.filter((msg) => msg.messageId !== responseMessageId);
|
currentMessages = currentMessages.filter((msg) => msg.messageId !== responseMessageId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.log('message_state', initialResponse);
|
||||||
const submission: TSubmission = {
|
const submission: TSubmission = {
|
||||||
conversation: {
|
conversation: {
|
||||||
...conversation,
|
...conversation,
|
||||||
|
|
|
||||||
|
|
@ -54,11 +54,11 @@ export default function useMessageProcess({ message }: { message?: TMessage | nu
|
||||||
latestText.current &&
|
latestText.current &&
|
||||||
convoId !== latestText.current.split(Constants.COMMON_DIVIDER)[2])
|
convoId !== latestText.current.split(Constants.COMMON_DIVIDER)[2])
|
||||||
) {
|
) {
|
||||||
logger.log('[useMessageProcess] Setting latest message: ', logInfo);
|
logger.log('latest_message', '[useMessageProcess] Setting latest message; logInfo:', logInfo);
|
||||||
latestText.current = textKey;
|
latestText.current = textKey;
|
||||||
setLatestMessage({ ...message });
|
setLatestMessage({ ...message });
|
||||||
} else {
|
} else {
|
||||||
logger.log('No change in latest message', logInfo);
|
logger.log('latest_message', 'No change in latest message; logInfo', logInfo);
|
||||||
}
|
}
|
||||||
}, [hasNoChildren, message, setLatestMessage, conversation?.conversationId]);
|
}, [hasNoChildren, message, setLatestMessage, conversation?.conversationId]);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,28 @@
|
||||||
import type { TMessage } from 'librechat-data-provider';
|
|
||||||
import { EModelEndpoint, isAssistantsEndpoint } from 'librechat-data-provider';
|
import { EModelEndpoint, isAssistantsEndpoint } from 'librechat-data-provider';
|
||||||
|
|
||||||
type TUseGenerations = {
|
type TUseGenerations = {
|
||||||
|
error?: boolean;
|
||||||
endpoint?: string;
|
endpoint?: string;
|
||||||
message?: TMessage;
|
messageId?: string;
|
||||||
isSubmitting: boolean;
|
|
||||||
isEditing?: boolean;
|
isEditing?: boolean;
|
||||||
latestMessage: TMessage | null;
|
isSubmitting: boolean;
|
||||||
|
searchResult?: boolean;
|
||||||
|
finish_reason?: string;
|
||||||
|
latestMessageId?: string;
|
||||||
|
isCreatedByUser?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function useGenerationsByLatest({
|
export default function useGenerationsByLatest({
|
||||||
|
error = false,
|
||||||
endpoint,
|
endpoint,
|
||||||
message,
|
messageId,
|
||||||
isSubmitting,
|
|
||||||
isEditing = false,
|
isEditing = false,
|
||||||
latestMessage,
|
isSubmitting,
|
||||||
|
searchResult = false,
|
||||||
|
finish_reason = '',
|
||||||
|
latestMessageId,
|
||||||
|
isCreatedByUser = false,
|
||||||
}: TUseGenerations) {
|
}: TUseGenerations) {
|
||||||
const {
|
|
||||||
messageId,
|
|
||||||
searchResult = false,
|
|
||||||
error = false,
|
|
||||||
finish_reason = '',
|
|
||||||
isCreatedByUser = false,
|
|
||||||
} = message ?? {};
|
|
||||||
const isEditableEndpoint = Boolean(
|
const isEditableEndpoint = Boolean(
|
||||||
[
|
[
|
||||||
EModelEndpoint.openAI,
|
EModelEndpoint.openAI,
|
||||||
|
|
@ -37,7 +37,7 @@ export default function useGenerationsByLatest({
|
||||||
);
|
);
|
||||||
|
|
||||||
const continueSupported =
|
const continueSupported =
|
||||||
latestMessage?.messageId === messageId &&
|
latestMessageId === messageId &&
|
||||||
finish_reason &&
|
finish_reason &&
|
||||||
finish_reason !== 'stop' &&
|
finish_reason !== 'stop' &&
|
||||||
!isEditing &&
|
!isEditing &&
|
||||||
|
|
|
||||||
|
|
@ -204,9 +204,9 @@ export function getIconEndpoint({
|
||||||
iconURL,
|
iconURL,
|
||||||
endpoint,
|
endpoint,
|
||||||
}: {
|
}: {
|
||||||
endpointsConfig: t.TEndpointsConfig | undefined;
|
endpointsConfig?: t.TEndpointsConfig;
|
||||||
iconURL: string | undefined;
|
iconURL?: string | null;
|
||||||
endpoint: string | null | undefined;
|
endpoint?: string | null;
|
||||||
}) {
|
}) {
|
||||||
return (endpointsConfig?.[iconURL ?? ''] ? iconURL ?? endpoint : endpoint) ?? '';
|
return (endpointsConfig?.[iconURL ?? ''] ? iconURL ?? endpoint : endpoint) ?? '';
|
||||||
}
|
}
|
||||||
|
|
@ -219,7 +219,7 @@ export function getIconKey({
|
||||||
endpointIconURL: iconURL,
|
endpointIconURL: iconURL,
|
||||||
}: {
|
}: {
|
||||||
endpoint?: string | null;
|
endpoint?: string | null;
|
||||||
endpointsConfig?: t.TEndpointsConfig | undefined;
|
endpointsConfig?: t.TEndpointsConfig;
|
||||||
endpointType?: string | null;
|
endpointType?: string | null;
|
||||||
endpointIconURL?: string;
|
endpointIconURL?: string;
|
||||||
}) {
|
}) {
|
||||||
|
|
|
||||||
|
|
@ -475,7 +475,7 @@ export const tMessageSchema = z.object({
|
||||||
/* assistant */
|
/* assistant */
|
||||||
thread_id: z.string().optional(),
|
thread_id: z.string().optional(),
|
||||||
/* frontend components */
|
/* frontend components */
|
||||||
iconURL: z.string().optional(),
|
iconURL: z.string().nullable().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TAttachmentMetadata = { messageId: string; toolCallId: string };
|
export type TAttachmentMetadata = { messageId: string; toolCallId: string };
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue