From 46ff008b07a5683d9ed262e824fdfe6c7eeabc50 Mon Sep 17 00:00:00 2001 From: Marco Beretta <81851188+berry-13@users.noreply.github.com> Date: Thu, 12 Jun 2025 23:34:04 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=A4=96=20refactor:=20Improve=20Speech=20S?= =?UTF-8?q?ettings=20Initialization=20(#7869)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ✨ feat: Implement speech settings initialization and update settings handling * 🔧 fix: Ensure setters reference is included in useEffect dependencies for speech settings initialization * chore: Update setter reference in useSpeechSettingsInit for improved type safety --------- Co-authored-by: Danny Avila --- .../Nav/SettingsTabs/Speech/Speech.tsx | 21 ++++---- client/src/hooks/Config/index.ts | 1 + client/src/hooks/Config/useAppStartup.ts | 3 ++ .../src/hooks/Config/useSpeechSettingsInit.ts | 50 +++++++++++++++++++ 4 files changed, 65 insertions(+), 10 deletions(-) create mode 100644 client/src/hooks/Config/useSpeechSettingsInit.ts diff --git a/client/src/components/Nav/SettingsTabs/Speech/Speech.tsx b/client/src/components/Nav/SettingsTabs/Speech/Speech.tsx index 0eaf2ffd0c..5fa967284b 100644 --- a/client/src/components/Nav/SettingsTabs/Speech/Speech.tsx +++ b/client/src/components/Nav/SettingsTabs/Speech/Speech.tsx @@ -76,16 +76,10 @@ function Speech() { playbackRate: { value: playbackRate, setFunc: setPlaybackRate }, }; - if ( - (settings[key].value !== newValue || settings[key].value === newValue || !settings[key]) && - settings[key].value === 'sttExternal' && - settings[key].value === 'ttsExternal' - ) { - return; - } - const setting = settings[key]; - setting.setFunc(newValue); + if (setting) { + setting.setFunc(newValue); + } }, [ sttExternal, @@ -130,13 +124,20 @@ function Speech() { useEffect(() => { if (data && data.message !== 'not_found') { Object.entries(data).forEach(([key, value]) => { - updateSetting(key, value); + // Only apply config values as defaults if no user preference exists in localStorage + const existingValue = localStorage.getItem(key); + if (existingValue === null && key !== 'sttExternal' && key !== 'ttsExternal') { + updateSetting(key, value); + } else if (key === 'sttExternal' || key === 'ttsExternal') { + updateSetting(key, value); + } }); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [data]); // Reset engineTTS if it is set to a removed/invalid value (e.g., 'edge') + // TODO: remove this once the 'edge' engine is fully deprecated useEffect(() => { const validEngines = ['browser', 'external']; if (!validEngines.includes(engineTTS)) { diff --git a/client/src/hooks/Config/index.ts b/client/src/hooks/Config/index.ts index 88e4a5716d..64fb1eb4f3 100644 --- a/client/src/hooks/Config/index.ts +++ b/client/src/hooks/Config/index.ts @@ -1,2 +1,3 @@ export { default as useAppStartup } from './useAppStartup'; export { default as useClearStates } from './useClearStates'; +export { default as useSpeechSettingsInit } from './useSpeechSettingsInit'; diff --git a/client/src/hooks/Config/useAppStartup.ts b/client/src/hooks/Config/useAppStartup.ts index 25c16ce959..77e9e7af80 100644 --- a/client/src/hooks/Config/useAppStartup.ts +++ b/client/src/hooks/Config/useAppStartup.ts @@ -5,6 +5,7 @@ import { LocalStorageKeys } from 'librechat-data-provider'; import { useAvailablePluginsQuery } from 'librechat-data-provider/react-query'; import type { TStartupConfig, TPlugin, TUser } from 'librechat-data-provider'; import { mapPlugins, selectPlugins, processPlugins } from '~/utils'; +import useSpeechSettingsInit from './useSpeechSettingsInit'; import store from '~/store'; const pluginStore: TPlugin = { @@ -31,6 +32,8 @@ export default function useAppStartup({ select: selectPlugins, }); + useSpeechSettingsInit(!!user); + /** Set the app title */ useEffect(() => { const appTitle = startupConfig?.appTitle ?? ''; diff --git a/client/src/hooks/Config/useSpeechSettingsInit.ts b/client/src/hooks/Config/useSpeechSettingsInit.ts new file mode 100644 index 0000000000..a6ce69e52b --- /dev/null +++ b/client/src/hooks/Config/useSpeechSettingsInit.ts @@ -0,0 +1,50 @@ +import { useEffect, useRef } from 'react'; +import { useSetRecoilState } from 'recoil'; +import { useGetCustomConfigSpeechQuery } from 'librechat-data-provider/react-query'; +import { logger } from '~/utils'; +import store from '~/store'; + +/** + * Initializes speech-related Recoil values from the server-side custom + * configuration on first load (only when the user is authenticated) + */ +export default function useSpeechSettingsInit(isAuthenticated: boolean) { + const { data } = useGetCustomConfigSpeechQuery({ enabled: isAuthenticated }); + + const setters = useRef({ + conversationMode: useSetRecoilState(store.conversationMode), + advancedMode: useSetRecoilState(store.advancedMode), + speechToText: useSetRecoilState(store.speechToText), + textToSpeech: useSetRecoilState(store.textToSpeech), + cacheTTS: useSetRecoilState(store.cacheTTS), + engineSTT: useSetRecoilState(store.engineSTT), + languageSTT: useSetRecoilState(store.languageSTT), + autoTranscribeAudio: useSetRecoilState(store.autoTranscribeAudio), + decibelValue: useSetRecoilState(store.decibelValue), + autoSendText: useSetRecoilState(store.autoSendText), + engineTTS: useSetRecoilState(store.engineTTS), + voice: useSetRecoilState(store.voice), + cloudBrowserVoices: useSetRecoilState(store.cloudBrowserVoices), + languageTTS: useSetRecoilState(store.languageTTS), + automaticPlayback: useSetRecoilState(store.automaticPlayback), + playbackRate: useSetRecoilState(store.playbackRate), + }).current; + + useEffect(() => { + if (!isAuthenticated || !data || data.message === 'not_found') return; + + logger.log('Initializing speech settings from config:', data); + + Object.entries(data).forEach(([key, value]) => { + if (key === 'sttExternal' || key === 'ttsExternal') return; + + if (localStorage.getItem(key) !== null) return; + + const setter = setters[key as keyof typeof setters]; + if (setter) { + logger.log(`Setting default speech setting: ${key} = ${value}`); + setter(value as any); + } + }); + }, [isAuthenticated, data, setters]); +}