Add comment box to negative feedback reaction (#27934)

Thomas Mickley-Doyle created

Release Notes:

- N/A

Change summary

crates/agent/src/active_thread.rs | 151 ++++++++++++++++++++++++++++++--
1 file changed, 141 insertions(+), 10 deletions(-)

Detailed changes

crates/agent/src/active_thread.rs 🔗

@@ -55,6 +55,8 @@ 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>>,
 }
 
 struct RenderedMessage {
@@ -369,6 +371,8 @@ impl ActiveThread {
             notifications: Vec::new(),
             _subscriptions: subscriptions,
             notification_subscriptions: HashMap::default(),
+            showing_feedback_comments: false,
+            feedback_comments_editor: None,
         };
 
         for message in thread.read(cx).messages().cloned().collect::<Vec<_>>() {
@@ -896,19 +900,100 @@ impl ActiveThread {
     fn handle_feedback_click(
         &mut self,
         feedback: ThreadFeedback,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+    ) {
+        match feedback {
+            ThreadFeedback::Positive => {
+                let report = self
+                    .thread
+                    .update(cx, |thread, cx| thread.report_feedback(feedback, cx));
+
+                let this = cx.entity().downgrade();
+                cx.spawn(async move |_, cx| {
+                    report.await?;
+                    this.update(cx, |_this, cx| cx.notify())
+                })
+                .detach_and_log_err(cx);
+            }
+            ThreadFeedback::Negative => {
+                self.handle_show_feedback_comments(window, cx);
+            }
+        }
+    }
+
+    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)
+            });
+
+            let editor = cx.new(|cx| {
+                Editor::new(
+                    editor::EditorMode::AutoHeight { max_lines: 4 },
+                    buffer,
+                    None,
+                    window,
+                    cx,
+                )
+            });
+
+            self.feedback_comments_editor = Some(editor);
+        }
+
+        cx.notify();
+    }
+
+    fn handle_submit_comments(
+        &mut self,
+        _: &ClickEvent,
         _window: &mut Window,
         cx: &mut Context<Self>,
     ) {
-        let report = self
-            .thread
-            .update(cx, |thread, cx| thread.report_feedback(feedback, cx));
+        if let Some(editor) = self.feedback_comments_editor.clone() {
+            let comments = editor.read(cx).text(cx);
 
-        let this = cx.entity().downgrade();
-        cx.spawn(async move |_, cx| {
-            report.await?;
-            this.update(cx, |_this, cx| cx.notify())
-        })
-        .detach_and_log_err(cx);
+            // Submit negative feedback
+            let report = self.thread.update(cx, |thread, cx| {
+                thread.report_feedback(ThreadFeedback::Negative, cx)
+            });
+
+            if !comments.is_empty() {
+                let thread_id = self.thread.read(cx).id().clone();
+                let comments_value = String::from(comments.as_str());
+
+                // Log comments as a separate telemetry event
+                telemetry::event!(
+                    "Assistant Thread Feedback Comments",
+                    thread_id,
+                    comments = comments_value
+                );
+            }
+
+            self.showing_feedback_comments = false;
+            self.feedback_comments_editor = None;
+
+            let this = cx.entity().downgrade();
+            cx.spawn(async move |_, cx| {
+                report.await?;
+                this.update(cx, |_this, cx| cx.notify())
+            })
+            .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 {
@@ -1318,7 +1403,53 @@ impl ActiveThread {
             .child(styled_message)
             .when(
                 show_feedback && !self.thread.read(cx).is_generating(),
-                |parent| parent.child(feedback_items),
+                |parent| {
+                    parent
+                        .child(feedback_items)
+                        .when(self.showing_feedback_comments, |parent| {
+                            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(),
+                                            ),
+                                    )
+                                    .child(
+                                        h_flex()
+                                            .gap_1()
+                                            .justify_end()
+                                            .pb_2()
+                                            .child(
+                                                Button::new("cancel-comments", "Cancel").on_click(
+                                                    cx.listener(Self::handle_cancel_comments),
+                                                ),
+                                            )
+                                            .child(
+                                                Button::new("submit-comments", "Submit").on_click(
+                                                    cx.listener(Self::handle_submit_comments),
+                                                ),
+                                            ),
+                                    ),
+                            )
+                        })
+                },
             )
             .into_any()
     }