Skip to content

Commit 09f4f30

Browse files
committed
✨ (v2): allow interrupting AI speech
1 parent 5a14dfa commit 09f4f30

File tree

2 files changed

+25
-4
lines changed

2 files changed

+25
-4
lines changed

assistant_v2/FEATURE_PROGRESS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,6 @@ This document tracks which features from the original assistant have been implem
1818
| Mute/unmute voice output | Pending |
1919
| Open OpenAI billing page | Pending |
2020
| Push-to-talk text-to-speech interface | Done |
21+
| Interrupt AI speech with push-to-talk | Done |
2122

2223
Update this table as features are migrated and verified to work in `assistant_v2`.

assistant_v2/src/main.rs

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use clipboard::{ClipboardContext, ClipboardProvider};
1616
use std::error::Error;
1717
use std::path::PathBuf;
1818
use std::sync::{Arc, Mutex};
19+
use std::sync::atomic::{AtomicBool, Ordering};
1920
use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};
2021

2122
mod record;
@@ -128,7 +129,13 @@ async fn main() -> Result<(), Box<dyn Error>> {
128129
)));
129130

130131
let (audio_tx, audio_rx) = flume::unbounded();
131-
start_ptt_thread(audio_tx.clone(), speak_stream.clone(), opt.duck_ptt);
132+
let interrupt_flag = Arc::new(AtomicBool::new(false));
133+
start_ptt_thread(
134+
audio_tx.clone(),
135+
speak_stream.clone(),
136+
opt.duck_ptt,
137+
interrupt_flag.clone(),
138+
);
132139

133140
loop {
134141
let audio_path = audio_rx.recv().unwrap();
@@ -162,6 +169,9 @@ async fn main() -> Result<(), Box<dyn Error>> {
162169
let mut displayed_ai_label = false;
163170

164171
while let Some(event) = event_stream.next().await {
172+
if interrupt_flag.swap(false, Ordering::SeqCst) {
173+
break;
174+
}
165175
match event {
166176
Ok(event) => match event {
167177
AssistantStreamEvent::ThreadRunRequiresAction(run_object) => {
@@ -204,7 +214,12 @@ async fn main() -> Result<(), Box<dyn Error>> {
204214
}
205215
}
206216

207-
fn start_ptt_thread(audio_tx: Sender<PathBuf>, speak_stream: Arc<Mutex<SpeakStream>>, duck_ptt: bool) {
217+
fn start_ptt_thread(
218+
audio_tx: Sender<PathBuf>,
219+
speak_stream: Arc<Mutex<SpeakStream>>,
220+
duck_ptt: bool,
221+
interrupt_flag: Arc<AtomicBool>,
222+
) {
208223
thread::spawn(move || {
209224
let mut recorder = rec::Recorder::new();
210225
let tmp_dir = tempdir().unwrap();
@@ -216,8 +231,13 @@ fn start_ptt_thread(audio_tx: Sender<PathBuf>, speak_stream: Arc<Mutex<SpeakStre
216231
match event.event_type {
217232
EventType::KeyPress(key) if key == ptt_key && !key_pressed => {
218233
key_pressed = true;
219-
if duck_ptt {
220-
speak_stream.lock().unwrap().start_audio_ducking();
234+
interrupt_flag.store(true, Ordering::SeqCst);
235+
{
236+
let mut ss = speak_stream.lock().unwrap();
237+
ss.stop_speech();
238+
if duck_ptt {
239+
ss.start_audio_ducking();
240+
}
221241
}
222242
let path = tmp_dir.path().join(format!("{}.wav", Uuid::new_v4()));
223243
if recorder.start_recording(&path, None).is_ok() {

0 commit comments

Comments
 (0)