🤖 refactor: Improve Speech Settings Initialization (#7869)

*  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 <danny@librechat.ai>
This commit is contained in:
Marco Beretta 2025-06-12 23:34:04 +02:00 committed by GitHub
parent 55f79bd2d1
commit 46ff008b07
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 65 additions and 10 deletions

View file

@ -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)) {

View file

@ -1,2 +1,3 @@
export { default as useAppStartup } from './useAppStartup';
export { default as useClearStates } from './useClearStates';
export { default as useSpeechSettingsInit } from './useSpeechSettingsInit';

View file

@ -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 ?? '';

View file

@ -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]);
}