T24c: Agent, changes

- FileChangeCard: a proposed change to one file, its path and counts,
  Reject and Accept in a slot that keeps presses off the header; opened,
  the diff through a headless git::DiffViewer with its folds opening
- MultiFileDiffReview: every file at once, the total counts, how many are
  reviewed, Reject all and Accept all; verdicts as collab::Decision
- git::DiffViewer gains headless, leaving the file's header to its owner
- CommandExecutionCard is terminal::CommandBlock
- gallery: three proposed files and a command's block
This commit is contained in:
ZacharyZhang-NY committed 2026-09-26 08:37:45 -04:00
1 parent 859a9051d8
commit 0a44a2d905
9 files changed
+591 -26

No files matched your search

+93
View File
@@ -0,0 +1,93 @@
use std::time::Duration;
use ely_gpui_component::{
agent::{ChangeState, FileChange, MultiFileDiffReview},
collab::Decision,
terminal::{CommandBlock, CommandState},
};
use gpui::{App, IntoElement, ParentElement, Styled, Window, div, px};
use crate::{
probe::probe,
ui::{keep, section, set},
};
const LIFT_OLD: &str =
"pub fn lift(color: Hsla, by: f32) -> Hsla {\n color.mix(white(), by)\n}\n";
const LIFT_NEW: &str = "pub fn lift(color: Hsla, by: f32, dark: bool) -> Hsla {\n let by = if dark { by * 1.5 } else { by };\n color.mix(white(), by.min(1.0))\n}\n";
const TOKENS_OLD: &str =
"pub const ACCENT_LIFT: f32 = 0.12;\npub const SURFACE_LIFT: f32 = 0.04;\n";
const TOKENS_NEW: &str =
"pub const ACCENT_LIFT: f32 = 0.18;\npub const SURFACE_LIFT: f32 = 0.04;\n";
const NOTE_NEW: &str = "## 0.9\n\nDark themes lift their accents further.\n";
fn changes() -> Vec<FileChange> {
[
("src/theme/lift.rs", LIFT_OLD, LIFT_NEW),
("src/theme/tokens.rs", TOKENS_OLD, TOKENS_NEW),
("CHANGELOG.md", "", NOTE_NEW),
]
.into_iter()
.map(|(path, old, new)| FileChange {
path: path.into(),
old: old.into(),
new: new.into(),
state: ChangeState::Proposed,
})
.collect()
}
/// `changes` after `decision`.
fn decided(mut changes: Vec<FileChange>, decision: Decision) -> Vec<FileChange> {
let verdict = |accepted| {
if accepted {
ChangeState::Accepted
} else {
ChangeState::Rejected
}
};
match decision {
Decision::Accept(ix) => changes[ix].state = verdict(true),
Decision::Reject(ix) => changes[ix].state = verdict(false),
Decision::AcceptAll | Decision::RejectAll => {
let state = verdict(decision == Decision::AcceptAll);
for change in changes
.iter_mut()
.filter(|change| change.state == ChangeState::Proposed)
{
change.state = state;
}
}
}
changes
}
pub fn changes_section(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
let files = keep("agent-changes", changes, window, cx);
let now = files.read(cx).clone();
section(
"FileChangeCard / MultiFileDiff Review / CommandExecutionCard",
"What an agent would change, file by file: how much moves, the diff a press away, and a verdict each or for all. A command it ran is terminal::CommandBlock.",
cx,
)
.child(
probe(
"agent-changes",
div().w(px(640.)).child(
MultiFileDiffReview::new("agent-review", now).on_decide(move |decision, _, cx| {
let next = decided(files.read(cx).clone(), decision);
set(&files, next, cx)
}),
),
),
)
.child(
div().w(px(640.)).child(CommandBlock::new(
"agent-command",
"~/ely",
"cargo test --lib theme",
"\u{1b}[32m Compiling\u{1b}[0m ely-gpui-component v0.9.0\n\u{1b}[32m Finished\u{1b}[0m test profile in 8.4s\nrunning 12 tests\ntest theme::lift_keeps_hue ... \u{1b}[32mok\u{1b}[0m\ntest theme::dark_lifts_further ... \u{1b}[32mok\u{1b}[0m\n\ntest result: \u{1b}[32mok\u{1b}[0m. 12 passed; 0 failed",
CommandState::Done { code: 0, took: Duration::from_millis(9_200) },
)),
)
}
+10 -1
View File
@@ -1,3 +1,4 @@
mod changes;
mod progress;
mod tools;
@@ -10,7 +11,7 @@ pub const PAGE: Page = Page {
number: 24,
slug: "agent",
title: "Agent",
summary: "An assistant at work: the tools it calls, what it asks before it acts, and how far it has come.",
summary: "An assistant at work: the tools it calls, what it asks before it acts, how far it has come and what it would change.",
render,
script: SCRIPT,
};
@@ -31,6 +32,13 @@ const SCRIPT: &[Step] = &[
Step::Click("agent-advance"),
Step::Wait(300),
Step::Shot("plan"),
Step::DownAt("agent-changes", 30.0, 51.0),
Step::UpAt("agent-changes", 30.0, 51.0),
Step::Wait(300),
Step::Shot("changes"),
Step::DownAt("agent-changes", 30.0, 51.0),
Step::UpAt("agent-changes", 30.0, 51.0),
Step::Wait(200),
];
fn render(window: &mut Window, cx: &mut App) -> AnyElement {
@@ -39,5 +47,6 @@ fn render(window: &mut Window, cx: &mut App) -> AnyElement {
.child(tools::approval(window, cx))
.child(progress::steps(window, cx))
.child(progress::agents(window, cx))
.child(changes::changes_section(window, cx))
.into_any_element()
}