Merge branch 'danny-avila:main' into Speech-to-Text

This commit is contained in:
bsu3338 2023-08-05 11:25:19 -05:00 committed by GitHub
commit 5ad99272e2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
36 changed files with 342 additions and 344 deletions

View file

@ -1,7 +1,7 @@
import exportFromJSON from 'export-from-json';
import { useEffect, useState } from 'react';
import { useRecoilValue, useRecoilState } from 'recoil';
import { EditPresetProps, SetOption, TPreset } from 'librechat-data-provider';
import { EditPresetProps, SetOption, tPresetSchema } from 'librechat-data-provider';
import { Dialog, DialogButton } from '~/components/ui';
import DialogTemplate from '~/components/ui/DialogTemplate';
import SaveAsPresetDialog from './SaveAsPresetDialog';
@ -21,12 +21,11 @@ const EndpointOptionsDialog = ({ open, onOpenChange, preset: _preset, title }: E
const setOption: SetOption = (param) => (newValue) => {
const update = {};
update[param] = newValue;
setPreset(
(prevState) =>
({
...prevState,
...update,
} as TPreset),
setPreset((prevState) =>
tPresetSchema.parse({
...prevState,
...update,
}),
);
};

View file

@ -1,6 +1,6 @@
import React, { useEffect, useState } from 'react';
import { useRecoilValue } from 'recoil';
import { useCreatePresetMutation, EditPresetProps, TPreset } from 'librechat-data-provider';
import { useCreatePresetMutation, EditPresetProps } from 'librechat-data-provider';
import { Dialog, Input, Label } from '~/components/ui/';
import DialogTemplate from '~/components/ui/DialogTemplate';
import { cn, defaultTextPropsLabel, removeFocusOutlines, cleanupPreset } from '~/utils/';
@ -20,7 +20,7 @@ const SaveAsPresetDialog = ({ open, onOpenChange, preset }: EditPresetProps) =>
title,
},
endpointsConfig,
}) as TPreset;
});
createPresetMutation.mutate(_preset);
};

View file

@ -68,14 +68,14 @@ function Examples({ readonly, examples, setExample, addExample, removeExample }:
<div className="flex justify-center">
<Button
type="button"
className="mr-2 mt-1 h-auto items-center justify-center bg-transparent px-3 py-2 text-xs font-medium font-normal text-black hover:bg-slate-200 hover:text-black focus:ring-0 focus:ring-offset-0 dark:bg-transparent dark:text-white dark:hover:bg-gray-600 dark:hover:text-white dark:focus:outline-none dark:focus:ring-offset-0"
className="mr-2 mt-1 h-auto items-center justify-center bg-transparent px-3 py-2 text-xs font-medium font-normal text-black hover:bg-slate-200 hover:text-black focus:ring-0 focus:ring-offset-0 dark:bg-transparent dark:text-white dark:hover:bg-gray-700 dark:hover:text-white dark:focus:outline-none dark:focus:ring-offset-0"
onClick={removeExample}
>
<Minus className="w-[16px]" />
</Button>
<Button
type="button"
className="mt-1 h-auto items-center justify-center bg-transparent px-3 py-2 text-xs font-medium font-normal text-black hover:bg-slate-200 hover:text-black focus:ring-0 focus:ring-offset-0 dark:bg-transparent dark:text-white dark:hover:bg-gray-600 dark:hover:text-white dark:focus:outline-none dark:focus:ring-offset-0"
className="mt-1 h-auto items-center justify-center bg-transparent px-3 py-2 text-xs font-medium font-normal text-black hover:bg-slate-200 hover:text-black focus:ring-0 focus:ring-offset-0 dark:bg-transparent dark:text-white dark:hover:bg-gray-700 dark:hover:text-white dark:focus:outline-none dark:focus:ring-offset-0"
onClick={addExample}
>
<Plus className="w-[16px]" />

View file

@ -12,7 +12,7 @@ export default function Anthropic({ conversation, setOption, models }: ModelSele
showLabel={false}
className={cn(
cardStyle,
'min-w-48 z-50 flex h-[40px] w-48 flex-none items-center justify-center px-4 ring-0 transition duration-700 ease-in-out hover:cursor-pointer hover:bg-slate-50 hover:shadow-md focus:ring-0 focus:ring-offset-0 data-[state=open]:bg-slate-50 dark:bg-gray-700 dark:hover:bg-gray-600 dark:data-[state=open]:bg-gray-600',
'min-w-48 z-50 flex h-[40px] w-48 flex-none items-center justify-center px-4 ring-0 hover:cursor-pointer',
)}
/>
);

View file

@ -34,15 +34,15 @@ export default function BingAI({ conversation, setOption, models }: ModelSelectP
showLabel={false}
className={cn(
cardStyle,
'z-50 flex h-[40px] w-36 flex-none items-center justify-center px-4 ring-0 transition duration-700 ease-in-out hover:cursor-pointer hover:bg-slate-50 hover:shadow-md focus:ring-0 focus:ring-offset-0 data-[state=open]:bg-slate-50 dark:bg-gray-700 dark:hover:bg-gray-600 dark:data-[state=open]:bg-gray-600',
'z-50 flex h-[40px] w-36 flex-none items-center justify-center px-4 ring-0 hover:cursor-pointer hover:bg-slate-50 focus:ring-0 focus:ring-offset-0 data-[state=open]:bg-slate-50 dark:bg-gray-800 dark:hover:bg-gray-700 dark:data-[state=open]:bg-gray-600',
showBingToneSetting ? 'hidden' : '',
)}
/>
<Tabs
value={toneStyle}
value={toneStyle ?? 'creative'}
className={cn(
cardStyle,
'z-50 flex h-[40px] flex-none items-center justify-center px-0 transition duration-700 ease-in-out hover:bg-slate-50 hover:shadow-md dark:hover:bg-gray-600',
'z-50 flex h-[40px] flex-none items-center justify-center px-0 hover:bg-slate-50 dark:hover:bg-gray-700',
)}
onValueChange={(value) => setOption('toneStyle')(value.toLowerCase())}
>

View file

@ -20,7 +20,7 @@ export default function ChatGPT({ conversation, setOption, models }: ModelSelect
showLabel={false}
className={cn(
cardStyle,
'min-w-48 z-50 flex h-[40px] w-60 flex-none items-center justify-center px-4 ring-0 transition duration-700 ease-in-out hover:cursor-pointer hover:bg-slate-50 hover:shadow-md focus:ring-0 focus:ring-offset-0 data-[state=open]:bg-slate-50 dark:bg-gray-700 dark:hover:bg-gray-600 dark:data-[state=open]:bg-gray-600',
'min-w-48 z-50 flex h-[40px] w-60 flex-none items-center justify-center px-4 ring-0 hover:cursor-pointer',
)}
/>
);

View file

@ -12,7 +12,7 @@ export default function Google({ conversation, setOption, models }: ModelSelectP
showLabel={false}
className={cn(
cardStyle,
'min-w-48 z-50 flex h-[40px] w-48 flex-none items-center justify-center px-4 ring-0 transition duration-700 ease-in-out hover:cursor-pointer hover:bg-slate-50 hover:shadow-md focus:ring-0 focus:ring-offset-0 data-[state=open]:bg-slate-50 dark:bg-gray-700 dark:hover:bg-gray-600 dark:data-[state=open]:bg-gray-600',
'min-w-48 z-50 flex h-[40px] w-48 flex-none items-center justify-center px-4 ring-0 hover:cursor-pointer',
)}
/>
);

View file

@ -12,7 +12,7 @@ export default function OpenAI({ conversation, setOption, models }: ModelSelectP
showLabel={false}
className={cn(
cardStyle,
'min-w-48 z-50 flex h-[40px] w-48 flex-none items-center justify-center px-4 ring-0 transition duration-700 ease-in-out hover:cursor-pointer hover:bg-slate-50 hover:shadow-md focus:ring-0 focus:ring-offset-0 data-[state=open]:bg-slate-50 dark:bg-gray-700 dark:hover:bg-gray-600 dark:data-[state=open]:bg-gray-600',
'min-w-48 z-50 flex h-[40px] w-48 flex-none items-center justify-center px-4 hover:cursor-pointer',
)}
/>
);

View file

@ -46,7 +46,7 @@ export default function Plugins({ conversation, setOption, models }: ModelSelect
}
const tools = [...user.plugins]
.map((el) => allPlugins.find((plugin) => plugin.pluginKey === el))
.map((el) => allPlugins.find((plugin: TPlugin) => plugin.pluginKey === el))
.filter((el): el is TPlugin => el !== undefined);
/* Filter Last Selected Tools */
@ -71,7 +71,7 @@ export default function Plugins({ conversation, setOption, models }: ModelSelect
type="button"
className={cn(
cardStyle,
'min-w-4 z-40 flex h-[40px] flex-none items-center justify-center px-3 transition duration-700 ease-in-out hover:bg-white hover:shadow-md focus:ring-0 focus:ring-offset-0 dark:hover:bg-gray-700',
'min-w-4 z-40 flex h-[40px] flex-none items-center justify-center px-3 hover:bg-white focus:ring-0 focus:ring-offset-0 dark:hover:bg-gray-700',
)}
onClick={() => setVisibility((prev) => !prev)}
>
@ -87,11 +87,7 @@ export default function Plugins({ conversation, setOption, models }: ModelSelect
setValue={setOption('model')}
availableValues={models}
showAbove={true}
className={cn(
cardStyle,
'min-w-60 z-40 flex w-64 transition duration-700 ease-in-out hover:shadow-md sm:w-48',
visible ? '' : 'hidden',
)}
className={cn(cardStyle, 'min-w-60 z-40 flex w-64 sm:w-48', visible ? '' : 'hidden')}
/>
<MultiSelectDropDown
value={conversation.tools || []}
@ -100,11 +96,7 @@ export default function Plugins({ conversation, setOption, models }: ModelSelect
availableValues={availableTools}
optionValueKey="pluginKey"
showAbove={true}
className={cn(
cardStyle,
'min-w-60 z-50 w-64 transition duration-700 ease-in-out hover:shadow-md sm:w-48',
visible ? '' : 'hidden',
)}
className={cn(cardStyle, 'min-w-60 z-50 w-64 sm:w-48', visible ? '' : 'hidden')}
/>
</>
);

View file

@ -1,7 +1,7 @@
import { Settings2 } from 'lucide-react';
import { useState, useEffect, useMemo } from 'react';
import { useRecoilValue, useRecoilState, useSetRecoilState } from 'recoil';
import { TPreset } from 'librechat-data-provider';
import { tPresetSchema } from 'librechat-data-provider';
import { PluginStoreDialog } from '~/components';
import {
EndpointSettings,
@ -98,6 +98,21 @@ export default function OptionsBar() {
}
setOpacityClass('show');
}}
onFocus={() => {
if (showPopover) {
return;
}
setOpacityClass('full-opacity');
}}
onBlur={() => {
if (showPopover) {
return;
}
if (!messagesTree || messagesTree.length === 0) {
return;
}
setOpacityClass('show');
}}
>
<ModelSelect conversation={conversation} setOption={setOption} />
{!noSettings[endpoint] && (
@ -105,7 +120,7 @@ export default function OptionsBar() {
type="button"
className={cn(
cardStyle,
'min-w-4 z-50 flex h-[40px] flex-none items-center justify-center px-3 transition duration-700 ease-in-out hover:bg-slate-50 hover:shadow-md focus:ring-0 focus:ring-offset-0 dark:hover:bg-gray-600',
'min-w-4 z-50 flex h-[40px] flex-none items-center justify-center px-3 focus:ring-0 focus:ring-offset-0',
)}
onClick={triggerAdvancedMode}
>
@ -126,7 +141,7 @@ export default function OptionsBar() {
<SaveAsPresetDialog
open={saveAsDialogShow}
onOpenChange={setSaveAsDialogShow}
preset={{ ...conversation } as TPreset}
preset={tPresetSchema.parse({ ...conversation })}
/>
<PluginStoreDialog isOpen={showPluginStoreDialog} setIsOpen={setShowPluginStoreDialog} />
</span>

View file

@ -28,6 +28,9 @@ export default function Messages({ isSearchView = false }) {
const { screenshotTargetRef } = useScreenshot();
const handleScroll = () => {
if (!scrollableRef.current) {
return;
}
const { scrollTop, scrollHeight, clientHeight } = scrollableRef.current;
const diff = Math.abs(scrollHeight - scrollTop);
const percent = Math.abs(clientHeight - diff) / clientHeight;
@ -40,6 +43,9 @@ export default function Messages({ isSearchView = false }) {
useEffect(() => {
const timeoutId = setTimeout(() => {
if (!scrollableRef.current) {
return;
}
const { scrollTop, scrollHeight, clientHeight } = scrollableRef.current;
const diff = Math.abs(scrollHeight - scrollTop);
const percent = Math.abs(clientHeight - diff) / clientHeight;
@ -58,6 +64,19 @@ export default function Messages({ isSearchView = false }) {
// eslint-disable-next-line react-hooks/exhaustive-deps
const scrollToBottom = useCallback(
throttle(
() => {
messagesEndRef.current?.scrollIntoView({ behavior: 'instant' });
setShowScrollButton(false);
},
450,
{ leading: true },
),
[messagesEndRef],
);
// eslint-disable-next-line react-hooks/exhaustive-deps
const scrollToBottomSmooth = useCallback(
throttle(
() => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
@ -77,7 +96,7 @@ export default function Messages({ isSearchView = false }) {
const scrollHandler = (e) => {
e.preventDefault();
scrollToBottom();
scrollToBottomSmooth();
};
return (

View file

@ -1,13 +1,11 @@
import { forwardRef } from 'react';
import { LogOutIcon } from '../svg';
import { useAuthContext } from '~/hooks/AuthContext';
import { useRecoilValue } from 'recoil';
import store from '~/store';
import { localize } from '~/localization/Translation';
import { useLocalize } from '~/hooks';
const Logout = forwardRef(() => {
const { user, logout } = useAuthContext();
const lang = useRecoilValue(store.lang);
const { logout } = useAuthContext();
const localize = useLocalize();
const handleLogout = () => {
logout();
@ -20,8 +18,7 @@ const Logout = forwardRef(() => {
onClick={handleLogout}
>
<LogOutIcon />
{user?.username || localize(lang, 'com_nav_user')}
<small>{localize(lang, 'com_nav_log_out')}</small>
{localize('com_nav_log_out')}
</button>
);
});

View file

@ -7,6 +7,7 @@ import {
import { useCallback, useEffect, useRef, useState } from 'react';
import { useRecoilValue, useSetRecoilState } from 'recoil';
import NewChat from './NewChat';
import SearchBar from './SearchBar';
import NavLinks from './NavLinks';
import { Panel, Spinner } from '~/components';
import { Conversations, Pages } from '../Conversations';
@ -166,7 +167,7 @@ export default function Nav({ navVisible, setNavVisible }) {
<div className="flex h-full min-h-0 flex-col ">
<div className="scrollbar-trigger relative flex h-full w-full flex-1 items-start border-white/20">
<nav className="relative flex h-full flex-1 flex-col space-y-1 p-2">
<div className="mb-2 flex h-11 flex-row">
<div className="mb-1 flex h-11 flex-row">
<NewChat />
<button
type="button"
@ -179,6 +180,7 @@ export default function Nav({ navVisible, setNavVisible }) {
<Panel open={false} />
</button>
</div>
{isSearchEnabled && <SearchBar clearSearch={clearSearch} />}
<div
className={`flex-1 flex-col overflow-y-auto ${
isHovering ? '' : 'scrollbar-transparent'
@ -202,7 +204,7 @@ export default function Nav({ navVisible, setNavVisible }) {
/>
</div>
</div>
<NavLinks clearSearch={clearSearch} isSearchEnabled={isSearchEnabled} />
<NavLinks />
</nav>
</div>
</div>

View file

@ -2,20 +2,19 @@ import { Download } from 'lucide-react';
import { useRecoilValue } from 'recoil';
import { Fragment, useState } from 'react';
import { Menu, Transition } from '@headlessui/react';
import SearchBar from './SearchBar';
import ClearConvos from './ClearConvos';
import Settings from './Settings';
import NavLink from './NavLink';
import Logout from './Logout';
import { ExportModel } from './ExportConversation';
import { LinkIcon, DotsIcon, GearIcon, TrashIcon } from '~/components';
import { LinkIcon, DotsIcon, GearIcon } from '~/components';
import { localize } from '~/localization/Translation';
import { useAuthContext } from '~/hooks/AuthContext';
import { cn } from '~/utils/';
import store from '~/store';
export default function NavLinks({ clearSearch, isSearchEnabled }) {
export default function NavLinks() {
const [showExports, setShowExports] = useState(false);
const [showClearConvos, setShowClearConvos] = useState(false);
const [showSettings, setShowSettings] = useState(false);
@ -76,11 +75,6 @@ export default function NavLinks({ clearSearch, isSearchEnabled }) {
leaveTo="transform opacity-0 scale-95"
>
<Menu.Items className="absolute bottom-full left-0 z-20 mb-2 w-full translate-y-0 overflow-hidden rounded-md bg-[#050509] py-1.5 opacity-100 outline-none">
{isSearchEnabled && (
<Menu.Item>
<SearchBar clearSearch={clearSearch} />
</Menu.Item>
)}
<Menu.Item as="div">
<NavLink
className={cn(
@ -93,14 +87,6 @@ export default function NavLinks({ clearSearch, isSearchEnabled }) {
/>
</Menu.Item>
<div className="my-1.5 h-px bg-white/20" role="none" />
<Menu.Item as="div">
<NavLink
className="flex w-full cursor-pointer items-center gap-3 rounded-none px-3 py-3 text-sm text-white transition-colors duration-200 hover:bg-gray-700"
svg={() => <TrashIcon />}
text={localize(lang, 'com_nav_clear_conversation')}
clickHandler={() => setShowClearConvos(true)}
/>
</Menu.Item>
<Menu.Item as="div">
<NavLink
className="flex w-full cursor-pointer items-center gap-3 rounded-none px-3 py-3 text-sm text-white transition-colors duration-200 hover:bg-gray-700"

View file

@ -16,7 +16,7 @@ export default function NewChat() {
return (
<a
onClick={clickHandler}
className="mb-2 flex h-11 flex-shrink-0 flex-grow cursor-pointer items-center gap-3 rounded-md border border-white/20 px-3 py-3 text-sm text-white transition-colors duration-200 hover:bg-gray-500/10"
className="flex h-11 flex-shrink-0 flex-grow cursor-pointer items-center gap-3 rounded-md border border-white/20 px-3 py-3 text-sm text-white transition-colors duration-200 hover:bg-gray-500/10"
>
<svg
stroke="currentColor"

View file

@ -35,7 +35,7 @@ const SearchBar = forwardRef((props, ref) => {
return (
<div
ref={ref}
className="relative flex w-full cursor-pointer items-center gap-3 px-3 py-3 text-sm text-white transition-colors duration-200 hover:bg-gray-700"
className="relative flex w-full cursor-pointer items-center gap-3 rounded-md border border-white/20 px-3 py-3 text-sm text-white transition-colors duration-200 hover:bg-gray-500/10"
>
{<Search className="absolute left-3 h-4 w-4" />}
<input

View file

@ -11,7 +11,7 @@ import {
useUpdateUserPluginsMutation,
TPlugin,
TPluginAction,
TConversation,
tConversationSchema,
TError,
} from 'librechat-data-provider';
import { useAuthContext } from '~/hooks/AuthContext';
@ -69,12 +69,11 @@ function PluginStoreDialog({ isOpen, setIsOpen }: TPluginStoreDialogProps) {
return t.pluginKey !== plugin;
});
localStorage.setItem('lastSelectedTools', JSON.stringify(tools));
setConversation(
(prevState) =>
({
...prevState,
tools,
} as TConversation),
setConversation((prevState) =>
tConversationSchema.parse({
...prevState,
tools,
}),
);
},
},

View file

@ -11,9 +11,9 @@ function PluginTooltip({ content, position }: TPluginTooltipProps) {
<HoverCardPortal>
<HoverCardContent side={position} className="w-80 ">
<div className="space-y-2">
<p className="text-sm text-gray-600 dark:text-gray-300">
<div className="text-sm text-gray-600 dark:text-gray-300">
<div dangerouslySetInnerHTML={{ __html: content }} />
</p>
</div>
</div>
</HoverCardContent>
</HoverCardPortal>

View file

@ -12,7 +12,7 @@ function Dropdown({ value, onChange, options, className, containerClassName }) {
<Listbox value={value} onChange={onChange}>
<Listbox.Button
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:border-green-600 focus:outline-none focus:ring-1 focus:ring-green-600 dark:border-white/20 dark:bg-gray-800 sm:text-sm',
'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-1 dark:border-white/20 dark:bg-gray-800 sm:text-sm',
className || '',
)}
>

View file

@ -43,7 +43,7 @@ function MultiSelectDropDown({
<>
<Listbox.Button
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:border-green-600 focus:outline-none focus:ring-1 focus:ring-green-600 dark:border-white/20 dark:bg-gray-800 sm:text-sm',
'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',
className ?? '',
)}
id={excludeIds[0]}

View file

@ -41,7 +41,7 @@ function SelectDropDown({
<>
<Listbox.Button
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:border-green-600 focus:outline-none focus:ring-1 focus:ring-green-600 dark:border-white/20 dark:bg-gray-800 sm:text-sm',
'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',
className ?? '',
)}
>

View file

@ -10,7 +10,7 @@ const Tabs = TabsPrimitive.Root;
const TabsList = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.List>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
>(({ className, ...props }, ref) => (
>(({ className = '', ...props }, ref) => (
<TabsPrimitive.List
ref={ref}
className={cn(
@ -25,7 +25,7 @@ TabsList.displayName = TabsPrimitive.List.displayName;
const TabsTrigger = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
>(({ className, ...props }, ref) => (
>(({ className = '', ...props }, ref) => (
<TabsPrimitive.Trigger
className={cn(
'inline-flex min-w-[100px] items-center justify-center rounded-[0.185rem] px-3 py-1.5 text-sm font-medium text-gray-700 transition-all disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-white data-[state=active]:text-gray-900 data-[state=active]:shadow-sm dark:text-gray-200 dark:data-[state=active]:bg-gray-700 dark:data-[state=active]:text-gray-100',
@ -40,7 +40,7 @@ TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
const TabsContent = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
>(({ className, ...props }, ref) => (
>(({ className = '', ...props }, ref) => (
<TabsPrimitive.Content
className={cn('mt-2 rounded-md border border-gray-200 p-6 dark:border-gray-700', className)}
{...props}