mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-02-12 12:34:24 +01:00
♿️ fix: Accessibility, UI consistency, dialog & avatar refactors (#9975)
* 🔧 refactor: Improve accessibility and styling in ChatGroupItem and FilterPrompts components * 🔧 fix: Add button type and keyboard accessibility to dropdown menu trigger in ChatGroupItem * 🔧 fix(757): Enhance accessibility by updating aria-labels and adding localization for prompt groups * 🔧 fix(618): Update version to 0.3.1 and enhance accessibility in InfoHoverCard component * 🔧 fix(618): Update aria-label in InfoHoverCard to use dynamic text prop for improved accessibility * 🔧 fix: Enhance accessibility by updating aria-labels and roles in Conversations components * 🔧 fix(620): Enhance accessibility by adding tabIndex to Tabs.Content components in ArtifactTabs, Settings, and Speech components * refactor: remove RevokeKeysButton component and update related components for accessibility - Deleted RevokeKeysButton component. - Updated SharedLinks and General components to use Label for accessibility. - Enhanced Personalization component with aria-labelledby and aria-describedby attributes. - Refactored ConversationModeSwitch to use ToggleSwitch for better state management. - Improved AutoSendTextSelector with local state management and accessibility attributes. - Replaced Switch components with ToggleSwitch in various Speech and TTS components for consistency. - Added aria-labelledby attributes to Dropdown components for better accessibility. - Updated translation.json to include new localization keys and improved existing ones. - Enhanced Slider component to support aria attributes for better accessibility. * 🔧 fix: Enhance user feedback for API key operations with success and error messages * 🔧 fix: Update aria-labels in Avatar component for improved localization and accessibility * 🔧 fix: Refactor handleFile and handleDrop functions for improved readability and maintainability
This commit is contained in:
parent
bcd97aad2f
commit
a5189052ec
56 changed files with 1158 additions and 857 deletions
|
|
@ -1,6 +1,5 @@
|
|||
import { Switch } from '@librechat/client';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import ToggleSwitch from '../ToggleSwitch';
|
||||
import store from '~/store';
|
||||
|
||||
export default function ConversationModeSwitch({
|
||||
|
|
@ -8,8 +7,6 @@ export default function ConversationModeSwitch({
|
|||
}: {
|
||||
onCheckedChange?: (value: boolean) => void;
|
||||
}) {
|
||||
const localize = useLocalize();
|
||||
const [conversationMode, setConversationMode] = useRecoilState<boolean>(store.conversationMode);
|
||||
const speechToText = useRecoilValue(store.speechToText);
|
||||
const textToSpeech = useRecoilValue(store.textToSpeech);
|
||||
const [, setAutoSendText] = useRecoilState(store.autoSendText);
|
||||
|
|
@ -20,27 +17,19 @@ export default function ConversationModeSwitch({
|
|||
setAutoTranscribeAudio(value);
|
||||
setAutoSendText(3);
|
||||
setDecibelValue(-45);
|
||||
setConversationMode(value);
|
||||
if (onCheckedChange) {
|
||||
onCheckedChange(value);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<strong>{localize('com_nav_conversation_mode')}</strong>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<Switch
|
||||
id="ConversationMode"
|
||||
checked={conversationMode}
|
||||
onCheckedChange={handleCheckedChange}
|
||||
className="ml-4"
|
||||
data-testid="ConversationMode"
|
||||
disabled={!textToSpeech || !speechToText}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<ToggleSwitch
|
||||
stateAtom={store.conversationMode}
|
||||
localizationKey={'com_nav_conversation_mode' as const}
|
||||
switchId="ConversationMode"
|
||||
onCheckedChange={handleCheckedChange}
|
||||
disabled={!textToSpeech || !speechToText}
|
||||
strongLabel={true}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { Slider, InputNumber } from '@librechat/client';
|
||||
import { Slider, InputNumber, Switch } from '@librechat/client';
|
||||
import { cn, defaultTextProps, optionText } from '~/utils/';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import store from '~/store';
|
||||
|
|
@ -11,40 +11,104 @@ export default function AutoSendTextSelector() {
|
|||
const speechToText = useRecoilValue(store.speechToText);
|
||||
const [autoSendText, setAutoSendText] = useRecoilState(store.autoSendText);
|
||||
|
||||
// Local state for enabled/disabled toggle
|
||||
const [isEnabled, setIsEnabled] = useState(autoSendText !== -1);
|
||||
const [delayValue, setDelayValue] = useState(autoSendText === -1 ? 3 : autoSendText);
|
||||
|
||||
// Sync local state when autoSendText changes externally
|
||||
useEffect(() => {
|
||||
setIsEnabled(autoSendText !== -1);
|
||||
if (autoSendText !== -1) {
|
||||
setDelayValue(autoSendText);
|
||||
}
|
||||
}, [autoSendText]);
|
||||
|
||||
const handleToggle = (enabled: boolean) => {
|
||||
setIsEnabled(enabled);
|
||||
if (enabled) {
|
||||
setAutoSendText(delayValue);
|
||||
} else {
|
||||
setAutoSendText(-1);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSliderChange = (value: number[]) => {
|
||||
const newValue = value[0];
|
||||
setDelayValue(newValue);
|
||||
if (isEnabled) {
|
||||
setAutoSendText(newValue);
|
||||
}
|
||||
};
|
||||
|
||||
const handleInputChange = (value: number[] | null) => {
|
||||
const newValue = value ? value[0] : 3;
|
||||
setDelayValue(newValue);
|
||||
if (isEnabled) {
|
||||
setAutoSendText(newValue);
|
||||
}
|
||||
};
|
||||
|
||||
const labelId = 'auto-send-text-label';
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>{localize('com_nav_auto_send_text')}</div>
|
||||
<div className="w-2" />
|
||||
<small className="opacity-40">({localize('com_nav_auto_send_text_disabled')})</small>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<Slider
|
||||
value={[autoSendText ?? -1]}
|
||||
onValueChange={(value) => setAutoSendText(value[0])}
|
||||
onDoubleClick={() => setAutoSendText(-1)}
|
||||
min={-1}
|
||||
max={60}
|
||||
step={1}
|
||||
className="ml-4 flex h-4 w-24"
|
||||
<div className="flex items-center space-x-2">
|
||||
<div id={labelId}>{localize('com_nav_auto_send_text')}</div>
|
||||
</div>
|
||||
<Switch
|
||||
id="autoSendTextToggle"
|
||||
checked={isEnabled}
|
||||
onCheckedChange={handleToggle}
|
||||
className="ml-4"
|
||||
data-testid="autoSendTextToggle"
|
||||
aria-labelledby={labelId}
|
||||
disabled={!speechToText}
|
||||
/>
|
||||
<div className="w-2" />
|
||||
<InputNumber
|
||||
value={`${autoSendText} s`}
|
||||
disabled={!speechToText}
|
||||
onChange={(value) => setAutoSendText(value ? value[0] : 0)}
|
||||
min={-1}
|
||||
max={60}
|
||||
className={cn(
|
||||
defaultTextProps,
|
||||
cn(
|
||||
optionText,
|
||||
'reset-rc-number-input reset-rc-number-input-text-right h-auto w-12 border-0 group-hover/temp:border-gray-200',
|
||||
),
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
{isEnabled && (
|
||||
<div className="mt-2 flex items-center justify-between">
|
||||
<div className="flex items-center justify-between">
|
||||
<div id="auto-send-delay-label" className="text-sm text-text-secondary">
|
||||
{localize('com_nav_setting_delay')}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<Slider
|
||||
value={[delayValue]}
|
||||
onValueChange={handleSliderChange}
|
||||
onDoubleClick={() => {
|
||||
setDelayValue(3);
|
||||
if (isEnabled) {
|
||||
setAutoSendText(3);
|
||||
}
|
||||
}}
|
||||
min={0}
|
||||
max={60}
|
||||
step={1}
|
||||
className="ml-4 flex h-4 w-24"
|
||||
disabled={!speechToText || !isEnabled}
|
||||
aria-labelledby="auto-send-delay-label"
|
||||
/>
|
||||
<div className="w-2" />
|
||||
<InputNumber
|
||||
value={`${delayValue} s`}
|
||||
disabled={!speechToText || !isEnabled}
|
||||
onChange={handleInputChange}
|
||||
min={0}
|
||||
max={60}
|
||||
aria-labelledby="auto-send-delay-label"
|
||||
className={cn(
|
||||
defaultTextProps,
|
||||
cn(
|
||||
optionText,
|
||||
'reset-rc-number-input reset-rc-number-input-text-right h-auto w-12 border-0 group-hover/temp:border-gray-200',
|
||||
),
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import { Switch } from '@librechat/client';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import ToggleSwitch from '../../ToggleSwitch';
|
||||
import store from '~/store';
|
||||
|
||||
export default function AutoTranscribeAudioSwitch({
|
||||
|
|
@ -8,30 +7,15 @@ export default function AutoTranscribeAudioSwitch({
|
|||
}: {
|
||||
onCheckedChange?: (value: boolean) => void;
|
||||
}) {
|
||||
const localize = useLocalize();
|
||||
const [autoTranscribeAudio, setAutoTranscribeAudio] = useRecoilState<boolean>(
|
||||
store.autoTranscribeAudio,
|
||||
);
|
||||
const speechToText = useRecoilValue(store.speechToText);
|
||||
|
||||
const handleCheckedChange = (value: boolean) => {
|
||||
setAutoTranscribeAudio(value);
|
||||
if (onCheckedChange) {
|
||||
onCheckedChange(value);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
<div>{localize('com_nav_auto_transcribe_audio')}</div>
|
||||
<Switch
|
||||
id="AutoTranscribeAudio"
|
||||
checked={autoTranscribeAudio}
|
||||
onCheckedChange={handleCheckedChange}
|
||||
className="ml-4"
|
||||
data-testid="AutoTranscribeAudio"
|
||||
disabled={!speechToText}
|
||||
/>
|
||||
</div>
|
||||
<ToggleSwitch
|
||||
stateAtom={store.autoTranscribeAudio}
|
||||
localizationKey={'com_nav_auto_transcribe_audio' as const}
|
||||
switchId="AutoTranscribeAudio"
|
||||
onCheckedChange={onCheckedChange}
|
||||
disabled={!speechToText}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ export default function DecibelSelector() {
|
|||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>{localize('com_nav_db_sensitivity')}</div>
|
||||
<div id="decibel-selector-label">{localize('com_nav_db_sensitivity')}</div>
|
||||
<div className="w-2" />
|
||||
<small className="opacity-40">
|
||||
({localize('com_endpoint_default_with_num', { 0: '-45' })})
|
||||
|
|
@ -29,6 +29,7 @@ export default function DecibelSelector() {
|
|||
step={1}
|
||||
className="ml-4 flex h-4 w-24"
|
||||
disabled={!speechToText}
|
||||
aria-labelledby="decibel-selector-label"
|
||||
/>
|
||||
<div className="w-2" />
|
||||
<InputNumber
|
||||
|
|
@ -37,6 +38,7 @@ export default function DecibelSelector() {
|
|||
onChange={(value) => setDecibelValue(value ? value[0] : 0)}
|
||||
min={-100}
|
||||
max={-30}
|
||||
aria-labelledby="decibel-selector-label"
|
||||
className={cn(
|
||||
defaultTextProps,
|
||||
cn(
|
||||
|
|
|
|||
|
|
@ -23,9 +23,11 @@ const EngineSTTDropdown: React.FC<EngineSTTDropdownProps> = ({ external }) => {
|
|||
setEngineSTT(value);
|
||||
};
|
||||
|
||||
const labelId = 'engine-stt-dropdown-label';
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
<div>{localize('com_nav_engine')}</div>
|
||||
<div id={labelId}>{localize('com_nav_engine')}</div>
|
||||
<Dropdown
|
||||
value={engineSTT}
|
||||
onChange={handleSelect}
|
||||
|
|
@ -33,6 +35,7 @@ const EngineSTTDropdown: React.FC<EngineSTTDropdownProps> = ({ external }) => {
|
|||
sizeClasses="w-[180px]"
|
||||
testId="EngineSTTDropdown"
|
||||
className="z-50"
|
||||
aria-labelledby={labelId}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -94,9 +94,11 @@ export default function LanguageSTTDropdown() {
|
|||
setLanguageSTT(value);
|
||||
};
|
||||
|
||||
const labelId = 'language-stt-dropdown-label';
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
<div>{localize('com_nav_language')}</div>
|
||||
<div id={labelId}>{localize('com_nav_language')}</div>
|
||||
<Dropdown
|
||||
value={languageSTT}
|
||||
onChange={handleSelect}
|
||||
|
|
@ -104,6 +106,7 @@ export default function LanguageSTTDropdown() {
|
|||
sizeClasses="[--anchor-max-height:256px]"
|
||||
testId="LanguageSTTDropdown"
|
||||
className="z-50"
|
||||
aria-labelledby={labelId}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
import { useRecoilState } from 'recoil';
|
||||
import { Switch } from '@librechat/client';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import ToggleSwitch from '../../ToggleSwitch';
|
||||
import store from '~/store';
|
||||
|
||||
export default function SpeechToTextSwitch({
|
||||
|
|
@ -8,28 +6,13 @@ export default function SpeechToTextSwitch({
|
|||
}: {
|
||||
onCheckedChange?: (value: boolean) => void;
|
||||
}) {
|
||||
const localize = useLocalize();
|
||||
const [speechToText, setSpeechToText] = useRecoilState<boolean>(store.speechToText);
|
||||
|
||||
const handleCheckedChange = (value: boolean) => {
|
||||
setSpeechToText(value);
|
||||
if (onCheckedChange) {
|
||||
onCheckedChange(value);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<strong>{localize('com_nav_speech_to_text')}</strong>
|
||||
</div>
|
||||
<Switch
|
||||
id="SpeechToText"
|
||||
checked={speechToText}
|
||||
onCheckedChange={handleCheckedChange}
|
||||
className="ml-4"
|
||||
data-testid="SpeechToText"
|
||||
/>
|
||||
</div>
|
||||
<ToggleSwitch
|
||||
stateAtom={store.speechToText}
|
||||
localizationKey={'com_nav_speech_to_text' as const}
|
||||
switchId="SpeechToText"
|
||||
onCheckedChange={onCheckedChange}
|
||||
strongLabel={true}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ import {
|
|||
} from './STT';
|
||||
import ConversationModeSwitch from './ConversationModeSwitch';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import { cn, logger } from '~/utils';
|
||||
import { cn } from '~/utils';
|
||||
import store from '~/store';
|
||||
|
||||
function Speech() {
|
||||
|
|
@ -186,7 +186,7 @@ function Speech() {
|
|||
</Tabs.List>
|
||||
</div>
|
||||
|
||||
<Tabs.Content value={'simple'}>
|
||||
<Tabs.Content value={'simple'} tabIndex={-1}>
|
||||
<div className="flex flex-col gap-3 text-sm text-text-primary">
|
||||
<SpeechToTextSwitch />
|
||||
<EngineSTTDropdown external={sttExternal} />
|
||||
|
|
@ -198,7 +198,7 @@ function Speech() {
|
|||
</div>
|
||||
</Tabs.Content>
|
||||
|
||||
<Tabs.Content value={'advanced'}>
|
||||
<Tabs.Content value={'advanced'} tabIndex={-1}>
|
||||
<div className="flex flex-col gap-3 text-sm text-text-primary">
|
||||
<ConversationModeSwitch />
|
||||
<div className="mt-2 h-px bg-border-medium" role="none" />
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
import { useRecoilState } from 'recoil';
|
||||
import { Switch } from '@librechat/client';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import ToggleSwitch from '../../ToggleSwitch';
|
||||
import store from '~/store';
|
||||
|
||||
export default function AutomaticPlaybackSwitch({
|
||||
|
|
@ -8,26 +6,12 @@ export default function AutomaticPlaybackSwitch({
|
|||
}: {
|
||||
onCheckedChange?: (value: boolean) => void;
|
||||
}) {
|
||||
const localize = useLocalize();
|
||||
const [automaticPlayback, setAutomaticPlayback] = useRecoilState(store.automaticPlayback);
|
||||
|
||||
const handleCheckedChange = (value: boolean) => {
|
||||
setAutomaticPlayback(value);
|
||||
if (onCheckedChange) {
|
||||
onCheckedChange(value);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
<div>{localize('com_nav_automatic_playback')}</div>
|
||||
<Switch
|
||||
id="AutomaticPlayback"
|
||||
checked={automaticPlayback}
|
||||
onCheckedChange={handleCheckedChange}
|
||||
className="ml-4"
|
||||
data-testid="AutomaticPlayback"
|
||||
/>
|
||||
</div>
|
||||
<ToggleSwitch
|
||||
stateAtom={store.automaticPlayback}
|
||||
localizationKey={'com_nav_automatic_playback' as const}
|
||||
switchId="AutomaticPlayback"
|
||||
onCheckedChange={onCheckedChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import { useRecoilState } from 'recoil';
|
||||
import { Switch } from '@librechat/client';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import ToggleSwitch from '../../ToggleSwitch';
|
||||
import store from '~/store';
|
||||
|
||||
export default function CacheTTSSwitch({
|
||||
|
|
@ -8,28 +7,15 @@ export default function CacheTTSSwitch({
|
|||
}: {
|
||||
onCheckedChange?: (value: boolean) => void;
|
||||
}) {
|
||||
const localize = useLocalize();
|
||||
const [cacheTTS, setCacheTTS] = useRecoilState<boolean>(store.cacheTTS);
|
||||
const [textToSpeech] = useRecoilState<boolean>(store.textToSpeech);
|
||||
|
||||
const handleCheckedChange = (value: boolean) => {
|
||||
setCacheTTS(value);
|
||||
if (onCheckedChange) {
|
||||
onCheckedChange(value);
|
||||
}
|
||||
};
|
||||
const textToSpeech = useRecoilValue(store.textToSpeech);
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
<div>{localize('com_nav_enable_cache_tts')}</div>
|
||||
<Switch
|
||||
id="CacheTTS"
|
||||
checked={cacheTTS}
|
||||
onCheckedChange={handleCheckedChange}
|
||||
className="ml-4"
|
||||
data-testid="CacheTTS"
|
||||
disabled={!textToSpeech}
|
||||
/>
|
||||
</div>
|
||||
<ToggleSwitch
|
||||
stateAtom={store.cacheTTS}
|
||||
localizationKey={'com_nav_enable_cache_tts' as const}
|
||||
switchId="CacheTTS"
|
||||
onCheckedChange={onCheckedChange}
|
||||
disabled={!textToSpeech}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import { useRecoilState } from 'recoil';
|
||||
import { Switch } from '@librechat/client';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import ToggleSwitch from '../../ToggleSwitch';
|
||||
import store from '~/store';
|
||||
|
||||
export default function CloudBrowserVoicesSwitch({
|
||||
|
|
@ -8,30 +7,15 @@ export default function CloudBrowserVoicesSwitch({
|
|||
}: {
|
||||
onCheckedChange?: (value: boolean) => void;
|
||||
}) {
|
||||
const localize = useLocalize();
|
||||
const [cloudBrowserVoices, setCloudBrowserVoices] = useRecoilState<boolean>(
|
||||
store.cloudBrowserVoices,
|
||||
);
|
||||
const [textToSpeech] = useRecoilState<boolean>(store.textToSpeech);
|
||||
|
||||
const handleCheckedChange = (value: boolean) => {
|
||||
setCloudBrowserVoices(value);
|
||||
if (onCheckedChange) {
|
||||
onCheckedChange(value);
|
||||
}
|
||||
};
|
||||
const textToSpeech = useRecoilValue(store.textToSpeech);
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
<div>{localize('com_nav_enable_cloud_browser_voice')}</div>
|
||||
<Switch
|
||||
id="CloudBrowserVoices"
|
||||
checked={cloudBrowserVoices}
|
||||
onCheckedChange={handleCheckedChange}
|
||||
className="ml-4"
|
||||
data-testid="CloudBrowserVoices"
|
||||
disabled={!textToSpeech}
|
||||
/>
|
||||
</div>
|
||||
<ToggleSwitch
|
||||
stateAtom={store.cloudBrowserVoices}
|
||||
localizationKey={'com_nav_enable_cloud_browser_voice' as const}
|
||||
switchId="CloudBrowserVoices"
|
||||
onCheckedChange={onCheckedChange}
|
||||
disabled={!textToSpeech}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,9 +23,11 @@ const EngineTTSDropdown: React.FC<EngineTTSDropdownProps> = ({ external }) => {
|
|||
setEngineTTS(value);
|
||||
};
|
||||
|
||||
const labelId = 'engine-tts-dropdown-label';
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
<div>{localize('com_nav_engine')}</div>
|
||||
<div id={labelId}>{localize('com_nav_engine')}</div>
|
||||
<Dropdown
|
||||
value={engineTTS}
|
||||
onChange={handleSelect}
|
||||
|
|
@ -33,6 +35,7 @@ const EngineTTSDropdown: React.FC<EngineTTSDropdownProps> = ({ external }) => {
|
|||
sizeClasses="w-[180px]"
|
||||
testId="EngineTTSDropdown"
|
||||
className="z-50"
|
||||
aria-labelledby={labelId}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ export default function DecibelSelector() {
|
|||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>{localize('com_nav_playback_rate')}</div>
|
||||
<div id="playback-rate-label">{localize('com_nav_playback_rate')}</div>
|
||||
<div className="w-2" />
|
||||
<small className="opacity-40">
|
||||
({localize('com_endpoint_default_with_num', { 0: '1' })})
|
||||
|
|
@ -29,6 +29,7 @@ export default function DecibelSelector() {
|
|||
step={0.1}
|
||||
className="ml-4 flex h-4 w-24"
|
||||
disabled={!textToSpeech}
|
||||
aria-labelledby="playback-rate-label"
|
||||
/>
|
||||
<div className="w-2" />
|
||||
<InputNumber
|
||||
|
|
@ -37,6 +38,7 @@ export default function DecibelSelector() {
|
|||
onChange={(value) => setPlaybackRate(value ? value[0] : 0)}
|
||||
min={0.1}
|
||||
max={2}
|
||||
aria-labelledby="playback-rate-label"
|
||||
className={cn(
|
||||
defaultTextProps,
|
||||
cn(
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
import { useRecoilState } from 'recoil';
|
||||
import { Switch } from '@librechat/client';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import ToggleSwitch from '../../ToggleSwitch';
|
||||
import store from '~/store';
|
||||
|
||||
export default function TextToSpeechSwitch({
|
||||
|
|
@ -8,28 +6,13 @@ export default function TextToSpeechSwitch({
|
|||
}: {
|
||||
onCheckedChange?: (value: boolean) => void;
|
||||
}) {
|
||||
const localize = useLocalize();
|
||||
const [TextToSpeech, setTextToSpeech] = useRecoilState<boolean>(store.textToSpeech);
|
||||
|
||||
const handleCheckedChange = (value: boolean) => {
|
||||
setTextToSpeech(value);
|
||||
if (onCheckedChange) {
|
||||
onCheckedChange(value);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<strong>{localize('com_nav_text_to_speech')}</strong>
|
||||
</div>
|
||||
<Switch
|
||||
id="TextToSpeech"
|
||||
checked={TextToSpeech}
|
||||
onCheckedChange={handleCheckedChange}
|
||||
className="ml-4"
|
||||
data-testid="TextToSpeech"
|
||||
/>
|
||||
</div>
|
||||
<ToggleSwitch
|
||||
stateAtom={store.textToSpeech}
|
||||
localizationKey={'com_nav_text_to_speech' as const}
|
||||
switchId="TextToSpeech"
|
||||
onCheckedChange={onCheckedChange}
|
||||
strongLabel={true}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue