From 0eccceaa94e460c51325cb2cb93dbc73e5bac466 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=BCmer=20Cip?= Date: Tue, 10 Feb 2026 13:57:05 +0300 Subject: [PATCH] auto close on long silence --- main.go | 44 +++++++++++++++++++++++++++++++++++--------- tui.go | 3 ++- 2 files changed, 37 insertions(+), 10 deletions(-) diff --git a/main.go b/main.go index 64d2747..803c1fb 100644 --- a/main.go +++ b/main.go @@ -521,6 +521,8 @@ func record(capture audio.CaptureDevice, keyup <-chan struct{}, sess transcriber var voiceDetected bool var lastVoiceTime time.Time done := make(chan struct{}) + var closeOnce sync.Once + closeDone := func() { closeOnce.Do(func() { close(done) }) } capture.SetCallback(func(data []byte, frameCount uint32) { bufMu.Lock() @@ -569,6 +571,7 @@ func record(capture audio.CaptureDevice, keyup <-chan struct{}, sess transcriber go func() { ticker := time.NewTicker(100 * time.Millisecond) defer ticker.Stop() + var lastTranscriptWarn time.Time for { select { case <-done: @@ -584,7 +587,15 @@ func record(capture audio.CaptureDevice, keyup <-chan struct{}, sess transcriber checkSilenceDuring(&bufMu, &lastVoiceTime) } if streamEnabled { - checkTranscriptSilence(lastTranscript) + checkTranscriptSilence(lastTranscript, &lastTranscriptWarn) + if shouldAutoClose(lastTranscript, recordStart) { + log.Info("silence_auto_close") + tuiSend(SilenceAutoCloseMsg{}) + go beep.PlayEnd() + time.Sleep(recordTail) + closeDone() + return + } } } } @@ -598,7 +609,7 @@ func record(capture audio.CaptureDevice, keyup <-chan struct{}, sess transcriber if streamEnabled { time.Sleep(recordTail) } - close(done) + closeDone() }() <-done @@ -628,6 +639,7 @@ func checkNoVoice(mu *sync.Mutex, elapsed float64, peakLevel *float64, beeped *b } const silenceTimeout = 8 * time.Second +const silenceAutoCloseTimeout = 30 * time.Second func checkSilenceDuring(mu *sync.Mutex, lastVoiceTime *time.Time) { mu.Lock() @@ -643,17 +655,31 @@ func checkSilenceDuring(mu *sync.Mutex, lastVoiceTime *time.Time) { } } -func checkTranscriptSilence(lastTranscript *atomic.Int64) { +func shouldAutoClose(lastTranscript *atomic.Int64, recordStart time.Time) bool { + ref := recordStart + if ts := lastTranscript.Load(); ts != 0 { + ref = time.Unix(0, ts) + } + return time.Since(ref) > silenceAutoCloseTimeout +} + +func checkTranscriptSilence(lastTranscript *atomic.Int64, lastWarn *time.Time) { ts := lastTranscript.Load() if ts == 0 { - return // no transcript received yet + return } - if time.Since(time.Unix(0, ts)) > silenceTimeout { - lastTranscript.Store(time.Now().UnixNano()) - log.Info("transcript_silence_warning") - tuiSend(TranscriptSilenceMsg{}) - beep.PlayError() + timeSinceTranscript := time.Since(time.Unix(0, ts)) + if timeSinceTranscript <= silenceTimeout { + *lastWarn = time.Time{} + return + } + if !lastWarn.IsZero() && time.Since(*lastWarn) <= silenceTimeout { + return } + *lastWarn = time.Now() + log.Info("transcript_silence_warning") + tuiSend(TranscriptSilenceMsg{}) + beep.PlayError() } func updatePercentileStats() { diff --git a/tui.go b/tui.go index 71ab86c..be377d6 100644 --- a/tui.go +++ b/tui.go @@ -31,6 +31,7 @@ type RateLimitMsg struct{ Text string } // Rate limit info type RequestDeviceSelectionMsg struct{} // Request to change microphone type NoVoiceWarningMsg struct{} // No voice detected during recording type TranscriptSilenceMsg struct{} // No transcript updates from backend +type SilenceAutoCloseMsg struct{} // Recording auto-closed due to prolonged silence type HybridHelpMsg struct{ Enabled bool } // Whether hybrid tap+hold is enabled type UpdateAvailableMsg struct{ Version string } // New version available type tickMsg time.Time @@ -194,7 +195,7 @@ func (m tuiModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.liveText = "" m.clampViewIdx() - case RecordingStopMsg: + case RecordingStopMsg, SilenceAutoCloseMsg: m.state = tuiStateIdle m.audioLevel = 0 m.clampViewIdx()