Skip to content

feat(dictation): optional silence auto-stop for Toggle mode (#860) - #903

Open
H-Chris233 wants to merge 1 commit into
Open-Less:betafrom
H-Chris233:codex/silence-auto-stop-toggle
Open

feat(dictation): optional silence auto-stop for Toggle mode (#860)#903
H-Chris233 wants to merge 1 commit into
Open-Less:betafrom
H-Chris233:codex/silence-auto-stop-toggle

Conversation

@H-Chris233

@H-Chris233 H-Chris233 commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

User description

Closes #860

Summary

Adds an optional "auto-stop after silence" behavior to Toggle recording mode, disabled by default so existing behavior is unchanged. When enabled, after real speech is detected, the recording stops and submits automatically once a continuous silence reaches the chosen threshold (1 / 1.5 / 2 / 3 / 4 / 5 s, default 3 s). If no speech is detected at all within 10 s, the session is cancelled instead of submitting an empty recording.

Implementation

  • New pure-logic silence detector (coordinator/silence_auto_stop.rs): consumes the existing RMS level stream from the recorder's level_handler, emits a one-shot Stop / Cancel decision. Speech requires level ≥ 0.02 for 3 consecutive audio blocks (~15 ms) to reject keyboard taps / background noise.
  • Wired into start_recorder_for_starting: settings are snapshotted at session start; detection runs before the UI-level throttle so every frame is evaluated. The decision is delivered over a channel to a task that runs the existing end_session (stop+submit) or cancel_session (no-speech cancel), with a stale-session guard.
  • New preferences: silenceAutoStopEnabled (default false) and silenceAutoStopSeconds (default 3), backward compatible via serde defaults (old config files unaffected).
  • Settings UI: switch + duration dropdown shown only in Toggle mode (Recording & input). i18n for en / zh-CN / zh-TW / ja / ko.

Behavior guarantees

  • Push-to-talk (Hold), Auto, and DoubleClick modes are untouched.
  • Manual second hotkey press and Esc cancel keep working; they race with the automatic path.
  • At most one auto decision per session.

Tests

  • 6 new unit tests for the detector (threshold stop, short-silence no-op, 10 s no-speech cancel, noise burst rejection, speech-resets-timer, one-shot).
  • cargo test --lib: 915/916 pass (the single failure is a pre-existing Windows symlink-privilege test, unrelated).
  • Frontend: tsc --noEmit clean; frontend test runner 30/30 pass.

PR Type

Enhancement, Tests


Description

  • Add optional silence auto-stop for Toggle mode.

  • New pure-logic detector consumes RMS level stream.

  • Auto-stops after speech plus silence; cancels on no speech.

  • Add preferences, settings UI, and translations with tests.


Diagram Walkthrough

flowchart LR
  A["level_handler RMS ~185 Hz"] --> B["SilenceAutoStop detector"]
  B -- "speech then silence" --> C["Stop"]
  B -- "no speech in 10s" --> D["Cancel"]
  C --> E["end_session: stop + submit"]
  D --> F["cancel_session"]
Loading

File Walkthrough

Relevant files
Enhancement
5 files
coordinator.rs
Register silence auto-stop module                                               
+1/-0     
dictation.rs
Wire silence detector into dictation session                         
+63/-0   
silence_auto_stop.rs
Add pure-logic silence auto-stop detector with tests         
+211/-0 
types.ts
Add silence auto-stop fields to UserPreferences                   
+5/-0     
RecordingInputSection.tsx
Add Toggle-mode silence auto-stop settings UI                       
+32/-0   
Configuration changes
1 files
types.rs
Add silence auto-stop preference fields                                   
+22/-0   
Documentation
5 files
en.ts
Add English silence auto-stop strings                                       
+4/-0     
ja.ts
Add Japanese silence auto-stop strings                                     
+4/-0     
ko.ts
Add Korean silence auto-stop strings                                         
+4/-0     
zh-CN.ts
Add Simplified Chinese silence auto-stop strings                 
+4/-0     
zh-TW.ts
Add Traditional Chinese silence auto-stop strings               
+4/-0     
Tests
2 files
mock-data.ts
Update mock preferences with silence auto-stop                     
+2/-0     
stylePrefs.test.ts
Extend test fixture with new preferences                                 
+2/-0     

@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

🎫 Ticket compliance analysis 🔶

860 - Partially compliant

Compliant requirements:

  • Feature is restricted to Toggle mode and is disabled by default.
  • No-speech cancellation after 10 seconds is implemented.
  • Silence duration is configurable in the settings UI with 1 / 1.5 / 2 / 3 / 4 / 5 s options and default 3 s.
  • Manual second-press stop and Esc cancel paths remain available.
  • Other trigger modes (Push-to-talk, Auto, DoubleClick) are not affected.
  • Noise rejection is attempted via a level threshold plus 3 consecutive speech blocks.
  • New preference fields have serde defaults, so old configuration files remain valid.
  • The detector is isolated in its own module (silence_auto_stop.rs).

Non-compliant requirements:

  • “Stop only after continuous silence after speech” is not fully met: the silence timer is measured from the first detected speech block rather than from the most recent speech block, so continuous speech can be cut off mid-sentence.

Requires further human verification:

  • The speech/noise detection thresholds (0.02 level, 3 consecutive blocks) need real-world testing across different microphones and background-noise conditions.
  • The settings UI behavior (showing the option only in Toggle mode, i18n strings, dropdown interaction) should be manually checked.
⏱️ Estimated effort to review: 3 🔵🔵🔵⚪⚪
🧪 PR contains tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Possible Issue

last_speech_at is only assigned when consecutive_speech_blocks first reaches MIN_SPEECH_BLOCKS; later speech frames never update it. As a result, the silence threshold is measured from the onset of the detected speech instead of from the most recent speech. With the default 3-second threshold, a user speaking continuously for longer than 3 seconds will be stopped mid-sentence, and a 2-second utterance followed by silence will be stopped after only 1 second of silence. Update last_speech_at on every speech frame (or otherwise track the last non-silent frame) so the timer reflects actual silence after speech.

if level >= SPEECH_LEVEL_THRESHOLD {
    self.consecutive_speech_blocks += 1;
    if self.consecutive_speech_blocks >= MIN_SPEECH_BLOCKS {
        self.speech_detected = true;
        self.last_speech_at = Some(now);
    }
} else {
    self.consecutive_speech_blocks = 0;
}

if self.speech_detected {
    if let Some(last) = self.last_speech_at {
        if now.duration_since(last) >= self.silence_after_speech {
            self.decided = true;
            return Some(SilenceDecision::Stop);
        }
Possible race condition

The spawned auto-stop task checks the current session_id, releases the lock, and then calls end_session/cancel_session. If the user manually stops or Esc-cancels and starts a new session in the window between the check and the state-changing call, the auto-stop path may act on the new session. The stale-session guard and the session-ending operation should be atomic (or end_session/cancel_session should re-verify the session id internally).

    let Some(decision) = rx.recv().await else {
        return;
    };
    let current_session_id = task_inner.state.lock().session_id;
    if captured_session_id != current_session_id {
        log::info!(
            "[coord] silence auto-stop decision from stale session {captured_session_id} dropped (current={current_session_id})"
        );
        return;
    }
    match decision {
        silence_auto_stop::SilenceDecision::Stop => {
            log::info!(
                "[coord] silence auto-stop: session {captured_session_id} stopped after silence"
            );
            let _ = end_session(&task_inner).await;
        }
        silence_auto_stop::SilenceDecision::Cancel => {
            log::info!(
                "[coord] silence auto-stop: session {captured_session_id} cancelled (no speech detected)"
            );
            cancel_session(&task_inner);
        }
    }
});

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature request: automatically stop and submit toggle-mode dictation after silence

1 participant