diff --git a/crates/agent_ui/src/acp/thread_view.rs b/crates/agent_ui/src/acp/thread_view.rs index bfaaf1c2f03637253fcf2392f373c8887ed9681b..8ef7c7bbb0597e704a4b3b98b694601c27cacd5e 100644 --- a/crates/agent_ui/src/acp/thread_view.rs +++ b/crates/agent_ui/src/acp/thread_view.rs @@ -2051,6 +2051,15 @@ impl AcpThreadView { .into_any(), }; + let needs_confirmation = if let AgentThreadEntry::ToolCall(tool_call) = entry { + matches!( + tool_call.status, + ToolCallStatus::WaitingForConfirmation { .. } + ) + } else { + false + }; + let Some(thread) = self.thread() else { return primary; }; @@ -2059,7 +2068,13 @@ impl AcpThreadView { v_flex() .w_full() .child(primary) - .child(self.render_thread_controls(&thread, cx)) + .map(|this| { + if needs_confirmation { + this.child(self.render_generating(true)) + } else { + this.child(self.render_thread_controls(&thread, cx)) + } + }) .when_some( self.thread_feedback.comments_editor.clone(), |this, editor| this.child(Self::render_feedback_feedback_editor(editor, cx)), @@ -4829,6 +4844,31 @@ impl AcpThreadView { } } + fn render_generating(&self, confirmation: bool) -> impl IntoElement { + h_flex() + .id("generating-spinner") + .py_2() + .px(rems_from_px(22.)) + .map(|this| { + if confirmation { + this.gap_2() + .child( + h_flex() + .w_2() + .child(SpinnerLabel::sand().size(LabelSize::Small)), + ) + .child( + LoadingLabel::new("Waiting Confirmation") + .size(LabelSize::Small) + .color(Color::Muted), + ) + } else { + this.child(SpinnerLabel::new().size(LabelSize::Small)) + } + }) + .into_any_element() + } + fn render_thread_controls( &self, thread: &Entity, @@ -4836,12 +4876,7 @@ impl AcpThreadView { ) -> impl IntoElement { let is_generating = matches!(thread.read(cx).status(), ThreadStatus::Generating); if is_generating { - return h_flex().id("thread-controls-container").child( - div() - .py_2() - .px(rems_from_px(22.)) - .child(SpinnerLabel::new().size(LabelSize::Small)), - ); + return self.render_generating(false).into_any_element(); } let open_as_markdown = IconButton::new("open-as-markdown", IconName::FileMarkdown) @@ -4929,7 +4964,10 @@ impl AcpThreadView { ); } - container.child(open_as_markdown).child(scroll_to_top) + container + .child(open_as_markdown) + .child(scroll_to_top) + .into_any_element() } fn render_feedback_feedback_editor(editor: Entity, cx: &Context) -> Div { diff --git a/crates/ui/src/components/label/loading_label.rs b/crates/ui/src/components/label/loading_label.rs index 2a1e7059794d2ebd61399e5f7bdb85a8a8ac28b3..0b6b027e4775aa960df975c0507e4ac08fbbb545 100644 --- a/crates/ui/src/components/label/loading_label.rs +++ b/crates/ui/src/components/label/loading_label.rs @@ -1,5 +1,5 @@ use crate::prelude::*; -use gpui::{Animation, AnimationExt, FontWeight, pulsating_between}; +use gpui::{Animation, AnimationExt, FontWeight}; use std::time::Duration; #[derive(IntoElement)] @@ -84,38 +84,29 @@ impl RenderOnce for LoadingLabel { fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement { let text = self.text.clone(); - self.base - .color(Color::Muted) - .with_animations( - "loading_label", - vec![ - Animation::new(Duration::from_secs(1)), - Animation::new(Duration::from_secs(1)).repeat(), - ], - move |mut label, animation_ix, delta| { - match animation_ix { - 0 => { - let chars_to_show = (delta * text.len() as f32).ceil() as usize; - let text = SharedString::from(text[0..chars_to_show].to_string()); - label.set_text(text); - } - 1 => match delta { - d if d < 0.25 => label.set_text(text.clone()), - d if d < 0.5 => label.set_text(format!("{}.", text)), - d if d < 0.75 => label.set_text(format!("{}..", text)), - _ => label.set_text(format!("{}...", text)), - }, - _ => {} + self.base.color(Color::Muted).with_animations( + "loading_label", + vec![ + Animation::new(Duration::from_secs(1)), + Animation::new(Duration::from_secs(1)).repeat(), + ], + move |mut label, animation_ix, delta| { + match animation_ix { + 0 => { + let chars_to_show = (delta * text.len() as f32).ceil() as usize; + let text = SharedString::from(text[0..chars_to_show].to_string()); + label.set_text(text); } - label - }, - ) - .with_animation( - "pulsating-label", - Animation::new(Duration::from_secs(2)) - .repeat() - .with_easing(pulsating_between(0.6, 1.)), - |label, delta| label.map_element(|label| label.alpha(delta)), - ) + 1 => match delta { + d if d < 0.25 => label.set_text(text.clone()), + d if d < 0.5 => label.set_text(format!("{}.", text)), + d if d < 0.75 => label.set_text(format!("{}..", text)), + _ => label.set_text(format!("{}...", text)), + }, + _ => {} + } + label + }, + ) } } diff --git a/crates/ui/src/components/label/spinner_label.rs b/crates/ui/src/components/label/spinner_label.rs index b7b65fbcc98c175e4407d72c3e07df236364f552..de88e9bb7ab04a3d595183513c2b00da70e172aa 100644 --- a/crates/ui/src/components/label/spinner_label.rs +++ b/crates/ui/src/components/label/spinner_label.rs @@ -8,6 +8,7 @@ pub enum SpinnerVariant { #[default] Dots, DotsVariant, + Sand, } /// A spinner indication, based on the label component, that loops through @@ -41,6 +42,11 @@ impl SpinnerVariant { match self { SpinnerVariant::Dots => vec!["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"], SpinnerVariant::DotsVariant => vec!["⣼", "⣹", "⢻", "⠿", "⡟", "⣏", "⣧", "⣶"], + SpinnerVariant::Sand => vec![ + "⠁", "⠂", "⠄", "⡀", "⡈", "⡐", "⡠", "⣀", "⣁", "⣂", "⣄", "⣌", "⣔", "⣤", "⣥", "⣦", + "⣮", "⣶", "⣷", "⣿", "⡿", "⠿", "⢟", "⠟", "⡛", "⠛", "⠫", "⢋", "⠋", "⠍", "⡉", "⠉", + "⠑", "⠡", "⢁", + ], } } @@ -48,6 +54,7 @@ impl SpinnerVariant { match self { SpinnerVariant::Dots => Duration::from_millis(1000), SpinnerVariant::DotsVariant => Duration::from_millis(1000), + SpinnerVariant::Sand => Duration::from_millis(2000), } } @@ -55,6 +62,7 @@ impl SpinnerVariant { match self { SpinnerVariant::Dots => "spinner_label_dots", SpinnerVariant::DotsVariant => "spinner_label_dots_variant", + SpinnerVariant::Sand => "spinner_label_dots_variant_2", } } } @@ -83,6 +91,10 @@ impl SpinnerLabel { pub fn dots_variant() -> Self { Self::with_variant(SpinnerVariant::DotsVariant) } + + pub fn sand() -> Self { + Self::with_variant(SpinnerVariant::Sand) + } } impl LabelCommon for SpinnerLabel { @@ -185,6 +197,7 @@ impl Component for SpinnerLabel { "Dots Variant", SpinnerLabel::dots_variant().into_any_element(), ), + single_example("Sand Variant", SpinnerLabel::sand().into_any_element()), ]; Some(example_group(examples).vertical().into_any_element())