🗣️ feat: Edge TTS engine (#3358)

* feat: MS Edge TTS

* feat: Edge TTS; fix: STT hook
This commit is contained in:
Marco Beretta 2024-08-07 20:15:41 +02:00 committed by GitHub
parent 01a88991ab
commit b390ba781f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 379 additions and 129 deletions

View file

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

View file

@ -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"