🎨 style: settings tab update (#3088)

* style: settings UI  update

* style: update UI

* style: update button style

* fix: scroll settings on mobile

* feat: `?` for settings
This commit is contained in:
Marco Beretta 2024-06-21 15:58:04 +02:00 committed by GitHub
parent 4319c62e66
commit 0424f8fe55
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
36 changed files with 359 additions and 173 deletions

View file

@ -15,9 +15,10 @@ export default function Settings({ open, onOpenChange }: TDialogProps) {
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent
disableScroll={isSmallScreen}
className={cn(
'overflow-hidden shadow-2xl md:min-h-[373px] md:w-[680px]',
isSmallScreen ? 'top-5 -translate-y-0' : '',
'max-h-[90vh] overflow-auto shadow-2xl md:min-h-[500px] md:w-[680px]',
isSmallScreen ? 'min-h-[200px]' : '',
)}
>
<DialogHeader>
@ -28,7 +29,7 @@ export default function Settings({ open, onOpenChange }: TDialogProps) {
<div className="max-h-[373px] overflow-auto px-6 md:min-h-[373px] md:w-[680px]">
<Tabs.Root
defaultValue={SettingsTabValues.GENERAL}
className="flex flex-col gap-10 md:flex-row"
className="flex flex-col gap-3 md:flex-row"
orientation="horizontal"
>
<Tabs.List
@ -36,17 +37,18 @@ export default function Settings({ open, onOpenChange }: TDialogProps) {
role="tablist"
aria-orientation="horizontal"
className={cn(
'min-w-auto max-w-auto -ml-[8px] flex flex-shrink-0 flex-col flex-wrap overflow-auto sm:max-w-none',
isSmallScreen ? 'flex-row rounded-lg bg-gray-200 p-1 dark:bg-gray-800' : '',
isSmallScreen
? 'hide-scrollbar flex flex-row space-x-4 overflow-x-auto'
: 'min-w-auto max-w-auto -ml-[8px] flex flex-shrink-0 flex-col flex-col flex-wrap overflow-auto sm:max-w-none',
)}
style={{ outline: 'none' }}
>
<Tabs.Trigger
className={cn(
'group m-1 flex items-center justify-start gap-2 rounded-md px-2 py-1.5 text-sm text-black 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-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',
isSmallScreen
? 'flex-1 flex-col items-center justify-center text-sm dark:text-gray-500 dark:radix-state-active:text-white'
: 'bg-white radix-state-active:bg-gray-200',
? 'flex-row items-center justify-center text-sm radix-state-active:bg-gray-100'
: 'bg-white radix-state-active:bg-gray-100',
isSmallScreen ? '' : 'dark:bg-gray-700',
)}
value={SettingsTabValues.GENERAL}
@ -57,10 +59,10 @@ export default function Settings({ open, onOpenChange }: TDialogProps) {
</Tabs.Trigger>
<Tabs.Trigger
className={cn(
'group m-1 flex items-center justify-start gap-2 rounded-md px-2 py-1.5 text-sm text-black 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-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',
isSmallScreen
? 'flex-1 flex-col items-center justify-center text-sm dark:text-gray-500 dark:radix-state-active:text-white'
: 'bg-white radix-state-active:bg-gray-200',
? 'flex-row items-center justify-center text-sm radix-state-active:bg-gray-100'
: 'bg-white radix-state-active:bg-gray-100',
isSmallScreen ? '' : 'dark:bg-gray-700',
)}
value={SettingsTabValues.MESSAGES}
@ -71,10 +73,10 @@ export default function Settings({ open, onOpenChange }: TDialogProps) {
</Tabs.Trigger>
<Tabs.Trigger
className={cn(
'group m-1 flex items-center justify-start gap-2 rounded-md px-2 py-1.5 text-sm text-black 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-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',
isSmallScreen
? 'flex-1 flex-col items-center justify-center text-sm dark:text-gray-500 dark:radix-state-active:text-white'
: 'bg-white radix-state-active:bg-gray-200',
? 'flex-row items-center justify-center text-sm radix-state-active:bg-gray-100'
: 'bg-white radix-state-active:bg-gray-100',
isSmallScreen ? '' : 'dark:bg-gray-700',
)}
value={SettingsTabValues.BETA}
@ -85,10 +87,10 @@ export default function Settings({ open, onOpenChange }: TDialogProps) {
</Tabs.Trigger>
<Tabs.Trigger
className={cn(
'group m-1 flex items-center justify-start gap-2 rounded-md px-2 py-1.5 text-sm text-black 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-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',
isSmallScreen
? 'flex-1 flex-col items-center justify-center text-sm dark:text-gray-500 dark:radix-state-active:text-white'
: 'bg-white radix-state-active:bg-gray-200',
? 'flex-row items-center justify-center text-sm radix-state-active:bg-gray-100'
: 'bg-white radix-state-active:bg-gray-100',
isSmallScreen ? '' : 'dark:bg-gray-700',
)}
value={SettingsTabValues.SPEECH}
@ -99,10 +101,10 @@ export default function Settings({ open, onOpenChange }: TDialogProps) {
</Tabs.Trigger>
<Tabs.Trigger
className={cn(
'group m-1 flex items-center justify-start gap-2 rounded-md px-2 py-1.5 text-sm text-black 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-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',
isSmallScreen
? 'flex-1 flex-col items-center justify-center text-sm dark:text-gray-500 dark:radix-state-active:text-white'
: 'bg-white radix-state-active:bg-gray-200',
? 'flex-row items-center justify-center text-sm radix-state-active:bg-gray-100'
: 'bg-white radix-state-active:bg-gray-100',
isSmallScreen ? '' : 'dark:bg-gray-700',
)}
value={SettingsTabValues.DATA}
@ -113,10 +115,10 @@ export default function Settings({ open, onOpenChange }: TDialogProps) {
</Tabs.Trigger>
<Tabs.Trigger
className={cn(
'group m-1 flex items-center justify-start gap-2 rounded-md px-2 py-1.5 text-sm text-black 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-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',
isSmallScreen
? 'flex-1 flex-col items-center justify-center text-sm dark:text-gray-500 dark:radix-state-active:text-white'
: 'bg-white radix-state-active:bg-gray-200',
? 'flex-row items-center justify-center text-sm radix-state-active:bg-gray-100'
: 'bg-white radix-state-active:bg-gray-100',
isSmallScreen ? '' : 'dark:bg-gray-700',
)}
value={SettingsTabValues.ACCOUNT}
@ -126,7 +128,7 @@ export default function Settings({ open, onOpenChange }: TDialogProps) {
{localize('com_nav_setting_account')}
</Tabs.Trigger>
</Tabs.List>
<div className="h-screen max-h-[373px] overflow-auto sm:w-full sm:max-w-none">
<div className="h-auto min-h-[280px] overflow-auto sm:w-full sm:max-w-none">
<General />
<Messages />
<Beta />

View file

@ -2,6 +2,7 @@ import React from 'react';
import { useRecoilState } from 'recoil';
import * as Tabs from '@radix-ui/react-tabs';
import { SettingsTabValues } from 'librechat-data-provider';
import HoverCardSettings from '../HoverCardSettings';
import DeleteAccount from './DeleteAccount';
import { Switch } from '~/components/ui';
import { useLocalize } from '~/hooks';
@ -25,7 +26,7 @@ function Account({ onCheckedChange }: { onCheckedChange?: (value: boolean) => vo
role="tabpanel"
className="w-full md:min-h-[271px]"
>
<div className="flex flex-col gap-3 text-sm text-gray-600 dark:text-gray-50">
<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">
<Avatar />
</div>
@ -33,7 +34,10 @@ function Account({ onCheckedChange }: { onCheckedChange?: (value: boolean) => vo
<DeleteAccount />
</div>
<div className="flex items-center justify-between">
<div> {localize('com_nav_user_name_display')} </div>
<div className="flex items-center space-x-2">
<div>{localize('com_nav_user_name_display')}</div>
<HoverCardSettings side="bottom" text="com_nav_info_user_name_display" />
</div>
<Switch
id="UsernameDisplay"
checked={UsernameDisplay}

View file

@ -80,11 +80,8 @@ function Avatar() {
<>
<div className="flex items-center justify-between">
<span>{localize('com_nav_profile_picture')}</span>
<label
htmlFor={'file-upload-avatar'}
className="flex h-auto cursor-pointer items-center rounded bg-transparent px-2 py-1 text-xs font-normal transition-colors hover:bg-gray-100 hover:text-green-700 dark:bg-transparent dark:text-white dark:hover:bg-gray-600 dark:hover:text-green-500"
>
<FileImage className="mr-1 flex w-[22px] items-center stroke-1" />
<label htmlFor={'file-upload-avatar'} className="btn btn-neutral relative">
<FileImage className="mr-2 flex w-[22px] items-center stroke-1" />
<span>{localize('com_nav_change_picture')}</span>
<input
id={'file-upload-avatar'}

View file

@ -1,17 +1,11 @@
import React, { useState, useCallback } from 'react';
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogButton,
Input,
} from '~/components/ui';
import { Dialog, DialogContent, DialogHeader, DialogTitle, Input } from '~/components/ui';
import { cn, defaultTextProps, removeFocusOutlines } from '~/utils';
import { useDeleteUserMutation } from '~/data-provider';
import { Spinner, LockIcon } from '~/components/svg';
import { useAuthContext } from '~/hooks/AuthContext';
import { useLocalize } from '~/hooks';
import DangerButton from '../DangerButton';
const DeleteAccount = ({ disabled = false }: { title?: string; disabled?: boolean }) => {
const localize = useLocalize();
@ -50,16 +44,19 @@ const DeleteAccount = ({ disabled = false }: { title?: string; disabled?: boolea
<div className="flex items-center justify-between">
<span>{localize('com_nav_delete_account')}</span>
<label>
<DialogButton
<DangerButton
id={'delete-user-account'}
disabled={disabled}
onClick={onClick}
actionTextCode="com_ui_delete"
className={cn(
'btn btn-danger relative border-none bg-red-700 text-white hover:bg-red-800 dark:hover:bg-red-800',
'btn relative border-none bg-red-500 text-white hover:bg-red-700 dark:hover:bg-red-700',
)}
>
{localize('com_ui_delete')}
</DialogButton>
confirmClear={false}
infoTextCode={''}
dataTestIdInitial={''}
dataTestIdConfirm={''}
/>
</label>
</div>
<Dialog open={isDialogOpen} onOpenChange={() => setDialogOpen(false)}>

View file

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

View file

@ -1,4 +1,5 @@
import { useRecoilState } from 'recoil';
import HoverCardSettings from '../HoverCardSettings';
import { Switch } from '~/components/ui';
import { useLocalize } from '~/hooks';
import store from '~/store';
@ -20,7 +21,10 @@ export default function LaTeXParsingSwitch({
return (
<div className="flex items-center justify-between">
<div>{localize('com_nav_latex_parsing')} </div>
<div className="flex items-center space-x-2">
<div>{localize('com_nav_latex_parsing')}</div>
<HoverCardSettings side="bottom" text="com_nav_info_latex_parsing" />
</div>
<Switch
id="LaTeXParsing"
checked={LaTeXParsing}

View file

@ -20,7 +20,7 @@ export default function ModularChatSwitch({
return (
<div className="flex items-center justify-between">
<div>{localize('com_nav_modular_chat')} </div>
<div> {localize('com_nav_modular_chat')} </div>
<Switch
id="modularChat"
checked={modularChat}

View file

@ -6,6 +6,7 @@ import { Spinner } from '~/components/svg';
import type { TDangerButtonProps } from '~/common';
import { useLocalize } from '~/hooks';
import { cn } from '~/utils';
import HoverCardSettings from './HoverCardSettings';
const DangerButton = (props: TDangerButtonProps, ref: ForwardedRef<HTMLButtonElement>) => {
const {
@ -20,6 +21,7 @@ const DangerButton = (props: TDangerButtonProps, ref: ForwardedRef<HTMLButtonEle
showText = true,
dataTestIdInitial,
dataTestIdConfirm,
infoDescriptionCode,
confirmActionTextCode = 'com_ui_confirm_action',
} = props;
const localize = useLocalize();
@ -33,14 +35,19 @@ const DangerButton = (props: TDangerButtonProps, ref: ForwardedRef<HTMLButtonEle
return (
<div className="flex items-center justify-between">
{showText && <div> {localize(infoTextCode)} </div>}
{showText && (
<div className={`flex items-center ${infoDescriptionCode ? 'space-x-2' : ''}`}>
<div>{localize(infoTextCode)}</div>
{infoDescriptionCode && <HoverCardSettings side="bottom" text={infoDescriptionCode} />}
</div>
)}
<DialogButton
id={id}
ref={ref}
disabled={disabled}
onClick={onClick}
className={cn(
' btn btn-danger relative min-w-[70px] border-none bg-red-700 text-white hover:bg-red-800 dark:hover:bg-red-800',
'btn relative border-none bg-red-500 text-white hover:bg-red-700 dark:hover:bg-red-700',
className,
)}
>

View file

@ -43,7 +43,7 @@ function Data() {
className="w-full md:min-h-[271px]"
ref={dataTabRef}
>
<div className="flex flex-col gap-3 text-sm text-gray-600 dark:text-gray-50">
<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">
<ImportConversations />
</div>

View file

@ -45,6 +45,7 @@ export const DeleteCacheButton = ({
id={'delete-cache'}
actionTextCode={'com_ui_delete'}
infoTextCode={'com_nav_delete_cache_storage'}
infoDescriptionCode={'com_nav_info_delete_cache_storage'}
dataTestIdInitial={'delete-cache-initial'}
dataTestIdConfirm={'delete-cache-confirm'}
/>

View file

@ -68,11 +68,8 @@ function ImportConversations() {
return (
<div className="flex items-center justify-between">
<span>{localize('com_ui_import_conversation_info')}</span>
<label
htmlFor={'import-conversations-file'}
className="flex h-auto cursor-pointer items-center rounded bg-transparent px-2 py-3 text-xs font-medium transition-colors hover:bg-gray-100 hover:text-green-700 dark:bg-transparent dark:text-white dark:hover:bg-gray-600 dark:hover:text-green-500"
>
<div>{localize('com_ui_import_conversation_info')}</div>
<label htmlFor={'import-conversations-file'} className="btn btn-neutral relative">
{allowImport ? (
<Import className="mr-1 flex h-4 w-4 items-center stroke-1" />
) : (

View file

@ -56,6 +56,7 @@ export const RevokeKeysButton = ({
id={'revoke-all-user-keys'}
actionTextCode={'com_ui_revoke'}
infoTextCode={'com_ui_revoke_info'}
infoDescriptionCode={'com_nav_info_revoke'}
dataTestIdInitial={'revoke-all-keys-initial'}
dataTestIdConfirm={'revoke-all-keys-confirm'}
mutation={all ? revokeKeysMutation : revokeKeyMutation}

View file

@ -9,7 +9,7 @@ export default function SharedLinks() {
return (
<div className="flex items-center justify-between">
<div> {localize('com_nav_shared_links')} </div>
<div>{localize('com_nav_shared_links')}</div>
<Dialog>
<DialogTrigger asChild>

View file

@ -1,6 +1,6 @@
import { useLocalize } from '~/hooks';
import { Dialog, DialogTrigger } from '~/components/ui';
import DialogTemplate from '~/components/ui/DialogTemplate';
import { Dialog, DialogTrigger } from '~/components/ui';
import ArchivedChatsTable from './ArchivedChatsTable';
@ -9,8 +9,7 @@ export default function ArchivedChats() {
return (
<div className="flex items-center justify-between">
<div> {localize('com_nav_archived_chats')} </div>
<div>{localize('com_nav_archived_chats')}</div>
<Dialog>
<DialogTrigger asChild>
<button className="btn btn-neutral relative ">

View file

@ -28,12 +28,13 @@ export const ThemeSelector = ({
return (
<div className="flex items-center justify-between">
<div> {localize('com_nav_theme')} </div>
<div>{localize('com_nav_theme')}</div>
<Dropdown
value={theme}
onChange={onChange}
options={themeOptions}
width={220}
width={180}
position={'left'}
maxHeight="200px"
testId="theme-selector"
@ -64,6 +65,7 @@ export const ClearChatsButton = ({
confirmActionTextCode="com_nav_confirm_clear"
dataTestIdInitial="clear-convos-initial"
dataTestIdConfirm="clear-convos-confirm"
infoDescriptionCode="com_nav_info_clear_all_chats"
onClick={onClick}
/>
);
@ -104,7 +106,8 @@ export const LangSelector = ({
return (
<div className="flex items-center justify-between">
<div> {localize('com_nav_language')} </div>
<div>{localize('com_nav_language')}</div>
<Dropdown
value={langcode}
onChange={onChange}
@ -153,7 +156,7 @@ function General() {
className="w-full md:min-h-[271px]"
ref={contentRef}
>
<div className="flex flex-col gap-3 text-sm text-gray-600 dark:text-gray-50">
<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">
<ThemeSelector theme={theme} onChange={changeTheme} />
</div>

View file

@ -20,7 +20,8 @@ export default function HideSidePanelSwitch({
return (
<div className="flex items-center justify-between">
<div> {localize('com_nav_hide_panel')} </div>
<div>{localize('com_nav_hide_panel')}</div>
<Switch
id="hideSidePanel"
checked={hideSidePanel}

View file

@ -0,0 +1,25 @@
import React from 'react';
import { HoverCard, HoverCardTrigger, HoverCardPortal, HoverCardContent } from '~/components/ui';
import { CircleHelpIcon } from '~/components/svg';
import { useLocalize } from '~/hooks';
const HoverCardSettings = ({ side, text }) => {
const localize = useLocalize();
return (
<HoverCard openDelay={500}>
<HoverCardTrigger>
<CircleHelpIcon className="h-5 w-5 text-gray-500" />{' '}
</HoverCardTrigger>
<HoverCardPortal>
<HoverCardContent side={side} className="z-[999] w-80">
<div className="space-y-2">
<p className="text-sm text-gray-600 dark:text-gray-300">{localize(text)}</p>
</div>
</HoverCardContent>
</HoverCardPortal>
</HoverCard>
);
};
export default HoverCardSettings;

View file

@ -1,4 +1,5 @@
import { useRecoilState } from 'recoil';
import HoverCardSettings from '../HoverCardSettings';
import { Switch } from '~/components/ui/Switch';
import useLocalize from '~/hooks/useLocalize';
import store from '~/store';
@ -20,7 +21,10 @@ export default function SendMessageKeyEnter({
return (
<div className="flex items-center justify-between">
<div> {localize('com_nav_enter_to_send')} </div>
<div className="flex items-center space-x-2">
<div>{localize('com_nav_enter_to_send')}</div>
<HoverCardSettings side="bottom" text="com_nav_info_enter_to_send" />
</div>
<Switch
id="enterToSend"
checked={enterToSend}

View file

@ -1,4 +1,5 @@
import { useRecoilState } from 'recoil';
import HoverCardSettings from '../HoverCardSettings';
import { ForkOptions } from 'librechat-data-provider';
import { Dropdown, Switch } from '~/components/ui';
import { useLocalize } from '~/hooks';
@ -20,12 +21,14 @@ export const ForkSettings = () => {
<>
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-600">
<div className="flex items-center justify-between">
<div> {localize('com_ui_fork_change_default')} </div>
<div className="flex items-center space-x-2">
<div>{localize('com_ui_fork_change_default')}</div>
<HoverCardSettings side="bottom" text="com_nav_info_fork_change_default" />
</div>
<Dropdown
value={forkSetting}
onChange={setForkSetting}
options={forkOptions}
width={200}
position={'left'}
maxHeight="199px"
testId="fork-setting-dropdown"
@ -46,7 +49,10 @@ export const ForkSettings = () => {
</div>
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-600">
<div className="flex items-center justify-between">
<div> {localize('com_ui_fork_split_target_setting')} </div>
<div className="flex items-center space-x-2">
<div>{localize('com_ui_fork_split_target_setting')}</div>
<HoverCardSettings side="bottom" text="com_nav_info_fork_split_target_setting" />
</div>
<Switch
id="splitAtTarget"
checked={splitAtTarget}

View file

@ -9,7 +9,7 @@ import SaveDraft from './SaveDraft';
function Messages() {
return (
<Tabs.Content value={SettingsTabValues.MESSAGES} role="tabpanel" className="md: w-full">
<div className="flex flex-col gap-3 text-sm text-gray-600 dark:text-gray-50">
<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">
<SendMessageKeyEnter />
</div>

View file

@ -1,5 +1,6 @@
import { useRecoilState } from 'recoil';
import { Switch } from '~/components/ui/Switch';
import HoverCardSettings from '../HoverCardSettings';
import { Switch } from '~/components/ui';
import useLocalize from '~/hooks/useLocalize';
import store from '~/store';
@ -20,7 +21,10 @@ export default function SaveDraft({
return (
<div className="flex items-center justify-between">
<div>{localize('com_nav_save_drafts')}</div>
<div className="flex items-center space-x-2">
<div>{localize('com_nav_save_drafts')}</div>
<HoverCardSettings side="bottom" text="com_nav_info_save_draft" />
</div>
<Switch
id="saveDrafts"
checked={saveDrafts}

View file

@ -1,4 +1,5 @@
import { useRecoilState } from 'recoil';
import HoverCardSettings from '../HoverCardSettings';
import { Switch } from '~/components/ui';
import { useLocalize } from '~/hooks';
import store from '~/store';

View file

@ -10,7 +10,7 @@ export default function ConversationModeSwitch({
}) {
const localize = useLocalize();
const [conversationMode, setConversationMode] = useRecoilState<boolean>(store.conversationMode);
const [advancedMode, setAdvancedMode] = useRecoilState<boolean>(store.advancedMode);
const [advancedMode] = useRecoilState<boolean>(store.advancedMode);
const [textToSpeech] = useRecoilState<boolean>(store.TextToSpeech);
const [, setAutoSendText] = useRecoilState<boolean>(store.autoSendText);
const [, setDecibelValue] = useRecoilState(store.decibelValue);
@ -34,13 +34,6 @@ export default function ConversationModeSwitch({
<strong>{localize('com_nav_conversation_mode')}</strong>
</div>
<div className="flex items-center justify-between">
<label
className="flex h-auto cursor-pointer items-center rounded border border-gray-500/70 bg-transparent px-2 py-1 text-xs font-medium font-normal transition-colors hover:border-gray-500/95 hover:bg-gray-100 hover:text-green-700 dark:bg-transparent dark:text-white dark:hover:bg-gray-600 dark:hover:text-green-500"
onClick={() => setAdvancedMode(!advancedMode)}
>
<span>{advancedMode ? 'Advanced Mode' : 'Simple Mode'}</span>
</label>
<div className="w-2" />
<Switch
id="ConversationMode"
checked={conversationMode}

View file

@ -22,7 +22,7 @@ export default function EngineSTTDropdown() {
value={endpointSTT}
onChange={handleSelect}
options={endpointOptions}
width={220}
width={180}
position={'left'}
testId="EngineSTTDropdown"
/>

View file

@ -2,8 +2,10 @@ import * as Tabs from '@radix-ui/react-tabs';
import { SettingsTabValues } from 'librechat-data-provider';
import React, { useState, useRef } from 'react';
import { useRecoilState } from 'recoil';
import { useOnClickOutside } from '~/hooks';
import { Lightbulb, Cog } from 'lucide-react';
import { useOnClickOutside, useMediaQuery } from '~/hooks';
import store from '~/store';
import { cn } from '~/utils';
import ConversationModeSwitch from './ConversationModeSwitch';
import {
TextToSpeechSwitch,
@ -22,9 +24,10 @@ import {
} from './STT';
function Speech() {
const [confirmClear, setConfirmClear] = useState(false);
const [advancedMode] = useRecoilState<boolean>(store.advancedMode);
const isSmallScreen = useMediaQuery('(max-width: 767px)');
const [advancedMode, setAdvancedMode] = useRecoilState<boolean>(store.advancedMode);
const [autoTranscribeAudio] = useRecoilState<boolean>(store.autoTranscribeAudio);
const [confirmClear, setConfirmClear] = useState(false);
const contentRef = useRef(null);
useOnClickOutside(contentRef, () => confirmClear && setConfirmClear(false), []);
@ -36,56 +39,119 @@ function Speech() {
className="w-full px-4 md:min-h-[300px]"
ref={contentRef}
>
<div className="flex flex-col gap-3 text-sm text-gray-600 dark:text-gray-50">
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
<ConversationModeSwitch />
<Tabs.Root
defaultValue={'simple'}
orientation="horizontal"
value={advancedMode ? 'advanced' : 'simple'}
>
<div className="sticky top-0 z-50 bg-white dark:bg-gray-700">
<Tabs.List className="sticky top-0 mb-4 flex justify-center bg-white dark:bg-gray-700">
<Tabs.Trigger
onClick={() => setAdvancedMode(false)}
className={cn(
'group m-1 flex items-center justify-center gap-2 rounded-md px-4 py-2 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',
isSmallScreen
? 'flex-row items-center justify-center text-sm text-gray-700 radix-state-active:bg-gray-100 radix-state-active:text-black dark:text-gray-300 dark:radix-state-active:text-white'
: 'bg-white radix-state-active:bg-gray-100 dark:bg-gray-700',
'w-full',
)}
value="simple"
style={{ userSelect: 'none' }}
>
<Lightbulb />
Simple
</Tabs.Trigger>
<Tabs.Trigger
onClick={() => setAdvancedMode(true)}
className={cn(
'group m-1 flex items-center justify-center gap-2 rounded-md px-4 py-2 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',
isSmallScreen
? 'flex-row items-center justify-center text-sm text-gray-700 radix-state-active:bg-gray-100 radix-state-active:text-black dark:text-gray-300 dark:radix-state-active:text-white'
: 'bg-white radix-state-active:bg-gray-100 dark:bg-gray-700',
'w-full',
)}
value="advanced"
style={{ userSelect: 'none' }}
>
<Cog />
Advanced
</Tabs.Trigger>
</Tabs.List>
</div>
<div className="h-px bg-black/20 bg-white/20" role="none" />
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
<SpeechToTextSwitch />
</div>
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
<EngineSTTDropdown />
</div>
{advancedMode && (
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
<AutoTranscribeAudioSwitch />
<Tabs.Content value={'simple'}>
<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-700">
<ConversationModeSwitch />
</div>
<div className="h-px bg-black/20 bg-white/20" role="none" />
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
<SpeechToTextSwitch />
</div>
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
<EngineSTTDropdown />
</div>
<div className="h-px bg-black/20 bg-white/20" role="none" />
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
<TextToSpeechSwitch />
</div>
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
<AutomaticPlayback />
</div>
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
<EngineTTSDropdown />
</div>
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
<VoiceDropdown />
</div>
</div>
)}
{autoTranscribeAudio && advancedMode && (
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
<DecibelSelector />
</Tabs.Content>
<Tabs.Content value={'advanced'}>
<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-700">
<ConversationModeSwitch />
</div>
<div className="h-px bg-black/20 bg-white/20" role="none" />
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
<SpeechToTextSwitch />
</div>
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
<EngineSTTDropdown />
</div>
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
<AutoTranscribeAudioSwitch />
</div>
{autoTranscribeAudio && (
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
<DecibelSelector />
</div>
)}
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
<AutoSendTextSwitch />
</div>
<div className="h-px bg-black/20 bg-white/20" role="none" />
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
<TextToSpeechSwitch />
</div>
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
<AutomaticPlayback />
</div>
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
<EngineTTSDropdown />
</div>
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
<VoiceDropdown />
</div>
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
<PlaybackRate />
</div>
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
<CacheTTSSwitch />
</div>
</div>
)}
{advancedMode && (
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
<AutoSendTextSwitch />
</div>
)}
<div className="h-px bg-black/20 bg-white/20" role="none" />
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
<TextToSpeechSwitch />
</div>
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
<AutomaticPlayback />
</div>
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
<EngineTTSDropdown />
</div>
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
<VoiceDropdown />
</div>
{advancedMode && (
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
<PlaybackRate />
</div>
)}
{advancedMode && (
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
<CacheTTSSwitch />
</div>
)}
</div>
</Tabs.Content>
</Tabs.Root>
</Tabs.Content>
);
}

View file

@ -22,7 +22,7 @@ export default function EngineTTSDropdown() {
value={endpointTTS}
onChange={handleSelect}
options={endpointOptions}
width={220}
width={180}
position={'left'}
testId="EngineTTSDropdown"
/>

View file

@ -28,7 +28,6 @@ export default function VoiceDropdown() {
value={voice}
onChange={(value: string) => setVoice(value)}
options={voiceOptions}
width={220}
position={'left'}
testId="VoiceDropdown"
/>