From 8e786bcd871fa3545a4aa17d20c4e2440fe73d3b 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 c319b7c6ae..be7a2ff8c1 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 8fe72ecbc6..a0bd957bda 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 637bc66f0c..05a65626d0 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 81a3ba715d..ee25811e6c 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 1378a9f02d..f1d70741e8 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 bd8f9eb6ec..d13257ce5f 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.