From 36a23c2ff12ff0007f6df3abe6d6997df66b1c23 Mon Sep 17 00:00:00 2001 From: PuQing Date: Wed, 8 Apr 2026 05:59:30 +0800 Subject: [PATCH] Reduce agent spinner GPU usage (#51756) Closes #39532 ## Summary Move the long-running agent "generating" spinner into a dedicated `GeneratingSpinner` view. Previously, the generating state rendered `SpinnerLabel` inline as part of the full thread view. This change keeps the same spinner styling, but isolates it into its own small view so the animation no longer needs to live directly inside the larger thread UI subtree while the agent is running. before: image after: image ## Testing - `cargo run -j 4 ~/Downloads/WorkSpace/zed/` - `cargo check -p agent_ui` Release Notes: - Fixed high GPU usage from the agent panel's generating spinner while an agent is running. --------- Co-authored-by: Anthony Eid --- crates/agent_ui/src/conversation_view.rs | 4 +- .../src/conversation_view/thread_view.rs | 56 +++++++++++++++++-- 2 files changed, 53 insertions(+), 7 deletions(-) diff --git a/crates/agent_ui/src/conversation_view.rs b/crates/agent_ui/src/conversation_view.rs index 7c9acfdf27d5b750afe4b8817af7f657f5fcdecc..e2b02d814be7950afd061ac9778be6587527e016 100644 --- a/crates/agent_ui/src/conversation_view.rs +++ b/crates/agent_ui/src/conversation_view.rs @@ -54,8 +54,8 @@ use theme_settings::AgentFontSize; use ui::{ Callout, CircularProgress, CommonAnimationExt, ContextMenu, ContextMenuEntry, CopyButton, DecoratedIcon, DiffStat, Disclosure, Divider, DividerColor, IconDecoration, IconDecorationKind, - KeyBinding, PopoverMenu, PopoverMenuHandle, SpinnerLabel, TintColor, Tooltip, WithScrollbar, - prelude::*, right_click_menu, + KeyBinding, PopoverMenu, PopoverMenuHandle, TintColor, Tooltip, WithScrollbar, prelude::*, + right_click_menu, }; use util::{ResultExt, size::format_file_size, time::duration_alt_display}; use util::{debug_panic, defer}; diff --git a/crates/agent_ui/src/conversation_view/thread_view.rs b/crates/agent_ui/src/conversation_view/thread_view.rs index 27ebadade8047db5f2b4de63c5c3731708d9af59..63df627cade5b96f47cbc338914780c23934e56d 100644 --- a/crates/agent_ui/src/conversation_view/thread_view.rs +++ b/crates/agent_ui/src/conversation_view/thread_view.rs @@ -14,7 +14,7 @@ use gpui::{Corner, List}; use heapless::Vec as ArrayVec; use language_model::{LanguageModelEffortLevel, Speed}; use settings::update_settings_file; -use ui::{ButtonLike, SplitButton, SplitButtonStyle, Tab}; +use ui::{ButtonLike, SpinnerLabel, SpinnerVariant, SplitButton, SplitButtonStyle, Tab}; use workspace::SERIALIZATION_THROTTLE_TIME; use super::*; @@ -164,6 +164,46 @@ impl ThreadFeedbackState { } } +struct GeneratingSpinner { + variant: SpinnerVariant, +} + +impl GeneratingSpinner { + fn new(variant: SpinnerVariant) -> Self { + Self { variant } + } +} + +impl Render for GeneratingSpinner { + fn render(&mut self, _window: &mut Window, _cx: &mut Context) -> impl IntoElement { + SpinnerLabel::with_variant(self.variant).size(LabelSize::Small) + } +} + +#[derive(IntoElement)] +struct GeneratingSpinnerElement { + variant: SpinnerVariant, +} + +impl GeneratingSpinnerElement { + fn new(variant: SpinnerVariant) -> Self { + Self { variant } + } +} + +impl RenderOnce for GeneratingSpinnerElement { + fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement { + let id = match self.variant { + SpinnerVariant::Dots => "generating-spinner-view", + SpinnerVariant::Sand => "confirmation-spinner-view", + _ => "spinner-view", + }; + window.with_id(id, |window| { + window.use_state(cx, |_, _| GeneratingSpinner::new(self.variant)) + }) + } +} + pub enum AcpThreadViewEvent { FirstSendRequested { content: Vec }, MessageSentOrQueued, @@ -4275,10 +4315,10 @@ impl Render for TokenUsageTooltip { } impl ThreadView { - pub(crate) fn render_entries(&mut self, cx: &mut Context) -> List { + fn render_entries(&mut self, cx: &mut Context) -> List { list( self.list_state.clone(), - cx.processor(|this, index: usize, window, cx| { + cx.processor(move |this, index: usize, window, cx| { let entries = this.thread.read(cx).entries(); if let Some(entry) = entries.get(index) { this.render_entry(index, entries.len(), entry, window, cx) @@ -5193,7 +5233,8 @@ impl ThreadView { this.child( h_flex() .w_2() - .child(SpinnerLabel::sand().size(LabelSize::Small)), + .justify_center() + .child(GeneratingSpinnerElement::new(SpinnerVariant::Sand)), ) .child( div().min_w(rems(8.)).child( @@ -5205,7 +5246,12 @@ impl ThreadView { } else if is_blocked_on_terminal_command { this } else { - this.child(SpinnerLabel::new().size(LabelSize::Small)) + this.child( + h_flex() + .w_2() + .justify_center() + .child(GeneratingSpinnerElement::new(SpinnerVariant::Dots)), + ) } }) .when_some(elapsed_label, |this, elapsed| {