Skip to content

Commit 46ff008

Browse files
🤖 refactor: Improve Speech Settings Initialization (danny-avila#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>
1 parent 55f79bd commit 46ff008

4 files changed

Lines changed: 65 additions & 10 deletions

File tree

‎client/src/components/Nav/SettingsTabs/Speech/Speech.tsx‎

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -76,16 +76,10 @@ function Speech() {
7676
playbackRate: { value: playbackRate, setFunc: setPlaybackRate },
7777
};
7878

79-
if (
80-
(settings[key].value !== newValue || settings[key].value === newValue || !settings[key]) &&
81-
settings[key].value === 'sttExternal' &&
82-
settings[key].value === 'ttsExternal'
83-
) {
84-
return;
85-
}
86-
8779
const setting = settings[key];
88-
setting.setFunc(newValue);
80+
if (setting) {
81+
setting.setFunc(newValue);
82+
}
8983
},
9084
[
9185
sttExternal,
@@ -130,13 +124,20 @@ function Speech() {
130124
useEffect(() => {
131125
if (data && data.message !== 'not_found') {
132126
Object.entries(data).forEach(([key, value]) => {
133-
updateSetting(key, value);
127+
// Only apply config values as defaults if no user preference exists in localStorage
128+
const existingValue = localStorage.getItem(key);
129+
if (existingValue === null && key !== 'sttExternal' && key !== 'ttsExternal') {
130+
updateSetting(key, value);
131+
} else if (key === 'sttExternal' || key === 'ttsExternal') {
132+
updateSetting(key, value);
133+
}
134134
});
135135
}
136136
// eslint-disable-next-line react-hooks/exhaustive-deps
137137
}, [data]);
138138

139139
// Reset engineTTS if it is set to a removed/invalid value (e.g., 'edge')
140+
// TODO: remove this once the 'edge' engine is fully deprecated
140141
useEffect(() => {
141142
const validEngines = ['browser', 'external'];
142143
if (!validEngines.includes(engineTTS)) {
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export { default as useAppStartup } from './useAppStartup';
22
export { default as useClearStates } from './useClearStates';
3+
export { default as useSpeechSettingsInit } from './useSpeechSettingsInit';

‎client/src/hooks/Config/useAppStartup.ts‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { LocalStorageKeys } from 'librechat-data-provider';
55
import { useAvailablePluginsQuery } from 'librechat-data-provider/react-query';
66
import type { TStartupConfig, TPlugin, TUser } from 'librechat-data-provider';
77
import { mapPlugins, selectPlugins, processPlugins } from '~/utils';
8+
import useSpeechSettingsInit from './useSpeechSettingsInit';
89
import store from '~/store';
910

1011
const pluginStore: TPlugin = {
@@ -31,6 +32,8 @@ export default function useAppStartup({
3132
select: selectPlugins,
3233
});
3334

35+
useSpeechSettingsInit(!!user);
36+
3437
/** Set the app title */
3538
useEffect(() => {
3639
const appTitle = startupConfig?.appTitle ?? '';
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { useEffect, useRef } from 'react';
2+
import { useSetRecoilState } from 'recoil';
3+
import { useGetCustomConfigSpeechQuery } from 'librechat-data-provider/react-query';
4+
import { logger } from '~/utils';
5+
import store from '~/store';
6+
7+
/**
8+
* Initializes speech-related Recoil values from the server-side custom
9+
* configuration on first load (only when the user is authenticated)
10+
*/
11+
export default function useSpeechSettingsInit(isAuthenticated: boolean) {
12+
const { data } = useGetCustomConfigSpeechQuery({ enabled: isAuthenticated });
13+
14+
const setters = useRef({
15+
conversationMode: useSetRecoilState(store.conversationMode),
16+
advancedMode: useSetRecoilState(store.advancedMode),
17+
speechToText: useSetRecoilState(store.speechToText),
18+
textToSpeech: useSetRecoilState(store.textToSpeech),
19+
cacheTTS: useSetRecoilState(store.cacheTTS),
20+
engineSTT: useSetRecoilState(store.engineSTT),
21+
languageSTT: useSetRecoilState(store.languageSTT),
22+
autoTranscribeAudio: useSetRecoilState(store.autoTranscribeAudio),
23+
decibelValue: useSetRecoilState(store.decibelValue),
24+
autoSendText: useSetRecoilState(store.autoSendText),
25+
engineTTS: useSetRecoilState(store.engineTTS),
26+
voice: useSetRecoilState(store.voice),
27+
cloudBrowserVoices: useSetRecoilState(store.cloudBrowserVoices),
28+
languageTTS: useSetRecoilState(store.languageTTS),
29+
automaticPlayback: useSetRecoilState(store.automaticPlayback),
30+
playbackRate: useSetRecoilState(store.playbackRate),
31+
}).current;
32+
33+
useEffect(() => {
34+
if (!isAuthenticated || !data || data.message === 'not_found') return;
35+
36+
logger.log('Initializing speech settings from config:', data);
37+
38+
Object.entries(data).forEach(([key, value]) => {
39+
if (key === 'sttExternal' || key === 'ttsExternal') return;
40+
41+
if (localStorage.getItem(key) !== null) return;
42+
43+
const setter = setters[key as keyof typeof setters];
44+
if (setter) {
45+
logger.log(`Setting default speech setting: ${key} = ${value}`);
46+
setter(value as any);
47+
}
48+
});
49+
}, [isAuthenticated, data, setters]);
50+
}

0 commit comments

Comments
 (0)