mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-17 08:50:15 +01:00
style: Minor Beta UI fixes (#1197)
* style(Header): hide scrollbar but still allow side scroll/swipe/drag/touch * feat: make menu buttons flexiblewith min-width, delete passed in classes, add pointer-cursor * refactor: use conditional for visibility of plugins settings * fix: make advanced settings popover appear over nav * refactor(textarea): minor padding restyling, add max height * style: make menuItem checkmark invisible instead of hidden so it takes up width space * style: make presetsMenu trigger an icon button, remove max-width of presets except in mobile view * style: improve advanced settings mobile styling * feat: newchat and convo items toggle nav on small screens * style: improve no presets view * style: make sure toggle hover effect does not apply on smaller screens
This commit is contained in:
parent
c64970525b
commit
1ba8d4ffa9
18 changed files with 171 additions and 92 deletions
|
|
@ -6,8 +6,8 @@ import HeaderOptions from './Input/HeaderOptions';
|
||||||
export default function Header() {
|
export default function Header() {
|
||||||
const { navVisible } = useOutletContext<ContextType>();
|
const { navVisible } = useOutletContext<ContextType>();
|
||||||
return (
|
return (
|
||||||
<div className="sticky top-0 z-10 flex h-14 w-full items-center justify-between bg-white/95 p-2 font-semibold dark:bg-gray-800/90 dark:text-white">
|
<div className="sticky top-0 z-10 flex h-14 w-full items-center justify-between bg-white/95 p-2 font-semibold dark:bg-gray-800/90 dark:text-white ">
|
||||||
<div className="flex items-center gap-2 overflow-x-auto">
|
<div className="hide-scrollbar flex items-center gap-2 overflow-x-auto">
|
||||||
{!navVisible && <NewChat />}
|
{!navVisible && <NewChat />}
|
||||||
<EndpointsMenu />
|
<EndpointsMenu />
|
||||||
<HeaderOptions />
|
<HeaderOptions />
|
||||||
|
|
|
||||||
|
|
@ -28,15 +28,22 @@ export default function OptionsPopover({
|
||||||
popoverRef,
|
popoverRef,
|
||||||
() => closePopover(),
|
() => closePopover(),
|
||||||
['dialog-template-content', 'shadcn-button', 'advanced-settings'],
|
['dialog-template-content', 'shadcn-button', 'advanced-settings'],
|
||||||
(target) => {
|
(_target) => {
|
||||||
const tagName = (target as Element)?.tagName;
|
const target = _target as Element;
|
||||||
|
if (
|
||||||
|
target?.id === 'presets-button' ||
|
||||||
|
(target?.parentNode instanceof Element && target.parentNode.id === 'presets-button')
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const tagName = target?.tagName;
|
||||||
return tagName === 'path' || tagName === 'svg' || tagName === 'circle';
|
return tagName === 'path' || tagName === 'svg' || tagName === 'circle';
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const localize = useLocalize();
|
const localize = useLocalize();
|
||||||
const cardStyle =
|
const cardStyle =
|
||||||
'shadow-xl rounded-md min-w-[75px] font-normal bg-white border-black/10 border dark:bg-gray-700 text-black dark:text-white';
|
'shadow-xl rounded-md min-w-[75px] font-normal bg-white border-black/10 border dark:bg-gray-700 text-black dark:text-white ';
|
||||||
|
|
||||||
if (!visible) {
|
if (!visible) {
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -45,7 +52,7 @@ export default function OptionsPopover({
|
||||||
return (
|
return (
|
||||||
<Portal>
|
<Portal>
|
||||||
<Content sideOffset={8} align="start" ref={popoverRef} asChild>
|
<Content sideOffset={8} align="start" ref={popoverRef} asChild>
|
||||||
<div className="z-0 flex w-full flex-col items-center md:px-4">
|
<div className="z-[70] flex w-screen flex-col items-center md:w-full md:px-4">
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
cardStyle,
|
cardStyle,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import TextareaAutosize from 'react-textarea-autosize';
|
import TextareaAutosize from 'react-textarea-autosize';
|
||||||
import { supportsFiles } from '~/common';
|
import { supportsFiles } from '~/common';
|
||||||
import { useTextarea } from '~/hooks';
|
import { useTextarea } from '~/hooks';
|
||||||
|
import { cn, removeFocusOutlines } from '~/utils';
|
||||||
|
|
||||||
export default function Textarea({ value, onChange, setText, submitMessage, endpoint }) {
|
export default function Textarea({ value, onChange, setText, submitMessage, endpoint }) {
|
||||||
const {
|
const {
|
||||||
|
|
@ -14,8 +15,10 @@ export default function Textarea({ value, onChange, setText, submitMessage, endp
|
||||||
} = useTextarea({ setText, submitMessage });
|
} = useTextarea({ setText, submitMessage });
|
||||||
|
|
||||||
const className = supportsFiles[endpoint]
|
const className = supportsFiles[endpoint]
|
||||||
? 'm-0 w-full resize-none border-0 bg-transparent py-3.5 pr-10 focus:ring-0 focus-visible:ring-0 dark:bg-transparent placeholder-black/50 dark:placeholder-white/50 pl-10 md:py-3.5 md:pr-12 md:pl-[55px]'
|
? // ? 'm-0 w-full resize-none border-0 bg-transparent py-3.5 pr-10 focus:ring-0 focus-visible:ring-0 dark:bg-transparent placeholder-black/50 dark:placeholder-white/50 pl-10 md:py-3.5 md:pr-12 md:pl-[55px]'
|
||||||
: 'm-0 w-full resize-none border-0 bg-transparent py-[10px] pr-10 focus:ring-0 focus-visible:ring-0 dark:bg-transparent md:py-4 md:pr-12 gizmo:md:py-3.5 gizmo:placeholder-black/50 gizmo:dark:placeholder-white/50 pl-3 md:pl-4';
|
// : 'm-0 w-full resize-none border-0 bg-transparent py-[10px] pr-10 focus:ring-0 focus-visible:ring-0 dark:bg-transparent md:py-4 md:pr-12 gizmo:md:py-3.5 gizmo:placeholder-black/50 gizmo:dark:placeholder-white/50 pl-3 md:pl-4';
|
||||||
|
'm-0 w-full resize-none border-0 bg-transparent py-3.5 pr-10 focus:ring-0 focus-visible:ring-0 dark:bg-transparent placeholder-black/50 dark:placeholder-white/50 pl-10 md:py-3.5 md:pr-12 md:pl-[55px]'
|
||||||
|
: 'm-0 w-full resize-none border-0 bg-transparent py-[10px] pr-10 focus:ring-0 focus-visible:ring-0 dark:bg-transparent md:py-3.5 md:pr-12 placeholder-black/50 dark:placeholder-white/50 pl-3 md:pl-4';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TextareaAutosize
|
<TextareaAutosize
|
||||||
|
|
@ -36,7 +39,7 @@ export default function Textarea({ value, onChange, setText, submitMessage, endp
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
// className="m-0 w-full resize-none border-0 bg-transparent py-[10px] pr-10 focus:ring-0 focus-visible:ring-0 dark:bg-transparent md:py-4 md:pr-12 gizmo:md:py-3.5 gizmo:placeholder-black/50 gizmo:dark:placeholder-white/50 pl-12 gizmo:pl-10 md:pl-[46px] gizmo:md:pl-[55px]"
|
// className="m-0 w-full resize-none border-0 bg-transparent py-[10px] pr-10 focus:ring-0 focus-visible:ring-0 dark:bg-transparent md:py-4 md:pr-12 gizmo:md:py-3.5 gizmo:placeholder-black/50 gizmo:dark:placeholder-white/50 pl-12 gizmo:pl-10 md:pl-[46px] gizmo:md:pl-[55px]"
|
||||||
// className="gizmo:md:py-3.5 gizmo:placeholder-black/50 gizmo:dark:placeholder-white/50 gizmo:pl-10 gizmo:md:pl-[55px] m-0 h-auto max-h-52 w-full resize-none overflow-y-hidden border-0 bg-transparent py-[10px] pl-12 pr-10 focus:ring-0 focus-visible:ring-0 dark:bg-transparent md:py-4 md:pl-[46px] md:pr-12"
|
// className="gizmo:md:py-3.5 gizmo:placeholder-black/50 gizmo:dark:placeholder-white/50 gizmo:pl-10 gizmo:md:pl-[55px] m-0 h-auto max-h-52 w-full resize-none overflow-y-hidden border-0 bg-transparent py-[10px] pl-12 pr-10 focus:ring-0 focus-visible:ring-0 dark:bg-transparent md:py-4 md:pl-[46px] md:pr-12"
|
||||||
className={className}
|
className={cn(className, removeFocusOutlines, 'max-h-52')}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,16 @@
|
||||||
import { useChatContext } from '~/Providers';
|
import { useChatContext } from '~/Providers';
|
||||||
|
import { useMediaQuery } from '~/hooks';
|
||||||
|
|
||||||
export default function Header() {
|
export default function Header() {
|
||||||
const { newConversation } = useChatContext();
|
const { newConversation } = useChatContext();
|
||||||
|
const isSmallScreen = useMediaQuery('(max-width: 768px)');
|
||||||
|
if (isSmallScreen) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className=" btn btn-neutral btn-small border-token-border-medium relative ml-2 flex h-9 w-9 items-center justify-center whitespace-nowrap rounded-lg rounded-lg border focus:ring-0 focus:ring-offset-0"
|
className="btn btn-neutral btn-small border-token-border-medium relative ml-2 flex hidden h-9 w-9 items-center justify-center whitespace-nowrap rounded-lg rounded-lg border focus:ring-0 focus:ring-offset-0 md:flex"
|
||||||
onClick={() => newConversation()}
|
onClick={() => newConversation()}
|
||||||
>
|
>
|
||||||
<div className="flex w-full items-center justify-center gap-2">
|
<div className="flex w-full items-center justify-center gap-2">
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,20 @@ const PresetItems: FC<{
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{presets && presets.length === 0 && (
|
||||||
|
<div
|
||||||
|
role="menuitem"
|
||||||
|
className="pointer-none group m-1.5 flex h-8 min-w-[170px] gap-2 rounded px-5 py-2.5 !pr-3 text-sm !opacity-100 hover:bg-black/5 focus:ring-0 radix-disabled:pointer-events-none radix-disabled:opacity-50 dark:hover:bg-white/5 md:min-w-[240px]"
|
||||||
|
tabIndex={-1}
|
||||||
|
>
|
||||||
|
<div className="flex h-full grow items-center justify-end gap-2">
|
||||||
|
{/* TODO: Create Preset from here */}
|
||||||
|
{localize('com_endpoint_no_presets')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{presets &&
|
{presets &&
|
||||||
|
presets.length > 0 &&
|
||||||
presets.map((preset, i) => {
|
presets.map((preset, i) => {
|
||||||
if (!preset) {
|
if (!preset) {
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -70,8 +83,7 @@ const PresetItems: FC<{
|
||||||
<div key={`preset-${preset.presetId}`}>
|
<div key={`preset-${preset.presetId}`}>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
key={`preset-item-${preset.presetId}`}
|
key={`preset-item-${preset.presetId}`}
|
||||||
className="w-[380px] md:min-w-[240px]"
|
textClassName="text-xs max-w-[200px] truncate md:max-w-full "
|
||||||
textClassName="text-xs max-w-[180px] md:max-w-[250px]"
|
|
||||||
title={getPresetTitle(preset)}
|
title={getPresetTitle(preset)}
|
||||||
disableHover={true}
|
disableHover={true}
|
||||||
onClick={() => onSelectPreset(preset)}
|
onClick={() => onSelectPreset(preset)}
|
||||||
|
|
@ -83,7 +95,7 @@ const PresetItems: FC<{
|
||||||
>
|
>
|
||||||
<div className="flex h-full items-center justify-end gap-1">
|
<div className="flex h-full items-center justify-end gap-1">
|
||||||
<button
|
<button
|
||||||
className="m-0 h-full rounded-md px-4 text-gray-400 hover:text-gray-700 dark:bg-gray-700 dark:text-gray-400 dark:hover:text-gray-200 sm:invisible sm:p-2 sm:group-hover:visible"
|
className="m-0 h-full rounded-md p-2 px-4 text-gray-400 hover:text-gray-700 dark:bg-gray-700 dark:text-gray-400 dark:hover:text-gray-200 sm:invisible sm:group-hover:visible"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
@ -93,7 +105,7 @@ const PresetItems: FC<{
|
||||||
<TrashIcon />
|
<TrashIcon />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className="m-0 h-full rounded-md px-4 text-gray-400 hover:text-gray-700 dark:bg-gray-700 dark:text-gray-400 dark:hover:text-gray-200 sm:invisible sm:p-2 sm:group-hover:visible"
|
className="m-0 h-full rounded-md p-2 px-4 text-gray-400 hover:text-gray-700 dark:bg-gray-700 dark:text-gray-400 dark:hover:text-gray-200 sm:invisible sm:group-hover:visible"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
onChangePreset(preset);
|
onChangePreset(preset);
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,18 @@
|
||||||
import type { FC } from 'react';
|
import type { FC } from 'react';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useRecoilState } from 'recoil';
|
import { useRecoilState } from 'recoil';
|
||||||
|
import { BookCopy } from 'lucide-react';
|
||||||
import {
|
import {
|
||||||
modularEndpoints,
|
modularEndpoints,
|
||||||
useDeletePresetMutation,
|
useDeletePresetMutation,
|
||||||
useCreatePresetMutation,
|
useCreatePresetMutation,
|
||||||
} from 'librechat-data-provider';
|
} from 'librechat-data-provider';
|
||||||
import type { TPreset } from 'librechat-data-provider';
|
import type { TPreset } from 'librechat-data-provider';
|
||||||
import { Content, Portal, Root } from '@radix-ui/react-popover';
|
import { Content, Portal, Root, Trigger } from '@radix-ui/react-popover';
|
||||||
import { useLocalize, useDefaultConvo, useNavigateToConvo } from '~/hooks';
|
import { useLocalize, useDefaultConvo, useNavigateToConvo } from '~/hooks';
|
||||||
import { EditPresetDialog, PresetItems } from './Presets';
|
import { EditPresetDialog, PresetItems } from './Presets';
|
||||||
|
import { cleanupPreset, cn } from '~/utils';
|
||||||
import { useChatContext } from '~/Providers';
|
import { useChatContext } from '~/Providers';
|
||||||
import TitleButton from './UI/TitleButton';
|
|
||||||
import { cleanupPreset } from '~/utils';
|
|
||||||
import store from '~/store';
|
import store from '~/store';
|
||||||
|
|
||||||
const PresetsMenu: FC = () => {
|
const PresetsMenu: FC = () => {
|
||||||
|
|
@ -86,7 +86,20 @@ const PresetsMenu: FC = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Root>
|
<Root>
|
||||||
<TitleButton primaryText={'Presets'} />
|
<Trigger asChild>
|
||||||
|
<button
|
||||||
|
className={cn(
|
||||||
|
'pointer-cursor relative flex flex-col rounded-md border border-black/10 bg-white text-left focus:outline-none focus:ring-0 focus:ring-offset-0 dark:border-white/20 dark:bg-gray-800 sm:text-sm',
|
||||||
|
'hover:bg-gray-50 radix-state-open:bg-gray-50 dark:hover:bg-black/10 dark:radix-state-open:bg-black/20',
|
||||||
|
'min-w-4 z-50 flex h-[40px] flex-none items-center justify-center px-3 focus:ring-0 focus:ring-offset-0',
|
||||||
|
)}
|
||||||
|
id="presets-button"
|
||||||
|
data-testid="presets-button"
|
||||||
|
title={localize('com_ui_presets')}
|
||||||
|
>
|
||||||
|
<BookCopy className="icon-sm" id="presets-button" />
|
||||||
|
</button>
|
||||||
|
</Trigger>
|
||||||
<Portal>
|
<Portal>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
|
|
@ -100,10 +113,9 @@ const PresetsMenu: FC = () => {
|
||||||
>
|
>
|
||||||
<Content
|
<Content
|
||||||
side="bottom"
|
side="bottom"
|
||||||
align="start"
|
align="center"
|
||||||
className="mt-2 max-h-[495px] max-w-[370px] overflow-x-hidden rounded-lg border border-gray-100 bg-white shadow-lg dark:border-gray-700 dark:bg-gray-900 dark:text-white md:min-w-[400px]"
|
className="mt-2 max-h-[495px] overflow-x-hidden rounded-lg border border-gray-100 bg-white shadow-lg dark:border-gray-700 dark:bg-gray-900 dark:text-white md:min-w-[400px]"
|
||||||
>
|
>
|
||||||
{presets.length ? (
|
|
||||||
<PresetItems
|
<PresetItems
|
||||||
presets={presets}
|
presets={presets}
|
||||||
onSelectPreset={onSelectPreset}
|
onSelectPreset={onSelectPreset}
|
||||||
|
|
@ -112,17 +124,10 @@ const PresetsMenu: FC = () => {
|
||||||
clearAllPresets={clearAllPresets}
|
clearAllPresets={clearAllPresets}
|
||||||
onFileSelected={onFileSelected}
|
onFileSelected={onFileSelected}
|
||||||
/>
|
/>
|
||||||
) : (
|
|
||||||
<div className="dark:text-gray-300">{localize('com_endpoint_no_presets')}</div>
|
|
||||||
)}
|
|
||||||
</Content>
|
</Content>
|
||||||
</div>
|
</div>
|
||||||
</Portal>
|
</Portal>
|
||||||
<EditPresetDialog
|
<EditPresetDialog open={presetModalVisible} onOpenChange={setPresetModalVisible} />
|
||||||
open={presetModalVisible}
|
|
||||||
onOpenChange={setPresetModalVisible}
|
|
||||||
// preset={preset as TPreset}
|
|
||||||
/>
|
|
||||||
</Root>
|
</Root>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,7 @@ const MenuItem: FC<MenuItemProps> = ({
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
fill="none"
|
fill="none"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
className="icon-md block hidden gap-x-1 group-hover:flex "
|
className="icon-md invisible block gap-x-1 group-hover:visible group-hover:flex"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
fillRule="evenodd"
|
fillRule="evenodd"
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,11 @@ import { TConversation } from 'librechat-data-provider';
|
||||||
export default function Conversations({
|
export default function Conversations({
|
||||||
conversations,
|
conversations,
|
||||||
moveToTop,
|
moveToTop,
|
||||||
|
toggleNav,
|
||||||
}: {
|
}: {
|
||||||
conversations: TConversation[];
|
conversations: TConversation[];
|
||||||
moveToTop: () => void;
|
moveToTop: () => void;
|
||||||
|
toggleNav: () => void;
|
||||||
}) {
|
}) {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const { pathname } = location;
|
const { pathname } = location;
|
||||||
|
|
@ -24,6 +26,7 @@ export default function Conversations({
|
||||||
key={convo.conversationId}
|
key={convo.conversationId}
|
||||||
conversation={convo}
|
conversation={convo}
|
||||||
retainView={moveToTop}
|
retainView={moveToTop}
|
||||||
|
toggleNav={toggleNav}
|
||||||
i={i}
|
i={i}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ import store from '~/store';
|
||||||
|
|
||||||
type KeyEvent = KeyboardEvent<HTMLInputElement>;
|
type KeyEvent = KeyboardEvent<HTMLInputElement>;
|
||||||
|
|
||||||
export default function Conversation({ conversation, retainView, i }) {
|
export default function Conversation({ conversation, retainView, toggleNav, i }) {
|
||||||
const { conversationId: currentConvoId } = useParams();
|
const { conversationId: currentConvoId } = useParams();
|
||||||
const activeConvos = useRecoilValue(store.allConversationsSelector);
|
const activeConvos = useRecoilValue(store.allConversationsSelector);
|
||||||
const updateConvoMutation = useUpdateConversationMutation(currentConvoId ?? '');
|
const updateConvoMutation = useUpdateConversationMutation(currentConvoId ?? '');
|
||||||
|
|
@ -31,6 +31,8 @@ export default function Conversation({ conversation, retainView, i }) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toggleNav();
|
||||||
|
|
||||||
// set document title
|
// set document title
|
||||||
document.title = title;
|
document.title = title;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,9 @@ export default function Settings({
|
||||||
|
|
||||||
if (OptionComponent) {
|
if (OptionComponent) {
|
||||||
return (
|
return (
|
||||||
<div className={cn('h-[480px] overflow-y-auto md:mb-2 md:h-[350px]', className)}>
|
<div
|
||||||
|
className={cn('hide-scrollbar h-[500px] overflow-y-auto md:mb-2 md:h-[350px]', className)}
|
||||||
|
>
|
||||||
<OptionComponent
|
<OptionComponent
|
||||||
conversation={conversation}
|
conversation={conversation}
|
||||||
setOption={setOption}
|
setOption={setOption}
|
||||||
|
|
@ -41,7 +43,7 @@ export default function Settings({
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cn('h-[480px] overflow-y-auto md:mb-2 md:h-[350px]', className)}>
|
<div className={cn('hide-scrollbar h-[500px] overflow-y-auto md:mb-2 md:h-[350px]', className)}>
|
||||||
<MultiViewComponent conversation={conversation} models={models} isPreset={isPreset} />
|
<MultiViewComponent conversation={conversation} models={models} isPreset={isPreset} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -102,13 +102,14 @@ export default function PluginsByIndex({
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
|
{visible && (
|
||||||
|
<>
|
||||||
<Menu
|
<Menu
|
||||||
value={conversation.model ?? ''}
|
value={conversation.model ?? ''}
|
||||||
setValue={setOption('model')}
|
setValue={setOption('model')}
|
||||||
availableValues={models}
|
availableValues={models}
|
||||||
showAbove={showAbove}
|
showAbove={showAbove}
|
||||||
showLabel={false}
|
showLabel={false}
|
||||||
className={cn(cardStyle, 'min-w-60 z-40 flex w-64 sm:w-48 ', visible ? '' : 'hidden')}
|
|
||||||
/>
|
/>
|
||||||
<PluginsMenu
|
<PluginsMenu
|
||||||
value={conversation.tools || []}
|
value={conversation.tools || []}
|
||||||
|
|
@ -118,8 +119,9 @@ export default function PluginsByIndex({
|
||||||
optionValueKey="pluginKey"
|
optionValueKey="pluginKey"
|
||||||
showAbove={false}
|
showAbove={false}
|
||||||
showLabel={false}
|
showLabel={false}
|
||||||
className={cn(cardStyle, 'min-w-60 z-50 w-64 sm:w-48 ', visible ? '' : 'hidden')}
|
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -144,6 +144,12 @@ export default function Nav({ navVisible, setNavVisible }) {
|
||||||
setNavVisible((prev: boolean) => !prev);
|
setNavVisible((prev: boolean) => !prev);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const itemToggleNav = () => {
|
||||||
|
if (isSmallScreen) {
|
||||||
|
toggleNavVisible();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const containerClasses =
|
const containerClasses =
|
||||||
getConversationsQuery.isLoading && pageNumber === 1
|
getConversationsQuery.isLoading && pageNumber === 1
|
||||||
? 'flex flex-col gap-2 text-gray-100 text-sm h-full justify-center items-center'
|
? 'flex flex-col gap-2 text-gray-100 text-sm h-full justify-center items-center'
|
||||||
|
|
@ -167,12 +173,12 @@ export default function Nav({ navVisible, setNavVisible }) {
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'scrollbar-trigger relative flex h-full w-full flex-1 items-start border-white/20 transition-opacity',
|
'scrollbar-trigger relative flex h-full w-full flex-1 items-start border-white/20 transition-opacity',
|
||||||
isToggleHovering ? 'opacity-50' : 'opacity-100',
|
isToggleHovering && !isSmallScreen ? 'opacity-50' : 'opacity-100',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<nav className="relative flex h-full flex-1 flex-col space-y-1 p-2">
|
<nav className="relative flex h-full flex-1 flex-col space-y-1 p-2">
|
||||||
<div className="mb-1 flex h-11 flex-row">
|
<div className="mb-1 flex h-11 flex-row">
|
||||||
<NewChat />
|
<NewChat toggleNav={itemToggleNav} />
|
||||||
</div>
|
</div>
|
||||||
{isSearchEnabled && <SearchBar clearSearch={clearSearch} />}
|
{isSearchEnabled && <SearchBar clearSearch={clearSearch} />}
|
||||||
<div
|
<div
|
||||||
|
|
@ -187,7 +193,11 @@ export default function Nav({ navVisible, setNavVisible }) {
|
||||||
{(getConversationsQuery.isLoading && pageNumber === 1) || isFetching ? (
|
{(getConversationsQuery.isLoading && pageNumber === 1) || isFetching ? (
|
||||||
<Spinner />
|
<Spinner />
|
||||||
) : (
|
) : (
|
||||||
<Conversations conversations={conversations} moveToTop={moveToTop} />
|
<Conversations
|
||||||
|
conversations={conversations}
|
||||||
|
moveToTop={moveToTop}
|
||||||
|
toggleNav={itemToggleNav}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
<Pages
|
<Pages
|
||||||
pageNumber={pageNumber}
|
pageNumber={pageNumber}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { useLocalize, useConversation, useNewConvo, useOriginNavigate } from '~/hooks';
|
import { useLocalize, useConversation, useNewConvo, useOriginNavigate } from '~/hooks';
|
||||||
|
|
||||||
export default function NewChat() {
|
export default function NewChat({ toggleNav }: { toggleNav: () => void }) {
|
||||||
const { newConversation } = useConversation();
|
const { newConversation } = useConversation();
|
||||||
const { newConversation: newConvo } = useNewConvo();
|
const { newConversation: newConvo } = useNewConvo();
|
||||||
const navigate = useOriginNavigate();
|
const navigate = useOriginNavigate();
|
||||||
|
|
@ -10,6 +10,7 @@ export default function NewChat() {
|
||||||
newConvo();
|
newConvo();
|
||||||
newConversation();
|
newConversation();
|
||||||
navigate('new');
|
navigate('new');
|
||||||
|
toggleNav();
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Wrench, ArrowRight } from 'lucide-react';
|
import { Wrench } from 'lucide-react';
|
||||||
import { Root, Trigger, Content, Portal } from '@radix-ui/react-popover';
|
import { Root, Trigger, Content, Portal } from '@radix-ui/react-popover';
|
||||||
import type { TPlugin } from 'librechat-data-provider';
|
import type { TPlugin } from 'librechat-data-provider';
|
||||||
import MenuItem from '~/components/Chat/Menus/UI/MenuItem';
|
import MenuItem from '~/components/Chat/Menus/UI/MenuItem';
|
||||||
|
|
@ -19,18 +19,15 @@ type SelectDropDownProps = {
|
||||||
optionValueKey?: string;
|
optionValueKey?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
function SelectDropDownPop({
|
function MultiSelectPop({
|
||||||
title: _title = 'Plugins',
|
title: _title = 'Plugins',
|
||||||
value,
|
value,
|
||||||
// TODO: do we need disabled here?
|
|
||||||
disabled,
|
|
||||||
setSelected,
|
setSelected,
|
||||||
availableValues,
|
availableValues,
|
||||||
showAbove = false,
|
showAbove = false,
|
||||||
showLabel = true,
|
showLabel = true,
|
||||||
containerClassName,
|
containerClassName,
|
||||||
isSelected,
|
isSelected,
|
||||||
className,
|
|
||||||
optionValueKey = 'value',
|
optionValueKey = 'value',
|
||||||
}: SelectDropDownProps) {
|
}: SelectDropDownProps) {
|
||||||
// const localize = useLocalize();
|
// const localize = useLocalize();
|
||||||
|
|
@ -46,8 +43,9 @@ function SelectDropDownPop({
|
||||||
<button
|
<button
|
||||||
data-testid="select-dropdown-button"
|
data-testid="select-dropdown-button"
|
||||||
className={cn(
|
className={cn(
|
||||||
'relative flex cursor-default flex-col rounded-md border border-black/10 bg-white py-2 pl-3 pr-10 text-left focus:outline-none focus:ring-0 focus:ring-offset-0 dark:border-white/20 dark:bg-gray-800 dark:bg-gray-800 sm:text-sm',
|
'relative flex flex-col rounded-md border border-black/10 bg-white py-2 pl-3 pr-10 text-left focus:outline-none focus:ring-0 focus:ring-offset-0 dark:border-white/20 dark:bg-gray-800 dark:bg-gray-800 sm:text-sm',
|
||||||
className ?? '',
|
'pointer-cursor font-normal',
|
||||||
|
'hover:bg-gray-50 radix-state-open:bg-gray-50 dark:hover:bg-black/10 dark:radix-state-open:bg-black/20',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{' '}
|
{' '}
|
||||||
|
|
@ -85,7 +83,7 @@ function SelectDropDownPop({
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
|
<span className="absolute inset-y-0 right-0 flex items-center pr-2">
|
||||||
<svg
|
<svg
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
fill="none"
|
fill="none"
|
||||||
|
|
@ -107,7 +105,7 @@ function SelectDropDownPop({
|
||||||
<Portal>
|
<Portal>
|
||||||
<Content
|
<Content
|
||||||
side="bottom"
|
side="bottom"
|
||||||
align="start"
|
align="center"
|
||||||
className="mt-2 max-h-60 min-w-full overflow-hidden overflow-y-auto rounded-lg border border-gray-100 bg-white shadow-lg dark:border-gray-700 dark:bg-gray-900 dark:text-white"
|
className="mt-2 max-h-60 min-w-full overflow-hidden overflow-y-auto rounded-lg border border-gray-100 bg-white shadow-lg dark:border-gray-700 dark:bg-gray-900 dark:text-white"
|
||||||
>
|
>
|
||||||
{availableValues.map((option) => {
|
{availableValues.map((option) => {
|
||||||
|
|
@ -144,4 +142,4 @@ function SelectDropDownPop({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default SelectDropDownPop;
|
export default MultiSelectPop;
|
||||||
|
|
|
||||||
|
|
@ -17,12 +17,6 @@ type SelectDropDownProps = {
|
||||||
showLabel?: boolean;
|
showLabel?: boolean;
|
||||||
iconSide?: 'left' | 'right';
|
iconSide?: 'left' | 'right';
|
||||||
renderOption?: () => React.ReactNode;
|
renderOption?: () => React.ReactNode;
|
||||||
containerClassName?: string;
|
|
||||||
currentValueClass?: string;
|
|
||||||
optionsListClass?: string;
|
|
||||||
optionsClass?: string;
|
|
||||||
subContainerClassName?: string;
|
|
||||||
className?: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function SelectDropDownPop({
|
function SelectDropDownPop({
|
||||||
|
|
@ -33,10 +27,6 @@ function SelectDropDownPop({
|
||||||
showAbove = false,
|
showAbove = false,
|
||||||
showLabel = true,
|
showLabel = true,
|
||||||
emptyTitle = false,
|
emptyTitle = false,
|
||||||
containerClassName,
|
|
||||||
currentValueClass,
|
|
||||||
subContainerClassName,
|
|
||||||
className,
|
|
||||||
}: SelectDropDownProps) {
|
}: SelectDropDownProps) {
|
||||||
const localize = useLocalize();
|
const localize = useLocalize();
|
||||||
const transitionProps = { className: 'top-full mt-3' };
|
const transitionProps = { className: 'top-full mt-3' };
|
||||||
|
|
@ -54,14 +44,13 @@ function SelectDropDownPop({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Root>
|
<Root>
|
||||||
<div className={cn('flex items-center justify-center gap-2 ', containerClassName ?? '')}>
|
<div className={'flex items-center justify-center gap-2 '}>
|
||||||
<div className={cn('relative w-full', subContainerClassName ?? '')}>
|
<div className={'relative w-full'}>
|
||||||
<Trigger asChild>
|
<Trigger asChild>
|
||||||
<button
|
<button
|
||||||
data-testid="select-dropdown-button"
|
data-testid="select-dropdown-button"
|
||||||
className={cn(
|
className={cn(
|
||||||
'relative flex w-full cursor-default flex-col rounded-md border border-black/10 bg-white py-2 pl-3 pr-10 text-left focus:outline-none focus:ring-0 focus:ring-offset-0 dark:border-white/20 dark:bg-gray-800 sm:text-sm',
|
'pointer-cursor relative flex flex-col rounded-md border border-black/10 bg-white py-2 pl-3 pr-10 text-left focus:outline-none focus:ring-0 focus:ring-offset-0 dark:border-white/20 dark:bg-gray-800 sm:text-sm',
|
||||||
className ?? '',
|
|
||||||
'hover:bg-gray-50 radix-state-open:bg-gray-50 dark:hover:bg-black/10 dark:radix-state-open:bg-black/20',
|
'hover:bg-gray-50 radix-state-open:bg-gray-50 dark:hover:bg-black/10 dark:radix-state-open:bg-black/20',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|
@ -69,12 +58,12 @@ function SelectDropDownPop({
|
||||||
{showLabel && (
|
{showLabel && (
|
||||||
<label className="block text-xs text-gray-700 dark:text-gray-500 ">{title}</label>
|
<label className="block text-xs text-gray-700 dark:text-gray-500 ">{title}</label>
|
||||||
)}
|
)}
|
||||||
<span className="inline-flex w-full truncate">
|
<span className="inline-flex w-full ">
|
||||||
<span
|
<span
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex h-6 items-center gap-1 truncate text-sm text-gray-900 dark:text-white',
|
'flex h-6 items-center gap-1 text-sm text-gray-900 dark:text-white',
|
||||||
!showLabel ? 'text-xs' : '',
|
!showLabel ? 'text-xs' : '',
|
||||||
currentValueClass ?? '',
|
'min-w-[75px] font-normal',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{/* {!showLabel && !emptyTitle && (
|
{/* {!showLabel && !emptyTitle && (
|
||||||
|
|
@ -83,7 +72,7 @@ function SelectDropDownPop({
|
||||||
{typeof value !== 'string' && value ? value?.label ?? '' : value ?? ''}
|
{typeof value !== 'string' && value ? value?.label ?? '' : value ?? ''}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
|
<span className="absolute inset-y-0 right-0 flex items-center pr-2">
|
||||||
<svg
|
<svg
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
fill="none"
|
fill="none"
|
||||||
|
|
|
||||||
|
|
@ -192,7 +192,7 @@ export default {
|
||||||
com_endpoint_presets_clear_warning:
|
com_endpoint_presets_clear_warning:
|
||||||
'Are you sure you want to clear all presets? This is irreversible.',
|
'Are you sure you want to clear all presets? This is irreversible.',
|
||||||
com_endpoint_not_implemented: 'Not implemented',
|
com_endpoint_not_implemented: 'Not implemented',
|
||||||
com_endpoint_no_presets: 'No preset yet',
|
com_endpoint_no_presets: 'No presets yet, use the settings button to create one',
|
||||||
com_endpoint_not_available: 'No endpoint available',
|
com_endpoint_not_available: 'No endpoint available',
|
||||||
com_endpoint_view_options: 'View Options',
|
com_endpoint_view_options: 'View Options',
|
||||||
com_endpoint_save_convo_as_preset: 'Save Conversation as Preset',
|
com_endpoint_save_convo_as_preset: 'Save Conversation as Preset',
|
||||||
|
|
|
||||||
|
|
@ -111,3 +111,13 @@
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hide-scrollbar {
|
||||||
|
/* Hide scrollbar for Chrome, Safari, and Opera */
|
||||||
|
scrollbar-width: none; /* For Firefox */
|
||||||
|
-ms-overflow-style: none; /* For Internet Explorer and Edge */
|
||||||
|
}
|
||||||
|
|
||||||
|
.hide-scrollbar::-webkit-scrollbar {
|
||||||
|
display: none; /* For WebKit browsers */
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1709,3 +1709,33 @@ html {
|
||||||
height:234px;
|
height:234px;
|
||||||
width:234px
|
width:234px
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.border-token-border-heavy {
|
||||||
|
border-color: #c5c5d2;
|
||||||
|
border-color: var(--border-heavy)
|
||||||
|
}
|
||||||
|
|
||||||
|
.border-token-border-light {
|
||||||
|
border-color: #ececf1;
|
||||||
|
border-color: var(--border-light)
|
||||||
|
}
|
||||||
|
|
||||||
|
.border-token-border-medium {
|
||||||
|
border-color: #d9d9e3;
|
||||||
|
border-color: var(--border-medium)
|
||||||
|
}
|
||||||
|
|
||||||
|
.border-token-surface-primary {
|
||||||
|
border-color: #fff;
|
||||||
|
border-color: var(--surface-primary)
|
||||||
|
}
|
||||||
|
|
||||||
|
.border-token-surface-secondary {
|
||||||
|
border-color: #f7f7f8;
|
||||||
|
border-color: var(--surface-secondary)
|
||||||
|
}
|
||||||
|
|
||||||
|
.border-token-surface-tertiary {
|
||||||
|
border-color: #ececf1;
|
||||||
|
border-color: var(--surface-tertiary)
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue