Skip to content

Commit 7ee2cf0

Browse files
a692570claude
andcommitted
fix: surface rate limit state to user with Basso sound and overlay message
Previously _start_recording silently returned when rate-limited. Now it plays the Basso system sound, shows the overlay with the seconds remaining, and auto-hides after 1.5s. Also wires vocabulary keywords into batch STT requests so Deepgram nova-3 receives preferred terms (Telnyx, Bolo, Remotion, etc.) via model_config.keywords, improving recognition accuracy. Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
1 parent 338cab4 commit 7ee2cf0

1 file changed

Lines changed: 29 additions & 1 deletion

File tree

bolo.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ def _load_env_value(name: str) -> str:
100100
LLM_CLEANUP_MODE = _load_env_value("BOLO_LLM_CLEANUP").strip().lower() or "auto"
101101
DELETE_KEYCODE = 51
102102
RATE_LIMIT_BACKOFF_SECONDS = 45.0
103+
MAX_RECORDING_SECONDS = 90.0 # force-stop if stuck recording longer than this
103104

104105
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
105106
ICON_IDLE = os.path.join(BASE_DIR, "icon_idle.png")
@@ -218,6 +219,7 @@ def __init__(self):
218219
rumps.Timer(self._process_key_events, 0.02).start()
219220
rumps.Timer(self._watchdog_tap, 2).start()
220221
rumps.Timer(self._watchdog_overlay_preview, 0.5).start()
222+
rumps.Timer(self._watchdog_recording, 5).start()
221223
self._ensure_warm_stream()
222224

223225
def _begin_session(self):
@@ -466,6 +468,17 @@ def _start_recording(self):
466468
if self._overlay_hide_timer is not None:
467469
self._overlay_hide_timer.cancel()
468470
self._overlay_hide_timer = None
471+
472+
# Rate limit check -- give user feedback instead of silently ignoring
473+
backoff_remaining = self._rate_limit_backoff_until - time.time()
474+
if backoff_remaining > 0:
475+
self._play("Basso")
476+
wait_sec = int(backoff_remaining) + 1
477+
self.overlay.show()
478+
self.overlay.update("error", f"Rate limited - wait {wait_sec}s")
479+
self._hide_overlay_after_delay(1.5)
480+
return
481+
469482
with self.lock:
470483
if self.recording:
471484
return
@@ -536,6 +549,17 @@ def _watchdog_overlay_preview(self, _):
536549
self._overlay_stall_notice_shown = True
537550
self.overlay.update("listening", "Still listening...")
538551

552+
def _watchdog_recording(self, _):
553+
if not self.recording:
554+
return
555+
elapsed = time.time() - self._record_started_at
556+
if elapsed < MAX_RECORDING_SECONDS:
557+
return
558+
# Only force-stop if the key is genuinely not held — avoids cutting off long voice notes
559+
if not self._is_right_option_down():
560+
self._log(f"[watchdog] recording stuck for {elapsed:.0f}s, key not held — force-stopping")
561+
self._stop_recording()
562+
539563
def _shutdown_stream_async(self, stream):
540564
if stream is None:
541565
return
@@ -821,14 +845,18 @@ def _should_accept_stream_result(self, state, transcript, duration_seconds):
821845
def _batch_transcribe_request(self, wav_bytes):
822846
self._log("[stt] using batch fallback")
823847
try:
848+
vocab_terms = VOCAB_STORE.terms()
849+
model_config = {"smart_format": True, "punctuate": True}
850+
if vocab_terms:
851+
model_config["keywords"] = vocab_terms[:50]
824852
resp = requests.post(
825853
STT_ENDPOINT,
826854
headers={"Authorization": f"Bearer {TELNYX_API_KEY}"},
827855
files={"file": ("audio.wav", wav_bytes, "audio/wav")},
828856
data={
829857
"model": "deepgram/nova-3",
830858
"language": "en",
831-
"model_config": json.dumps({"smart_format": True, "punctuate": True}),
859+
"model_config": json.dumps(model_config),
832860
},
833861
timeout=8,
834862
)

0 commit comments

Comments
 (0)