agent: Add ability to interrupt current generation with a new message (#28762)

Danilo Leal created

If you wanted to interrupt the current LLM response that's generating to
send a follow up message, you'd need to stop it first, type your new
message, and then send it. Now, you can just type your new message while
there's a response generating and send it. This will interrupt the
previous response generation and kick off a new one.

Release Notes:

- agent: Allow to send a new message while a response is generating,
interrupting the LLM to focus instead on the most recent prompt.

Change summary

crates/agent/src/message_editor.rs | 109 +++++++++++++++++++++++--------
1 file changed, 79 insertions(+), 30 deletions(-)

Detailed changes

crates/agent/src/message_editor.rs 🔗

@@ -208,6 +208,7 @@ impl MessageEditor {
         }
 
         if self.thread.read(cx).is_generating() {
+            self.stop_current_and_send_new_message(window, cx);
             return;
         }
 
@@ -297,6 +298,17 @@ impl MessageEditor {
         .detach();
     }
 
+    fn stop_current_and_send_new_message(&mut self, window: &mut Window, cx: &mut Context<Self>) {
+        let cancelled = self
+            .thread
+            .update(cx, |thread, cx| thread.cancel_last_completion(cx));
+
+        if cancelled {
+            self.set_editor_is_expanded(false, cx);
+            self.send_to_model(RequestKind::Chat, window, cx);
+        }
+    }
+
     fn handle_context_strip_event(
         &mut self,
         _context_strip: &Entity<ContextStrip>,
@@ -464,39 +476,76 @@ impl MessageEditor {
                                 let focus_handle = focus_handle.clone();
                                 move |parent| {
                                     if is_generating {
-                                        parent.child(
-                                            IconButton::new(
-                                                "stop-generation",
-                                                IconName::StopFilled,
-                                            )
-                                            .icon_color(Color::Error)
-                                            .style(ButtonStyle::Tinted(ui::TintColor::Error))
-                                            .tooltip(move |window, cx| {
-                                                Tooltip::for_action(
-                                                    "Stop Generation",
-                                                    &editor::actions::Cancel,
-                                                    window,
-                                                    cx,
+                                        parent
+                                            .when(is_editor_empty, |parent| {
+                                                parent.child(
+                                                    IconButton::new(
+                                                        "stop-generation",
+                                                        IconName::StopFilled,
+                                                    )
+                                                    .icon_color(Color::Error)
+                                                    .style(ButtonStyle::Tinted(
+                                                        ui::TintColor::Error,
+                                                    ))
+                                                    .tooltip(move |window, cx| {
+                                                        Tooltip::for_action(
+                                                            "Stop Generation",
+                                                            &editor::actions::Cancel,
+                                                            window,
+                                                            cx,
+                                                        )
+                                                    })
+                                                    .on_click({
+                                                        let focus_handle = focus_handle.clone();
+                                                        move |_event, window, cx| {
+                                                            focus_handle.dispatch_action(
+                                                                &editor::actions::Cancel,
+                                                                window,
+                                                                cx,
+                                                            );
+                                                        }
+                                                    })
+                                                    .with_animation(
+                                                        "pulsating-label",
+                                                        Animation::new(Duration::from_secs(2))
+                                                            .repeat()
+                                                            .with_easing(pulsating_between(
+                                                                0.4, 1.0,
+                                                            )),
+                                                        |icon_button, delta| {
+                                                            icon_button.alpha(delta)
+                                                        },
+                                                    ),
                                                 )
                                             })
-                                            .on_click({
-                                                let focus_handle = focus_handle.clone();
-                                                move |_event, window, cx| {
-                                                    focus_handle.dispatch_action(
-                                                        &editor::actions::Cancel,
-                                                        window,
-                                                        cx,
-                                                    );
-                                                }
+                                            .when(!is_editor_empty, |parent| {
+                                                parent.child(
+                                                    IconButton::new("send-message", IconName::Send)
+                                                        .icon_color(Color::Accent)
+                                                        .style(ButtonStyle::Filled)
+                                                        .disabled(
+                                                            !is_model_selected
+                                                                || self
+                                                                    .waiting_for_summaries_to_send,
+                                                        )
+                                                        .on_click({
+                                                            let focus_handle = focus_handle.clone();
+                                                            move |_event, window, cx| {
+                                                                focus_handle.dispatch_action(
+                                                                    &Chat, window, cx,
+                                                                );
+                                                            }
+                                                        })
+                                                        .tooltip(move |window, cx| {
+                                                            Tooltip::for_action(
+                                                                "Stop and Send New Message",
+                                                                &Chat,
+                                                                window,
+                                                                cx,
+                                                            )
+                                                        }),
+                                                )
                                             })
-                                            .with_animation(
-                                                "pulsating-label",
-                                                Animation::new(Duration::from_secs(2))
-                                                    .repeat()
-                                                    .with_easing(pulsating_between(0.4, 1.0)),
-                                                |icon_button, delta| icon_button.alpha(delta),
-                                            ),
-                                        )
                                     } else {
                                         parent.child(
                                             IconButton::new("send-message", IconName::Send)