diff --git a/voicebot/bots/whisper.py b/voicebot/bots/whisper.py index c756e39..9ea3e0d 100644 --- a/voicebot/bots/whisper.py +++ b/voicebot/bots/whisper.py @@ -1375,6 +1375,11 @@ class OptimizedAudioProcessor: # partial chunks that haven't reached `chunk_size` yet. async def _queue_final_coroutine(): + # Prevent duplicate finals: if a final is already pending, skip + if getattr(self, "final_transcription_pending", False): + logger.info(f"Final transcription already pending for {self.peer_name}, skipping duplicate final queue.") + return + self.final_transcription_pending = True try: # Drain any samples remaining in the circular buffer available = 0 @@ -1391,11 +1396,7 @@ class OptimizedAudioProcessor: if len(self.current_phrase_audio) > self.sample_rate * 0.5: logger.info(f"Queueing final transcription for {self.peer_name} (drained={available if 'available' in locals() else 0})") - self.final_transcription_pending = True - - # Send an immediate lightweight final marker so the UI - # receives a quick final event while the heavy generate - # runs in the background. + # Send an immediate lightweight final marker so the UI receives a quick final event while the heavy generate runs in the background. try: marker_text = f"⚡ {self.peer_name}: (finalizing...)" message_id = self.current_message.id if self.current_message is not None else None @@ -1410,10 +1411,7 @@ class OptimizedAudioProcessor: except Exception as e: logger.debug(f"Failed to send final marker for {self.peer_name}: {e}") - # Run the blocking final transcription in a coroutine - # that offloads the heavy work to a threadpool (existing - # helper handles this). We await it here so we can clear - # state afterwards in the same coroutine context. + # Run the blocking final transcription in a coroutine that offloads the heavy work to a threadpool (existing helper handles this). We await it here so we can clear state afterwards in the same coroutine context. try: await self._blocking_transcribe_and_send( self.current_phrase_audio.copy(), is_final=True