From 9621fac835267a7ccd82fefcb3925b1136f4eca5 Mon Sep 17 00:00:00 2001 From: Danilo Leal <67129314+danilo-leal@users.noreply.github.com> Date: Wed, 14 Jan 2026 13:12:46 -0300 Subject: [PATCH] agent_ui: Queue prompts by default when sending them while generating (#46797) This PR makes queueing a prompt the default behavior when sending a new one while there's an on-going generation. You can still send a prompt that will immediately interrupt the agent with the `cmd-shift-enter` keybinding, though, which preserves the current behavior. The main motivation for this change is to make the queueing not only more discoverable, but more useful as well, as we're parting from the assumption that most of the time, what you want is to queue it as opposed to interrupting it (even though it's still possible to do either through the keybinding I mentioned above or simply by stopping the generation and sending a new one). Here's a quick video: https://github.com/user-attachments/assets/37f92433-70ef-459f-98ff-41ed80e3e43f In the video, I show sending one prompt and then sending two others that fall straight into the queue. Then, in the middle of the generation of my first prompt, I use the `cmd-shift-enter` keybinding to send a prompt immediately, interrupting the agent, effectively being sent in front of the queue. Release Notes: - Agent: Made queueing prompts the default behavior when sending them while there's an on-going generation. --- assets/keymaps/default-linux.json | 2 +- assets/keymaps/default-macos.json | 2 +- assets/keymaps/default-windows.json | 2 +- crates/agent_ui/src/acp/message_editor.rs | 30 +++++++--------- crates/agent_ui/src/acp/thread_view.rs | 43 +++++++++++++++-------- crates/agent_ui/src/agent_ui.rs | 4 +-- 6 files changed, 46 insertions(+), 37 deletions(-) diff --git a/assets/keymaps/default-linux.json b/assets/keymaps/default-linux.json index c319b7c6ae9f22fdc8d9ac266e01745115648214..be7a2ff8c1be9c2b91fd4ae6dabfdbc550784a91 100644 --- a/assets/keymaps/default-linux.json +++ b/assets/keymaps/default-linux.json @@ -308,7 +308,7 @@ "shift-alt-y": "agent::KeepAll", "shift-alt-z": "agent::RejectAll", "ctrl-enter": "agent::ChatWithFollow", - "ctrl-shift-enter": "agent::QueueMessage", + "ctrl-shift-enter": "agent::SendImmediately", "ctrl-shift-alt-enter": "agent::SendNextQueuedMessage", "ctrl-shift-backspace": "agent::ClearMessageQueue", "ctrl-shift-v": "agent::PasteRaw", diff --git a/assets/keymaps/default-macos.json b/assets/keymaps/default-macos.json index 8fe72ecbc6bb14bf3dcf800ca9e1c73ef60dff67..a0bd957bdab86241ad6e5ef6d0af6394fea6463a 100644 --- a/assets/keymaps/default-macos.json +++ b/assets/keymaps/default-macos.json @@ -356,7 +356,7 @@ "shift-alt-y": "agent::KeepAll", "shift-alt-z": "agent::RejectAll", "cmd-enter": "agent::ChatWithFollow", - "cmd-shift-enter": "agent::QueueMessage", + "cmd-shift-enter": "agent::SendImmediately", "cmd-shift-alt-enter": "agent::SendNextQueuedMessage", "cmd-shift-backspace": "agent::ClearMessageQueue", "cmd-shift-v": "agent::PasteRaw", diff --git a/assets/keymaps/default-windows.json b/assets/keymaps/default-windows.json index 637bc66f0cf9dbcc357a30276bd3a015518eb4fb..05a65626d0bd3afa74ab174a565ec2ec3e2f1020 100644 --- a/assets/keymaps/default-windows.json +++ b/assets/keymaps/default-windows.json @@ -310,7 +310,7 @@ "shift-alt-y": "agent::KeepAll", "shift-alt-z": "agent::RejectAll", "ctrl-enter": "agent::ChatWithFollow", - "ctrl-shift-enter": "agent::QueueMessage", + "ctrl-shift-enter": "agent::SendImmediately", "ctrl-shift-alt-enter": "agent::SendNextQueuedMessage", "ctrl-shift-backspace": "agent::ClearMessageQueue", "ctrl-shift-v": "agent::PasteRaw", diff --git a/crates/agent_ui/src/acp/message_editor.rs b/crates/agent_ui/src/acp/message_editor.rs index 81a3ba715d725dc4d966228ecc7a236f988afe06..ee25811e6c543eb5c135d6b76655df2e7633270f 100644 --- a/crates/agent_ui/src/acp/message_editor.rs +++ b/crates/agent_ui/src/acp/message_editor.rs @@ -1,4 +1,4 @@ -use crate::QueueMessage; +use crate::SendImmediately; use crate::acp::AcpThreadHistory; use crate::{ ChatWithFollow, @@ -53,7 +53,7 @@ pub struct MessageEditor { #[derive(Clone, Copy, Debug)] pub enum MessageEditorEvent { Send, - Queue, + SendImmediately, Cancel, Focus, LostFocus, @@ -508,18 +508,6 @@ impl MessageEditor { cx.emit(MessageEditorEvent::Send) } - pub fn queue(&mut self, cx: &mut Context) { - if self.is_empty(cx) { - return; - } - - self.editor.update(cx, |editor, cx| { - editor.clear_inlay_hints(cx); - }); - - cx.emit(MessageEditorEvent::Queue) - } - pub fn trigger_completion_menu(&mut self, window: &mut Window, cx: &mut Context) { let editor = self.editor.clone(); @@ -563,8 +551,16 @@ impl MessageEditor { self.send(cx); } - fn queue_message(&mut self, _: &QueueMessage, _: &mut Window, cx: &mut Context) { - self.queue(cx); + fn send_immediately(&mut self, _: &SendImmediately, _: &mut Window, cx: &mut Context) { + if self.is_empty(cx) { + return; + } + + self.editor.update(cx, |editor, cx| { + editor.clear_inlay_hints(cx); + }); + + cx.emit(MessageEditorEvent::SendImmediately) } fn chat_with_follow( @@ -1009,7 +1005,7 @@ impl Render for MessageEditor { div() .key_context("MessageEditor") .on_action(cx.listener(Self::chat)) - .on_action(cx.listener(Self::queue_message)) + .on_action(cx.listener(Self::send_immediately)) .on_action(cx.listener(Self::chat_with_follow)) .on_action(cx.listener(Self::cancel)) .on_action(cx.listener(Self::paste_raw)) diff --git a/crates/agent_ui/src/acp/thread_view.rs b/crates/agent_ui/src/acp/thread_view.rs index 1378a9f02d1eb201d658b8ba7f325fb17530455c..f1d70741e83fbaed3906bc63cb1b08cfa8899131 100644 --- a/crates/agent_ui/src/acp/thread_view.rs +++ b/crates/agent_ui/src/acp/thread_view.rs @@ -73,7 +73,7 @@ use crate::ui::{AgentNotification, AgentNotificationEvent, BurnModeTooltip, Usag use crate::{ AgentDiffPane, AgentPanel, AllowAlways, AllowOnce, ClearMessageQueue, ContinueThread, ContinueWithBurnMode, CycleFavoriteModels, CycleModeSelector, ExpandMessageEditor, Follow, - KeepAll, NewThread, OpenAgentDiff, OpenHistory, QueueMessage, RejectAll, RejectOnce, + KeepAll, NewThread, OpenAgentDiff, OpenHistory, RejectAll, RejectOnce, SendImmediately, SendNextQueuedMessage, ToggleBurnMode, ToggleProfileSelector, }; @@ -1204,7 +1204,7 @@ impl AcpThreadView { ) { match event { MessageEditorEvent::Send => self.send(window, cx), - MessageEditorEvent::Queue => self.queue_message(window, cx), + MessageEditorEvent::SendImmediately => self.interrupt_and_send(window, cx), MessageEditorEvent::Cancel => self.cancel_generation(cx), MessageEditorEvent::Focus => { self.cancel_editing(&Default::default(), window, cx); @@ -1256,7 +1256,7 @@ impl AcpThreadView { } } } - ViewEvent::MessageEditorEvent(_editor, MessageEditorEvent::Queue) => {} + ViewEvent::MessageEditorEvent(_editor, MessageEditorEvent::SendImmediately) => {} ViewEvent::MessageEditorEvent(editor, MessageEditorEvent::Send) => { self.regenerate(event.entry_index, editor.clone(), window, cx); } @@ -1300,7 +1300,7 @@ impl AcpThreadView { } if thread.read(cx).status() != ThreadStatus::Idle { - self.stop_current_and_send_new_message(window, cx); + self.queue_message(window, cx); return; } @@ -1344,6 +1344,23 @@ impl AcpThreadView { self.send_impl(self.message_editor.clone(), window, cx) } + fn interrupt_and_send(&mut self, window: &mut Window, cx: &mut Context) { + let Some(thread) = self.thread() else { + return; + }; + + if self.is_loading_contents { + return; + } + + if thread.read(cx).status() == ThreadStatus::Idle { + self.send_impl(self.message_editor.clone(), window, cx); + return; + } + + self.stop_current_and_send_new_message(window, cx); + } + fn stop_current_and_send_new_message(&mut self, window: &mut Window, cx: &mut Context) { let Some(thread) = self.thread().cloned() else { return; @@ -5617,13 +5634,7 @@ impl AcpThreadView { .tooltip(move |_window, cx| { if is_editor_empty && !is_generating { Tooltip::for_action("Type to Send", &Chat, cx) - } else { - let title = if is_generating { - "Stop and Send Message" - } else { - "Send" - }; - + } else if is_generating { let focus_handle = focus_handle.clone(); Tooltip::element(move |_window, cx| { @@ -5633,7 +5644,7 @@ impl AcpThreadView { h_flex() .gap_2() .justify_between() - .child(Label::new(title)) + .child(Label::new("Queue and Send")) .child(KeyBinding::for_action_in(&Chat, &focus_handle, cx)), ) .child( @@ -5643,15 +5654,17 @@ impl AcpThreadView { .justify_between() .border_t_1() .border_color(cx.theme().colors().border_variant) - .child(Label::new("Queue Message")) + .child(Label::new("Send Immediately")) .child(KeyBinding::for_action_in( - &QueueMessage, + &SendImmediately, &focus_handle, cx, )), ) .into_any_element() })(_window, cx) + } else { + Tooltip::for_action("Send Message", &Chat, cx) } }) .on_click(cx.listener(|this, _, window, cx| { @@ -8846,7 +8859,7 @@ pub(crate) mod tests { editor.set_text("Message 2", window, cx); }); thread_view.update_in(cx, |thread_view, window, cx| { - thread_view.send(window, cx); + thread_view.interrupt_and_send(window, cx); }); cx.update(|_, cx| { diff --git a/crates/agent_ui/src/agent_ui.rs b/crates/agent_ui/src/agent_ui.rs index bd8f9eb6ecda35ee2d3804406c07fb7637ee5557..d13257ce5fc0d58e96b8bfd04c81183514b498ce 100644 --- a/crates/agent_ui/src/agent_ui.rs +++ b/crates/agent_ui/src/agent_ui.rs @@ -123,8 +123,8 @@ actions!( ContinueWithBurnMode, /// Toggles burn mode for faster responses. ToggleBurnMode, - /// Queues a message to be sent when generation completes. - QueueMessage, + /// Interrupts the current generation and sends the message immediately. + SendImmediately, /// Sends the next queued message immediately. SendNextQueuedMessage, /// Clears all messages from the queue.