🎯 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,73 +1,79 @@
import { useEffect } from 'react';
import { useCallback } from 'react';
import { useChatFormContext, useToastContext } from '~/Providers';
import { ListeningIcon, Spinner } from '~/components/svg';
import { useLocalize, useSpeechToText } from '~/hooks';
import { useChatFormContext } from '~/Providers';
import { TooltipAnchor } from '~/components/ui';
import { globalAudioId } from '~/common';
import { cn } from '~/utils';
export default function AudioRecorder({
textAreaRef,
methods,
ask,
isRTL,
disabled,
ask,
methods,
textAreaRef,
isSubmitting,
}: {
textAreaRef: React.RefObject<HTMLTextAreaElement>;
methods: ReturnType<typeof useChatFormContext>;
ask: (data: { text: string }) => void;
isRTL: boolean;
disabled: boolean;
ask: (data: { text: string }) => void;
methods: ReturnType<typeof useChatFormContext>;
textAreaRef: React.RefObject<HTMLTextAreaElement>;
isSubmitting: boolean;
}) {
const { setValue, reset } = methods;
const localize = useLocalize();
const { showToast } = useToastContext();
const handleTranscriptionComplete = (text: string) => {
if (text) {
const globalAudio = document.getElementById(globalAudioId) as HTMLAudioElement;
if (globalAudio) {
console.log('Unmuting global audio');
globalAudio.muted = false;
const onTranscriptionComplete = useCallback(
(text: string) => {
if (isSubmitting) {
showToast({
message: localize('com_ui_speech_while_submitting'),
status: 'error',
});
return;
}
ask({ text });
methods.reset({ text: '' });
clearText();
}
};
if (text) {
const globalAudio = document.getElementById(globalAudioId) as HTMLAudioElement | null;
if (globalAudio) {
console.log('Unmuting global audio');
globalAudio.muted = false;
}
ask({ text });
reset({ text: '' });
}
},
[ask, reset, showToast, localize, isSubmitting],
);
const {
isListening,
isLoading,
startRecording,
stopRecording,
interimTranscript,
speechText,
clearText,
} = useSpeechToText(handleTranscriptionComplete);
useEffect(() => {
if (isListening && textAreaRef.current) {
methods.setValue('text', interimTranscript, {
const setText = useCallback(
(text: string) => {
setValue('text', text, {
shouldValidate: true,
});
} else if (textAreaRef.current) {
textAreaRef.current.value = speechText;
methods.setValue('text', speechText, { shouldValidate: true });
}
}, [interimTranscript, speechText, methods, textAreaRef]);
},
[setValue],
);
const handleStartRecording = async () => {
await startRecording();
};
const { isListening, isLoading, startRecording, stopRecording } = useSpeechToText(
setText,
onTranscriptionComplete,
);
const handleStopRecording = async () => {
await stopRecording();
};
if (!textAreaRef.current) {
return null;
}
const handleStartRecording = async () => startRecording();
const handleStopRecording = async () => stopRecording();
const renderIcon = () => {
if (isListening) {
if (isListening === true) {
return <ListeningIcon className="stroke-red-500" />;
}
if (isLoading) {
if (isLoading === true) {
return <Spinner className="stroke-gray-700 dark:stroke-gray-300" />;
}
return <ListeningIcon className="stroke-gray-700 dark:stroke-gray-300" />;
@ -77,7 +83,7 @@ export default function AudioRecorder({
<TooltipAnchor
id="audio-recorder"
aria-label={localize('com_ui_use_micrphone')}
onClick={isListening ? handleStopRecording : handleStartRecording}
onClick={isListening === true ? handleStopRecording : handleStartRecording}
disabled={disabled}
className={cn(
'absolute flex size-[35px] items-center justify-center rounded-full p-1 transition-colors hover:bg-surface-hover',