Skip to content

Commit

Permalink
🎛️ refactor: Add keyboard command toggles & only trigger for the 1st …
Browse files Browse the repository at this point in the history
…character (#3566)

* refactor: Improve handling of key up events in useHandleKeyUp hook to only trigger for first char and fix linter issues

* refactor: Update Beta component styling with theme twcss variables

* refactor: theming for Settings, add toggle enable/disable keyboard commands
  • Loading branch information
danny-avila authored Aug 7, 2024
1 parent 270c6d2 commit 01a8899
Show file tree
Hide file tree
Showing 12 changed files with 224 additions and 64 deletions.
90 changes: 49 additions & 41 deletions client/src/components/Nav/Settings.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,10 @@
import * as Tabs from '@radix-ui/react-tabs';
import { MessageSquare } from 'lucide-react';
import { MessageSquare, Command } from 'lucide-react';
import { SettingsTabValues } from 'librechat-data-provider';
import type { TDialogProps } from '~/common';
import {
Button,
Dialog,
DialogPanel,
DialogTitle,
Transition,
TransitionChild,
} from '@headlessui/react';
import { Dialog, DialogPanel, DialogTitle, Transition, TransitionChild } from '@headlessui/react';
import { GearIcon, DataIcon, SpeechIcon, UserIcon, ExperimentIcon } from '~/components/svg';
import { General, Chat, Speech, Beta, Data, Account } from './SettingsTabs';
import { General, Chat, Speech, Beta, Commands, Data, Account } from './SettingsTabs';
import { useMediaQuery, useLocalize } from '~/hooks';
import { cn } from '~/utils';

Expand All @@ -30,7 +23,7 @@ export default function Settings({ open, onOpenChange }: TDialogProps) {
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-black/50 dark:bg-black/80" aria-hidden="true" />
<div className="fixed inset-0 bg-black opacity-50 dark:opacity-80" aria-hidden="true" />
</TransitionChild>

<TransitionChild
Expand All @@ -49,19 +42,19 @@ export default function Settings({ open, onOpenChange }: TDialogProps) {
>
<DialogPanel
className={cn(
'overflow-hidden rounded-xl rounded-b-lg bg-white pb-6 shadow-2xl backdrop-blur-2xl animate-in dark:bg-gray-700 sm:rounded-lg md:min-h-[373px] md:w-[680px]',
'overflow-hidden rounded-xl rounded-b-lg bg-surface-tertiary-alt pb-6 shadow-2xl backdrop-blur-2xl animate-in sm:rounded-lg md:min-h-[373px] md:w-[680px]',
)}
>
<DialogTitle
className="mb-3 flex items-center justify-between border-b border-black/10 p-6 pb-5 text-left dark:border-white/10"
className="mb-3 flex items-center justify-between border-b border-border-medium p-6 pb-5 text-left"
as="div"
>
<h2 className="text-lg font-medium leading-6 text-gray-800 dark:text-gray-200">
<h2 className="text-lg font-medium leading-6 text-text-primary">
{localize('com_nav_settings')}
</h2>
<button
type="button"
className="rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-gray-400 focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-gray-100 dark:focus:ring-gray-400 dark:focus:ring-offset-gray-900 dark:data-[state=open]:bg-gray-800"
className="rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-border-xheavy focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-surface-primary dark:focus:ring-offset-surface-primary"
onClick={() => onOpenChange(false)}
>
<svg
Expand All @@ -74,7 +67,7 @@ export default function Settings({ open, onOpenChange }: TDialogProps) {
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="h-5 w-5 text-black dark:text-white"
className="h-5 w-5 text-text-primary"
>
<line x1="18" x2="6" y1="6" y2="18"></line>
<line x1="6" x2="18" y1="6" y2="18"></line>
Expand All @@ -94,17 +87,17 @@ export default function Settings({ open, onOpenChange }: TDialogProps) {
aria-orientation="horizontal"
className={cn(
'min-w-auto max-w-auto -ml-[8px] flex flex-shrink-0 flex-col flex-nowrap overflow-auto sm:max-w-none',
isSmallScreen ? 'flex-row rounded-lg bg-gray-200 p-1 dark:bg-gray-800' : '',
isSmallScreen ? 'flex-row rounded-lg bg-surface-secondary' : '',
)}
style={{ outline: 'none' }}
>
<Tabs.Trigger
tabIndex={0}
className={cn(
'group m-1 flex items-center justify-start gap-2 rounded-md px-2 py-1.5 text-sm text-black transition-all duration-200 ease-in-out radix-state-active:bg-white radix-state-active:text-black dark:text-white dark:radix-state-active:bg-gray-600',
'group m-1 flex items-center justify-start gap-2 rounded-md px-2 py-1.5 text-sm text-text-primary transition-all duration-200 ease-in-out radix-state-active:bg-surface-tertiary radix-state-active:text-text-primary dark:radix-state-active:bg-surface-primary',
isSmallScreen
? 'flex-1 items-center justify-center text-nowrap text-sm dark:text-gray-500 dark:radix-state-active:text-white'
: 'bg-white radix-state-active:bg-gray-200',
isSmallScreen ? '' : 'dark:bg-gray-700',
? 'flex-1 items-center justify-center text-nowrap p-1 px-3 text-sm text-text-secondary'
: 'bg-surface-tertiary-alt',
)}
value={SettingsTabValues.GENERAL}
style={{ userSelect: 'none' }}
Expand All @@ -113,12 +106,12 @@ export default function Settings({ open, onOpenChange }: TDialogProps) {
{localize('com_nav_setting_general')}
</Tabs.Trigger>
<Tabs.Trigger
tabIndex={0}
className={cn(
'group m-1 flex items-center justify-start gap-2 rounded-md px-2 py-1.5 text-sm text-black transition-all duration-200 ease-in-out radix-state-active:bg-white radix-state-active:text-black dark:text-white dark:radix-state-active:bg-gray-600',
'group m-1 flex items-center justify-start gap-2 rounded-md px-2 py-1.5 text-sm text-text-primary transition-all duration-200 ease-in-out radix-state-active:bg-surface-tertiary radix-state-active:text-text-primary dark:radix-state-active:bg-surface-primary',
isSmallScreen
? 'flex-1 items-center justify-center text-nowrap text-sm dark:text-gray-500 dark:radix-state-active:text-white'
: 'bg-white radix-state-active:bg-gray-200',
isSmallScreen ? '' : 'dark:bg-gray-700',
? 'flex-1 items-center justify-center text-nowrap p-1 px-3 text-sm text-text-secondary'
: 'bg-surface-tertiary-alt',
)}
value={SettingsTabValues.CHAT}
style={{ userSelect: 'none' }}
Expand All @@ -127,12 +120,12 @@ export default function Settings({ open, onOpenChange }: TDialogProps) {
{localize('com_nav_setting_chat')}
</Tabs.Trigger>
<Tabs.Trigger
tabIndex={0}
className={cn(
'group m-1 flex items-center justify-start gap-2 rounded-md px-2 py-1.5 text-sm text-black transition-all duration-200 ease-in-out radix-state-active:bg-white radix-state-active:text-black dark:text-white dark:radix-state-active:bg-gray-600',
'group m-1 flex items-center justify-start gap-2 rounded-md px-2 py-1.5 text-sm text-text-primary transition-all duration-200 ease-in-out radix-state-active:bg-surface-tertiary radix-state-active:text-text-primary dark:radix-state-active:bg-surface-primary',
isSmallScreen
? 'flex-1 items-center justify-center text-nowrap text-sm dark:text-gray-500 dark:radix-state-active:text-white'
: 'bg-white radix-state-active:bg-gray-200',
isSmallScreen ? '' : 'dark:bg-gray-700',
? 'flex-1 items-center justify-center text-nowrap p-1 px-3 text-sm text-text-secondary'
: 'bg-surface-tertiary-alt',
)}
value={SettingsTabValues.BETA}
style={{ userSelect: 'none' }}
Expand All @@ -141,12 +134,26 @@ export default function Settings({ open, onOpenChange }: TDialogProps) {
{localize('com_nav_setting_beta')}
</Tabs.Trigger>
<Tabs.Trigger
tabIndex={0}
className={cn(
'group m-1 flex items-center justify-start gap-2 rounded-md px-2 py-1.5 text-sm text-black transition-all duration-200 ease-in-out radix-state-active:bg-white radix-state-active:text-black dark:text-white dark:radix-state-active:bg-gray-600',
'group m-1 flex items-center justify-start gap-2 rounded-md px-2 py-1.5 text-sm text-text-primary transition-all duration-200 ease-in-out radix-state-active:bg-surface-tertiary radix-state-active:text-text-primary dark:radix-state-active:bg-surface-primary',
isSmallScreen
? 'flex-1 items-center justify-center text-nowrap text-sm dark:text-gray-500 dark:radix-state-active:text-white'
: 'bg-white radix-state-active:bg-gray-200',
isSmallScreen ? '' : 'dark:bg-gray-700',
? 'flex-1 items-center justify-center text-nowrap text-sm text-text-secondary'
: 'bg-surface-tertiary-alt',
)}
value={SettingsTabValues.COMMANDS}
style={{ userSelect: 'none' }}
>
<Command className="icon-sm" />
{localize('com_nav_commands')}
</Tabs.Trigger>
<Tabs.Trigger
tabIndex={0}
className={cn(
'group m-1 flex items-center justify-start gap-2 rounded-md px-2 py-1.5 text-sm text-text-primary transition-all duration-200 ease-in-out radix-state-active:bg-surface-tertiary radix-state-active:text-text-primary dark:radix-state-active:bg-surface-primary',
isSmallScreen
? 'flex-1 items-center justify-center text-nowrap p-1 px-3 text-sm text-text-secondary'
: 'bg-surface-tertiary-alt',
)}
value={SettingsTabValues.SPEECH}
style={{ userSelect: 'none' }}
Expand All @@ -155,12 +162,12 @@ export default function Settings({ open, onOpenChange }: TDialogProps) {
{localize('com_nav_setting_speech')}
</Tabs.Trigger>
<Tabs.Trigger
tabIndex={0}
className={cn(
'group m-1 flex items-center justify-start gap-2 rounded-md px-2 py-1.5 text-sm text-black transition-all duration-200 ease-in-out radix-state-active:bg-white radix-state-active:text-black dark:text-white dark:radix-state-active:bg-gray-600',
'group m-1 flex items-center justify-start gap-2 rounded-md px-2 py-1.5 text-sm text-text-primary transition-all duration-200 ease-in-out radix-state-active:bg-surface-tertiary radix-state-active:text-text-primary dark:radix-state-active:bg-surface-primary',
isSmallScreen
? 'flex-1 items-center justify-center text-nowrap text-sm dark:text-gray-500 dark:radix-state-active:text-white'
: 'bg-white radix-state-active:bg-gray-200',
isSmallScreen ? '' : 'dark:bg-gray-700',
? 'flex-1 items-center justify-center text-nowrap p-1 px-3 text-sm text-text-secondary'
: 'bg-surface-tertiary-alt',
)}
value={SettingsTabValues.DATA}
style={{ userSelect: 'none' }}
Expand All @@ -169,12 +176,12 @@ export default function Settings({ open, onOpenChange }: TDialogProps) {
{localize('com_nav_setting_data')}
</Tabs.Trigger>
<Tabs.Trigger
tabIndex={0}
className={cn(
'group m-1 flex items-center justify-start gap-2 rounded-md px-2 py-1.5 text-sm text-black transition-all duration-200 ease-in-out radix-state-active:bg-white radix-state-active:text-black dark:text-white dark:radix-state-active:bg-gray-600',
'group m-1 flex items-center justify-start gap-2 rounded-md px-2 py-1.5 text-sm text-text-primary transition-all duration-200 ease-in-out radix-state-active:bg-surface-tertiary radix-state-active:text-text-primary dark:radix-state-active:bg-surface-primary',
isSmallScreen
? 'flex-1 items-center justify-center text-nowrap text-sm dark:text-gray-500 dark:radix-state-active:text-white'
: 'bg-white radix-state-active:bg-gray-200',
isSmallScreen ? '' : 'dark:bg-gray-700',
? 'flex-1 items-center justify-center text-nowrap p-1 px-3 text-sm text-text-secondary'
: 'bg-surface-tertiary-alt',
)}
value={SettingsTabValues.ACCOUNT}
style={{ userSelect: 'none' }}
Expand All @@ -187,6 +194,7 @@ export default function Settings({ open, onOpenChange }: TDialogProps) {
<General />
<Chat />
<Beta />
<Commands />
<Speech />
<Data />
<Account />
Expand Down
6 changes: 3 additions & 3 deletions client/src/components/Nav/SettingsTabs/Beta/Beta.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ function Beta() {
role="tabpanel"
className="w-full md:min-h-[271px]"
>
<div className="flex flex-col gap-3 text-sm text-black dark:text-gray-50">
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-600">
<div className="flex flex-col gap-3 text-sm text-text-primary">
<div className="border-b border-border-medium pb-3 last-of-type:border-b-0">
<ModularChat />
</div>
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-600">
<div className="border-b border-border-medium pb-3 last-of-type:border-b-0">
<LaTeXParsing />
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { useRecoilState } from 'recoil';
import { Switch } from '~/components/ui';
import { useLocalize } from '~/hooks';
import store from '~/store';

export default function AtCommandSwitch() {
const [atCommand, setAtCommand] = useRecoilState<boolean>(store.atCommand);
const localize = useLocalize();

const handleCheckedChange = (value: boolean) => {
setAtCommand(value);
};

return (
<div className="flex items-center justify-between">
<div>{localize('com_nav_at_command_description')}</div>
<Switch
id="atCommand"
checked={atCommand}
onCheckedChange={handleCheckedChange}
className="ml-4 mt-2"
data-testid="atCommand"
/>
</div>
);
}
30 changes: 30 additions & 0 deletions client/src/components/Nav/SettingsTabs/Commands/Commands.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { memo } from 'react';
import * as Tabs from '@radix-ui/react-tabs';
import { SettingsTabValues } from 'librechat-data-provider';
import SlashCommandSwitch from './SlashCommandSwitch';
import PlusCommandSwitch from './PlusCommandSwitch';
import AtCommandSwitch from './AtCommandSwitch';

function Commands() {
return (
<Tabs.Content
value={SettingsTabValues.COMMANDS}
role="tabpanel"
className="w-full md:min-h-[271px]"
>
<div className="flex flex-col gap-3 text-sm text-text-primary">
<div className="border-b border-border-medium pb-3 last-of-type:border-b-0">
<AtCommandSwitch />
</div>
<div className="border-b border-border-medium pb-3 last-of-type:border-b-0">
<PlusCommandSwitch />
</div>
<div className="border-b border-border-medium pb-3 last-of-type:border-b-0">
<SlashCommandSwitch />
</div>
</div>
</Tabs.Content>
);
}

export default memo(Commands);
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { useRecoilState } from 'recoil';
import { Switch } from '~/components/ui';
import { useLocalize } from '~/hooks';
import store from '~/store';

export default function PlusCommandSwitch() {
const [plusCommand, setPlusCommand] = useRecoilState<boolean>(store.plusCommand);
const localize = useLocalize();

const handleCheckedChange = (value: boolean) => {
setPlusCommand(value);
};

return (
<div className="flex items-center justify-between">
<div>{localize('com_nav_plus_command_description')}</div>
<Switch
id="plusCommand"
checked={plusCommand}
onCheckedChange={handleCheckedChange}
className="ml-4 mt-2"
data-testid="plusCommand"
/>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { useRecoilState } from 'recoil';
import { Switch } from '~/components/ui';
import { useLocalize } from '~/hooks';
import store from '~/store';

export default function SlashCommandSwitch() {
const [slashCommand, setSlashCommand] = useRecoilState<boolean>(store.slashCommand);
const localize = useLocalize();

const handleCheckedChange = (value: boolean) => {
setSlashCommand(value);
};

return (
<div className="flex items-center justify-between">
<div>{localize('com_nav_slash_command_description')}</div>
<Switch
id="slashCommand"
checked={slashCommand}
onCheckedChange={handleCheckedChange}
className="ml-4 mt-2"
data-testid="slashCommand"
/>
</div>
);
}
1 change: 1 addition & 0 deletions client/src/components/Nav/SettingsTabs/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export { default as Chat } from './Chat/Chat';
export { ClearChatsButton } from './General/General';
export { default as Data } from './Data/Data';
export { default as Beta } from './Beta/Beta';
export { default as Commands } from './Commands/Commands';
export { RevokeKeysButton } from './Data/RevokeKeysButton';
export { default as Account } from './Account/Account';
export { default as Speech } from './Speech/Speech';
Loading

0 comments on commit 01a8899

Please sign in to comment.