T24f Agent: control and cost

HumanInputRequest asks a question with answers to pick and a line to
write one; CostBreakdown totals a session and splits it by part.
InterruptButton, TokenUsageChart, TraceViewer and EvalResultTable
point to chat::stop_button, a stacked BarChart, TimelineProfiler and
DataTable.

UsageBar shows amounts as its owner writes them. MemoryPanel and
HumanInputRequest share agent::line::send_line; the chapter's bool
callbacks take forms::OnFlag.

DataTable scrolls columns wider than its box sideways, pinned or not,
and leaves a plain wheel to the page; sortable headers are Tab stops.
TimelineProfiler fits its ruler to its lane, its spans are Tab stops,
and a picked span takes accent.
This commit is contained in:
ZacharyZhang-NY committed 2026-09-26 11:39:24 -04:00
1 parent 061be8a0ff
commit 37e53a5ecb
20 files changed
+934 -180

No files matched your search

+32 -3
View File
@@ -1,4 +1,7 @@
use std::f32::consts::{FRAC_PI_4, TAU};
use std::{
f32::consts::{FRAC_PI_4, TAU},
rc::Rc,
};
use gpui::{
Animation, AnimationExt, App, ElementId, FontWeight, Hsla, IntoElement, ParentElement,
@@ -282,11 +285,14 @@ impl RenderOnce for Gauge {
}
}
/// How a whole divides: one bar of colored parts with the free rest as track, and a legend of each part's share.
type Amounts = Rc<dyn Fn(f64) -> String>;
/// How a whole divides: one bar of colored parts with the free rest as track, and a legend of each part's share or amount.
#[derive(IntoElement)]
pub struct UsageBar {
total: f64,
parts: Vec<(SharedString, f64)>,
amounts: Option<Amounts>,
}
impl UsageBar {
@@ -299,9 +305,16 @@ impl UsageBar {
Self {
total,
parts: Vec::new(),
amounts: None,
}
}
/// Shows each part's amount, as `write` puts it, in place of its share.
pub fn amounts(mut self, write: impl Fn(f64) -> String + 'static) -> Self {
self.amounts = Some(Rc::new(write));
self
}
pub fn part(mut self, name: impl Into<SharedString>, amount: f64) -> Self {
assert!(amount >= 0.0, "a part cannot be negative, got {amount}");
self.parts.push((name.into(), amount));
@@ -320,6 +333,14 @@ impl UsageBar {
);
(used, used >= self.total * (1.0 - 1e-9))
}
/// A part's figure in the legend: its amount as `amounts` writes it, or its share.
fn figure(&self, amount: f64) -> String {
match &self.amounts {
Some(write) => write(amount),
None => format::percent(amount / self.total, 0, false),
}
}
}
impl RenderOnce for UsageBar {
@@ -372,7 +393,7 @@ impl RenderOnce for UsageBar {
.child(
tabular(div())
.text_color(colors.fg_muted)
.child(format::percent(amount / total, 0, false)),
.child(self.figure(*amount)),
)
});
div().flex().flex_col().gap_2().child(bar).child(
@@ -398,6 +419,14 @@ mod tests {
assert!(!UsageBar::new(1.0).part("a", 0.5).used().1);
}
#[test]
fn a_part_reads_as_its_share_or_as_written() {
let bar = UsageBar::new(4.0).part("a", 1.0);
assert_eq!(bar.figure(1.0), "25%");
let bar = bar.amounts(|amount| format!("{amount} GB"));
assert_eq!(bar.figure(1.0), "1 GB");
}
#[test]
fn a_share_turns_amber_then_red() {
let limits = (0.8, 0.95);