diff --git a/src/api/commcoachApi.ts b/src/api/commcoachApi.ts index 9a29be1..ef9b0be 100644 --- a/src/api/commcoachApi.ts +++ b/src/api/commcoachApi.ts @@ -306,6 +306,7 @@ export async function sendMessageStreamApi( onEvent: (event: SSEEvent) => void, onError?: (error: Error) => void, onComplete?: () => void, + signal?: AbortSignal, ): Promise { try { const baseURL = api.defaults.baseURL || ''; @@ -322,6 +323,7 @@ export async function sendMessageStreamApi( headers, body: JSON.stringify({ content }), credentials: 'include', + signal, }); if (!response.ok) { diff --git a/src/hooks/useCommcoach.ts b/src/hooks/useCommcoach.ts index d416280..b5f3bf0 100644 --- a/src/hooks/useCommcoach.ts +++ b/src/hooks/useCommcoach.ts @@ -91,6 +91,7 @@ export function useCommcoach(): CommcoachHookReturn { const isMountedRef = useRef(true); const currentAudioRef = useRef(null); + const abortControllerRef = useRef(null); const onTtsEventRef = useRef<((event: TtsEvent) => void) | null>(null); const onDocumentCreatedRef = useRef<((doc: any) => void) | null>(null); @@ -337,6 +338,11 @@ export function useCommcoach(): CommcoachHookReturn { const sendMessage = useCallback(async (content: string) => { const normalizedContent = content.trim(); if (!normalizedContent || !instanceId || !session) return; + + abortControllerRef.current?.abort(); + const ac = new AbortController(); + abortControllerRef.current = ac; + if (currentAudioRef.current) { currentAudioRef.current.pause(); currentAudioRef.current = null; @@ -364,7 +370,7 @@ export function useCommcoach(): CommcoachHookReturn { session.id, normalizedContent, (event: SSEEvent) => { - if (!isMountedRef.current) return; + if (!isMountedRef.current || ac.signal.aborted) return; const eventType = event.type; const eventData = event.data; @@ -404,6 +410,7 @@ export function useCommcoach(): CommcoachHookReturn { } }, (err) => { + if (err.name === 'AbortError') return; if (isMountedRef.current) { setError(err.message); setIsStreaming(false); @@ -417,8 +424,10 @@ export function useCommcoach(): CommcoachHookReturn { setStreamingMessage(null); } }, + ac.signal, ); } catch (err: any) { + if (err.name === 'AbortError') return; if (isMountedRef.current) { setError(err.message); setIsStreaming(false); diff --git a/src/pages/views/commcoach/CommcoachDossierView.tsx b/src/pages/views/commcoach/CommcoachDossierView.tsx index 41eac36..4eb9318 100644 --- a/src/pages/views/commcoach/CommcoachDossierView.tsx +++ b/src/pages/views/commcoach/CommcoachDossierView.tsx @@ -120,8 +120,9 @@ export const CommcoachDossierView: React.FC = () => { const handleSend = useCallback(async () => { if (!coach.inputValue.trim() || coach.isStreaming) return; + voice.cancelPendingSpeech(); await coach.sendMessage(coach.inputValue); - }, [coach]); + }, [coach, voice]); const handleKeyDown = useCallback((e: React.KeyboardEvent) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); handleSend(); } @@ -340,11 +341,11 @@ export const CommcoachDossierView: React.FC = () => { )}