mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-23 11:50:14 +01:00
🧭 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:
parent
c0ebb434a6
commit
550c7cc68a
37 changed files with 361 additions and 298 deletions
|
|
@ -3,7 +3,7 @@ import { useOutletContext } from 'react-router-dom';
|
|||
import { getConfigDefaults, PermissionTypes, Permissions } from 'librechat-data-provider';
|
||||
import type { ContextType } from '~/common';
|
||||
import ModelSelector from './Menus/Endpoints/ModelSelector';
|
||||
import { PresetsMenu, HeaderNewChat } from './Menus';
|
||||
import { PresetsMenu, HeaderNewChat, OpenSidebar } from './Menus';
|
||||
import { useGetStartupConfig } from '~/data-provider';
|
||||
import ExportAndShareMenu from './ExportAndShareMenu';
|
||||
import { useMediaQuery, useHasAccess } from '~/hooks';
|
||||
|
|
@ -15,7 +15,7 @@ const defaultInterface = getConfigDefaults().interface;
|
|||
|
||||
export default function Header() {
|
||||
const { data: startupConfig } = useGetStartupConfig();
|
||||
const { navVisible } = useOutletContext<ContextType>();
|
||||
const { navVisible, setNavVisible } = useOutletContext<ContextType>();
|
||||
const interfaceConfig = useMemo(
|
||||
() => startupConfig?.interface ?? defaultInterface,
|
||||
[startupConfig],
|
||||
|
|
@ -37,6 +37,7 @@ export default function Header() {
|
|||
<div className="sticky top-0 z-10 flex h-14 w-full items-center justify-between bg-white p-2 font-semibold text-text-primary dark:bg-gray-800">
|
||||
<div className="hide-scrollbar flex w-full items-center justify-between gap-2 overflow-x-auto">
|
||||
<div className="mx-2 flex items-center gap-2">
|
||||
{!navVisible && <OpenSidebar setNavVisible={setNavVisible} />}
|
||||
{!navVisible && <HeaderNewChat />}
|
||||
{<ModelSelector startupConfig={startupConfig} />}
|
||||
{interfaceConfig.presets === true && interfaceConfig.modelSelect && <PresetsMenu />}
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ const DragDropModal = ({ onOptionSelect, setShowModal, files, isVisible }: DragD
|
|||
label: localize('com_ui_upload_image_input'),
|
||||
value: undefined,
|
||||
icon: <ImageUpIcon className="icon-md" />,
|
||||
condition: files.every((file) => file.type.startsWith('image/')),
|
||||
condition: files.every((file) => file.type?.startsWith('image/')),
|
||||
},
|
||||
];
|
||||
for (const capability of capabilities) {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
/* eslint-disable react-hooks/rules-of-hooks */
|
||||
|
||||
import { ArrowUpDown, Database } from 'lucide-react';
|
||||
import { FileSources, FileContext } from 'librechat-data-provider';
|
||||
|
|
@ -68,7 +69,7 @@ export const columns: ColumnDef<TFile>[] = [
|
|||
},
|
||||
cell: ({ row }) => {
|
||||
const file = row.original;
|
||||
if (file.type.startsWith('image')) {
|
||||
if (file.type?.startsWith('image')) {
|
||||
return (
|
||||
<div className="flex gap-2">
|
||||
<ImagePreview
|
||||
|
|
@ -76,7 +77,7 @@ export const columns: ColumnDef<TFile>[] = [
|
|||
className="relative h-10 w-10 shrink-0 overflow-hidden rounded-md"
|
||||
source={file.source}
|
||||
/>
|
||||
<span className="self-center truncate ">{file.filename}</span>
|
||||
<span className="self-center truncate">{file.filename}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -212,4 +213,4 @@ export const columns: ColumnDef<TFile>[] = [
|
|||
return `${value}${suffix}`;
|
||||
},
|
||||
},
|
||||
];
|
||||
];
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import { useLocalize, useAuthContext } from '~/hooks';
|
|||
import { getIconEndpoint, getEntity } from '~/utils';
|
||||
|
||||
const containerClassName =
|
||||
'shadow-stroke relative flex h-full items-center justify-center rounded-full bg-white text-black';
|
||||
'shadow-stroke relative flex h-full items-center justify-center rounded-full bg-white dark:bg-presentation dark:text-white text-black dark:after:shadow-none ';
|
||||
|
||||
function getTextSizeClass(text: string | undefined | null) {
|
||||
if (!text) {
|
||||
|
|
@ -149,7 +149,7 @@ export default function Landing({ centerFormOnLanding }: { centerFormOnLanding:
|
|||
>
|
||||
<div ref={contentRef} className="flex flex-col items-center gap-0 p-2">
|
||||
<div
|
||||
className={`flex ${textHasMultipleLines ? 'flex-col' : 'flex-col md:flex-row'} items-center justify-center gap-4`}
|
||||
className={`flex ${textHasMultipleLines ? 'flex-col' : 'flex-col md:flex-row'} items-center justify-center gap-2`}
|
||||
>
|
||||
<div className={`relative size-10 justify-center ${textHasMultipleLines ? 'mb-2' : ''}`}>
|
||||
<ConvoIcon
|
||||
|
|
@ -159,7 +159,7 @@ export default function Landing({ centerFormOnLanding }: { centerFormOnLanding:
|
|||
endpointsConfig={endpointsConfig}
|
||||
containerClassName={containerClassName}
|
||||
context="landing"
|
||||
className="h-2/3 w-2/3"
|
||||
className="h-2/3 w-2/3 text-black dark:text-white"
|
||||
size={41}
|
||||
/>
|
||||
{startupConfig?.showBirthdayIcon && (
|
||||
|
|
|
|||
|
|
@ -1,36 +1,43 @@
|
|||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { QueryKeys, Constants } from 'librechat-data-provider';
|
||||
import type { TMessage } from 'librechat-data-provider';
|
||||
import { useMediaQuery, useLocalize } from '~/hooks';
|
||||
import { Button, NewChatIcon } from '~/components';
|
||||
import { TooltipAnchor, Button } from '~/components/ui';
|
||||
import { NewChatIcon } from '~/components/svg';
|
||||
import { useChatContext } from '~/Providers';
|
||||
import { useLocalize } from '~/hooks';
|
||||
|
||||
export default function HeaderNewChat() {
|
||||
const localize = useLocalize();
|
||||
const queryClient = useQueryClient();
|
||||
const { conversation, newConversation } = useChatContext();
|
||||
const isSmallScreen = useMediaQuery('(max-width: 768px)');
|
||||
const localize = useLocalize();
|
||||
|
||||
if (isSmallScreen) {
|
||||
return null;
|
||||
}
|
||||
const clickHandler: React.MouseEventHandler<HTMLButtonElement> = (e) => {
|
||||
if (e.button === 0 && (e.ctrlKey || e.metaKey)) {
|
||||
window.open('/c/new', '_blank');
|
||||
return;
|
||||
}
|
||||
queryClient.setQueryData<TMessage[]>(
|
||||
[QueryKeys.messages, conversation?.conversationId ?? Constants.NEW_CONVO],
|
||||
[],
|
||||
);
|
||||
newConversation();
|
||||
};
|
||||
|
||||
return (
|
||||
<Button
|
||||
size="icon"
|
||||
variant="outline"
|
||||
data-testid="wide-header-new-chat-button"
|
||||
aria-label={localize('com_ui_new_chat')}
|
||||
className="rounded-xl border border-border-light bg-surface-secondary p-2 hover:bg-surface-hover"
|
||||
onClick={() => {
|
||||
queryClient.setQueryData<TMessage[]>(
|
||||
[QueryKeys.messages, conversation?.conversationId ?? Constants.NEW_CONVO],
|
||||
[],
|
||||
);
|
||||
newConversation();
|
||||
}}
|
||||
>
|
||||
<NewChatIcon />
|
||||
</Button>
|
||||
<TooltipAnchor
|
||||
description={localize('com_ui_new_chat')}
|
||||
render={
|
||||
<Button
|
||||
size="icon"
|
||||
variant="outline"
|
||||
data-testid="wide-header-new-chat-button"
|
||||
aria-label={localize('com_ui_new_chat')}
|
||||
className="rounded-xl border border-border-light bg-surface-secondary p-2 hover:bg-surface-hover max-md:hidden"
|
||||
onClick={clickHandler}
|
||||
>
|
||||
<NewChatIcon />
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
33
client/src/components/Chat/Menus/OpenSidebar.tsx
Normal file
33
client/src/components/Chat/Menus/OpenSidebar.tsx
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
import { TooltipAnchor, Button } from '~/components/ui';
|
||||
import { Sidebar } from '~/components/svg';
|
||||
import { useLocalize } from '~/hooks';
|
||||
|
||||
export default function OpenSidebar({
|
||||
setNavVisible,
|
||||
}: {
|
||||
setNavVisible: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
}) {
|
||||
const localize = useLocalize();
|
||||
return (
|
||||
<TooltipAnchor
|
||||
description={localize('com_nav_open_sidebar')}
|
||||
render={
|
||||
<Button
|
||||
size="icon"
|
||||
variant="outline"
|
||||
data-testid="open-sidebar-button"
|
||||
aria-label={localize('com_nav_open_sidebar')}
|
||||
className="rounded-xl border border-border-light bg-surface-secondary p-2 hover:bg-surface-hover max-md:hidden"
|
||||
onClick={() =>
|
||||
setNavVisible((prev) => {
|
||||
localStorage.setItem('navVisible', JSON.stringify(!prev));
|
||||
return !prev;
|
||||
})
|
||||
}
|
||||
>
|
||||
<Sidebar />
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,2 +1,3 @@
|
|||
export { default as PresetsMenu } from './PresetsMenu';
|
||||
export { default as OpenSidebar } from './OpenSidebar';
|
||||
export { default as HeaderNewChat } from './HeaderNewChat';
|
||||
|
|
|
|||
|
|
@ -150,7 +150,7 @@ export const a: React.ElementType = memo(({ href, children }: TAnchorProps) => {
|
|||
|
||||
return (
|
||||
<a
|
||||
href={filepath.startsWith('files/') ? `/api/${filepath}` : `/api/files/${filepath}`}
|
||||
href={filepath?.startsWith('files/') ? `/api/${filepath}` : `/api/files/${filepath}`}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue