mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-21 10:50:14 +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
|
|
@ -68,6 +68,7 @@
|
|||
"lodash": "^4.17.21",
|
||||
"lucide-react": "^0.394.0",
|
||||
"match-sorter": "^6.3.4",
|
||||
"msedge-tts": "^1.3.4",
|
||||
"rc-input-number": "^7.4.2",
|
||||
"react": "^18.2.0",
|
||||
"react-avatar-editor": "^13.0.2",
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React, { FC, useContext, useState } from 'react';
|
||||
import React, { FC } from 'react';
|
||||
import {
|
||||
Listbox,
|
||||
ListboxButton,
|
||||
|
|
|
|||
|
|
@ -1,19 +1,25 @@
|
|||
import { useRecoilState } from 'recoil';
|
||||
import store from '~/store';
|
||||
|
||||
export enum AudioEndpoints {
|
||||
export enum STTEndpoints {
|
||||
browser = 'browser',
|
||||
external = 'external',
|
||||
}
|
||||
|
||||
export enum TTSEndpoints {
|
||||
browser = 'browser',
|
||||
edge = 'edge',
|
||||
external = 'external',
|
||||
}
|
||||
|
||||
const useGetAudioSettings = () => {
|
||||
const [engineSTT] = useRecoilState<string>(store.engineSTT);
|
||||
const [engineTTS] = useRecoilState<string>(store.engineTTS);
|
||||
|
||||
const externalSpeechToText = engineSTT === AudioEndpoints.external;
|
||||
const externalTextToSpeech = engineTTS === AudioEndpoints.external;
|
||||
const speechToTextEndpoint: STTEndpoints = engineSTT as STTEndpoints;
|
||||
const textToSpeechEndpoint: TTSEndpoints = engineTTS as TTSEndpoints;
|
||||
|
||||
return { externalSpeechToText, externalTextToSpeech };
|
||||
return { speechToTextEndpoint, textToSpeechEndpoint };
|
||||
};
|
||||
|
||||
export default useGetAudioSettings;
|
||||
|
|
|
|||
|
|
@ -4,8 +4,9 @@ import useSpeechToTextExternal from './useSpeechToTextExternal';
|
|||
import useGetAudioSettings from './useGetAudioSettings';
|
||||
|
||||
const useSpeechToText = (handleTranscriptionComplete: (text: string) => void) => {
|
||||
const { externalSpeechToText } = useGetAudioSettings();
|
||||
const { speechToTextEndpoint } = useGetAudioSettings();
|
||||
const [animatedText, setAnimatedText] = useState('');
|
||||
const externalSpeechToText = speechToTextEndpoint === 'external';
|
||||
|
||||
const {
|
||||
isListening: speechIsListeningBrowser,
|
||||
|
|
|
|||
|
|
@ -9,7 +9,8 @@ const useSpeechToTextBrowser = () => {
|
|||
const { showToast } = useToastContext();
|
||||
const [languageSTT] = useRecoilState<string>(store.languageSTT);
|
||||
const [autoTranscribeAudio] = useRecoilState<boolean>(store.autoTranscribeAudio);
|
||||
const { externalSpeechToText } = useGetAudioSettings();
|
||||
const { speechToTextEndpoint } = useGetAudioSettings();
|
||||
const isBrowserSTTEnabled = speechToTextEndpoint === 'browser';
|
||||
const [isListening, setIsListening] = useState(false);
|
||||
|
||||
const {
|
||||
|
|
@ -51,7 +52,7 @@ const useSpeechToTextBrowser = () => {
|
|||
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.shiftKey && e.altKey && e.code === 'KeyL' && !externalSpeechToText) {
|
||||
if (e.shiftKey && e.altKey && e.code === 'KeyL' && !isBrowserSTTEnabled) {
|
||||
toggleListening();
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@ import useGetAudioSettings from './useGetAudioSettings';
|
|||
|
||||
const useSpeechToTextExternal = (onTranscriptionComplete: (text: string) => void) => {
|
||||
const { showToast } = useToastContext();
|
||||
const { externalSpeechToText } = useGetAudioSettings();
|
||||
const { speechToTextEndpoint } = useGetAudioSettings();
|
||||
const isExternalSTTEnabled = speechToTextEndpoint === 'external';
|
||||
const [speechToText] = useRecoilState<boolean>(store.speechToText);
|
||||
const [autoTranscribeAudio] = useRecoilState<boolean>(store.autoTranscribeAudio);
|
||||
const [autoSendText] = useRecoilState(store.autoSendText);
|
||||
|
|
@ -194,7 +195,7 @@ const useSpeechToTextExternal = (onTranscriptionComplete: (text: string) => void
|
|||
};
|
||||
|
||||
const handleKeyDown = async (e: KeyboardEvent) => {
|
||||
if (e.shiftKey && e.altKey && e.code === 'KeyL' && !externalSpeechToText) {
|
||||
if (e.shiftKey && e.altKey && e.code === 'KeyL' && isExternalSTTEnabled) {
|
||||
if (!window.MediaRecorder) {
|
||||
showToast({ message: 'MediaRecorder is not supported in this browser', status: 'error' });
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -3,30 +3,67 @@ import { parseTextParts } from 'librechat-data-provider';
|
|||
import type { TMessage } from 'librechat-data-provider';
|
||||
import useTextToSpeechExternal from './useTextToSpeechExternal';
|
||||
import useTextToSpeechBrowser from './useTextToSpeechBrowser';
|
||||
import { usePauseGlobalAudio } from '../Audio';
|
||||
import useGetAudioSettings from './useGetAudioSettings';
|
||||
import useTextToSpeechEdge from './useTextToSpeechEdge';
|
||||
import { usePauseGlobalAudio } from '../Audio';
|
||||
|
||||
const useTextToSpeech = (message: TMessage, isLast: boolean, index = 0) => {
|
||||
const { externalTextToSpeech } = useGetAudioSettings();
|
||||
const useTextToSpeech = (message?: TMessage, isLast = false, index = 0) => {
|
||||
const { textToSpeechEndpoint } = useGetAudioSettings();
|
||||
const { pauseGlobalAudio } = usePauseGlobalAudio(index);
|
||||
const audioRef = useRef<HTMLAudioElement | null>(null);
|
||||
|
||||
const {
|
||||
generateSpeechLocal: generateSpeechLocal,
|
||||
cancelSpeechLocal: cancelSpeechLocal,
|
||||
generateSpeechLocal,
|
||||
cancelSpeechLocal,
|
||||
isSpeaking: isSpeakingLocal,
|
||||
voices: voicesLocal,
|
||||
} = useTextToSpeechBrowser();
|
||||
|
||||
const {
|
||||
generateSpeechExternal: generateSpeechExternal,
|
||||
generateSpeechEdge,
|
||||
cancelSpeechEdge,
|
||||
isSpeaking: isSpeakingEdge,
|
||||
voices: voicesEdge,
|
||||
} = useTextToSpeechEdge();
|
||||
|
||||
const {
|
||||
generateSpeechExternal,
|
||||
cancelSpeech: cancelSpeechExternal,
|
||||
isSpeaking: isSpeakingExternal,
|
||||
isLoading: isLoading,
|
||||
audioRef,
|
||||
} = useTextToSpeechExternal(message.messageId, isLast, index);
|
||||
const { pauseGlobalAudio } = usePauseGlobalAudio(index);
|
||||
isLoading: isLoadingExternal,
|
||||
audioRef: audioRefExternal,
|
||||
voices: voicesExternal,
|
||||
} = useTextToSpeechExternal(message?.messageId || '', isLast, index);
|
||||
|
||||
const generateSpeech = externalTextToSpeech ? generateSpeechExternal : generateSpeechLocal;
|
||||
const cancelSpeech = externalTextToSpeech ? cancelSpeechExternal : cancelSpeechLocal;
|
||||
const isSpeaking = externalTextToSpeech ? isSpeakingExternal : isSpeakingLocal;
|
||||
let generateSpeech, cancelSpeech, isSpeaking, isLoading, voices;
|
||||
|
||||
switch (textToSpeechEndpoint) {
|
||||
case 'external':
|
||||
generateSpeech = generateSpeechExternal;
|
||||
cancelSpeech = cancelSpeechExternal;
|
||||
isSpeaking = isSpeakingExternal;
|
||||
isLoading = isLoadingExternal;
|
||||
if (audioRefExternal) {
|
||||
audioRef.current = audioRefExternal.current;
|
||||
}
|
||||
voices = voicesExternal;
|
||||
break;
|
||||
case 'edge':
|
||||
generateSpeech = generateSpeechEdge;
|
||||
cancelSpeech = cancelSpeechEdge;
|
||||
isSpeaking = isSpeakingEdge;
|
||||
isLoading = false;
|
||||
voices = voicesEdge;
|
||||
break;
|
||||
case 'browser':
|
||||
default:
|
||||
generateSpeech = generateSpeechLocal;
|
||||
cancelSpeech = cancelSpeechLocal;
|
||||
isSpeaking = isSpeakingLocal;
|
||||
isLoading = false;
|
||||
voices = voicesLocal;
|
||||
break;
|
||||
}
|
||||
|
||||
const isMouseDownRef = useRef(false);
|
||||
const timerRef = useRef<number | undefined>(undefined);
|
||||
|
|
@ -52,7 +89,6 @@ const useTextToSpeech = (message: TMessage, isLast: boolean, index = 0) => {
|
|||
|
||||
const toggleSpeech = () => {
|
||||
if (isSpeaking) {
|
||||
console.log('canceling message audio speech');
|
||||
cancelSpeech();
|
||||
pauseGlobalAudio();
|
||||
} else {
|
||||
|
|
@ -69,6 +105,7 @@ const useTextToSpeech = (message: TMessage, isLast: boolean, index = 0) => {
|
|||
toggleSpeech,
|
||||
isSpeaking,
|
||||
isLoading,
|
||||
voices,
|
||||
audioRef,
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,6 +2,11 @@ import { useRecoilState } from 'recoil';
|
|||
import { useState } from 'react';
|
||||
import store from '~/store';
|
||||
|
||||
interface VoiceOption {
|
||||
value: string;
|
||||
display: string;
|
||||
}
|
||||
|
||||
function useTextToSpeechBrowser() {
|
||||
const [cloudBrowserVoices] = useRecoilState(store.cloudBrowserVoices);
|
||||
const [isSpeaking, setIsSpeaking] = useState(false);
|
||||
|
|
@ -32,7 +37,30 @@ function useTextToSpeechBrowser() {
|
|||
setIsSpeaking(false);
|
||||
};
|
||||
|
||||
return { generateSpeechLocal, cancelSpeechLocal, isSpeaking };
|
||||
const voices = (): Promise<VoiceOption[]> => {
|
||||
return new Promise((resolve) => {
|
||||
const getAndMapVoices = () => {
|
||||
const availableVoices = speechSynthesis
|
||||
.getVoices()
|
||||
.filter((v) => cloudBrowserVoices || v.localService === true);
|
||||
|
||||
const voiceOptions: VoiceOption[] = availableVoices.map((v) => ({
|
||||
value: v.name,
|
||||
display: v.name,
|
||||
}));
|
||||
|
||||
resolve(voiceOptions);
|
||||
};
|
||||
|
||||
if (speechSynthesis.getVoices().length) {
|
||||
getAndMapVoices();
|
||||
} else {
|
||||
speechSynthesis.onvoiceschanged = getAndMapVoices;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return { generateSpeechLocal, cancelSpeechLocal, isSpeaking, voices };
|
||||
}
|
||||
|
||||
export default useTextToSpeechBrowser;
|
||||
|
|
|
|||
201
client/src/hooks/Input/useTextToSpeechEdge.ts
Normal file
201
client/src/hooks/Input/useTextToSpeechEdge.ts
Normal file
|
|
@ -0,0 +1,201 @@
|
|||
import { useRecoilState } from 'recoil';
|
||||
import { useState, useCallback, useRef, useEffect } from 'react';
|
||||
import { MsEdgeTTS, OUTPUT_FORMAT } from 'msedge-tts';
|
||||
import { useToastContext } from '~/Providers';
|
||||
import useLocalize from '~/hooks/useLocalize';
|
||||
import store from '~/store';
|
||||
|
||||
interface Voice {
|
||||
value: string;
|
||||
display: string;
|
||||
}
|
||||
|
||||
interface UseTextToSpeechEdgeReturn {
|
||||
generateSpeechEdge: (text: string) => Promise<void>;
|
||||
cancelSpeechEdge: () => void;
|
||||
isSpeaking: boolean;
|
||||
voices: () => Promise<Voice[]>;
|
||||
}
|
||||
|
||||
function useTextToSpeechEdge(): UseTextToSpeechEdgeReturn {
|
||||
const localize = useLocalize();
|
||||
const [isSpeaking, setIsSpeaking] = useState<boolean>(false);
|
||||
const [voiceName] = useRecoilState<string>(store.voice);
|
||||
const ttsRef = useRef<MsEdgeTTS | null>(null);
|
||||
const audioElementRef = useRef<HTMLAudioElement | null>(null);
|
||||
const mediaSourceRef = useRef<MediaSource | null>(null);
|
||||
const sourceBufferRef = useRef<SourceBuffer | null>(null);
|
||||
const pendingBuffers = useRef<Uint8Array[]>([]);
|
||||
const { showToast } = useToastContext();
|
||||
|
||||
const initializeTTS = useCallback(async (): Promise<void> => {
|
||||
if (!ttsRef.current) {
|
||||
ttsRef.current = new MsEdgeTTS();
|
||||
}
|
||||
try {
|
||||
await ttsRef.current.setMetadata(voiceName, OUTPUT_FORMAT.AUDIO_24KHZ_48KBITRATE_MONO_MP3);
|
||||
} catch (error) {
|
||||
console.error('Error initializing TTS:', error);
|
||||
showToast({
|
||||
message: localize('com_nav_tts_init_error', (error as Error).message),
|
||||
status: 'error',
|
||||
});
|
||||
}
|
||||
}, [voiceName, showToast, localize]);
|
||||
|
||||
const onSourceOpen = useCallback((): void => {
|
||||
if (!sourceBufferRef.current && mediaSourceRef.current) {
|
||||
try {
|
||||
sourceBufferRef.current = mediaSourceRef.current.addSourceBuffer('audio/mpeg');
|
||||
sourceBufferRef.current.addEventListener('updateend', appendNextBuffer);
|
||||
} catch (error) {
|
||||
console.error('Error adding source buffer:', error);
|
||||
showToast({
|
||||
message: localize('com_nav_source_buffer_error'),
|
||||
status: 'error',
|
||||
});
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [showToast, localize]);
|
||||
|
||||
const initializeMediaSource = useCallback(async (): Promise<void> => {
|
||||
return new Promise<void>((resolve) => {
|
||||
if (!mediaSourceRef.current) {
|
||||
mediaSourceRef.current = new MediaSource();
|
||||
audioElementRef.current = new Audio();
|
||||
audioElementRef.current.src = URL.createObjectURL(mediaSourceRef.current);
|
||||
}
|
||||
|
||||
const mediaSource = mediaSourceRef.current;
|
||||
if (mediaSource.readyState === 'open') {
|
||||
onSourceOpen();
|
||||
resolve();
|
||||
} else {
|
||||
const onSourceOpenWrapper = (): void => {
|
||||
onSourceOpen();
|
||||
resolve();
|
||||
mediaSource.removeEventListener('sourceopen', onSourceOpenWrapper);
|
||||
};
|
||||
mediaSource.addEventListener('sourceopen', onSourceOpenWrapper);
|
||||
}
|
||||
});
|
||||
}, [onSourceOpen]);
|
||||
|
||||
const appendNextBuffer = useCallback((): void => {
|
||||
if (
|
||||
sourceBufferRef.current &&
|
||||
!sourceBufferRef.current.updating &&
|
||||
pendingBuffers.current.length > 0
|
||||
) {
|
||||
const nextBuffer = pendingBuffers.current.shift();
|
||||
if (nextBuffer) {
|
||||
try {
|
||||
sourceBufferRef.current.appendBuffer(nextBuffer);
|
||||
} catch (error) {
|
||||
console.error('Error appending buffer:', error);
|
||||
showToast({
|
||||
message: localize('com_nav_buffer_append_error'),
|
||||
status: 'error',
|
||||
});
|
||||
pendingBuffers.current.unshift(nextBuffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [showToast, localize]);
|
||||
|
||||
const generateSpeechEdge = useCallback(
|
||||
async (text: string): Promise<void> => {
|
||||
try {
|
||||
await initializeTTS();
|
||||
await initializeMediaSource();
|
||||
|
||||
if (!ttsRef.current || !audioElementRef.current) {
|
||||
throw new Error('TTS or Audio element not initialized');
|
||||
}
|
||||
|
||||
setIsSpeaking(true);
|
||||
pendingBuffers.current = [];
|
||||
|
||||
const readable = await ttsRef.current.toStream(text);
|
||||
|
||||
readable.on('data', (chunk: Buffer) => {
|
||||
pendingBuffers.current.push(new Uint8Array(chunk));
|
||||
appendNextBuffer();
|
||||
});
|
||||
|
||||
readable.on('end', () => {
|
||||
if (mediaSourceRef.current && mediaSourceRef.current.readyState === 'open') {
|
||||
mediaSourceRef.current.endOfStream();
|
||||
}
|
||||
});
|
||||
|
||||
audioElementRef.current.onended = () => {
|
||||
setIsSpeaking(false);
|
||||
};
|
||||
|
||||
await audioElementRef.current.play();
|
||||
} catch (error) {
|
||||
console.error('Error generating speech:', error);
|
||||
showToast({
|
||||
message: localize('com_nav_audio_play_error', (error as Error).message),
|
||||
status: 'error',
|
||||
});
|
||||
setIsSpeaking(false);
|
||||
}
|
||||
},
|
||||
[initializeTTS, initializeMediaSource, appendNextBuffer, showToast, localize],
|
||||
);
|
||||
|
||||
const cancelSpeechEdge = useCallback((): void => {
|
||||
try {
|
||||
if (audioElementRef.current) {
|
||||
audioElementRef.current.pause();
|
||||
audioElementRef.current.currentTime = 0;
|
||||
}
|
||||
if (mediaSourceRef.current && mediaSourceRef.current.readyState === 'open') {
|
||||
mediaSourceRef.current.endOfStream();
|
||||
}
|
||||
pendingBuffers.current = [];
|
||||
setIsSpeaking(false);
|
||||
} catch (error) {
|
||||
console.error('Error cancelling speech:', error);
|
||||
showToast({
|
||||
message: localize('com_nav_speech_cancel_error'),
|
||||
status: 'error',
|
||||
});
|
||||
}
|
||||
}, [showToast, localize]);
|
||||
|
||||
const voices = useCallback(async (): Promise<Voice[]> => {
|
||||
if (!ttsRef.current) {
|
||||
ttsRef.current = new MsEdgeTTS();
|
||||
}
|
||||
try {
|
||||
const voicesList = await ttsRef.current.getVoices();
|
||||
return voicesList.map((v) => ({
|
||||
value: v.ShortName,
|
||||
display: v.FriendlyName,
|
||||
}));
|
||||
} catch (error) {
|
||||
console.error('Error fetching voices:', error);
|
||||
showToast({
|
||||
message: localize('com_nav_voices_fetch_error'),
|
||||
status: 'error',
|
||||
});
|
||||
return [];
|
||||
}
|
||||
}, [showToast, localize]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (mediaSourceRef.current) {
|
||||
URL.revokeObjectURL(audioElementRef.current?.src || '');
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
return { generateSpeechEdge, cancelSpeechEdge, isSpeaking, voices };
|
||||
}
|
||||
|
||||
export default useTextToSpeechEdge;
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { useRecoilValue } from 'recoil';
|
||||
import { useState, useMemo, useRef, useCallback, useEffect } from 'react';
|
||||
import { useTextToSpeechMutation } from '~/data-provider';
|
||||
import { useTextToSpeechMutation, useVoicesQuery } from '~/data-provider';
|
||||
import useAudioRef from '~/hooks/Audio/useAudioRef';
|
||||
import useLocalize from '~/hooks/useLocalize';
|
||||
import { useToastContext } from '~/Providers';
|
||||
|
|
@ -178,7 +178,18 @@ function useTextToSpeechExternal(messageId: string, isLast: boolean, index = 0)
|
|||
return isLocalSpeaking || (isLast && globalIsPlaying);
|
||||
}, [isLocalSpeaking, globalIsPlaying, isLast]);
|
||||
|
||||
return { generateSpeechExternal, cancelSpeech, isLoading, isSpeaking, audioRef };
|
||||
const useVoices = () => {
|
||||
return useVoicesQuery().data ?? [];
|
||||
};
|
||||
|
||||
return {
|
||||
generateSpeechExternal,
|
||||
cancelSpeech,
|
||||
isLoading,
|
||||
isSpeaking,
|
||||
audioRef,
|
||||
voices: useVoices,
|
||||
};
|
||||
}
|
||||
|
||||
export default useTextToSpeechExternal;
|
||||
|
|
|
|||
|
|
@ -657,8 +657,17 @@ export default {
|
|||
com_nav_audio_play_error: 'Error playing audio: {0}',
|
||||
com_nav_audio_process_error: 'Error processing audio: {0}',
|
||||
com_nav_long_audio_warning: 'Longer texts will take longer to process.',
|
||||
com_nav_tts_init_error: 'Failed to initialize text-to-speech: {0}',
|
||||
com_nav_source_buffer_error: 'Error setting up audio playback. Please refresh the page.',
|
||||
com_nav_media_source_init_error:
|
||||
'Unable to prepare audio player. Please check your browser settings.',
|
||||
com_nav_buffer_append_error: 'Problem with audio streaming. The playback may be interrupted.',
|
||||
com_nav_speech_cancel_error: 'Unable to stop audio playback. You may need to refresh the page.',
|
||||
com_nav_voices_fetch_error:
|
||||
'Could not retrieve voice options. Please check your internet connection.',
|
||||
com_nav_engine: 'Engine',
|
||||
com_nav_browser: 'Browser',
|
||||
com_nav_edge: 'Edge',
|
||||
com_nav_external: 'External',
|
||||
com_nav_delete_cache_storage: 'Delete TTS cache storage',
|
||||
com_nav_enable_cache_tts: 'Enable cache TTS',
|
||||
|
|
|
|||
89
package-lock.json
generated
89
package-lock.json
generated
|
|
@ -1157,6 +1157,7 @@
|
|||
"lodash": "^4.17.21",
|
||||
"lucide-react": "^0.394.0",
|
||||
"match-sorter": "^6.3.4",
|
||||
"msedge-tts": "^1.3.4",
|
||||
"rc-input-number": "^7.4.2",
|
||||
"react": "^18.2.0",
|
||||
"react-avatar-editor": "^13.0.2",
|
||||
|
|
@ -12399,7 +12400,6 @@
|
|||
"version": "5.4.1",
|
||||
"resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz",
|
||||
"integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"bn.js": "^4.0.0",
|
||||
"inherits": "^2.0.1",
|
||||
|
|
@ -12410,8 +12410,7 @@
|
|||
"node_modules/asn1.js/node_modules/bn.js": {
|
||||
"version": "4.12.0",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
|
||||
"integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==",
|
||||
"dev": true
|
||||
"integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA=="
|
||||
},
|
||||
"node_modules/assert": {
|
||||
"version": "2.1.0",
|
||||
|
|
@ -12898,8 +12897,7 @@
|
|||
"node_modules/bn.js": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz",
|
||||
"integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==",
|
||||
"dev": true
|
||||
"integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ=="
|
||||
},
|
||||
"node_modules/body-parser": {
|
||||
"version": "1.20.2",
|
||||
|
|
@ -13031,8 +13029,7 @@
|
|||
"node_modules/brorand": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz",
|
||||
"integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==",
|
||||
"dev": true
|
||||
"integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w=="
|
||||
},
|
||||
"node_modules/browser-resolve": {
|
||||
"version": "2.0.0",
|
||||
|
|
@ -13047,7 +13044,6 @@
|
|||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
|
||||
"integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"buffer-xor": "^1.0.3",
|
||||
"cipher-base": "^1.0.0",
|
||||
|
|
@ -13061,7 +13057,6 @@
|
|||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz",
|
||||
"integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"browserify-aes": "^1.0.4",
|
||||
"browserify-des": "^1.0.0",
|
||||
|
|
@ -13072,7 +13067,6 @@
|
|||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz",
|
||||
"integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"cipher-base": "^1.0.1",
|
||||
"des.js": "^1.0.0",
|
||||
|
|
@ -13084,7 +13078,6 @@
|
|||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.0.tgz",
|
||||
"integrity": "sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"bn.js": "^5.0.0",
|
||||
"randombytes": "^2.0.1"
|
||||
|
|
@ -13094,7 +13087,6 @@
|
|||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.2.tgz",
|
||||
"integrity": "sha512-1rudGyeYY42Dk6texmv7c4VcQ0EsvVbLwZkA+AQB7SxvXxmcD93jcHie8bzecJ+ChDlmAm2Qyu0+Ccg5uhZXCg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"bn.js": "^5.2.1",
|
||||
"browserify-rsa": "^4.1.0",
|
||||
|
|
@ -13114,7 +13106,6 @@
|
|||
"version": "3.6.2",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
||||
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"inherits": "^2.0.3",
|
||||
"string_decoder": "^1.1.1",
|
||||
|
|
@ -13254,8 +13245,7 @@
|
|||
"node_modules/buffer-xor": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz",
|
||||
"integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==",
|
||||
"dev": true
|
||||
"integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ=="
|
||||
},
|
||||
"node_modules/builtin-modules": {
|
||||
"version": "3.3.0",
|
||||
|
|
@ -13552,7 +13542,6 @@
|
|||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz",
|
||||
"integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"inherits": "^2.0.1",
|
||||
"safe-buffer": "^5.0.1"
|
||||
|
|
@ -14141,7 +14130,6 @@
|
|||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz",
|
||||
"integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"bn.js": "^4.1.0",
|
||||
"elliptic": "^6.5.3"
|
||||
|
|
@ -14150,14 +14138,12 @@
|
|||
"node_modules/create-ecdh/node_modules/bn.js": {
|
||||
"version": "4.12.0",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
|
||||
"integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==",
|
||||
"dev": true
|
||||
"integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA=="
|
||||
},
|
||||
"node_modules/create-hash": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
|
||||
"integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"cipher-base": "^1.0.1",
|
||||
"inherits": "^2.0.1",
|
||||
|
|
@ -14170,7 +14156,6 @@
|
|||
"version": "1.1.7",
|
||||
"resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz",
|
||||
"integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"cipher-base": "^1.0.3",
|
||||
"create-hash": "^1.1.0",
|
||||
|
|
@ -14257,7 +14242,6 @@
|
|||
"version": "3.12.0",
|
||||
"resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz",
|
||||
"integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"browserify-cipher": "^1.0.0",
|
||||
"browserify-sign": "^4.0.0",
|
||||
|
|
@ -14725,7 +14709,6 @@
|
|||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/des.js/-/des.js-1.1.0.tgz",
|
||||
"integrity": "sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"inherits": "^2.0.1",
|
||||
"minimalistic-assert": "^1.0.0"
|
||||
|
|
@ -14807,7 +14790,6 @@
|
|||
"version": "5.0.3",
|
||||
"resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz",
|
||||
"integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"bn.js": "^4.1.0",
|
||||
"miller-rabin": "^4.0.0",
|
||||
|
|
@ -14817,8 +14799,7 @@
|
|||
"node_modules/diffie-hellman/node_modules/bn.js": {
|
||||
"version": "4.12.0",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
|
||||
"integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==",
|
||||
"dev": true
|
||||
"integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA=="
|
||||
},
|
||||
"node_modules/digest-fetch": {
|
||||
"version": "1.3.0",
|
||||
|
|
@ -15017,7 +14998,6 @@
|
|||
"version": "6.5.4",
|
||||
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz",
|
||||
"integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"bn.js": "^4.11.9",
|
||||
"brorand": "^1.1.0",
|
||||
|
|
@ -15031,8 +15011,7 @@
|
|||
"node_modules/elliptic/node_modules/bn.js": {
|
||||
"version": "4.12.0",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
|
||||
"integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==",
|
||||
"dev": true
|
||||
"integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA=="
|
||||
},
|
||||
"node_modules/emittery": {
|
||||
"version": "0.13.1",
|
||||
|
|
@ -15929,7 +15908,6 @@
|
|||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz",
|
||||
"integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"md5.js": "^1.3.4",
|
||||
"safe-buffer": "^5.1.1"
|
||||
|
|
@ -17534,7 +17512,6 @@
|
|||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz",
|
||||
"integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"inherits": "^2.0.4",
|
||||
"readable-stream": "^3.6.0",
|
||||
|
|
@ -17548,7 +17525,6 @@
|
|||
"version": "3.6.2",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
||||
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"inherits": "^2.0.3",
|
||||
"string_decoder": "^1.1.1",
|
||||
|
|
@ -17562,7 +17538,6 @@
|
|||
"version": "1.1.7",
|
||||
"resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz",
|
||||
"integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"inherits": "^2.0.3",
|
||||
"minimalistic-assert": "^1.0.1"
|
||||
|
|
@ -17771,7 +17746,6 @@
|
|||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
|
||||
"integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"hash.js": "^1.0.3",
|
||||
"minimalistic-assert": "^1.0.0",
|
||||
|
|
@ -19038,6 +19012,15 @@
|
|||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/isomorphic-ws": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz",
|
||||
"integrity": "sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"ws": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/istanbul-lib-coverage": {
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz",
|
||||
|
|
@ -21295,7 +21278,6 @@
|
|||
"version": "1.3.5",
|
||||
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
|
||||
"integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"hash-base": "^3.0.0",
|
||||
"inherits": "^2.0.1",
|
||||
|
|
@ -22152,7 +22134,6 @@
|
|||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz",
|
||||
"integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"bn.js": "^4.0.0",
|
||||
"brorand": "^1.0.1"
|
||||
|
|
@ -22164,8 +22145,7 @@
|
|||
"node_modules/miller-rabin/node_modules/bn.js": {
|
||||
"version": "4.12.0",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
|
||||
"integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==",
|
||||
"dev": true
|
||||
"integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA=="
|
||||
},
|
||||
"node_modules/mime": {
|
||||
"version": "3.0.0",
|
||||
|
|
@ -22228,14 +22208,12 @@
|
|||
"node_modules/minimalistic-assert": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
|
||||
"integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==",
|
||||
"dev": true
|
||||
"integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A=="
|
||||
},
|
||||
"node_modules/minimalistic-crypto-utils": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz",
|
||||
"integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==",
|
||||
"dev": true
|
||||
"integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg=="
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "3.1.2",
|
||||
|
|
@ -22506,6 +22484,21 @@
|
|||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||
},
|
||||
"node_modules/msedge-tts": {
|
||||
"version": "1.3.4",
|
||||
"resolved": "https://registry.npmjs.org/msedge-tts/-/msedge-tts-1.3.4.tgz",
|
||||
"integrity": "sha512-0dj86Gg9VzdOJZVCkSSK/O5Eg0NM9W5p8LsXAEPe7qUmsvdAugPUTcPwt9tyz4GThAzAFBBu554kevH8StLEHQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"axios": "^1.5.0",
|
||||
"buffer": "^6.0.3",
|
||||
"crypto-browserify": "^3.12.0",
|
||||
"isomorphic-ws": "^5.0.0",
|
||||
"process": "^0.11.10",
|
||||
"stream-browserify": "^3.0.0",
|
||||
"ws": "^8.14.1"
|
||||
}
|
||||
},
|
||||
"node_modules/multer": {
|
||||
"version": "1.4.5-lts.1",
|
||||
"resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.1.tgz",
|
||||
|
|
@ -23527,7 +23520,6 @@
|
|||
"version": "5.1.6",
|
||||
"resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz",
|
||||
"integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"asn1.js": "^5.2.0",
|
||||
"browserify-aes": "^1.0.0",
|
||||
|
|
@ -23795,7 +23787,6 @@
|
|||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz",
|
||||
"integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"create-hash": "^1.1.2",
|
||||
"create-hmac": "^1.1.4",
|
||||
|
|
@ -25521,7 +25512,6 @@
|
|||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz",
|
||||
"integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"bn.js": "^4.1.0",
|
||||
"browserify-rsa": "^4.0.0",
|
||||
|
|
@ -25534,8 +25524,7 @@
|
|||
"node_modules/public-encrypt/node_modules/bn.js": {
|
||||
"version": "4.12.0",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
|
||||
"integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==",
|
||||
"dev": true
|
||||
"integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA=="
|
||||
},
|
||||
"node_modules/pump": {
|
||||
"version": "3.0.0",
|
||||
|
|
@ -25652,7 +25641,6 @@
|
|||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
|
||||
"integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"safe-buffer": "^5.1.0"
|
||||
}
|
||||
|
|
@ -25661,7 +25649,6 @@
|
|||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz",
|
||||
"integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"randombytes": "^2.0.5",
|
||||
"safe-buffer": "^5.1.0"
|
||||
|
|
@ -26632,7 +26619,6 @@
|
|||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz",
|
||||
"integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"hash-base": "^3.0.0",
|
||||
"inherits": "^2.0.1"
|
||||
|
|
@ -27085,7 +27071,6 @@
|
|||
"version": "2.4.11",
|
||||
"resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
|
||||
"integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"inherits": "^2.0.1",
|
||||
"safe-buffer": "^5.0.1"
|
||||
|
|
@ -27489,7 +27474,6 @@
|
|||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz",
|
||||
"integrity": "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"inherits": "~2.0.4",
|
||||
"readable-stream": "^3.5.0"
|
||||
|
|
@ -27499,7 +27483,6 @@
|
|||
"version": "3.6.2",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
||||
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"inherits": "^2.0.3",
|
||||
"string_decoder": "^1.1.1",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue