| | |
| | import { useRef, useEffect, useState } from 'react'; |
| | import { useRecoilState, useRecoilValue } from 'recoil'; |
| | import { parseTextParts } from 'librechat-data-provider'; |
| | import type { TMessageContentParts } from 'librechat-data-provider'; |
| | import useTextToSpeechExternal from '~/hooks/Input/useTextToSpeechExternal'; |
| | import usePauseGlobalAudio from '~/hooks/Audio/usePauseGlobalAudio'; |
| | import useAudioRef from '~/hooks/Audio/useAudioRef'; |
| | import { logger } from '~/utils'; |
| | import store from '~/store'; |
| |
|
| | type TUseTextToSpeech = { |
| | messageId?: string; |
| | content?: TMessageContentParts[] | string; |
| | isLast?: boolean; |
| | index?: number; |
| | }; |
| |
|
| | const useTTSExternal = (props?: TUseTextToSpeech) => { |
| | const { messageId, content, isLast = false, index = 0 } = props ?? {}; |
| |
|
| | const isMouseDownRef = useRef(false); |
| | const timerRef = useRef<number | undefined>(undefined); |
| | const [isSpeakingState, setIsSpeaking] = useState(false); |
| | const { audioRef } = useAudioRef({ setIsPlaying: setIsSpeaking }); |
| |
|
| | const { pauseGlobalAudio } = usePauseGlobalAudio(index); |
| | const [voice, setVoice] = useRecoilState(store.voice); |
| | const globalIsPlaying = useRecoilValue(store.globalAudioPlayingFamily(index)); |
| |
|
| | const isSpeaking = isSpeakingState || (isLast && globalIsPlaying); |
| | const { |
| | cancelSpeech, |
| | generateSpeechExternal: generateSpeech, |
| | isLoading, |
| | voices, |
| | } = useTextToSpeechExternal({ |
| | setIsSpeaking, |
| | audioRef, |
| | messageId, |
| | isLast, |
| | index, |
| | }); |
| |
|
| | useEffect(() => { |
| | const firstVoice = voices[0]; |
| | if (voices.length) { |
| | const lastSelectedVoice = voices.find((v) => v === voice); |
| | if (lastSelectedVoice != null) { |
| | logger.log('useTextToSpeech.ts - Effect:', { voices, voice: lastSelectedVoice }); |
| | setVoice(lastSelectedVoice.toString()); |
| | return; |
| | } |
| | logger.log('useTextToSpeech.ts - Effect:', { voices, voice: firstVoice }); |
| | setVoice(firstVoice.toString()); |
| | } |
| | }, [setVoice, voice, voices]); |
| |
|
| | const handleMouseDown = () => { |
| | isMouseDownRef.current = true; |
| | timerRef.current = window.setTimeout(() => { |
| | if (isMouseDownRef.current) { |
| | const messageContent = content ?? ''; |
| | const parsedMessage = |
| | typeof messageContent === 'string' ? messageContent : parseTextParts(messageContent); |
| | generateSpeech(parsedMessage, false); |
| | } |
| | }, 1000); |
| | }; |
| |
|
| | const handleMouseUp = () => { |
| | isMouseDownRef.current = false; |
| | if (timerRef.current != null) { |
| | window.clearTimeout(timerRef.current); |
| | } |
| | }; |
| |
|
| | const toggleSpeech = () => { |
| | if (isSpeaking === true) { |
| | cancelSpeech(); |
| | pauseGlobalAudio(); |
| | } else { |
| | const messageContent = content ?? ''; |
| | const parsedMessage = |
| | typeof messageContent === 'string' ? messageContent : parseTextParts(messageContent); |
| | generateSpeech(parsedMessage, false); |
| | } |
| | }; |
| |
|
| | return { |
| | handleMouseDown, |
| | handleMouseUp, |
| | toggleSpeech, |
| | isSpeaking, |
| | isLoading, |
| | audioRef, |
| | voices, |
| | }; |
| | }; |
| |
|
| | export default useTTSExternal; |
| |
|