🎛️ refactor: Add keyboard command toggles & only trigger for the 1st 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
This commit is contained in:
Danny Avila 2024-08-06 22:18:07 -04:00 committed by GitHub
parent 270c6d2350
commit 01a88991ab
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 224 additions and 64 deletions

View file

@ -1,17 +1,10 @@
import * as Tabs from '@radix-ui/react-tabs'; 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 { SettingsTabValues } from 'librechat-data-provider';
import type { TDialogProps } from '~/common'; import type { TDialogProps } from '~/common';
import { import { Dialog, DialogPanel, DialogTitle, Transition, TransitionChild } from '@headlessui/react';
Button,
Dialog,
DialogPanel,
DialogTitle,
Transition,
TransitionChild,
} from '@headlessui/react';
import { GearIcon, DataIcon, SpeechIcon, UserIcon, ExperimentIcon } from '~/components/svg'; 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 { useMediaQuery, useLocalize } from '~/hooks';
import { cn } from '~/utils'; import { cn } from '~/utils';
@ -30,7 +23,7 @@ export default function Settings({ open, onOpenChange }: TDialogProps) {
leaveFrom="opacity-100" leaveFrom="opacity-100"
leaveTo="opacity-0" 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>
<TransitionChild <TransitionChild
@ -49,19 +42,19 @@ export default function Settings({ open, onOpenChange }: TDialogProps) {
> >
<DialogPanel <DialogPanel
className={cn( 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 <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" 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')} {localize('com_nav_settings')}
</h2> </h2>
<button <button
type="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)} onClick={() => onOpenChange(false)}
> >
<svg <svg
@ -74,7 +67,7 @@ export default function Settings({ open, onOpenChange }: TDialogProps) {
strokeWidth="2" strokeWidth="2"
strokeLinecap="round" strokeLinecap="round"
strokeLinejoin="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="18" x2="6" y1="6" y2="18"></line>
<line x1="6" x2="18" y1="6" y2="18"></line> <line x1="6" x2="18" y1="6" y2="18"></line>
@ -94,17 +87,17 @@ export default function Settings({ open, onOpenChange }: TDialogProps) {
aria-orientation="horizontal" aria-orientation="horizontal"
className={cn( className={cn(
'min-w-auto max-w-auto -ml-[8px] flex flex-shrink-0 flex-col flex-nowrap overflow-auto sm:max-w-none', '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' }} style={{ outline: 'none' }}
> >
<Tabs.Trigger <Tabs.Trigger
tabIndex={0}
className={cn( 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 isSmallScreen
? 'flex-1 items-center justify-center text-nowrap text-sm dark:text-gray-500 dark:radix-state-active:text-white' ? 'flex-1 items-center justify-center text-nowrap p-1 px-3 text-sm text-text-secondary'
: 'bg-white radix-state-active:bg-gray-200', : 'bg-surface-tertiary-alt',
isSmallScreen ? '' : 'dark:bg-gray-700',
)} )}
value={SettingsTabValues.GENERAL} value={SettingsTabValues.GENERAL}
style={{ userSelect: 'none' }} style={{ userSelect: 'none' }}
@ -113,12 +106,12 @@ export default function Settings({ open, onOpenChange }: TDialogProps) {
{localize('com_nav_setting_general')} {localize('com_nav_setting_general')}
</Tabs.Trigger> </Tabs.Trigger>
<Tabs.Trigger <Tabs.Trigger
tabIndex={0}
className={cn( 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 isSmallScreen
? 'flex-1 items-center justify-center text-nowrap text-sm dark:text-gray-500 dark:radix-state-active:text-white' ? 'flex-1 items-center justify-center text-nowrap p-1 px-3 text-sm text-text-secondary'
: 'bg-white radix-state-active:bg-gray-200', : 'bg-surface-tertiary-alt',
isSmallScreen ? '' : 'dark:bg-gray-700',
)} )}
value={SettingsTabValues.CHAT} value={SettingsTabValues.CHAT}
style={{ userSelect: 'none' }} style={{ userSelect: 'none' }}
@ -127,12 +120,12 @@ export default function Settings({ open, onOpenChange }: TDialogProps) {
{localize('com_nav_setting_chat')} {localize('com_nav_setting_chat')}
</Tabs.Trigger> </Tabs.Trigger>
<Tabs.Trigger <Tabs.Trigger
tabIndex={0}
className={cn( 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 isSmallScreen
? 'flex-1 items-center justify-center text-nowrap text-sm dark:text-gray-500 dark:radix-state-active:text-white' ? 'flex-1 items-center justify-center text-nowrap p-1 px-3 text-sm text-text-secondary'
: 'bg-white radix-state-active:bg-gray-200', : 'bg-surface-tertiary-alt',
isSmallScreen ? '' : 'dark:bg-gray-700',
)} )}
value={SettingsTabValues.BETA} value={SettingsTabValues.BETA}
style={{ userSelect: 'none' }} style={{ userSelect: 'none' }}
@ -141,12 +134,26 @@ export default function Settings({ open, onOpenChange }: TDialogProps) {
{localize('com_nav_setting_beta')} {localize('com_nav_setting_beta')}
</Tabs.Trigger> </Tabs.Trigger>
<Tabs.Trigger <Tabs.Trigger
tabIndex={0}
className={cn( 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 isSmallScreen
? 'flex-1 items-center justify-center text-nowrap text-sm dark:text-gray-500 dark:radix-state-active:text-white' ? 'flex-1 items-center justify-center text-nowrap text-sm text-text-secondary'
: 'bg-white radix-state-active:bg-gray-200', : 'bg-surface-tertiary-alt',
isSmallScreen ? '' : 'dark:bg-gray-700', )}
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} value={SettingsTabValues.SPEECH}
style={{ userSelect: 'none' }} style={{ userSelect: 'none' }}
@ -155,12 +162,12 @@ export default function Settings({ open, onOpenChange }: TDialogProps) {
{localize('com_nav_setting_speech')} {localize('com_nav_setting_speech')}
</Tabs.Trigger> </Tabs.Trigger>
<Tabs.Trigger <Tabs.Trigger
tabIndex={0}
className={cn( 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 isSmallScreen
? 'flex-1 items-center justify-center text-nowrap text-sm dark:text-gray-500 dark:radix-state-active:text-white' ? 'flex-1 items-center justify-center text-nowrap p-1 px-3 text-sm text-text-secondary'
: 'bg-white radix-state-active:bg-gray-200', : 'bg-surface-tertiary-alt',
isSmallScreen ? '' : 'dark:bg-gray-700',
)} )}
value={SettingsTabValues.DATA} value={SettingsTabValues.DATA}
style={{ userSelect: 'none' }} style={{ userSelect: 'none' }}
@ -169,12 +176,12 @@ export default function Settings({ open, onOpenChange }: TDialogProps) {
{localize('com_nav_setting_data')} {localize('com_nav_setting_data')}
</Tabs.Trigger> </Tabs.Trigger>
<Tabs.Trigger <Tabs.Trigger
tabIndex={0}
className={cn( 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 isSmallScreen
? 'flex-1 items-center justify-center text-nowrap text-sm dark:text-gray-500 dark:radix-state-active:text-white' ? 'flex-1 items-center justify-center text-nowrap p-1 px-3 text-sm text-text-secondary'
: 'bg-white radix-state-active:bg-gray-200', : 'bg-surface-tertiary-alt',
isSmallScreen ? '' : 'dark:bg-gray-700',
)} )}
value={SettingsTabValues.ACCOUNT} value={SettingsTabValues.ACCOUNT}
style={{ userSelect: 'none' }} style={{ userSelect: 'none' }}
@ -187,6 +194,7 @@ export default function Settings({ open, onOpenChange }: TDialogProps) {
<General /> <General />
<Chat /> <Chat />
<Beta /> <Beta />
<Commands />
<Speech /> <Speech />
<Data /> <Data />
<Account /> <Account />

View file

@ -11,11 +11,11 @@ function Beta() {
role="tabpanel" role="tabpanel"
className="w-full md:min-h-[271px]" className="w-full md:min-h-[271px]"
> >
<div className="flex flex-col gap-3 text-sm text-black dark:text-gray-50"> <div className="flex flex-col gap-3 text-sm text-text-primary">
<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">
<ModularChat /> <ModularChat />
</div> </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 /> <LaTeXParsing />
</div> </div>
</div> </div>

View file

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

View file

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

View file

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

View file

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

View file

@ -3,6 +3,7 @@ export { default as Chat } from './Chat/Chat';
export { ClearChatsButton } from './General/General'; export { ClearChatsButton } from './General/General';
export { default as Data } from './Data/Data'; export { default as Data } from './Data/Data';
export { default as Beta } from './Beta/Beta'; export { default as Beta } from './Beta/Beta';
export { default as Commands } from './Commands/Commands';
export { RevokeKeysButton } from './Data/RevokeKeysButton'; export { RevokeKeysButton } from './Data/RevokeKeysButton';
export { default as Account } from './Account/Account'; export { default as Account } from './Account/Account';
export { default as Speech } from './Speech/Speech'; export { default as Speech } from './Speech/Speech';

View file

@ -1,5 +1,5 @@
import { useSetRecoilState } from 'recoil';
import { useCallback, useMemo } from 'react'; import { useCallback, useMemo } from 'react';
import { useSetRecoilState, useRecoilValue } from 'recoil';
import { PermissionTypes, Permissions } from 'librechat-data-provider'; import { PermissionTypes, Permissions } from 'librechat-data-provider';
import type { SetterOrUpdater } from 'recoil'; import type { SetterOrUpdater } from 'recoil';
import useHasAccess from '~/hooks/Roles/useHasAccess'; import useHasAccess from '~/hooks/Roles/useHasAccess';
@ -20,20 +20,16 @@ const shouldTriggerCommand = (
commandChar: string, commandChar: string,
) => { ) => {
const text = textAreaRef.current?.value; const text = textAreaRef.current?.value;
if (!(text && text[text.length - 1] === commandChar)) { if (typeof text !== 'string' || text.length === 0 || text[0] !== commandChar) {
return false; return false;
} }
const startPos = textAreaRef.current?.selectionStart; const startPos = textAreaRef.current?.selectionStart;
if (!startPos) { if (typeof startPos !== 'number') {
return false; return false;
} }
const isAtStart = startPos === 1; return startPos === 1;
const isPrecededBySpace = textAreaRef.current?.value.charAt(startPos - 2) === ' ';
const shouldTrigger = isAtStart || isPrecededBySpace;
return shouldTrigger;
}; };
/** /**
@ -55,26 +51,32 @@ const useHandleKeyUp = ({
permission: Permissions.USE, permission: Permissions.USE,
}); });
const setShowPromptsPopover = useSetRecoilState(store.showPromptsPopoverFamily(index)); const setShowPromptsPopover = useSetRecoilState(store.showPromptsPopoverFamily(index));
// Get the current state of command toggles
const atCommandEnabled = useRecoilValue(store.atCommand);
const plusCommandEnabled = useRecoilValue(store.plusCommand);
const slashCommandEnabled = useRecoilValue(store.slashCommand);
const handleAtCommand = useCallback(() => { const handleAtCommand = useCallback(() => {
if (shouldTriggerCommand(textAreaRef, '@')) { if (atCommandEnabled && shouldTriggerCommand(textAreaRef, '@')) {
setShowMentionPopover(true); setShowMentionPopover(true);
} }
}, [textAreaRef, setShowMentionPopover]); }, [textAreaRef, setShowMentionPopover, atCommandEnabled]);
const handlePlusCommand = useCallback(() => { const handlePlusCommand = useCallback(() => {
if (shouldTriggerCommand(textAreaRef, '+')) { if (plusCommandEnabled && shouldTriggerCommand(textAreaRef, '+')) {
setShowPlusPopover(true); setShowPlusPopover(true);
} }
}, [textAreaRef, setShowPlusPopover]); }, [textAreaRef, setShowPlusPopover, plusCommandEnabled]);
const handlePromptsCommand = useCallback(() => { const handlePromptsCommand = useCallback(() => {
if (!hasAccess) { if (!hasAccess || !slashCommandEnabled) {
return; return;
} }
if (shouldTriggerCommand(textAreaRef, '/')) { if (shouldTriggerCommand(textAreaRef, '/')) {
setShowPromptsPopover(true); setShowPromptsPopover(true);
} }
}, [textAreaRef, hasAccess, setShowPromptsPopover]); }, [textAreaRef, hasAccess, setShowPromptsPopover, slashCommandEnabled]);
const commandHandlers = useMemo( const commandHandlers = useMemo(
() => ({ () => ({
@ -91,18 +93,18 @@ const useHandleKeyUp = ({
const handleKeyUp = useCallback( const handleKeyUp = useCallback(
(event: React.KeyboardEvent<HTMLTextAreaElement>) => { (event: React.KeyboardEvent<HTMLTextAreaElement>) => {
const text = textAreaRef.current?.value; const text = textAreaRef.current?.value;
if (!text) { if (typeof text !== 'string' || text.length === 0) {
return; return;
} }
if (invalidKeys[event.key]) { if (invalidKeys[event.key as keyof typeof invalidKeys]) {
return; return;
} }
const lastChar = text[text.length - 1]; const firstChar = text[0];
const handler = commandHandlers[lastChar]; const handler = commandHandlers[firstChar as keyof typeof commandHandlers];
if (handler) { if (typeof handler === 'function') {
handler(); handler();
} }
}, },

View file

@ -682,6 +682,18 @@ export default {
'This action will delete all cached TTS (Text-to-Speech) audio files stored on your device. Cached audio files are used to speed up playback of previously generated TTS audio, but they can consume storage space on your device.', 'This action will delete all cached TTS (Text-to-Speech) audio files stored on your device. Cached audio files are used to speed up playback of previously generated TTS audio, but they can consume storage space on your device.',
com_nav_info_bookmarks_rebuild: com_nav_info_bookmarks_rebuild:
'If the bookmark count is incorrect, please rebuild the bookmark information. The bookmark count will be recalculated and the data will be restored to its correct state.', 'If the bookmark count is incorrect, please rebuild the bookmark information. The bookmark count will be recalculated and the data will be restored to its correct state.',
// Command Settings Tab
com_nav_commands: 'Commands',
com_nav_commands_tab: 'Command Settings',
com_nav_at_command: '@-Command',
com_nav_at_command_description:
'Toggle command "@" for switching endpoints, models, presets, etc.',
com_nav_plus_command: '+-Command',
com_nav_plus_command_description: 'Toggle command "+" for adding a multi-response setting',
com_nav_slash_command: '/-Command',
com_nav_slash_command_description: 'Toggle command "/" for selecting a prompt via keyboard',
com_nav_command_settings: 'Command Settings',
com_nav_command_settings_description: 'Customize which commands are available in the chat',
com_nav_setting_general: 'General', com_nav_setting_general: 'General',
com_nav_setting_chat: 'Chat', com_nav_setting_chat: 'Chat',
com_nav_setting_beta: 'Beta features', com_nav_setting_beta: 'Beta features',

View file

@ -292,3 +292,23 @@
.scrollbar-gutter-stable { .scrollbar-gutter-stable {
scrollbar-gutter: stable; scrollbar-gutter: stable;
} }
/* Styles for Chrome scrollbar */
.chrome-scrollbar::-webkit-scrollbar {
width: 12px; /* Increase the width of the scrollbar */
}
.chrome-scrollbar::-webkit-scrollbar-thumb {
background-color: rgba(0, 0, 0, 0.2); /* Color of the scroll thumb */
border-radius: 6px; /* Rounded corners on the scroll thumb */
border: 2px solid transparent; /* Creates padding around scroll thumb */
background-clip: padding-box; /* Prevents background color from leaking outside the border */
}
.chrome-scrollbar::-webkit-scrollbar-thumb:hover {
background-color: rgba(0, 0, 0, 0.3); /* Darker color when hovering */
}
.chrome-scrollbar::-webkit-scrollbar-track {
background-color: transparent; /* Color of the tracking area */
}

View file

@ -37,6 +37,11 @@ const localStorageAtoms = {
modularChat: atomWithLocalStorage('modularChat', true), modularChat: atomWithLocalStorage('modularChat', true),
LaTeXParsing: atomWithLocalStorage('LaTeXParsing', true), LaTeXParsing: atomWithLocalStorage('LaTeXParsing', true),
// Commands settings
atCommand: atomWithLocalStorage('atCommand', true),
plusCommand: atomWithLocalStorage('plusCommand', true),
slashCommand: atomWithLocalStorage('slashCommand', true),
// Speech settings // Speech settings
conversationMode: atomWithLocalStorage('conversationMode', false), conversationMode: atomWithLocalStorage('conversationMode', false),
advancedMode: atomWithLocalStorage('advancedMode', false), advancedMode: atomWithLocalStorage('advancedMode', false),

View file

@ -895,6 +895,10 @@ export enum SettingsTabValues {
* Tab for Account Settings * Tab for Account Settings
*/ */
ACCOUNT = 'account', ACCOUNT = 'account',
/**
* Chat input commands
*/
COMMANDS = 'commands',
} }
export enum STTProviders { export enum STTProviders {