Show the context fill grade as a pie chart
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -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();
|
||||||
|
|||||||
@@ -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),
|
||||||
|
tooltip(
|
||||||
|
context_pie(context_fraction, 19),
|
||||||
|
container(
|
||||||
text(format!(
|
text(format!(
|
||||||
"{} / {} tokens ({:.0}%) • {}",
|
"{} / {} tokens ({:.0}%)",
|
||||||
self.context_used,
|
self.context_used,
|
||||||
self.context_limit,
|
self.context_limit,
|
||||||
context_fraction * 100.0,
|
context_fraction * 100.0
|
||||||
self.tokens_per_second.map_or_else(
|
))
|
||||||
|
.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(),
|
|| "— tok/s".to_owned(),
|
||||||
|speed| format!("{speed:.1} tok/s")
|
|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),
|
||||||
|
|||||||
Reference in New Issue
Block a user