From 39063ab3b1f449338236e274d9dbff1bee178295 Mon Sep 17 00:00:00 2001 From: Danilo Leal <67129314+danilo-leal@users.noreply.github.com> Date: Fri, 13 Feb 2026 18:33:04 -0300 Subject: [PATCH] agent_ui: Use circular progress component for displaying context window use (#49138) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR uses the recently introduced `CircularProgress` component to display context window use information. It creates more space for the message editor controls as well as simplifies the UI a little bit. Through a tooltip, we communicate the same things we communicated before (and more, actually, because rules use will be displayed there, too). Note that this doesn't touch the display of split token use (for models like GPT and whatnot). Screenshot 2026-02-13 at 6  16@2x Release Notes: - Agent: Simplified context window use display by using a circular progress component. --- crates/agent_ui/src/acp/thread_view.rs | 8 +- .../src/acp/thread_view/active_thread.rs | 100 ++++++++++++++++-- .../components/progress/circular_progress.rs | 10 +- 3 files changed, 103 insertions(+), 15 deletions(-) diff --git a/crates/agent_ui/src/acp/thread_view.rs b/crates/agent_ui/src/acp/thread_view.rs index d2ae5a26fd9567777e301c4d070c7ed7d976756e..6e7ff8486bda34cb017b98374acabc81b1f614fc 100644 --- a/crates/agent_ui/src/acp/thread_view.rs +++ b/crates/agent_ui/src/acp/thread_view.rs @@ -50,10 +50,10 @@ use terminal_view::terminal_panel::TerminalPanel; use text::{Anchor, ToPoint as _}; use theme::AgentFontSize; use ui::{ - Callout, CommonAnimationExt, ContextMenu, ContextMenuEntry, CopyButton, DecoratedIcon, - DiffStat, Disclosure, Divider, DividerColor, IconDecoration, IconDecorationKind, KeyBinding, - PopoverMenu, PopoverMenuHandle, SpinnerLabel, TintColor, Tooltip, WithScrollbar, prelude::*, - right_click_menu, + Callout, CircularProgress, CommonAnimationExt, ContextMenu, ContextMenuEntry, CopyButton, + DecoratedIcon, DiffStat, Disclosure, Divider, DividerColor, IconDecoration, IconDecorationKind, + KeyBinding, PopoverMenu, PopoverMenuHandle, SpinnerLabel, 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/acp/thread_view/active_thread.rs b/crates/agent_ui/src/acp/thread_view/active_thread.rs index 73b2408c02f2a9950a8c38b57bebc1fc1b3a51bc..1e8c7d35a3cb9214f557399a34244b7f2bf665c5 100644 --- a/crates/agent_ui/src/acp/thread_view/active_thread.rs +++ b/crates/agent_ui/src/acp/thread_view/active_thread.rs @@ -2672,7 +2672,7 @@ impl AcpThreadView { .is_some_and(|model| model.supports_split_token_display()) } - fn render_token_usage(&self, cx: &mut Context) -> Option
{ + fn render_token_usage(&self, cx: &mut Context) -> Option { let thread = self.thread.read(cx); let usage = thread.token_usage()?; let is_generating = thread.status() != ThreadStatus::Idle; @@ -2758,24 +2758,104 @@ impl AcpThreadView { .size(LabelSize::Small) .color(Color::Muted), ), - ), + ) + .into_any_element(), ) } else { let used = crate::text_thread_editor::humanize_token_count(usage.used_tokens); let max = crate::text_thread_editor::humanize_token_count(usage.max_tokens); + let progress_ratio = if usage.max_tokens > 0 { + usage.used_tokens as f32 / usage.max_tokens as f32 + } else { + 0.0 + }; + + let progress_color = if progress_ratio >= 0.85 { + cx.theme().status().warning + } else { + cx.theme().colors().text_muted + }; + let separator_color = Color::Custom(cx.theme().colors().text_disabled.opacity(0.6)); + + let percentage = format!("{}%", (progress_ratio * 100.0).round() as u32); + + let (user_rules_count, project_rules_count) = self + .as_native_thread(cx) + .map(|thread| { + let project_context = thread.read(cx).project_context().read(cx); + let user_rules = project_context.user_rules.len(); + let project_rules = project_context + .worktrees + .iter() + .filter(|wt| wt.rules_file.is_some()) + .count(); + (user_rules, project_rules) + }) + .unwrap_or((0, 0)); Some( h_flex() - .flex_shrink_0() - .gap_0p5() - .mr_1p5() - .child(token_label(used, "used-tokens-label")) + .id("circular_progress_tokens") + .mt_px() + .mr_1() .child( - Label::new("/") - .size(LabelSize::Small) - .color(separator_color), + CircularProgress::new( + usage.used_tokens as f32, + usage.max_tokens as f32, + px(16.0), + cx, + ) + .stroke_width(px(2.)) + .progress_color(progress_color), ) - .child(Label::new(max).size(LabelSize::Small).color(Color::Muted)), + .tooltip(Tooltip::element({ + move |_, cx| { + v_flex() + .min_w_40() + .child( + Label::new("Context") + .color(Color::Muted) + .size(LabelSize::Small), + ) + .child( + h_flex() + .gap_0p5() + .child(Label::new(percentage.clone())) + .child(Label::new("•").color(separator_color).mx_1()) + .child(Label::new(used.clone())) + .child(Label::new("/").color(separator_color)) + .child(Label::new(max.clone()).color(Color::Muted)), + ) + .when(user_rules_count > 0 || project_rules_count > 0, |this| { + this.child( + v_flex() + .mt_1p5() + .pt_1p5() + .border_t_1() + .border_color(cx.theme().colors().border_variant) + .child( + Label::new("Rules") + .color(Color::Muted) + .size(LabelSize::Small), + ) + .when(user_rules_count > 0, |this| { + this.child(Label::new(format!( + "{} user rules", + user_rules_count + ))) + }) + .when(project_rules_count > 0, |this| { + this.child(Label::new(format!( + "{} project rules", + project_rules_count + ))) + }), + ) + }) + .into_any_element() + } + })) + .into_any_element(), ) } } diff --git a/crates/ui/src/components/progress/circular_progress.rs b/crates/ui/src/components/progress/circular_progress.rs index ddd620282dc81836cefa79fc65f85bfb0bec7078..f571332edaec7387e5d083596db42879c56d01ac 100644 --- a/crates/ui/src/components/progress/circular_progress.rs +++ b/crates/ui/src/components/progress/circular_progress.rs @@ -10,6 +10,7 @@ pub struct CircularProgress { value: f32, max_value: f32, size: Pixels, + stroke_width: Pixels, bg_color: Hsla, progress_color: Hsla, } @@ -20,6 +21,7 @@ impl CircularProgress { value, max_value, size, + stroke_width: px(4.0), bg_color: cx.theme().colors().border_variant, progress_color: cx.theme().status().info, } @@ -43,6 +45,12 @@ impl CircularProgress { self } + /// Sets the stroke width of the circular progress indicator. + pub fn stroke_width(mut self, stroke_width: Pixels) -> Self { + self.stroke_width = stroke_width; + self + } + /// Sets the background circle color. pub fn bg_color(mut self, color: Hsla) -> Self { self.bg_color = color; @@ -72,7 +80,7 @@ impl RenderOnce for CircularProgress { let center_x = bounds.origin.x + bounds.size.width / 2.0; let center_y = bounds.origin.y + bounds.size.height / 2.0; - let stroke_width = px(4.0); + let stroke_width = self.stroke_width; let radius = (size / 2.0) - stroke_width; // Draw background circle (full 360 degrees)