🧭 refactor: Modernize Nav/Header (#7094)

* refactor: streamline model preset handling in conversation setup

* refactor: integrate navigation and location hooks in chat functions and event handlers, prevent cache from fetching on final event handling

* fix: prevent adding code interpreter non-image output to file list on message attachment event, fix all unhandled edge cases when this is done (treating the file download as an image attachment, undefined fields, message tokenCount issues, use of `startsWith` on undefined "text") although it is now prevent altogether

* chore: remove unused jailbreak prop from MinimalIcon component in EndpointIcon

* feat: add new SVG icons (MobileSidebar, Sidebar, XAIcon), fix: xAI styling in dark vs. light modes, adjust styling of Landing icons

* fix: open conversation in new tab on navigation with ctrl/meta key

* refactor: update Nav & Header to use close/open sidebar buttons, as well as redesign "New Chat"/"Bookmarks" buttons to the top of the Nav, matching the latest design of ChatGPT for simplicity and to free up space

* chore: remove unused isToggleHovering state and simplify opacity logic in Nav component

* style: match mobile nav to mobile header
This commit is contained in:
Danny Avila 2025-04-27 14:03:25 -04:00 committed by GitHub
parent c0ebb434a6
commit 550c7cc68a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
37 changed files with 361 additions and 298 deletions

View file

@ -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<string, unknown>) => {
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;

View file

@ -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<TMessage[]>([QueryKeys.messages, queryParam], messages);
if (queryParam === 'new') {
if (queryParam === 'new' && conversationId && conversationId !== 'new') {
queryClient.setQueryData<TMessage[]>([QueryKeys.messages, conversationId], messages);
}
},

View file

@ -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 (
<XAIcon
className={getKnownClass({
currentEndpoint,
context: context,
className,
})}
/>
);
}
if (iconURL) {
return <img className={className} src={iconURL} alt={`${endpoint} Icon`} />;
}

View file

@ -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 || [])];
});

View file

@ -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<TMessage[]>(
[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 {

View file

@ -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) {