🎤 feat: Cumulative Transcription Support for AudioRecorder (#9316)

- Added useRef to maintain existing text during audio recording.
- Updated setText to prepend existing text to new transcriptions.
- Modified handleStartRecording and handleStopRecording to manage existing text state.
- Improved spinner icon styling for better visibility.
This commit is contained in:
Danny Avila 2025-08-27 18:00:59 -04:00 committed by GitHub
parent ba424666f8
commit c3e88b97c8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -1,4 +1,4 @@
import { useCallback } from 'react'; import { useCallback, useRef } from 'react';
import { useToastContext, TooltipAnchor, ListeningIcon, Spinner } from '@librechat/client'; import { useToastContext, TooltipAnchor, ListeningIcon, Spinner } from '@librechat/client';
import { useLocalize, useSpeechToText } from '~/hooks'; import { useLocalize, useSpeechToText } from '~/hooks';
import { useChatFormContext } from '~/Providers'; import { useChatFormContext } from '~/Providers';
@ -18,9 +18,10 @@ export default function AudioRecorder({
textAreaRef: React.RefObject<HTMLTextAreaElement>; textAreaRef: React.RefObject<HTMLTextAreaElement>;
isSubmitting: boolean; isSubmitting: boolean;
}) { }) {
const { setValue, reset } = methods; const { setValue, reset, getValues } = methods;
const localize = useLocalize(); const localize = useLocalize();
const { showToast } = useToastContext(); const { showToast } = useToastContext();
const existingTextRef = useRef<string>('');
const onTranscriptionComplete = useCallback( const onTranscriptionComplete = useCallback(
(text: string) => { (text: string) => {
@ -39,6 +40,7 @@ export default function AudioRecorder({
} }
ask({ text }); ask({ text });
reset({ text: '' }); reset({ text: '' });
existingTextRef.current = '';
} }
}, },
[ask, reset, showToast, localize, isSubmitting], [ask, reset, showToast, localize, isSubmitting],
@ -46,7 +48,9 @@ export default function AudioRecorder({
const setText = useCallback( const setText = useCallback(
(text: string) => { (text: string) => {
setValue('text', text, { /** The transcript is cumulative, so we only need to prepend the existing text once */
const newText = existingTextRef.current ? `${existingTextRef.current} ${text}` : text;
setValue('text', newText, {
shouldValidate: true, shouldValidate: true,
}); });
}, },
@ -62,18 +66,24 @@ export default function AudioRecorder({
return null; return null;
} }
const handleStartRecording = async () => startRecording(); const handleStartRecording = async () => {
existingTextRef.current = getValues('text') || '';
startRecording();
};
const handleStopRecording = async () => stopRecording(); const handleStopRecording = async () => {
stopRecording();
existingTextRef.current = '';
};
const renderIcon = () => { const renderIcon = () => {
if (isListening === true) { if (isListening === true) {
return <ListeningIcon className="stroke-red-500" />; return <ListeningIcon className="stroke-red-500" />;
} }
if (isLoading === true) { if (isLoading === true) {
return <Spinner className="stroke-gray-700 dark:stroke-gray-300" />; return <Spinner className="stroke-text-secondary" />;
} }
return <ListeningIcon className="stroke-gray-700 dark:stroke-gray-300" />; return <ListeningIcon className="stroke-text-secondary" />;
}; };
return ( return (