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

+96
View File
@@ -0,0 +1,96 @@
use gpui::{
AppContext as _, Context, Entity, IntoElement, KeyBinding, ParentElement, Render, Styled,
TestAppContext, VisualTestContext, Window, div, px,
};
use super::{press, settle};
use crate::{
agent::{CostBreakdown, HumanInputRequest},
forms::{self, TextInput},
primitives::FocusNext,
theme::Theme,
};
/// A question with two answers to pick, and the answers given.
struct Asked {
field: Entity<TextInput>,
answers: Vec<String>,
}
impl Render for Asked {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let view = cx.entity();
div().w(px(420.0)).child(
HumanInputRequest::new(
"asked",
"Keep the accents?",
&self.field,
move |answer, _, cx| {
view.update(cx, |asked, _| asked.answers.push(answer.to_string()))
},
)
.choices(["Keep them", "Lift further"]),
)
}
}
fn asked(cx: &mut TestAppContext) -> (Entity<Asked>, &mut VisualTestContext) {
cx.update(|cx| {
Theme::init(cx);
forms::bind_keys(cx);
cx.bind_keys([KeyBinding::new("tab", FocusNext, None)]);
});
let (view, cx) = cx.add_window_view(|window, cx| Asked {
field: cx.new(|cx| TextInput::new(window, cx)),
answers: Vec::new(),
});
cx.update(|window, _| window.activate_window());
settle(cx);
(view, cx)
}
#[gpui::test]
fn a_pick_answers_the_question(cx: &mut TestAppContext) {
let (view, cx) = asked(cx);
cx.update(|window, _| window.focus_next());
cx.update(|window, _| window.focus_next());
press("enter", cx);
assert_eq!(
view.read_with(cx, |asked, _| asked.answers.clone()),
["Lift further"]
);
}
#[gpui::test]
fn enter_sends_a_written_answer_and_skips_a_blank_one(cx: &mut TestAppContext) {
let (view, cx) = asked(cx);
for _ in 0..3 {
cx.update(|window, _| window.focus_next());
}
cx.simulate_input(" ");
press("enter", cx);
cx.simulate_input("Lift them to 18%");
press("enter", cx);
view.read_with(cx, |asked, cx| {
assert_eq!(asked.answers, ["Lift them to 18%"]);
assert_eq!(asked.field.read(cx).text(), "");
});
}
/// A breakdown of a session that has cost nothing yet.
struct Nothing;
impl Render for Nothing {
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
div()
.w(px(320.0))
.child(CostBreakdown::new("This session", "USD"))
}
}
#[gpui::test]
fn a_session_that_cost_nothing_draws_its_total(cx: &mut TestAppContext) {
cx.update(Theme::init);
let (_, cx) = cx.add_window_view(|_, _| Nothing);
settle(cx);
}
+1
View File
@@ -4,6 +4,7 @@ use gpui::{
};
mod changes;
mod control;
mod environment;
mod previews;