-
-
-
{isSmallScreen &&
}
>
);
diff --git a/client/src/components/Nav/NewChat.tsx b/client/src/components/Nav/NewChat.tsx
index a891fe9386..a8a75c8b04 100644
--- a/client/src/components/Nav/NewChat.tsx
+++ b/client/src/components/Nav/NewChat.tsx
@@ -1,88 +1,27 @@
-import React, { useMemo, useCallback } from 'react';
-import { Search } from 'lucide-react';
+import React, { useCallback } from 'react';
import { useRecoilValue } from 'recoil';
import { useNavigate } from 'react-router-dom';
import { useQueryClient } from '@tanstack/react-query';
import { QueryKeys, Constants } from 'librechat-data-provider';
-import type { TConversation, TMessage } from 'librechat-data-provider';
-import { getEndpointField, getIconEndpoint, getIconKey } from '~/utils';
-import ConvoIconURL from '~/components/Endpoints/ConvoIconURL';
-import { useGetEndpointsQuery } from '~/data-provider';
+import type { TMessage, TStartupConfig } from 'librechat-data-provider';
+import { NewChatIcon, MobileSidebar, Sidebar } from '~/components/svg';
+import { getDefaultModelSpec, getModelSpecPreset } from '~/utils';
+import { TooltipAnchor, Button } from '~/components/ui';
import { useLocalize, useNewConvo } from '~/hooks';
-import { icons } from '~/hooks/Endpoint/Icons';
-import { NewChatIcon } from '~/components/svg';
-import { cn } from '~/utils';
import store from '~/store';
-const NewChatButtonIcon = React.memo(({ conversation }: { conversation: TConversation | null }) => {
- const { data: endpointsConfig } = useGetEndpointsQuery();
- const search = useRecoilValue(store.search);
- const searchQuery = search.debouncedQuery;
-
- const computedIcon = useMemo(() => {
- if (searchQuery) {
- return null;
- }
- let { endpoint = '' } = conversation ?? {};
- const iconURL = conversation?.iconURL ?? '';
- endpoint = getIconEndpoint({ endpointsConfig, iconURL, endpoint });
- const endpointType = getEndpointField(endpointsConfig, endpoint, 'type');
- const endpointIconURL = getEndpointField(endpointsConfig, endpoint, 'iconURL');
- const iconKey = getIconKey({ endpoint, endpointsConfig, endpointType, endpointIconURL });
- const Icon = icons[iconKey];
- return { iconURL, endpoint, endpointType, endpointIconURL, Icon };
- }, [searchQuery, conversation, endpointsConfig]);
-
- if (searchQuery) {
- return (
-
-
-
- );
- }
-
- if (!computedIcon) {
- return null;
- }
-
- const { iconURL, endpoint, endpointIconURL, Icon } = computedIcon;
-
- return (
-
- {iconURL && iconURL.includes('http') ? (
-
- ) : (
-
- {endpoint && Icon && (
-
- )}
-
- )}
-
- );
-});
-
export default function NewChat({
index = 0,
toggleNav,
subHeaders,
isSmallScreen,
+ headerButtons,
}: {
index?: number;
toggleNav: () => void;
+ isSmallScreen?: boolean;
subHeaders?: React.ReactNode;
- isSmallScreen: boolean;
+ headerButtons?: React.ReactNode;
}) {
const queryClient = useQueryClient();
/** Note: this component needs an explicit index passed if using more than one */
@@ -91,48 +30,64 @@ export default function NewChat({
const localize = useLocalize();
const { conversation } = store.useCreateConversationAtom(index);
- const clickHandler = useCallback(
- (event: React.MouseEvent
) => {
- if (event.button === 0 && !(event.ctrlKey || event.metaKey)) {
- event.preventDefault();
- queryClient.setQueryData(
- [QueryKeys.messages, conversation?.conversationId ?? Constants.NEW_CONVO],
- [],
- );
- newConvo();
- navigate('/c/new');
+ const clickHandler: React.MouseEventHandler = useCallback(
+ (e) => {
+ if (e.button === 0 && (e.ctrlKey || e.metaKey)) {
+ window.open('/c/new', '_blank');
+ return;
+ }
+ queryClient.setQueryData(
+ [QueryKeys.messages, conversation?.conversationId ?? Constants.NEW_CONVO],
+ [],
+ );
+ newConvo();
+ navigate('/c/new');
+ if (isSmallScreen) {
toggleNav();
}
},
- [queryClient, conversation, newConvo, navigate, toggleNav],
+ [queryClient, conversation, newConvo, navigate, toggleNav, isSmallScreen],
);
return (
-
-
+ >
);
}
diff --git a/client/src/components/Prompts/Groups/GroupSidePanel.tsx b/client/src/components/Prompts/Groups/GroupSidePanel.tsx
index 84a5c3a7a5..5cfd77ec2a 100644
--- a/client/src/components/Prompts/Groups/GroupSidePanel.tsx
+++ b/client/src/components/Prompts/Groups/GroupSidePanel.tsx
@@ -24,7 +24,7 @@ export default function GroupSidePanel({
} & ReturnType
) {
const location = useLocation();
const isSmallerScreen = useMediaQuery('(max-width: 1024px)');
- const isChatRoute = useMemo(() => location.pathname.startsWith('/c/'), [location.pathname]);
+ const isChatRoute = useMemo(() => location.pathname?.startsWith('/c/'), [location.pathname]);
return (
})
const file = row.original;
return (
- {file?.type.startsWith('image') === true ? (
+ {file?.type?.startsWith('image') === true ? (
+
+
+ );
+}
diff --git a/client/src/components/svg/Panel.tsx b/client/src/components/svg/Panel.tsx
deleted file mode 100644
index bb62833de9..0000000000
--- a/client/src/components/svg/Panel.tsx
+++ /dev/null
@@ -1,43 +0,0 @@
-export default function Panel({ open = false }) {
- const openPanel = (
-
- );
-
- const closePanel = (
-
- );
-
- if (open) {
- return openPanel;
- } else {
- return closePanel;
- }
-}
diff --git a/client/src/components/svg/Sidebar.tsx b/client/src/components/svg/Sidebar.tsx
new file mode 100644
index 0000000000..152215f635
--- /dev/null
+++ b/client/src/components/svg/Sidebar.tsx
@@ -0,0 +1,19 @@
+export default function Sidebar({ className }: { className?: string }) {
+ return (
+
+ );
+}
diff --git a/client/src/components/svg/XAIcon.tsx b/client/src/components/svg/XAIcon.tsx
new file mode 100644
index 0000000000..2ee679dcd3
--- /dev/null
+++ b/client/src/components/svg/XAIcon.tsx
@@ -0,0 +1,16 @@
+import React from 'react';
+
+export default function XAIcon({ className = '' }) {
+ return (
+
+ );
+}
diff --git a/client/src/components/svg/index.ts b/client/src/components/svg/index.ts
index 745c5210bd..f0cf35f6c3 100644
--- a/client/src/components/svg/index.ts
+++ b/client/src/components/svg/index.ts
@@ -4,7 +4,8 @@ export { default as Plugin } from './Plugin';
export { default as GPTIcon } from './GPTIcon';
export { default as EditIcon } from './EditIcon';
export { default as DataIcon } from './DataIcon';
-export { default as Panel } from './Panel';
+export { default as Sidebar } from './Sidebar';
+export { default as MobileSidebar } from './MobileSidebar';
export { default as Spinner } from './Spinner';
export { default as Clipboard } from './Clipboard';
export { default as CheckMark } from './CheckMark';
@@ -56,3 +57,4 @@ export { default as SpeechIcon } from './SpeechIcon';
export { default as SaveIcon } from './SaveIcon';
export { default as CircleHelpIcon } from './CircleHelpIcon';
export { default as BedrockIcon } from './BedrockIcon';
+export { default as XAIcon } from './XAIcon';
diff --git a/client/src/components/ui/ModelParameters.tsx b/client/src/components/ui/ModelParameters.tsx
index 881717e096..17fd8c14be 100644
--- a/client/src/components/ui/ModelParameters.tsx
+++ b/client/src/components/ui/ModelParameters.tsx
@@ -33,7 +33,8 @@ const ModelParameters: React.FC = ({
const rangeRef = useRef(null);
const id = `model-parameter-${ariaLabel.toLowerCase().replace(/\s+/g, '-')}`;
- const displayLabel = label.startsWith('com_') ? localize(label as TranslationKeys) : label;
+ const displayLabel =
+ label && label.startsWith('com_') ? localize(label as TranslationKeys) : label;
const getDecimalPlaces = (num: number) => {
const match = ('' + num).match(/(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/);
diff --git a/client/src/hooks/Chat/useChatFunctions.ts b/client/src/hooks/Chat/useChatFunctions.ts
index 770c8127d8..b75d97d6a2 100644
--- a/client/src/hooks/Chat/useChatFunctions.ts
+++ b/client/src/hooks/Chat/useChatFunctions.ts
@@ -25,6 +25,7 @@ import store, { useGetEphemeralAgent } from '~/store';
import { getArtifactsMode } from '~/utils/artifacts';
import { getEndpointField, logger } from '~/utils';
import useUserKey from '~/hooks/Input/useUserKey';
+import { useNavigate } from 'react-router-dom';
const logChatRequest = (request: Record) => {
logger.log('=====================================\nAsk function called with:');
@@ -69,6 +70,7 @@ export default function useChatFunctions({
const codeArtifacts = useRecoilValue(store.codeArtifacts);
const includeShadcnui = useRecoilValue(store.includeShadcnui);
const customPromptMode = useRecoilValue(store.customPromptMode);
+ const navigate = useNavigate();
const resetLatestMultiMessage = useResetRecoilState(store.latestMessageFamily(index + 1));
const setShowStopButton = useSetRecoilState(store.showStopButtonByIndex(index));
const setFilesToDelete = useSetFilesToDelete();
@@ -146,6 +148,7 @@ export default function useChatFunctions({
parentMessageId = Constants.NO_PARENT;
currentMessages = [];
conversationId = null;
+ navigate('/c/new');
}
const targetParentMessageId = isRegenerate ? messageId : latestMessage?.parentMessageId;
diff --git a/client/src/hooks/Chat/useChatHelpers.ts b/client/src/hooks/Chat/useChatHelpers.ts
index a79b860c57..c4e491c7df 100644
--- a/client/src/hooks/Chat/useChatHelpers.ts
+++ b/client/src/hooks/Chat/useChatHelpers.ts
@@ -23,10 +23,10 @@ export default function useChatHelpers(index = 0, paramId?: string) {
const { conversation, setConversation } = useCreateConversationAtom(index);
const { conversationId } = conversation ?? {};
- const queryParam = paramId === 'new' ? paramId : conversationId ?? paramId ?? '';
+ const queryParam = paramId === 'new' ? paramId : (conversationId ?? paramId ?? '');
/* Messages: here simply to fetch, don't export and use `getMessages()` instead */
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
+
const { data: _messages } = useGetMessagesByConvoId(conversationId ?? '', {
enabled: isAuthenticated,
});
@@ -41,7 +41,7 @@ export default function useChatHelpers(index = 0, paramId?: string) {
const setMessages = useCallback(
(messages: TMessage[]) => {
queryClient.setQueryData([QueryKeys.messages, queryParam], messages);
- if (queryParam === 'new') {
+ if (queryParam === 'new' && conversationId && conversationId !== 'new') {
queryClient.setQueryData([QueryKeys.messages, conversationId], messages);
}
},
diff --git a/client/src/hooks/Endpoint/UnknownIcon.tsx b/client/src/hooks/Endpoint/UnknownIcon.tsx
index 04bac0bdeb..6c69954a2d 100644
--- a/client/src/hooks/Endpoint/UnknownIcon.tsx
+++ b/client/src/hooks/Endpoint/UnknownIcon.tsx
@@ -1,6 +1,6 @@
import { memo } from 'react';
import { EModelEndpoint, KnownEndpoints } from 'librechat-data-provider';
-import { CustomMinimalIcon } from '~/components/svg';
+import { CustomMinimalIcon, XAIcon } from '~/components/svg';
import { IconContext } from '~/common';
import { cn } from '~/utils';
@@ -20,7 +20,6 @@ const knownEndpointAssets = {
[KnownEndpoints.shuttleai]: '/assets/shuttleai.png',
[KnownEndpoints['together.ai']]: '/assets/together.png',
[KnownEndpoints.unify]: '/assets/unify.webp',
- [KnownEndpoints.xai]: '/assets/xai.svg',
};
const knownEndpointClasses = {
@@ -29,9 +28,6 @@ const knownEndpointClasses = {
},
[KnownEndpoints.xai]: {
[IconContext.landing]: 'p-2',
- [IconContext.menuItem]: 'bg-white',
- [IconContext.message]: 'bg-white',
- [IconContext.nav]: 'bg-white',
},
};
@@ -72,6 +68,18 @@ function UnknownIcon({
const currentEndpoint = endpoint.toLowerCase();
+ if (currentEndpoint === KnownEndpoints.xai) {
+ return (
+
+ );
+ }
+
if (iconURL) {
return
;
}
diff --git a/client/src/hooks/SSE/useAttachmentHandler.ts b/client/src/hooks/SSE/useAttachmentHandler.ts
index 3610b9798e..2eb748ab1f 100644
--- a/client/src/hooks/SSE/useAttachmentHandler.ts
+++ b/client/src/hooks/SSE/useAttachmentHandler.ts
@@ -10,7 +10,7 @@ export default function useAttachmentHandler(queryClient?: QueryClient) {
return ({ data }: { data: TAttachment; submission: EventSubmission }) => {
const { messageId } = data;
- if (queryClient) {
+ if (queryClient && !data?.filepath?.startsWith('/api/files')) {
queryClient.setQueryData([QueryKeys.files], (oldData: TAttachment[] | undefined) => {
return [data, ...(oldData || [])];
});
diff --git a/client/src/hooks/SSE/useEventHandlers.ts b/client/src/hooks/SSE/useEventHandlers.ts
index 257331e485..003d5547df 100644
--- a/client/src/hooks/SSE/useEventHandlers.ts
+++ b/client/src/hooks/SSE/useEventHandlers.ts
@@ -1,7 +1,7 @@
import { v4 } from 'uuid';
import { useCallback, useRef } from 'react';
import { useSetRecoilState } from 'recoil';
-import { useParams } from 'react-router-dom';
+import { useParams, useNavigate, useLocation } from 'react-router-dom';
import { useQueryClient } from '@tanstack/react-query';
import {
QueryKeys,
@@ -172,6 +172,8 @@ export default function useEventHandlers({
const { announcePolite } = useLiveAnnouncer();
const applyAgentTemplate = useApplyNewAgentTemplate();
const setAbortScroll = useSetRecoilState(store.abortScroll);
+ const navigate = useNavigate();
+ const location = useLocation();
const lastAnnouncementTimeRef = useRef(Date.now());
const { conversationId: paramId } = useParams();
@@ -421,6 +423,7 @@ export default function useEventHandlers({
announcePolite,
setConversation,
resetLatestMessage,
+ applyAgentTemplate,
],
);
@@ -449,12 +452,20 @@ export default function useEventHandlers({
announcePolite({ message: getAllContentText(responseMessage) });
/* Update messages; if assistants endpoint, client doesn't receive responseMessage */
+ let finalMessages: TMessage[] = [];
if (runMessages) {
- setMessages([...runMessages]);
+ finalMessages = [...runMessages];
} else if (isRegenerate && responseMessage) {
- setMessages([...messages, responseMessage]);
+ finalMessages = [...messages, responseMessage];
} else if (requestMessage != null && responseMessage != null) {
- setMessages([...messages, requestMessage, responseMessage]);
+ finalMessages = [...messages, requestMessage, responseMessage];
+ }
+ if (finalMessages.length > 0) {
+ setMessages(finalMessages);
+ queryClient.setQueryData(
+ [QueryKeys.messages, conversation.conversationId],
+ finalMessages,
+ );
}
const isNewConvo = conversation.conversationId !== submissionConvo.conversationId;
@@ -476,8 +487,8 @@ export default function useEventHandlers({
}
if (setConversation && isAddedRequest !== true) {
- if (window.location.pathname === '/c/new') {
- window.history.pushState({}, '', '/c/' + conversation.conversationId);
+ if (location.pathname === '/c/new') {
+ navigate(`/c/${conversation.conversationId}`, { replace: true });
}
setConversation((prevState) => {
@@ -502,16 +513,18 @@ export default function useEventHandlers({
setIsSubmitting(false);
},
[
- genTitle,
- queryClient,
- getMessages,
- setMessages,
- setCompleted,
- isAddedRequest,
- announcePolite,
- setConversation,
- setIsSubmitting,
setShowStopButton,
+ setCompleted,
+ getMessages,
+ announcePolite,
+ genTitle,
+ setConversation,
+ isAddedRequest,
+ setIsSubmitting,
+ setMessages,
+ queryClient,
+ location.pathname,
+ navigate,
],
);
@@ -599,7 +612,7 @@ export default function useEventHandlers({
setIsSubmitting(false);
return;
},
- [setMessages, paramId, setIsSubmitting, setCompleted, newConversation],
+ [setCompleted, setMessages, paramId, newConversation, setIsSubmitting, getMessages],
);
const abortConversation = useCallback(
@@ -698,7 +711,15 @@ export default function useEventHandlers({
setIsSubmitting(false);
}
},
- [token, setIsSubmitting, finalHandler, cancelHandler, setMessages, newConversation],
+ [
+ finalHandler,
+ newConversation,
+ setIsSubmitting,
+ token,
+ cancelHandler,
+ getMessages,
+ setMessages,
+ ],
);
return {
diff --git a/client/src/hooks/useNewConvo.ts b/client/src/hooks/useNewConvo.ts
index f5933cc547..d27f80a306 100644
--- a/client/src/hooks/useNewConvo.ts
+++ b/client/src/hooks/useNewConvo.ts
@@ -22,8 +22,8 @@ import {
getEndpointField,
buildDefaultConvo,
getDefaultEndpoint,
+ getModelSpecPreset,
getDefaultModelSpec,
- getModelSpecIconURL,
updateLastSelectedModel,
} from '~/utils';
import { useDeleteFilesMutation, useGetEndpointsQuery, useGetStartupConfig } from '~/data-provider';
@@ -231,11 +231,7 @@ const useNewConvo = (index = 0) => {
(startupConfig.interface?.modelSelect ?? true) !== true) &&
defaultModelSpec
) {
- preset = {
- ...defaultModelSpec.preset,
- iconURL: getModelSpecIconURL(defaultModelSpec),
- spec: defaultModelSpec.name,
- } as TConversation;
+ preset = getModelSpecPreset(defaultModelSpec);
}
if (conversation.conversationId === 'new' && !modelsData) {
diff --git a/client/src/routes/ChatRoute.tsx b/client/src/routes/ChatRoute.tsx
index 4ce9cdef9e..48382fc548 100644
--- a/client/src/routes/ChatRoute.tsx
+++ b/client/src/routes/ChatRoute.tsx
@@ -9,8 +9,8 @@ import {
useGetStartupConfig,
useGetEndpointsQuery,
} from '~/data-provider';
+import { getDefaultModelSpec, getModelSpecPreset, logger } from '~/utils';
import { useNewConvo, useAppStartup, useAssistantListMap } from '~/hooks';
-import { getDefaultModelSpec, getModelSpecIconURL, logger } from '~/utils';
import { ToolCallsMapProvider } from '~/Providers';
import ChatView from '~/components/Chat/ChatView';
import useAuthRedirect from './useAuthRedirect';
@@ -65,15 +65,7 @@ export default function ChatRoute() {
newConversation({
modelsData: modelsQuery.data,
template: conversation ? conversation : undefined,
- ...(spec
- ? {
- preset: {
- ...spec.preset,
- iconURL: getModelSpecIconURL(spec),
- spec: spec.name,
- },
- }
- : {}),
+ ...(spec ? { preset: getModelSpecPreset(spec) } : {}),
});
hasSetConversation.current = true;
@@ -97,15 +89,7 @@ export default function ChatRoute() {
newConversation({
modelsData: modelsQuery.data,
template: conversation ? conversation : undefined,
- ...(spec
- ? {
- preset: {
- ...spec.preset,
- iconURL: getModelSpecIconURL(spec),
- spec: spec.name,
- },
- }
- : {}),
+ ...(spec ? { preset: getModelSpecPreset(spec) } : {}),
});
hasSetConversation.current = true;
} else if (
diff --git a/client/src/utils/endpoints.ts b/client/src/utils/endpoints.ts
index f86f81682e..497c76aca6 100644
--- a/client/src/utils/endpoints.ts
+++ b/client/src/utils/endpoints.ts
@@ -203,6 +203,17 @@ export function getDefaultModelSpec(startupConfig?: t.TStartupConfig) {
return list?.find((spec) => spec.name === lastConversationSetup.spec);
}
+export function getModelSpecPreset(modelSpec?: t.TModelSpec) {
+ if (!modelSpec) {
+ return;
+ }
+ return {
+ ...modelSpec.preset,
+ spec: modelSpec.name,
+ iconURL: getModelSpecIconURL(modelSpec),
+ };
+}
+
/** Gets the default spec iconURL by order or definition.
*
* First, the admin defined default, then last selected spec, followed by first spec