From 376a45528dff2aff9c25e4a93d0cb1e5bf266e70 Mon Sep 17 00:00:00 2001 From: Danilo Leal <67129314+danilo-leal@users.noreply.github.com> Date: Mon, 4 Nov 2024 10:12:27 -0300 Subject: [PATCH] assistant: Improve role button loading state (#20125) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We've received feedback that it wasn't clear how to cancel/interrupt the LLM while it's generating a response. Additionally, I also had folks telling me that the loading state was hard to notice—the pulsating animation is too subtle on its own. This PR attempts to improve both of these things. The end result is: ![llm](https://github.com/user-attachments/assets/57a94f8a-c254-4011-adc0-7c63ed13daa1) Release Notes: - N/A --- crates/assistant/src/assistant_panel.rs | 117 +++++++++++++++++------- 1 file changed, 84 insertions(+), 33 deletions(-) diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index 4f0462b66a..9a16b032ce 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -2598,57 +2598,108 @@ impl ContextEditor { let context = self.context.clone(); move |cx| { let message_id = MessageId(message.timestamp); - let show_spinner = message.role == Role::Assistant + let llm_loading = message.role == Role::Assistant && message.status == MessageStatus::Pending; - let label = match message.role { - Role::User => { - Label::new("You").color(Color::Default).into_any_element() - } + let (label, spinner, note) = match message.role { + Role::User => ( + Label::new("You").color(Color::Default).into_any_element(), + None, + None, + ), Role::Assistant => { - let label = Label::new("Assistant").color(Color::Info); - if show_spinner { - label + let base_label = Label::new("Assistant").color(Color::Info); + let mut spinner = None; + let mut note = None; + let animated_label = if llm_loading { + base_label .with_animation( "pulsating-label", Animation::new(Duration::from_secs(2)) .repeat() - .with_easing(pulsating_between(0.4, 0.8)), + .with_easing(pulsating_between(0.3, 0.9)), |label, delta| label.alpha(delta), ) .into_any_element() } else { - label.into_any_element() + base_label.into_any_element() + }; + if llm_loading { + spinner = Some( + Icon::new(IconName::ArrowCircle) + .size(IconSize::XSmall) + .color(Color::Muted) + .with_animation( + "arrow-circle", + Animation::new(Duration::from_secs(2)).repeat(), + |icon, delta| { + icon.transform(Transformation::rotate( + percentage(delta), + )) + }, + ) + .into_any_element(), + ); + note = Some( + div() + .font( + theme::ThemeSettings::get_global(cx) + .buffer_font + .clone(), + ) + .child( + Label::new("Press 'esc' to cancel") + .color(Color::Muted) + .size(LabelSize::XSmall), + ) + .into_any_element(), + ); } + (animated_label, spinner, note) } - - Role::System => Label::new("System") - .color(Color::Warning) - .into_any_element(), + Role::System => ( + Label::new("System") + .color(Color::Warning) + .into_any_element(), + None, + None, + ), }; - let sender = ButtonLike::new("role") - .style(ButtonStyle::Filled) - .child(label) - .tooltip(|cx| { - Tooltip::with_meta( - "Toggle message role", - None, - "Available roles: You (User), Assistant, System", - cx, - ) - }) - .on_click({ - let context = context.clone(); - move |_, cx| { - context.update(cx, |context, cx| { - context.cycle_message_roles( - HashSet::from_iter(Some(message_id)), + let sender = h_flex() + .items_center() + .gap_2() + .child( + ButtonLike::new("role") + .style(ButtonStyle::Filled) + .child( + h_flex() + .items_center() + .gap_1p5() + .child(label) + .children(spinner), + ) + .tooltip(|cx| { + Tooltip::with_meta( + "Toggle message role", + None, + "Available roles: You (User), Assistant, System", cx, ) }) - } - }); + .on_click({ + let context = context.clone(); + move |_, cx| { + context.update(cx, |context, cx| { + context.cycle_message_roles( + HashSet::from_iter(Some(message_id)), + cx, + ) + }) + } + }), + ) + .children(note); h_flex() .id(("message_header", message_id.as_u64()))