Skip to content

Commit eca5038

Browse files
authored
feat: allows users to exit out long running tool processes without also exiting chat (#670)
1 parent 457dc25 commit eca5038

File tree

1 file changed

+41
-9
lines changed
  • crates/q_cli/src/cli/chat

1 file changed

+41
-9
lines changed

crates/q_cli/src/cli/chat/mod.rs

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use std::io::{
1313
};
1414
use std::process::ExitCode;
1515
use std::sync::Arc;
16+
use std::sync::atomic::AtomicBool;
1617

1718
use color_eyre::owo_colors::OwoColorize;
1819
use conversation_state::ConversationState;
@@ -53,6 +54,7 @@ use spinners::{
5354
Spinners,
5455
};
5556
use tools::{
57+
InvokeOutput,
5658
Tool,
5759
ToolSpec,
5860
};
@@ -204,6 +206,18 @@ where
204206
}
205207

206208
async fn try_chat(&mut self) -> Result<()> {
209+
let should_terminate = Arc::new(AtomicBool::new(true));
210+
let should_terminate_ref = should_terminate.clone();
211+
let (tx, mut rx) = tokio::sync::mpsc::channel(1);
212+
ctrlc::set_handler(move || {
213+
if should_terminate_ref.load(std::sync::atomic::Ordering::SeqCst) {
214+
execute!(std::io::stdout(), cursor::Show).unwrap();
215+
#[allow(clippy::exit)]
216+
std::process::exit(0);
217+
} else {
218+
let _ = tx.blocking_send(());
219+
}
220+
})?;
207221
// todo: what should we set this to?
208222
if self.is_interactive {
209223
execute!(
@@ -222,7 +236,7 @@ Hi, I'm <g>Amazon Q</g>. Ask me anything.
222236

223237
loop {
224238
let mut response = loop {
225-
match self.prompt_and_send_request().await {
239+
match self.prompt_and_send_request(&mut rx, &should_terminate).await {
226240
Ok(resp) => {
227241
break resp;
228242
},
@@ -401,7 +415,11 @@ Hi, I'm <g>Amazon Q</g>. Ask me anything.
401415
Ok(())
402416
}
403417

404-
async fn prompt_and_send_request(&mut self) -> Result<Option<SendMessageOutput>> {
418+
async fn prompt_and_send_request(
419+
&mut self,
420+
sigint_recver: &mut tokio::sync::mpsc::Receiver<()>,
421+
should_terminate: &Arc<AtomicBool>,
422+
) -> Result<Option<SendMessageOutput>> {
405423
// Tool uses that need to be executed.
406424
let mut queued_tools: Vec<(String, Tool)> = Vec::new();
407425

@@ -582,8 +600,28 @@ Hi, I'm <g>Amazon Q</g>. Ask me anything.
582600
style::Print(format!("{}...\n\n", tool.1.display_name())),
583601
style::SetForegroundColor(Color::Reset),
584602
)?;
585-
let invoke_result = tool.1.invoke(&self.ctx, self.output).await;
603+
self.spinner = Some(Spinner::new(
604+
Spinners::Dots,
605+
"Running tool... press ctrl + c to terminate early".to_owned(),
606+
));
607+
should_terminate.store(false, std::sync::atomic::Ordering::SeqCst);
608+
let invoke_result = tokio::select! {
609+
output = tool.1.invoke(&self.ctx, self.output) => Some(output),
610+
_ = sigint_recver.recv() => None
611+
}
612+
.map_or(Ok(InvokeOutput::default()), |v| v);
613+
if self.is_interactive && self.spinner.is_some() {
614+
drop(self.spinner.take());
615+
queue!(
616+
self.output,
617+
terminal::Clear(terminal::ClearType::CurrentLine),
618+
cursor::MoveToColumn(0),
619+
cursor::Show
620+
)?;
621+
}
622+
should_terminate.store(true, std::sync::atomic::Ordering::SeqCst);
586623
execute!(self.output, style::Print("\n"))?;
624+
587625
let tool_time = std::time::Instant::now().duration_since(tool_start);
588626
let tool_time = format!("{}.{}", tool_time.as_secs(), tool_time.subsec_millis());
589627

@@ -662,12 +700,6 @@ Hi, I'm <g>Amazon Q</g>. Ask me anything.
662700
}
663701
queue!(self.output, style::SetForegroundColor(Color::Reset))?;
664702
queue!(self.output, cursor::Hide)?;
665-
tokio::spawn(async {
666-
tokio::signal::ctrl_c().await.unwrap();
667-
execute!(std::io::stdout(), cursor::Show).unwrap();
668-
#[allow(clippy::exit)]
669-
std::process::exit(0);
670-
});
671703
execute!(self.output, style::Print("\n"))?;
672704
self.spinner = Some(Spinner::new(Spinners::Dots, "Thinking...".to_owned()));
673705
}

0 commit comments

Comments
 (0)