mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-01-25 03:36:12 +01:00
🎛️ fix: Improve Frontend Practices for Audio Settings (#3624)
* refactor: do not call await inside useCallbacks, rely on updates for dropdown * fix: remember last selected voice * refactor: Update Speech component to use TypeScript in useCallback * refactor: Update Dropdown component styles to match header theme
This commit is contained in:
parent
8cbb6ba166
commit
05696233a9
20 changed files with 436 additions and 367 deletions
|
|
@ -25,11 +25,11 @@ export default function ExportModal({
|
|||
const [recursive, setRecursive] = useState<boolean | 'indeterminate'>(true);
|
||||
|
||||
const typeOptions = [
|
||||
{ value: 'screenshot', display: 'screenshot (.png)' },
|
||||
{ value: 'text', display: 'text (.txt)' },
|
||||
{ value: 'markdown', display: 'markdown (.md)' },
|
||||
{ value: 'json', display: 'json (.json)' },
|
||||
{ value: 'csv', display: 'csv (.csv)' },
|
||||
{ value: 'screenshot', label: 'screenshot (.png)' },
|
||||
{ value: 'text', label: 'text (.txt)' },
|
||||
{ value: 'markdown', label: 'markdown (.md)' },
|
||||
{ value: 'json', label: 'json (.json)' },
|
||||
{ value: 'csv', label: 'csv (.csv)' },
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
|||
|
|
@ -14,11 +14,11 @@ export default function FontSizeSelector() {
|
|||
};
|
||||
|
||||
const options = [
|
||||
{ value: 'text-xs', display: localize('com_nav_font_size_xs') },
|
||||
{ value: 'text-sm', display: localize('com_nav_font_size_sm') },
|
||||
{ value: 'text-base', display: localize('com_nav_font_size_base') },
|
||||
{ value: 'text-lg', display: localize('com_nav_font_size_lg') },
|
||||
{ value: 'text-xl', display: localize('com_nav_font_size_xl') },
|
||||
{ value: 'text-xs', label: localize('com_nav_font_size_xs') },
|
||||
{ value: 'text-sm', label: localize('com_nav_font_size_sm') },
|
||||
{ value: 'text-base', label: localize('com_nav_font_size_base') },
|
||||
{ value: 'text-lg', label: localize('com_nav_font_size_lg') },
|
||||
{ value: 'text-xl', label: localize('com_nav_font_size_xl') },
|
||||
];
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -12,9 +12,9 @@ export const ForkSettings = () => {
|
|||
const [remember, setRemember] = useRecoilState<boolean>(store.rememberForkOption);
|
||||
|
||||
const forkOptions = [
|
||||
{ value: ForkOptions.DIRECT_PATH, display: localize('com_ui_fork_visible') },
|
||||
{ value: ForkOptions.INCLUDE_BRANCHES, display: localize('com_ui_fork_branches') },
|
||||
{ value: ForkOptions.TARGET_LEVEL, display: localize('com_ui_fork_all_target') },
|
||||
{ value: ForkOptions.DIRECT_PATH, label: localize('com_ui_fork_visible') },
|
||||
{ value: ForkOptions.INCLUDE_BRANCHES, label: localize('com_ui_fork_branches') },
|
||||
{ value: ForkOptions.TARGET_LEVEL, label: localize('com_ui_fork_all_target') },
|
||||
];
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -21,9 +21,9 @@ export const ThemeSelector = ({
|
|||
const localize = useLocalize();
|
||||
|
||||
const themeOptions = [
|
||||
{ value: 'system', display: localize('com_nav_theme_system') },
|
||||
{ value: 'dark', display: localize('com_nav_theme_dark') },
|
||||
{ value: 'light', display: localize('com_nav_theme_light') },
|
||||
{ value: 'system', label: localize('com_nav_theme_system') },
|
||||
{ value: 'dark', label: localize('com_nav_theme_dark') },
|
||||
{ value: 'light', label: localize('com_nav_theme_light') },
|
||||
];
|
||||
|
||||
return (
|
||||
|
|
@ -81,27 +81,27 @@ export const LangSelector = ({
|
|||
|
||||
// Create an array of options for the Dropdown
|
||||
const languageOptions = [
|
||||
{ value: 'auto', display: localize('com_nav_lang_auto') },
|
||||
{ value: 'en-US', display: localize('com_nav_lang_english') },
|
||||
{ value: 'zh-CN', display: localize('com_nav_lang_chinese') },
|
||||
{ value: 'zh-TW', display: localize('com_nav_lang_traditionalchinese') },
|
||||
{ value: 'ar-EG', display: localize('com_nav_lang_arabic') },
|
||||
{ value: 'de-DE', display: localize('com_nav_lang_german') },
|
||||
{ value: 'es-ES', display: localize('com_nav_lang_spanish') },
|
||||
{ value: 'fr-FR', display: localize('com_nav_lang_french') },
|
||||
{ value: 'it-IT', display: localize('com_nav_lang_italian') },
|
||||
{ value: 'pl-PL', display: localize('com_nav_lang_polish') },
|
||||
{ value: 'pt-BR', display: localize('com_nav_lang_brazilian_portuguese') },
|
||||
{ value: 'ru-RU', display: localize('com_nav_lang_russian') },
|
||||
{ value: 'ja-JP', display: localize('com_nav_lang_japanese') },
|
||||
{ value: 'sv-SE', display: localize('com_nav_lang_swedish') },
|
||||
{ value: 'ko-KR', display: localize('com_nav_lang_korean') },
|
||||
{ value: 'vi-VN', display: localize('com_nav_lang_vietnamese') },
|
||||
{ value: 'tr-TR', display: localize('com_nav_lang_turkish') },
|
||||
{ value: 'nl-NL', display: localize('com_nav_lang_dutch') },
|
||||
{ value: 'id-ID', display: localize('com_nav_lang_indonesia') },
|
||||
{ value: 'he-HE', display: localize('com_nav_lang_hebrew') },
|
||||
{ value: 'fi-FI', display: localize('com_nav_lang_finnish') },
|
||||
{ value: 'auto', label: localize('com_nav_lang_auto') },
|
||||
{ value: 'en-US', label: localize('com_nav_lang_english') },
|
||||
{ value: 'zh-CN', label: localize('com_nav_lang_chinese') },
|
||||
{ value: 'zh-TW', label: localize('com_nav_lang_traditionalchinese') },
|
||||
{ value: 'ar-EG', label: localize('com_nav_lang_arabic') },
|
||||
{ value: 'de-DE', label: localize('com_nav_lang_german') },
|
||||
{ value: 'es-ES', label: localize('com_nav_lang_spanish') },
|
||||
{ value: 'fr-FR', label: localize('com_nav_lang_french') },
|
||||
{ value: 'it-IT', label: localize('com_nav_lang_italian') },
|
||||
{ value: 'pl-PL', label: localize('com_nav_lang_polish') },
|
||||
{ value: 'pt-BR', label: localize('com_nav_lang_brazilian_portuguese') },
|
||||
{ value: 'ru-RU', label: localize('com_nav_lang_russian') },
|
||||
{ value: 'ja-JP', label: localize('com_nav_lang_japanese') },
|
||||
{ value: 'sv-SE', label: localize('com_nav_lang_swedish') },
|
||||
{ value: 'ko-KR', label: localize('com_nav_lang_korean') },
|
||||
{ value: 'vi-VN', label: localize('com_nav_lang_vietnamese') },
|
||||
{ value: 'tr-TR', label: localize('com_nav_lang_turkish') },
|
||||
{ value: 'nl-NL', label: localize('com_nav_lang_dutch') },
|
||||
{ value: 'id-ID', label: localize('com_nav_lang_indonesia') },
|
||||
{ value: 'he-HE', label: localize('com_nav_lang_hebrew') },
|
||||
{ value: 'fi-FI', label: localize('com_nav_lang_finnish') },
|
||||
];
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -14,10 +14,10 @@ const EngineSTTDropdown: React.FC<EngineSTTDropdownProps> = ({ external }) => {
|
|||
|
||||
const endpointOptions = external
|
||||
? [
|
||||
{ value: 'browser', display: localize('com_nav_browser') },
|
||||
{ value: 'external', display: localize('com_nav_external') },
|
||||
{ value: 'browser', label: localize('com_nav_browser') },
|
||||
{ value: 'external', label: localize('com_nav_external') },
|
||||
]
|
||||
: [{ value: 'browser', display: localize('com_nav_browser') }];
|
||||
: [{ value: 'browser', label: localize('com_nav_browser') }];
|
||||
|
||||
const handleSelect = (value: string) => {
|
||||
setEngineSTT(value);
|
||||
|
|
|
|||
|
|
@ -8,83 +8,83 @@ export default function LanguageSTTDropdown() {
|
|||
const [languageSTT, setLanguageSTT] = useRecoilState<string>(store.languageSTT);
|
||||
|
||||
const languageOptions = [
|
||||
{ value: 'af', display: 'Afrikaans' },
|
||||
{ value: 'eu', display: 'Basque' },
|
||||
{ value: 'bg', display: 'Bulgarian' },
|
||||
{ value: 'ca', display: 'Catalan' },
|
||||
{ value: 'ar-EG', display: 'Arabic (Egypt)' },
|
||||
{ value: 'ar-JO', display: 'Arabic (Jordan)' },
|
||||
{ value: 'ar-KW', display: 'Arabic (Kuwait)' },
|
||||
{ value: 'ar-LB', display: 'Arabic (Lebanon)' },
|
||||
{ value: 'ar-QA', display: 'Arabic (Qatar)' },
|
||||
{ value: 'ar-AE', display: 'Arabic (UAE)' },
|
||||
{ value: 'ar-MA', display: 'Arabic (Morocco)' },
|
||||
{ value: 'ar-IQ', display: 'Arabic (Iraq)' },
|
||||
{ value: 'ar-DZ', display: 'Arabic (Algeria)' },
|
||||
{ value: 'ar-BH', display: 'Arabic (Bahrain)' },
|
||||
{ value: 'ar-LY', display: 'Arabic (Libya)' },
|
||||
{ value: 'ar-OM', display: 'Arabic (Oman)' },
|
||||
{ value: 'ar-SA', display: 'Arabic (Saudi Arabia)' },
|
||||
{ value: 'ar-TN', display: 'Arabic (Tunisia)' },
|
||||
{ value: 'ar-YE', display: 'Arabic (Yemen)' },
|
||||
{ value: 'cs', display: 'Czech' },
|
||||
{ value: 'nl-NL', display: 'Dutch' },
|
||||
{ value: 'en-AU', display: 'English (Australia)' },
|
||||
{ value: 'en-CA', display: 'English (Canada)' },
|
||||
{ value: 'en-IN', display: 'English (India)' },
|
||||
{ value: 'en-NZ', display: 'English (New Zealand)' },
|
||||
{ value: 'en-ZA', display: 'English (South Africa)' },
|
||||
{ value: 'en-GB', display: 'English (UK)' },
|
||||
{ value: 'en-US', display: 'English (US)' },
|
||||
{ value: 'fi', display: 'Finnish' },
|
||||
{ value: 'fr-FR', display: 'French' },
|
||||
{ value: 'gl', display: 'Galician' },
|
||||
{ value: 'de-DE', display: 'German' },
|
||||
{ value: 'el-GR', display: 'Greek' },
|
||||
{ value: 'he', display: 'Hebrew' },
|
||||
{ value: 'hu', display: 'Hungarian' },
|
||||
{ value: 'is', display: 'Icelandic' },
|
||||
{ value: 'it-IT', display: 'Italian' },
|
||||
{ value: 'id', display: 'Indonesian' },
|
||||
{ value: 'ja', display: 'Japanese' },
|
||||
{ value: 'ko', display: 'Korean' },
|
||||
{ value: 'la', display: 'Latin' },
|
||||
{ value: 'zh-CN', display: 'Mandarin Chinese' },
|
||||
{ value: 'zh-TW', display: 'Taiwanese' },
|
||||
{ value: 'zh-HK', display: 'Cantonese' },
|
||||
{ value: 'ms-MY', display: 'Malaysian' },
|
||||
{ value: 'no-NO', display: 'Norwegian' },
|
||||
{ value: 'pl', display: 'Polish' },
|
||||
{ value: 'xx-piglatin', display: 'Pig Latin' },
|
||||
{ value: 'pt-PT', display: 'Portuguese' },
|
||||
{ value: 'pt-br', display: 'Portuguese (Brasil)' },
|
||||
{ value: 'ro-RO', display: 'Romanian' },
|
||||
{ value: 'ru', display: 'Russian' },
|
||||
{ value: 'sr-SP', display: 'Serbian' },
|
||||
{ value: 'sk', display: 'Slovak' },
|
||||
{ value: 'es-AR', display: 'Spanish (Argentina)' },
|
||||
{ value: 'es-BO', display: 'Spanish (Bolivia)' },
|
||||
{ value: 'es-CL', display: 'Spanish (Chile)' },
|
||||
{ value: 'es-CO', display: 'Spanish (Colombia)' },
|
||||
{ value: 'es-CR', display: 'Spanish (Costa Rica)' },
|
||||
{ value: 'es-DO', display: 'Spanish (Dominican Republic)' },
|
||||
{ value: 'es-EC', display: 'Spanish (Ecuador)' },
|
||||
{ value: 'es-SV', display: 'Spanish (El Salvador)' },
|
||||
{ value: 'es-GT', display: 'Spanish (Guatemala)' },
|
||||
{ value: 'es-HN', display: 'Spanish (Honduras)' },
|
||||
{ value: 'es-MX', display: 'Spanish (Mexico)' },
|
||||
{ value: 'es-NI', display: 'Spanish (Nicaragua)' },
|
||||
{ value: 'es-PA', display: 'Spanish (Panama)' },
|
||||
{ value: 'es-PY', display: 'Spanish (Paraguay)' },
|
||||
{ value: 'es-PE', display: 'Spanish (Peru)' },
|
||||
{ value: 'es-PR', display: 'Spanish (Puerto Rico)' },
|
||||
{ value: 'es-ES', display: 'Spanish (Spain)' },
|
||||
{ value: 'es-US', display: 'Spanish (US)' },
|
||||
{ value: 'es-UY', display: 'Spanish (Uruguay)' },
|
||||
{ value: 'es-VE', display: 'Spanish (Venezuela)' },
|
||||
{ value: 'sv-SE', display: 'Swedish' },
|
||||
{ value: 'tr', display: 'Turkish' },
|
||||
{ value: 'zu', display: 'Zulu' },
|
||||
{ value: 'af', label: 'Afrikaans' },
|
||||
{ value: 'eu', label: 'Basque' },
|
||||
{ value: 'bg', label: 'Bulgarian' },
|
||||
{ value: 'ca', label: 'Catalan' },
|
||||
{ value: 'ar-EG', label: 'Arabic (Egypt)' },
|
||||
{ value: 'ar-JO', label: 'Arabic (Jordan)' },
|
||||
{ value: 'ar-KW', label: 'Arabic (Kuwait)' },
|
||||
{ value: 'ar-LB', label: 'Arabic (Lebanon)' },
|
||||
{ value: 'ar-QA', label: 'Arabic (Qatar)' },
|
||||
{ value: 'ar-AE', label: 'Arabic (UAE)' },
|
||||
{ value: 'ar-MA', label: 'Arabic (Morocco)' },
|
||||
{ value: 'ar-IQ', label: 'Arabic (Iraq)' },
|
||||
{ value: 'ar-DZ', label: 'Arabic (Algeria)' },
|
||||
{ value: 'ar-BH', label: 'Arabic (Bahrain)' },
|
||||
{ value: 'ar-LY', label: 'Arabic (Libya)' },
|
||||
{ value: 'ar-OM', label: 'Arabic (Oman)' },
|
||||
{ value: 'ar-SA', label: 'Arabic (Saudi Arabia)' },
|
||||
{ value: 'ar-TN', label: 'Arabic (Tunisia)' },
|
||||
{ value: 'ar-YE', label: 'Arabic (Yemen)' },
|
||||
{ value: 'cs', label: 'Czech' },
|
||||
{ value: 'nl-NL', label: 'Dutch' },
|
||||
{ value: 'en-AU', label: 'English (Australia)' },
|
||||
{ value: 'en-CA', label: 'English (Canada)' },
|
||||
{ value: 'en-IN', label: 'English (India)' },
|
||||
{ value: 'en-NZ', label: 'English (New Zealand)' },
|
||||
{ value: 'en-ZA', label: 'English (South Africa)' },
|
||||
{ value: 'en-GB', label: 'English (UK)' },
|
||||
{ value: 'en-US', label: 'English (US)' },
|
||||
{ value: 'fi', label: 'Finnish' },
|
||||
{ value: 'fr-FR', label: 'French' },
|
||||
{ value: 'gl', label: 'Galician' },
|
||||
{ value: 'de-DE', label: 'German' },
|
||||
{ value: 'el-GR', label: 'Greek' },
|
||||
{ value: 'he', label: 'Hebrew' },
|
||||
{ value: 'hu', label: 'Hungarian' },
|
||||
{ value: 'is', label: 'Icelandic' },
|
||||
{ value: 'it-IT', label: 'Italian' },
|
||||
{ value: 'id', label: 'Indonesian' },
|
||||
{ value: 'ja', label: 'Japanese' },
|
||||
{ value: 'ko', label: 'Korean' },
|
||||
{ value: 'la', label: 'Latin' },
|
||||
{ value: 'zh-CN', label: 'Mandarin Chinese' },
|
||||
{ value: 'zh-TW', label: 'Taiwanese' },
|
||||
{ value: 'zh-HK', label: 'Cantonese' },
|
||||
{ value: 'ms-MY', label: 'Malaysian' },
|
||||
{ value: 'no-NO', label: 'Norwegian' },
|
||||
{ value: 'pl', label: 'Polish' },
|
||||
{ value: 'xx-piglatin', label: 'Pig Latin' },
|
||||
{ value: 'pt-PT', label: 'Portuguese' },
|
||||
{ value: 'pt-br', label: 'Portuguese (Brasil)' },
|
||||
{ value: 'ro-RO', label: 'Romanian' },
|
||||
{ value: 'ru', label: 'Russian' },
|
||||
{ value: 'sr-SP', label: 'Serbian' },
|
||||
{ value: 'sk', label: 'Slovak' },
|
||||
{ value: 'es-AR', label: 'Spanish (Argentina)' },
|
||||
{ value: 'es-BO', label: 'Spanish (Bolivia)' },
|
||||
{ value: 'es-CL', label: 'Spanish (Chile)' },
|
||||
{ value: 'es-CO', label: 'Spanish (Colombia)' },
|
||||
{ value: 'es-CR', label: 'Spanish (Costa Rica)' },
|
||||
{ value: 'es-DO', label: 'Spanish (Dominican Republic)' },
|
||||
{ value: 'es-EC', label: 'Spanish (Ecuador)' },
|
||||
{ value: 'es-SV', label: 'Spanish (El Salvador)' },
|
||||
{ value: 'es-GT', label: 'Spanish (Guatemala)' },
|
||||
{ value: 'es-HN', label: 'Spanish (Honduras)' },
|
||||
{ value: 'es-MX', label: 'Spanish (Mexico)' },
|
||||
{ value: 'es-NI', label: 'Spanish (Nicaragua)' },
|
||||
{ value: 'es-PA', label: 'Spanish (Panama)' },
|
||||
{ value: 'es-PY', label: 'Spanish (Paraguay)' },
|
||||
{ value: 'es-PE', label: 'Spanish (Peru)' },
|
||||
{ value: 'es-PR', label: 'Spanish (Puerto Rico)' },
|
||||
{ value: 'es-ES', label: 'Spanish (Spain)' },
|
||||
{ value: 'es-US', label: 'Spanish (US)' },
|
||||
{ value: 'es-UY', label: 'Spanish (Uruguay)' },
|
||||
{ value: 'es-VE', label: 'Spanish (Venezuela)' },
|
||||
{ value: 'sv-SE', label: 'Swedish' },
|
||||
{ value: 'tr', label: 'Turkish' },
|
||||
{ value: 'zu', label: 'Zulu' },
|
||||
];
|
||||
|
||||
const handleSelect = (value: string) => {
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ function Speech() {
|
|||
const [decibelValue, setDecibelValue] = useRecoilState(store.decibelValue);
|
||||
const [autoSendText, setAutoSendText] = useRecoilState(store.autoSendText);
|
||||
const [engineTTS, setEngineTTS] = useRecoilState<string>(store.engineTTS);
|
||||
const [voice, setVoice] = useRecoilState<string>(store.voice);
|
||||
const [voice, setVoice] = useRecoilState(store.voice);
|
||||
const [cloudBrowserVoices, setCloudBrowserVoices] = useRecoilState<boolean>(
|
||||
store.cloudBrowserVoices,
|
||||
);
|
||||
|
|
@ -53,7 +53,7 @@ function Speech() {
|
|||
const [playbackRate, setPlaybackRate] = useRecoilState(store.playbackRate);
|
||||
|
||||
const updateSetting = useCallback(
|
||||
(key, newValue) => {
|
||||
(key: string, newValue: string | number) => {
|
||||
const settings = {
|
||||
sttExternal: { value: sttExternal, setFunc: setSttExternal },
|
||||
ttsExternal: { value: ttsExternal, setFunc: setTtsExternal },
|
||||
|
|
|
|||
|
|
@ -14,13 +14,13 @@ const EngineTTSDropdown: React.FC<EngineTTSDropdownProps> = ({ external }) => {
|
|||
|
||||
const endpointOptions = external
|
||||
? [
|
||||
{ value: 'browser', display: localize('com_nav_browser') },
|
||||
{ value: 'edge', display: localize('com_nav_edge') },
|
||||
{ value: 'external', display: localize('com_nav_external') },
|
||||
{ value: 'browser', label: localize('com_nav_browser') },
|
||||
{ value: 'edge', label: localize('com_nav_edge') },
|
||||
{ value: 'external', label: localize('com_nav_external') },
|
||||
]
|
||||
: [
|
||||
{ value: 'browser', display: localize('com_nav_browser') },
|
||||
{ value: 'edge', display: localize('com_nav_edge') },
|
||||
{ value: 'browser', label: localize('com_nav_browser') },
|
||||
{ value: 'edge', label: localize('com_nav_edge') },
|
||||
];
|
||||
|
||||
const handleSelect = (value: string) => {
|
||||
|
|
|
|||
|
|
@ -1,39 +1,33 @@
|
|||
import React, { useEffect, useState, useMemo } from 'react';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import Dropdown from '~/components/ui/DropdownNoState';
|
||||
import React from 'react';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import type { Option } from '~/common';
|
||||
import DropdownNoState from '~/components/ui/DropdownNoState';
|
||||
import { useLocalize, useTextToSpeech } from '~/hooks';
|
||||
import { logger } from '~/utils';
|
||||
import store from '~/store';
|
||||
|
||||
export default function VoiceDropdown() {
|
||||
const localize = useLocalize();
|
||||
const { voices = [] } = useTextToSpeech();
|
||||
const [voice, setVoice] = useRecoilState(store.voice);
|
||||
const { voices } = useTextToSpeech();
|
||||
const [voiceOptions, setVoiceOptions] = useState([]);
|
||||
const [engineTTS] = useRecoilState(store.engineTTS);
|
||||
const engineTTS = useRecoilValue<string>(store.engineTTS);
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchVoices() {
|
||||
const options = await voices();
|
||||
setVoiceOptions(options);
|
||||
|
||||
if (!voice && options.length > 0) {
|
||||
setVoice(options[0]);
|
||||
}
|
||||
const handleVoiceChange = (newValue?: string | Option) => {
|
||||
logger.log('Voice changed:', newValue);
|
||||
const newVoice = typeof newValue === 'string' ? newValue : newValue?.value;
|
||||
if (newVoice != null) {
|
||||
return setVoice(newVoice.toString());
|
||||
}
|
||||
|
||||
fetchVoices();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [engineTTS]);
|
||||
|
||||
const memoizedVoiceOptions = useMemo(() => voiceOptions, [voiceOptions]);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
<div>{localize('com_nav_voice_select')}</div>
|
||||
<Dropdown
|
||||
<DropdownNoState
|
||||
key={`voice-dropdown-${engineTTS}-${voices.length}`}
|
||||
value={voice}
|
||||
onChange={setVoice}
|
||||
options={memoizedVoiceOptions}
|
||||
options={voices}
|
||||
onChange={handleVoiceChange}
|
||||
sizeClasses="min-w-[200px] !max-w-[400px] [--anchor-max-width:400px]"
|
||||
anchor="bottom start"
|
||||
testId="VoiceDropdown"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue