🎯 fix: Prevent UI De-sync By Removing Redundant States (#5333)

* fix: remove local state from Dropdown causing de-sync

* refactor: cleanup STT code, avoid redundant states to prevent de-sync and side effects

* fix: reset transcript after sending final text to prevent data loss

* fix: clear timeout on component unmount to prevent memory leaks
This commit is contained in:
Danny Avila 2025-01-16 17:38:59 -05:00 committed by GitHub
parent b55e695541
commit e309c6abef
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 149 additions and 145 deletions

View file

@ -1,83 +1,48 @@
import { useState, useEffect } from 'react';
import useSpeechToTextBrowser from './useSpeechToTextBrowser';
import useSpeechToTextExternal from './useSpeechToTextExternal';
import useGetAudioSettings from './useGetAudioSettings';
const useSpeechToText = (handleTranscriptionComplete: (text: string) => void) => {
const useSpeechToText = (
setText: (text: string) => void,
onTranscriptionComplete: (text: string) => void,
): {
isLoading?: boolean;
isListening?: boolean;
stopRecording: () => void | (() => Promise<void>);
startRecording: () => void | (() => Promise<void>);
} => {
const { speechToTextEndpoint } = useGetAudioSettings();
const [animatedText, setAnimatedText] = useState('');
const externalSpeechToText = speechToTextEndpoint === 'external';
const {
isListening: speechIsListeningBrowser,
isLoading: speechIsLoadingBrowser,
interimTranscript: interimTranscriptBrowser,
text: speechTextBrowser,
startRecording: startSpeechRecordingBrowser,
stopRecording: stopSpeechRecordingBrowser,
} = useSpeechToTextBrowser();
} = useSpeechToTextBrowser(setText, onTranscriptionComplete);
const {
isListening: speechIsListeningExternal,
isLoading: speechIsLoadingExternal,
text: speechTextExternal,
externalStartRecording: startSpeechRecordingExternal,
externalStopRecording: stopSpeechRecordingExternal,
clearText,
} = useSpeechToTextExternal(handleTranscriptionComplete);
} = useSpeechToTextExternal(setText, onTranscriptionComplete);
const isListening = externalSpeechToText ? speechIsListeningExternal : speechIsListeningBrowser;
const isLoading = externalSpeechToText ? speechIsLoadingExternal : speechIsLoadingBrowser;
const speechTextForm = externalSpeechToText ? speechTextExternal : speechTextBrowser;
const startRecording = externalSpeechToText
? startSpeechRecordingExternal
: startSpeechRecordingBrowser;
const stopRecording = externalSpeechToText
? stopSpeechRecordingExternal
: stopSpeechRecordingBrowser;
const speechText =
isListening || (speechTextExternal && speechTextExternal.length > 0)
? speechTextExternal
: speechTextForm || '';
// for a future real-time STT external
const interimTranscript = externalSpeechToText ? '' : interimTranscriptBrowser;
const animateTextTyping = (text: string) => {
const totalDuration = 2000;
const frameRate = 60;
const totalFrames = totalDuration / (1000 / frameRate);
const charsPerFrame = Math.ceil(text.length / totalFrames);
let currentIndex = 0;
const animate = () => {
currentIndex += charsPerFrame;
const currentText = text.substring(0, currentIndex);
setAnimatedText(currentText);
if (currentIndex < text.length) {
requestAnimationFrame(animate);
} else {
setAnimatedText(text);
}
};
requestAnimationFrame(animate);
};
useEffect(() => {
if (speechText && externalSpeechToText) {
animateTextTyping(speechText);
}
}, [speechText, externalSpeechToText]);
return {
isListening,
isLoading,
startRecording,
isListening,
stopRecording,
interimTranscript,
speechText: externalSpeechToText ? animatedText : speechText,
clearText,
startRecording,
};
};