agent: Refine feedback message input (#27948)

Danilo Leal and Bennet Bo Fenner created

<img
src="https://github.com/user-attachments/assets/cde37a88-9973-4c27-80b7-459f5e986c74"
width="650" />

Release Notes:

- N/A

---------

Co-authored-by: Bennet Bo Fenner <bennetbo@gmx.de>

Change summary

assets/keymaps/default-linux.json |   9 +
assets/keymaps/default-macos.json |   9 +
crates/agent/src/active_thread.rs | 161 +++++++++++++++++---------------
3 files changed, 104 insertions(+), 75 deletions(-)

Detailed changes

assets/keymaps/default-linux.json 🔗

@@ -657,6 +657,15 @@
       "alt-enter": "editor::Newline"
     }
   },
+  {
+    "context": "AgentFeedbackMessageEditor > Editor",
+    "use_key_equivalents": true,
+    "bindings": {
+      "escape": "menu::Cancel",
+      "enter": "menu::Confirm",
+      "alt-enter": "editor::Newline"
+    }
+  },
   {
     "context": "ContextStrip",
     "bindings": {

assets/keymaps/default-macos.json 🔗

@@ -317,6 +317,15 @@
       "alt-enter": "editor::Newline"
     }
   },
+  {
+    "context": "AgentFeedbackMessageEditor > Editor",
+    "use_key_equivalents": true,
+    "bindings": {
+      "escape": "menu::Cancel",
+      "enter": "menu::Confirm",
+      "alt-enter": "editor::Newline"
+    }
+  },
   {
     "context": "ContextStrip",
     "use_key_equivalents": true,

crates/agent/src/active_thread.rs 🔗

@@ -55,8 +55,7 @@ pub struct ActiveThread {
     notifications: Vec<WindowHandle<AgentNotification>>,
     _subscriptions: Vec<Subscription>,
     notification_subscriptions: HashMap<WindowHandle<AgentNotification>, Vec<Subscription>>,
-    showing_feedback_comments: bool,
-    feedback_comments_editor: Option<Entity<Editor>>,
+    feedback_message_editor: Option<Entity<Editor>>,
 }
 
 struct RenderedMessage {
@@ -371,8 +370,7 @@ impl ActiveThread {
             notifications: Vec::new(),
             _subscriptions: subscriptions,
             notification_subscriptions: HashMap::default(),
-            showing_feedback_comments: false,
-            feedback_comments_editor: None,
+            feedback_message_editor: None,
         };
 
         for message in thread.read(cx).messages().cloned().collect::<Vec<_>>() {
@@ -923,37 +921,37 @@ impl ActiveThread {
     }
 
     fn handle_show_feedback_comments(&mut self, window: &mut Window, cx: &mut Context<Self>) {
-        self.showing_feedback_comments = true;
-
-        if self.feedback_comments_editor.is_none() {
-            let buffer = cx.new(|cx| {
-                let empty_string = String::new();
-                MultiBuffer::singleton(cx.new(|cx| Buffer::local(empty_string, cx)), cx)
-            });
+        if self.feedback_message_editor.is_some() {
+            return;
+        }
 
-            let editor = cx.new(|cx| {
-                Editor::new(
-                    editor::EditorMode::AutoHeight { max_lines: 4 },
-                    buffer,
-                    None,
-                    window,
-                    cx,
-                )
-            });
+        let buffer = cx.new(|cx| {
+            let empty_string = String::new();
+            MultiBuffer::singleton(cx.new(|cx| Buffer::local(empty_string, cx)), cx)
+        });
 
-            self.feedback_comments_editor = Some(editor);
-        }
+        let editor = cx.new(|cx| {
+            let mut editor = Editor::new(
+                editor::EditorMode::AutoHeight { max_lines: 4 },
+                buffer,
+                None,
+                window,
+                cx,
+            );
+            editor.set_placeholder_text(
+                "What went wrong? Share your feedback so we can improve.",
+                cx,
+            );
+            editor
+        });
 
+        editor.read(cx).focus_handle(cx).focus(window);
+        self.feedback_message_editor = Some(editor);
         cx.notify();
     }
 
-    fn handle_submit_comments(
-        &mut self,
-        _: &ClickEvent,
-        _window: &mut Window,
-        cx: &mut Context<Self>,
-    ) {
-        let Some(editor) = self.feedback_comments_editor.clone() else {
+    fn submit_feedback_message(&mut self, cx: &mut Context<Self>) {
+        let Some(editor) = self.feedback_message_editor.clone() else {
             return;
         };
 
@@ -968,8 +966,7 @@ impl ActiveThread {
             telemetry::event!("Assistant Thread Feedback Comments", thread_id, comments);
         }
 
-        self.showing_feedback_comments = false;
-        self.feedback_comments_editor = None;
+        self.feedback_message_editor = None;
 
         let this = cx.entity().downgrade();
         cx.spawn(async move |_, cx| {
@@ -979,17 +976,6 @@ impl ActiveThread {
         .detach_and_log_err(cx);
     }
 
-    fn handle_cancel_comments(
-        &mut self,
-        _: &ClickEvent,
-        _window: &mut Window,
-        cx: &mut Context<Self>,
-    ) {
-        self.showing_feedback_comments = false;
-        self.feedback_comments_editor = None;
-        cx.notify();
-    }
-
     fn render_message(&self, ix: usize, window: &mut Window, cx: &mut Context<Self>) -> AnyElement {
         let message_id = self.messages[ix];
         let Some(message) = self.thread.read(cx).message(message_id) else {
@@ -1398,51 +1384,76 @@ impl ActiveThread {
             .when(
                 show_feedback && !self.thread.read(cx).is_generating(),
                 |parent| {
-                    parent
-                        .child(feedback_items)
-                        .when(self.showing_feedback_comments, |parent| {
+                    parent.child(feedback_items).when_some(
+                        self.feedback_message_editor.clone(),
+                        |parent, feedback_editor| {
+                            let focus_handle = feedback_editor.focus_handle(cx);
                             parent.child(
                                 v_flex()
-                                    .gap_1()
-                                    .px_4()
-                                    .child(
-                                        Label::new(
-                                            "Please share your feedback to help us improve:",
-                                        )
-                                        .size(LabelSize::Small),
-                                    )
-                                    .child(
-                                        div()
-                                            .p_2()
-                                            .rounded_md()
-                                            .border_1()
-                                            .border_color(cx.theme().colors().border)
-                                            .bg(cx.theme().colors().editor_background)
-                                            .child(
-                                                self.feedback_comments_editor
-                                                    .as_ref()
-                                                    .unwrap()
-                                                    .clone(),
-                                            ),
-                                    )
+                                    .key_context("AgentFeedbackMessageEditor")
+                                    .on_action(cx.listener(|this, _: &menu::Cancel, _, cx| {
+                                        this.feedback_message_editor = None;
+                                        cx.notify();
+                                    }))
+                                    .on_action(cx.listener(|this, _: &menu::Confirm, _, cx| {
+                                        this.submit_feedback_message(cx);
+                                        cx.notify();
+                                    }))
+                                    .on_action(cx.listener(Self::confirm_editing_message))
+                                    .mx_4()
+                                    .mb_3()
+                                    .p_2()
+                                    .rounded_md()
+                                    .border_1()
+                                    .border_color(cx.theme().colors().border)
+                                    .bg(cx.theme().colors().editor_background)
+                                    .child(feedback_editor)
                                     .child(
                                         h_flex()
                                             .gap_1()
                                             .justify_end()
-                                            .pb_2()
                                             .child(
-                                                Button::new("cancel-comments", "Cancel").on_click(
-                                                    cx.listener(Self::handle_cancel_comments),
-                                                ),
+                                                Button::new("dismiss-feedback-message", "Cancel")
+                                                    .label_size(LabelSize::Small)
+                                                    .key_binding(
+                                                        KeyBinding::for_action_in(
+                                                            &menu::Cancel,
+                                                            &focus_handle,
+                                                            window,
+                                                            cx,
+                                                        )
+                                                        .map(|kb| kb.size(rems_from_px(10.))),
+                                                    )
+                                                    .on_click(cx.listener(|this, _, _, cx| {
+                                                        this.feedback_message_editor = None;
+                                                        cx.notify();
+                                                    })),
                                             )
                                             .child(
-                                                Button::new("submit-comments", "Submit").on_click(
-                                                    cx.listener(Self::handle_submit_comments),
-                                                ),
+                                                Button::new(
+                                                    "submit-feedback-message",
+                                                    "Share Feedback",
+                                                )
+                                                .style(ButtonStyle::Tinted(ui::TintColor::Accent))
+                                                .label_size(LabelSize::Small)
+                                                .key_binding(
+                                                    KeyBinding::for_action_in(
+                                                        &menu::Confirm,
+                                                        &focus_handle,
+                                                        window,
+                                                        cx,
+                                                    )
+                                                    .map(|kb| kb.size(rems_from_px(10.))),
+                                                )
+                                                .on_click(cx.listener(|this, _, _, cx| {
+                                                    this.submit_feedback_message(cx);
+                                                    cx.notify();
+                                                })),
                                             ),
                                     ),
                             )
-                        })
+                        },
+                    )
                 },
             )
             .into_any()