mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-01-07 11:08:52 +01:00
🗣️ feat: Edge TTS engine (#3358)
* feat: MS Edge TTS * feat: Edge TTS; fix: STT hook
This commit is contained in:
parent
01a88991ab
commit
b390ba781f
14 changed files with 379 additions and 129 deletions
|
|
@ -15,9 +15,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', display: localize('com_nav_browser') }];
|
||||
: [
|
||||
{ value: 'browser', display: localize('com_nav_browser') },
|
||||
{ value: 'edge', display: localize('com_nav_edge') },
|
||||
];
|
||||
|
||||
const handleSelect = (value: string) => {
|
||||
setEngineTTS(value);
|
||||
|
|
|
|||
|
|
@ -1,64 +1,31 @@
|
|||
import React, { useMemo, useEffect, useState } from 'react';
|
||||
import React, { useEffect, useState, useMemo } from 'react';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import Dropdown from '~/components/ui/DropdownNoState';
|
||||
import { useVoicesQuery } from '~/data-provider';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import { useLocalize, useTextToSpeech } from '~/hooks';
|
||||
import store from '~/store';
|
||||
|
||||
const getLocalVoices = (): Promise<SpeechSynthesisVoice[]> => {
|
||||
return new Promise((resolve) => {
|
||||
const voices = speechSynthesis.getVoices();
|
||||
console.log('voices', voices);
|
||||
|
||||
if (voices.length) {
|
||||
resolve(voices);
|
||||
} else {
|
||||
speechSynthesis.onvoiceschanged = () => resolve(speechSynthesis.getVoices());
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
type VoiceOption = {
|
||||
value: string;
|
||||
display: string;
|
||||
};
|
||||
|
||||
export default function VoiceDropdown() {
|
||||
const localize = useLocalize();
|
||||
const [voice, setVoice] = useRecoilState(store.voice);
|
||||
const { voices } = useTextToSpeech();
|
||||
const [voiceOptions, setVoiceOptions] = useState([]);
|
||||
const [engineTTS] = useRecoilState(store.engineTTS);
|
||||
const [cloudBrowserVoices] = useRecoilState(store.cloudBrowserVoices);
|
||||
const externalTextToSpeech = engineTTS === 'external';
|
||||
const { data: externalVoices = [] } = useVoicesQuery();
|
||||
const [localVoices, setLocalVoices] = useState<SpeechSynthesisVoice[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!externalTextToSpeech) {
|
||||
getLocalVoices().then(setLocalVoices);
|
||||
}
|
||||
}, [externalTextToSpeech]);
|
||||
async function fetchVoices() {
|
||||
const options = await voices();
|
||||
setVoiceOptions(options);
|
||||
|
||||
useEffect(() => {
|
||||
if (voice) {
|
||||
return;
|
||||
if (!voice && options.length > 0) {
|
||||
setVoice(options[0]);
|
||||
}
|
||||
}
|
||||
|
||||
if (externalTextToSpeech && externalVoices.length) {
|
||||
setVoice(externalVoices[0]);
|
||||
} else if (!externalTextToSpeech && localVoices.length) {
|
||||
setVoice(localVoices[0].name);
|
||||
}
|
||||
}, [voice, setVoice, externalTextToSpeech, externalVoices, localVoices]);
|
||||
fetchVoices();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [engineTTS]);
|
||||
|
||||
const voiceOptions: VoiceOption[] = useMemo(() => {
|
||||
if (externalTextToSpeech) {
|
||||
return externalVoices.map((v) => ({ value: v, display: v }));
|
||||
} else {
|
||||
return localVoices
|
||||
.filter((v) => cloudBrowserVoices || v.localService === true)
|
||||
.map((v) => ({ value: v.name, display: v.name }));
|
||||
}
|
||||
}, [externalTextToSpeech, externalVoices, localVoices, cloudBrowserVoices]);
|
||||
const memoizedVoiceOptions = useMemo(() => voiceOptions, [voiceOptions]);
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
|
|
@ -66,7 +33,7 @@ export default function VoiceDropdown() {
|
|||
<Dropdown
|
||||
value={voice}
|
||||
onChange={setVoice}
|
||||
options={voiceOptions}
|
||||
options={memoizedVoiceOptions}
|
||||
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