Show the context fill grade as a pie chart

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Georg Bauer
2026-07-26 09:33:14 +02:00
parent 76a5dd5b26
commit 3f2c42513f
2 changed files with 75 additions and 9 deletions

View File

@@ -845,6 +845,51 @@ fn sidebar_style(_: &Theme) -> container::Style {
container::Style::default().background(Color::from_rgb8(23, 23, 25)) container::Style::default().background(Color::from_rgb8(23, 23, 25))
} }
/// Fill grade from which the context pie warns that compaction is near.
const CONTEXT_WARN: f32 = 0.8;
/// Draws the context fill grade as a pie the size of an icon; the exact token
/// counts live in a tooltip so the composer row stays compact.
fn context_pie<'a>(fraction: f32, size: u16) -> Svg<'a> {
svg(svg::Handle::from_memory(
context_pie_svg(fraction).into_bytes(),
))
.width(size as f32)
.height(size as f32)
}
fn context_pie_svg(fraction: f32) -> String {
let fraction = fraction.clamp(0.0, 1.0);
let palette = app_theme().palette();
let fill = hex(if fraction >= CONTEXT_WARN {
Color::from_rgb8(224, 140, 58)
} else {
palette.primary
});
let track = hex(muted_text());
let wedge = if fraction >= 1.0 {
format!(r#"<circle cx="10" cy="10" r="8" fill="{fill}"/>"#)
} else {
let angle = fraction * std::f32::consts::TAU;
let (x, y) = (10.0 + 8.0 * angle.sin(), 10.0 - 8.0 * angle.cos());
let large = u8::from(fraction > 0.5);
format!(r#"<path d="M10 10 L10 2 A8 8 0 {large} 1 {x:.2} {y:.2} Z" fill="{fill}"/>"#)
};
format!(
r#"<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><circle cx="10" cy="10" r="8.5" fill="none" stroke="{track}" stroke-width="1.5" stroke-opacity="0.5"/>{wedge}</svg>"#
)
}
fn hex(color: Color) -> String {
let channel = |value: f32| (value.clamp(0.0, 1.0) * 255.0).round() as u8;
format!(
"#{:02x}{:02x}{:02x}",
channel(color.r),
channel(color.g),
channel(color.b)
)
}
// The lifetime is free: the handle owns its bytes and the style closure is // The lifetime is free: the handle owns its bytes and the style closure is
// 'static, so an icon can join a row that borrows shorter-lived data. // 'static, so an icon can join a row that borrows shorter-lived data.
fn icon<'a>(data: &'static [u8], size: u16) -> Svg<'a> { fn icon<'a>(data: &'static [u8], size: u16) -> Svg<'a> {
@@ -860,6 +905,17 @@ fn icon<'a>(data: &'static [u8], size: u16) -> Svg<'a> {
mod tests { mod tests {
use super::*; use super::*;
#[test]
fn context_pie_wedge_follows_the_fill_grade() {
assert!(context_pie_svg(0.0).contains(r#"A8 8 0 0 1 10.00 2.00"#));
assert!(context_pie_svg(0.25).contains(r#"A8 8 0 0 1 18.00 10.00"#));
assert!(context_pie_svg(0.75).contains(r#"A8 8 0 1 1 2.00 10.00"#));
assert!(context_pie_svg(1.5).contains("<circle cx=\"10\" cy=\"10\" r=\"8\""));
let warning = hex(Color::from_rgb8(224, 140, 58));
assert!(!context_pie_svg(CONTEXT_WARN - 0.01).contains(&warning));
assert!(context_pie_svg(CONTEXT_WARN).contains(&warning));
}
#[test] #[test]
fn raised_surfaces_stay_dark() { fn raised_surfaces_stay_dark() {
let theme = app_theme(); let theme = app_theme();

View File

@@ -159,18 +159,27 @@ impl App {
container( container(
column![ column![
composer, composer,
progress_bar(0.0..=1.0, context_fraction).height(3),
row![ row![
icon(ICON_PAPERCLIP, 19), icon(ICON_PAPERCLIP, 19),
text(format!( tooltip(
"{} / {} tokens ({:.0}%) • {}", context_pie(context_fraction, 19),
self.context_used, container(
self.context_limit, text(format!(
context_fraction * 100.0, "{} / {} tokens ({:.0}%)",
self.tokens_per_second.map_or_else( self.context_used,
|| "— tok/s".to_owned(), self.context_limit,
|speed| format!("{speed:.1} tok/s") context_fraction * 100.0
))
.size(12)
) )
.padding(10)
.style(preference_group_style),
tooltip::Position::Top,
)
.gap(6),
text(self.tokens_per_second.map_or_else(
|| "— tok/s".to_owned(),
|speed| format!("{speed:.1} tok/s")
)) ))
.size(11) .size(11)
.color(muted_text()), .color(muted_text()),
@@ -184,6 +193,7 @@ impl App {
.size(12), .size(12),
action, action,
] ]
.spacing(6)
.align_y(Alignment::Center), .align_y(Alignment::Center),
] ]
.spacing(8), .spacing(8),