fix: stt streaming response handler
This commit is contained in:
@@ -2,6 +2,7 @@ import {
|
|||||||
fetchChunkedDataPostFormData,
|
fetchChunkedDataPostFormData,
|
||||||
readStreamData
|
readStreamData
|
||||||
} from '@/utils/fetch-chunk-data';
|
} from '@/utils/fetch-chunk-data';
|
||||||
|
import _ from 'lodash';
|
||||||
import { useCallback, useRef, useState } from 'react';
|
import { useCallback, useRef, useState } from 'react';
|
||||||
import { AUDIO_SPEECH_TO_TEXT_API } from '../../apis';
|
import { AUDIO_SPEECH_TO_TEXT_API } from '../../apis';
|
||||||
import { extractErrorMessage } from '../../config';
|
import { extractErrorMessage } from '../../config';
|
||||||
@@ -72,30 +73,29 @@ export const useStreamSTT = (params?: UseStreamSTTParams) => {
|
|||||||
await readStreamData(
|
await readStreamData(
|
||||||
reader,
|
reader,
|
||||||
decoder,
|
decoder,
|
||||||
(chunks: any[]) => {
|
(chunk: any) => {
|
||||||
chunks.forEach((chunk) => {
|
if (chunk.error) {
|
||||||
if (chunk.error) {
|
const errorMessage = extractErrorMessage(chunk.error);
|
||||||
const errorMessage = extractErrorMessage(chunk.error);
|
setError({
|
||||||
setError({
|
error: true,
|
||||||
error: true,
|
errorMessage
|
||||||
errorMessage
|
});
|
||||||
});
|
params?.onError?.(errorMessage);
|
||||||
params?.onError?.(errorMessage);
|
return;
|
||||||
return;
|
} else {
|
||||||
}
|
const deltaContent =
|
||||||
|
_.get(chunk, 'choices.0.delta.content', '') === null
|
||||||
|
? ''
|
||||||
|
: _.get(chunk, 'choices.0.delta.content', '');
|
||||||
|
|
||||||
// STT stream response format: { text: "..." }
|
fullText += deltaContent;
|
||||||
if (chunk.text) {
|
params?.onChunk?.(fullText);
|
||||||
fullText += chunk.text;
|
setProgress((prev) => prev + 1);
|
||||||
params?.onChunk?.(fullText);
|
}
|
||||||
setProgress((prev) => prev + 1);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
100 // throttle delay
|
100
|
||||||
);
|
);
|
||||||
|
|
||||||
// Stream completed
|
|
||||||
params?.onComplete?.(fullText);
|
params?.onComplete?.(fullText);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
if (err.name === 'AbortError') {
|
if (err.name === 'AbortError') {
|
||||||
|
|||||||
@@ -68,7 +68,6 @@ const GroundSTT: React.FC<MessageProps> = forwardRef((props, ref) => {
|
|||||||
|
|
||||||
const { initialize, updateScrollerPosition } = useOverlayScroller();
|
const { initialize, updateScrollerPosition } = useOverlayScroller();
|
||||||
|
|
||||||
// Initialize non-stream STT hook
|
|
||||||
const nonStreamSTT = useNonStreamSTT({
|
const nonStreamSTT = useNonStreamSTT({
|
||||||
onSuccess: (result) => {
|
onSuccess: (result) => {
|
||||||
setMessageList([
|
setMessageList([
|
||||||
@@ -86,10 +85,8 @@ const GroundSTT: React.FC<MessageProps> = forwardRef((props, ref) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Initialize stream STT hook
|
|
||||||
const streamSTT = useStreamSTT({
|
const streamSTT = useStreamSTT({
|
||||||
onChunk: (text) => {
|
onChunk: (text) => {
|
||||||
// Update message list with streaming text
|
|
||||||
setMessageList([
|
setMessageList([
|
||||||
{
|
{
|
||||||
content: text,
|
content: text,
|
||||||
@@ -158,12 +155,9 @@ const GroundSTT: React.FC<MessageProps> = forwardRef((props, ref) => {
|
|||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
// Choose stream or non-stream based on parameters
|
|
||||||
if (parameters.stream) {
|
if (parameters.stream) {
|
||||||
// Stream mode: text will be updated in real-time
|
|
||||||
await streamSTT.generate(params);
|
await streamSTT.generate(params);
|
||||||
} else {
|
} else {
|
||||||
// Non-stream mode: get complete text at once
|
|
||||||
await nonStreamSTT.generate(params, getCanceltToken());
|
await nonStreamSTT.generate(params, getCanceltToken());
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
@@ -417,7 +411,7 @@ const GroundSTT: React.FC<MessageProps> = forwardRef((props, ref) => {
|
|||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
padding: '8px 14px',
|
padding: '8px 14px',
|
||||||
lineHeight: '20px',
|
lineHeight: '22px',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
wordBreak: 'break-word'
|
wordBreak: 'break-word'
|
||||||
|
|||||||
@@ -178,6 +178,19 @@ export const readStreamData = async (
|
|||||||
try {
|
try {
|
||||||
textBuffer += decoder.decode(value, { stream: true });
|
textBuffer += decoder.decode(value, { stream: true });
|
||||||
|
|
||||||
|
// Check if it's a pure JSON error response (not SSE format)
|
||||||
|
if (!textBuffer.startsWith('data:') && !textBuffer.startsWith('error:')) {
|
||||||
|
try {
|
||||||
|
const jsonData = JSON.parse(textBuffer);
|
||||||
|
bufferManager.add({ error: jsonData });
|
||||||
|
textBuffer = '';
|
||||||
|
throttledCallback();
|
||||||
|
continue;
|
||||||
|
} catch {
|
||||||
|
// Not a complete JSON yet, might be SSE format or incomplete data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (textBuffer.startsWith('error:')) {
|
if (textBuffer.startsWith('error:')) {
|
||||||
const errorStr = textBuffer.slice(7).trim();
|
const errorStr = textBuffer.slice(7).trim();
|
||||||
const jsonData = JSON.parse(errorStr);
|
const jsonData = JSON.parse(errorStr);
|
||||||
|
|||||||
Reference in New Issue
Block a user