GPUI Box GitHub

Components / agent

ClarificationPanel view

One clarifying question, with the answer left to the caller.

A view survives a frame. Hold it in an Entity with cx.new(..) and reach it with .update(..).

use gpui_kit::agent::ClarificationPanel;

Reviewed in clarification.

Construct
new(ident: impl Into<Ident>, question: impl Into<SharedString>, _window: &mut Window, cx: &mut Context<Self>) -> Self
Options

Chain onto the value.

option(option: ClarificationOption) -> Self
options(options: impl IntoIterator<Item = ClarificationOption>) -> Self
multiple() -> Self
skippable() -> Self
status(status: ClarificationStatus) -> Self
Commands

Need a Context, so they need a view.

set_status(status: ClarificationStatus, cx: &mut Context<Self>)
choose(id: impl Into<SharedString>, cx: &mut Context<Self>)
answer(cx: &mut Context<Self>)
skip(cx: &mut Context<Self>)
Queries

Only answer; they change nothing.

current_status() -> &ClarificationStatus
candidates() -> &[ClarificationOption]
chosen() -> &[SharedString]
Example · clarification
fn clarification(window: &mut Window, cx: &mut App) -> AnyElement {
    if !cx.has_global::<SceneClarifications>() {
        let candidates = || {
            [
                ClarificationOption::new("src", "src/camera/follow.ts")
                    .detail("Changed in this run"),
                ClarificationOption::new("test", "src/camera/follow.test.ts")
                    .detail("Covers the module above"),
                ClarificationOption::new("docs", "docs/camera.md")
                    .detail("Describes the behaviour"),
            ]
        };
        let one = cx.new(|cx| {
            ClarificationPanel::new(
                "scene.clarification.one",
                "Three files match \"follow\". Which one did you mean?",
                window,
                cx,
            )
            .options(candidates())
        });
        let several = cx.new(|cx| {
            ClarificationPanel::new(
                "scene.clarification.several",
                "Which of these should the release note cover?",
                window,
                cx,
            )
            .multiple()
            .skippable()
            .option(
                ClarificationOption::new("radius", "The node card radius fix")
                    .detail("Landed in 2e79cbea"),
            )
            .option(
                ClarificationOption::new("cjk", "The bundled Chinese face")
                    .detail("Landed in 56c400bc"),
            )
            // A candidate the agent offered and can no longer take. It keeps
            // its place and says why, rather than leaving a list that silently
            // shrank between two frames.
            .option(
                ClarificationOption::new("glass", "The glass radius override")
                    .unavailable("Not released yet, so it has no note to cover."),
            )
        });
        let answered = cx.new(|cx| {
            ClarificationPanel::new(
                "scene.clarification.answered",
                "Three files match \"follow\". Which one did you mean?",
                window,
                cx,
            )
            .options(candidates())
            .status(ClarificationStatus::Answered(vec!["test".into()]))
        });
        let skipped = cx.new(|cx| {
            ClarificationPanel::new(
                "scene.clarification.skipped",
                "Should the summary quote the failing output in full?",
                window,
                cx,
            )
            .skippable()
            .option(ClarificationOption::new("full", "Quote all of it"))
            .option(ClarificationOption::new("head", "Quote the first failure"))
            .status(ClarificationStatus::Skipped)
        });
        let withdrawn = cx.new(|cx| {
            ClarificationPanel::new(
                "scene.clarification.withdrawn",
                "Which branch should the fix land on?",
                window,
                cx,
            )
            .option(ClarificationOption::new("main", "main"))
            .option(ClarificationOption::new("release", "release/0.1"))
            .status(ClarificationStatus::Withdrawn(
                "the repository has only one branch".into(),
            ))
        });
        let superseded = cx.new(|cx| {
            ClarificationPanel::new(
                "scene.clarification.superseded",
                "Which test suite should run first?",
                window,
                cx,
            )
            .option(ClarificationOption::new("unit", "The unit tests"))
            .option(ClarificationOption::new("visual", "The visual gate"))
            .status(ClarificationStatus::Superseded {
                by: "a later question covering the whole gate".into(),
            })
        });
        let empty = cx.new(|cx| {
            ClarificationPanel::new(
                "scene.clarification.empty",
                "Which of the cached results should be reused?",
                window,
                cx,
            )
        });
        cx.set_global(SceneClarifications {
            one,
            several,
            answered,
            skipped,
            withdrawn,
            superseded,
            empty,
        });
    }
    let panels = cx.global::<SceneClarifications>();
    let one = panels.one.clone();
    let several = panels.several.clone();
    let answered = panels.answered.clone();
    let skipped = panels.skipped.clone();
    let withdrawn = panels.withdrawn.clone();
    let superseded = panels.superseded.clone();
    let empty = panels.empty.clone();
    let theme = cx.theme().clone();

    let asking = div()
        .column()
        .w(px(430.0))
        .gap(px(theme.spacing.md))
        .child(caption(
            &theme,
            "One answer, one gesture: picking is answering, so there is no confirm",
        ))
        .child(one)
        .child(caption(
            &theme,
            "Several accumulate, so these get a confirming control — and one candidate the agent can no longer take",
        ))
        .child(several)
        .child(caption(
            &theme,
            "A question with nothing to pick from says so, rather than drawing an empty form",
        ))
        .child(empty);

    let settled = div()
        .column()
        .w(px(430.0))
        .gap(px(theme.spacing.md))
        .child(caption(
            &theme,
            "Answered, left to the agent, withdrawn, and replaced are four different things",
        ))
        .child(answered)
        .child(skipped)
        .child(withdrawn)
        .child(superseded);

    stack(&theme)
        .child(
            div()
                .row()
                .items_start()
                .gap(px(theme.spacing.md))
                .child(asking)
                .child(settled),
        )
        .into_any_element()
}

Source: crates/gpui-kit/src/agent/clarification.rs