mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-17 00:40:14 +01:00
🎛️ 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:
parent
270c6d2350
commit
01a88991ab
12 changed files with 224 additions and 64 deletions
|
|
@ -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';
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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>
|
||||
|
|
@ -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' }}
|
||||
|
|
@ -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' }}
|
||||
|
|
@ -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' }}
|
||||
|
|
@ -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' }}
|
||||
|
|
@ -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' }}
|
||||
|
|
@ -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' }}
|
||||
|
|
@ -187,6 +194,7 @@ export default function Settings({ open, onOpenChange }: TDialogProps) {
|
|||
<General />
|
||||
<Chat />
|
||||
<Beta />
|
||||
<Commands />
|
||||
<Speech />
|
||||
<Data />
|
||||
<Account />
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
client/src/components/Nav/SettingsTabs/Commands/Commands.tsx
Normal file
30
client/src/components/Nav/SettingsTabs/Commands/Commands.tsx
Normal 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);
|
||||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { useSetRecoilState } from 'recoil';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useSetRecoilState, useRecoilValue } from 'recoil';
|
||||
import { PermissionTypes, Permissions } from 'librechat-data-provider';
|
||||
import type { SetterOrUpdater } from 'recoil';
|
||||
import useHasAccess from '~/hooks/Roles/useHasAccess';
|
||||
|
|
@ -20,20 +20,16 @@ const shouldTriggerCommand = (
|
|||
commandChar: string,
|
||||
) => {
|
||||
const text = textAreaRef.current?.value;
|
||||
if (!(text && text[text.length - 1] === commandChar)) {
|
||||
if (typeof text !== 'string' || text.length === 0 || text[0] !== commandChar) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const startPos = textAreaRef.current?.selectionStart;
|
||||
if (!startPos) {
|
||||
if (typeof startPos !== 'number') {
|
||||
return false;
|
||||
}
|
||||
|
||||
const isAtStart = startPos === 1;
|
||||
const isPrecededBySpace = textAreaRef.current?.value.charAt(startPos - 2) === ' ';
|
||||
|
||||
const shouldTrigger = isAtStart || isPrecededBySpace;
|
||||
return shouldTrigger;
|
||||
return startPos === 1;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -55,26 +51,32 @@ const useHandleKeyUp = ({
|
|||
permission: Permissions.USE,
|
||||
});
|
||||
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(() => {
|
||||
if (shouldTriggerCommand(textAreaRef, '@')) {
|
||||
if (atCommandEnabled && shouldTriggerCommand(textAreaRef, '@')) {
|
||||
setShowMentionPopover(true);
|
||||
}
|
||||
}, [textAreaRef, setShowMentionPopover]);
|
||||
}, [textAreaRef, setShowMentionPopover, atCommandEnabled]);
|
||||
|
||||
const handlePlusCommand = useCallback(() => {
|
||||
if (shouldTriggerCommand(textAreaRef, '+')) {
|
||||
if (plusCommandEnabled && shouldTriggerCommand(textAreaRef, '+')) {
|
||||
setShowPlusPopover(true);
|
||||
}
|
||||
}, [textAreaRef, setShowPlusPopover]);
|
||||
}, [textAreaRef, setShowPlusPopover, plusCommandEnabled]);
|
||||
|
||||
const handlePromptsCommand = useCallback(() => {
|
||||
if (!hasAccess) {
|
||||
if (!hasAccess || !slashCommandEnabled) {
|
||||
return;
|
||||
}
|
||||
if (shouldTriggerCommand(textAreaRef, '/')) {
|
||||
setShowPromptsPopover(true);
|
||||
}
|
||||
}, [textAreaRef, hasAccess, setShowPromptsPopover]);
|
||||
}, [textAreaRef, hasAccess, setShowPromptsPopover, slashCommandEnabled]);
|
||||
|
||||
const commandHandlers = useMemo(
|
||||
() => ({
|
||||
|
|
@ -91,18 +93,18 @@ const useHandleKeyUp = ({
|
|||
const handleKeyUp = useCallback(
|
||||
(event: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
||||
const text = textAreaRef.current?.value;
|
||||
if (!text) {
|
||||
if (typeof text !== 'string' || text.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (invalidKeys[event.key]) {
|
||||
if (invalidKeys[event.key as keyof typeof invalidKeys]) {
|
||||
return;
|
||||
}
|
||||
|
||||
const lastChar = text[text.length - 1];
|
||||
const handler = commandHandlers[lastChar];
|
||||
const firstChar = text[0];
|
||||
const handler = commandHandlers[firstChar as keyof typeof commandHandlers];
|
||||
|
||||
if (handler) {
|
||||
if (typeof handler === 'function') {
|
||||
handler();
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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.',
|
||||
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.',
|
||||
// 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_chat: 'Chat',
|
||||
com_nav_setting_beta: 'Beta features',
|
||||
|
|
|
|||
|
|
@ -291,4 +291,24 @@
|
|||
|
||||
.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 */
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,6 +37,11 @@ const localStorageAtoms = {
|
|||
modularChat: atomWithLocalStorage('modularChat', true),
|
||||
LaTeXParsing: atomWithLocalStorage('LaTeXParsing', true),
|
||||
|
||||
// Commands settings
|
||||
atCommand: atomWithLocalStorage('atCommand', true),
|
||||
plusCommand: atomWithLocalStorage('plusCommand', true),
|
||||
slashCommand: atomWithLocalStorage('slashCommand', true),
|
||||
|
||||
// Speech settings
|
||||
conversationMode: atomWithLocalStorage('conversationMode', false),
|
||||
advancedMode: atomWithLocalStorage('advancedMode', false),
|
||||
|
|
|
|||
|
|
@ -895,6 +895,10 @@ export enum SettingsTabValues {
|
|||
* Tab for Account Settings
|
||||
*/
|
||||
ACCOUNT = 'account',
|
||||
/**
|
||||
* Chat input commands
|
||||
*/
|
||||
COMMANDS = 'commands',
|
||||
}
|
||||
|
||||
export enum STTProviders {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue