@@ -447,7 +447,7 @@ def _prewarm_cloud(self) -> None:
447447 t0 = time .perf_counter ()
448448 sess .get (
449449 'https://api.groq.com/openai/v1/models' ,
450- timeout = 3 .0 ,
450+ timeout = 10 .0 ,
451451 )
452452 logger .info (
453453 f'Cloud TLS pre-warmed in { (time .perf_counter ()- t0 )* 1000 :.0f} ms'
@@ -466,7 +466,7 @@ def _cloud_enabled(self) -> bool:
466466 return True
467467
468468 def _cloud_reachable (self , host : str = 'api.groq.com' ,
469- port : int = 443 , timeout : float = 0.5 ) -> bool :
469+ port : int = 443 , timeout : float = 3.0 ) -> bool :
470470 """Fast non-blocking TCP probe to detect whether the cloud
471471 Whisper host is reachable RIGHT NOW. Returns True on successful
472472 connect, False on any failure (DNS resolution failure, network
@@ -728,9 +728,14 @@ def _transcribe_hybrid(self, audio: np.ndarray):
728728 'skipping cloud, going to local.' )
729729 return self ._transcribe (audio , _already_denoised = denoised_here )
730730
731- # Cloud timeout budget, short, because local is the safety net.
732- # 3 s covers 99 % of Groq calls; anything slower → just go local.
733- cloud_timeout = float (getattr (self ._cfg .audio , 'cloud_timeout_s' , 3.0 ))
731+ # Cloud timeout budget. Was 3s (too aggressive: users on slow /
732+ # rural / congested WiFi never completed the upload phase for a
733+ # 2s audio clip before we aborted to local, even when the network
734+ # would eventually deliver the response). Bumped to 15s so slow
735+ # uploaders on ~20-50 kbps connections still get the higher-
736+ # quality Groq result. Local pipeline stays as safety net for
737+ # anything slower than that.
738+ cloud_timeout = float (getattr (self ._cfg .audio , 'cloud_timeout_s' , 15.0 ))
734739 lang_hint = self ._cfg .transcription .language or None
735740
736741 t0 = time .perf_counter ()
@@ -768,17 +773,37 @@ def _transcribe_hybrid(self, audio: np.ndarray):
768773 # AV-blocked / permission-denied / other "cloud reachable but
769774 # refusing" cases, the local fallback runs transparently and
770775 # produces a correct result, so we stay silent.
776+ exc_name = type (e ).__name__ .lower ()
771777 if 'name resolution' in msg_l or 'getaddrinfo' in msg_l :
772778 self ._cloud_last_error = 'Cloud unreachable (DNS) — using local model'
773779 elif 'connection' in msg_l and ('refus' in msg_l or 'reset' in msg_l ):
774780 self ._cloud_last_error = 'Cloud refused connection — using local model'
775781 elif 'timeout' in msg_l or 'timed out' in msg_l :
776- self ._cloud_last_error = 'Cloud timed out — using local model'
782+ # Timeout on slow internet is the most common cause of the
783+ # silent "why is my dictation worse" complaint. Make the
784+ # cause explicit so the user knows to check connection.
785+ self ._cloud_last_error = 'Cloud too slow (internet slow?) — used local model'
786+ elif (
787+ # Antivirus HTTPS interception. AVG / Avast / Kaspersky /
788+ # Bitdefender / ESET all MITM outbound TLS by default and
789+ # present their own cert, which our TLS stack rejects
790+ # because it isn't in truststore. Signature: SSL /
791+ # certificate errors, PermissionError 13, "wrong version
792+ # number", "unknown ca", "self signed certificate".
793+ 'ssl' in msg_l or 'certificate' in msg_l
794+ or 'winerror 13' in msg_l or 'permission' in exc_name
795+ or 'wrong version number' in msg_l or 'unknown ca' in msg_l
796+ or 'self signed' in msg_l or 'self-signed' in msg_l
797+ or 'cert_' in msg_l
798+ ):
799+ self ._cloud_last_error = (
800+ 'Antivirus blocked cloud — add api.groq.com to AV exceptions' )
777801 else :
778- # AV blocks (PermissionError 13), unknown errors, etc:
779- # log for debugging but do not pop a pill — local result
780- # speaks for itself.
781- self ._cloud_last_error = None
802+ # Unknown errors: still surface something so the user has
803+ # a hint. Truncated exception name is enough for us to
804+ # diagnose from a screenshot without needing full logs.
805+ self ._cloud_last_error = (
806+ f'Cloud failed ({ type (e ).__name__ } ) — used local model' )
782807 logger .warning (
783808 f'Cloud transcription failed in { dt * 1000 :.0f} ms, falling back '
784809 f'to local Whisper. ({ type (e ).__name__ } : { e } )'
0 commit comments