parallel_agents_illustration.rs

  1use crate::{DiffStat, Divider, prelude::*};
  2use gpui::{Animation, AnimationExt, pulsating_between};
  3use std::time::Duration;
  4
  5#[derive(IntoElement)]
  6pub struct ParallelAgentsIllustration;
  7
  8impl ParallelAgentsIllustration {
  9    pub fn new() -> Self {
 10        Self
 11    }
 12}
 13
 14impl RenderOnce for ParallelAgentsIllustration {
 15    fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
 16        let icon_container = || h_flex().size_4().flex_shrink_0().justify_center();
 17
 18        let loading_bar = |id: &'static str, width: DefiniteLength, duration_ms: u64| {
 19            div()
 20                .h(rems_from_px(5.))
 21                .w(width)
 22                .rounded_full()
 23                .bg(cx.theme().colors().element_selected)
 24                .with_animation(
 25                    id,
 26                    Animation::new(Duration::from_millis(duration_ms))
 27                        .repeat()
 28                        .with_easing(pulsating_between(0.1, 0.8)),
 29                    |label, delta| label.opacity(delta),
 30                )
 31        };
 32
 33        let skeleton_bar = |width: DefiniteLength| {
 34            div().h(rems_from_px(5.)).w(width).rounded_full().bg(cx
 35                .theme()
 36                .colors()
 37                .text_muted
 38                .opacity(0.05))
 39        };
 40
 41        let time =
 42            |time: SharedString| Label::new(time).size(LabelSize::XSmall).color(Color::Muted);
 43
 44        let worktree = |worktree: SharedString| {
 45            h_flex()
 46                .gap_0p5()
 47                .child(
 48                    Icon::new(IconName::GitWorktree)
 49                        .color(Color::Muted)
 50                        .size(IconSize::Indicator),
 51                )
 52                .child(
 53                    Label::new(worktree)
 54                        .size(LabelSize::XSmall)
 55                        .color(Color::Muted),
 56                )
 57        };
 58
 59        let dot_separator = || {
 60            Label::new("")
 61                .size(LabelSize::Small)
 62                .color(Color::Muted)
 63                .alpha(0.5)
 64        };
 65
 66        let agent = |title: SharedString, icon: IconName, selected: bool, data: Vec<AnyElement>| {
 67            v_flex()
 68                .when(selected, |this| {
 69                    this.bg(cx.theme().colors().element_active.opacity(0.2))
 70                })
 71                .p_1()
 72                .child(
 73                    h_flex()
 74                        .w_full()
 75                        .gap_1()
 76                        .child(
 77                            icon_container()
 78                                .child(Icon::new(icon).size(IconSize::XSmall).color(Color::Muted)),
 79                        )
 80                        .map(|this| {
 81                            if selected {
 82                                this.child(
 83                                    Label::new(title)
 84                                        .color(Color::Muted)
 85                                        .size(LabelSize::XSmall),
 86                                )
 87                            } else {
 88                                this.child(skeleton_bar(relative(0.7)))
 89                            }
 90                        }),
 91                )
 92                .child(
 93                    h_flex()
 94                        .opacity(0.8)
 95                        .w_full()
 96                        .gap_1()
 97                        .child(icon_container())
 98                        .children(data),
 99                )
100        };
101
102        let agents = v_flex()
103            .col_span(3)
104            .bg(cx.theme().colors().elevated_surface_background)
105            .child(agent(
106                "Fix branch label".into(),
107                IconName::ZedAgent,
108                true,
109                vec![
110                    worktree("bug-fix".into()).into_any_element(),
111                    dot_separator().into_any_element(),
112                    DiffStat::new("ds", 5, 2)
113                        .label_size(LabelSize::XSmall)
114                        .into_any_element(),
115                    dot_separator().into_any_element(),
116                    time("2m".into()).into_any_element(),
117                ],
118            ))
119            .child(Divider::horizontal())
120            .child(agent(
121                "Improve thread id".into(),
122                IconName::AiClaude,
123                false,
124                vec![
125                    DiffStat::new("ds", 120, 84)
126                        .label_size(LabelSize::XSmall)
127                        .into_any_element(),
128                    dot_separator().into_any_element(),
129                    time("16m".into()).into_any_element(),
130                ],
131            ))
132            .child(Divider::horizontal())
133            .child(agent(
134                "Refactor archive view".into(),
135                IconName::AiOpenAi,
136                false,
137                vec![
138                    worktree("silent-forest".into()).into_any_element(),
139                    dot_separator().into_any_element(),
140                    time("37m".into()).into_any_element(),
141                ],
142            ));
143
144        let thread_view = v_flex()
145            .col_span(3)
146            .h_full()
147            .flex_1()
148            .border_l_1()
149            .border_color(cx.theme().colors().border.opacity(0.5))
150            .bg(cx.theme().colors().panel_background)
151            .child(
152                h_flex()
153                    .px_1p5()
154                    .py_0p5()
155                    .w_full()
156                    .justify_between()
157                    .border_b_1()
158                    .border_color(cx.theme().colors().border.opacity(0.5))
159                    .child(
160                        Label::new("Fix branch label")
161                            .size(LabelSize::XSmall)
162                            .color(Color::Muted),
163                    )
164                    .child(
165                        Icon::new(IconName::Plus)
166                            .size(IconSize::Indicator)
167                            .color(Color::Muted),
168                    ),
169            )
170            .child(
171                div().p_1().child(
172                    v_flex()
173                        .px_1()
174                        .py_1p5()
175                        .gap_1()
176                        .border_1()
177                        .border_color(cx.theme().colors().border.opacity(0.5))
178                        .bg(cx.theme().colors().editor_background)
179                        .rounded_sm()
180                        .shadow_sm()
181                        .child(skeleton_bar(relative(0.7)))
182                        .child(skeleton_bar(relative(0.2))),
183                ),
184            )
185            .child(
186                v_flex()
187                    .p_2()
188                    .gap_1()
189                    .child(loading_bar("a", relative(0.55), 2200))
190                    .child(loading_bar("b", relative(0.75), 2000))
191                    .child(loading_bar("c", relative(0.25), 2400)),
192            );
193
194        let file_row = |indent: usize, is_folder: bool, bar_width: Rems| {
195            let indent_px = rems_from_px((indent as f32) * 4.0);
196
197            h_flex()
198                .px_2()
199                .py_px()
200                .gap_1()
201                .pl(indent_px)
202                .child(
203                    icon_container().child(
204                        Icon::new(if is_folder {
205                            IconName::FolderOpen
206                        } else {
207                            IconName::FileRust
208                        })
209                        .size(IconSize::Indicator)
210                        .color(Color::Custom(cx.theme().colors().icon_muted.opacity(0.2))),
211                    ),
212                )
213                .child(
214                    div().h_1p5().w(bar_width).rounded_sm().bg(cx
215                        .theme()
216                        .colors()
217                        .text
218                        .opacity(if is_folder { 0.15 } else { 0.1 })),
219                )
220        };
221
222        let project_panel = v_flex()
223            .col_span(1)
224            .h_full()
225            .flex_1()
226            .border_l_1()
227            .border_color(cx.theme().colors().border.opacity(0.5))
228            .bg(cx.theme().colors().panel_background)
229            .child(
230                v_flex()
231                    .child(file_row(0, true, rems_from_px(42.0)))
232                    .child(file_row(1, true, rems_from_px(28.0)))
233                    .child(file_row(2, false, rems_from_px(52.0)))
234                    .child(file_row(2, false, rems_from_px(36.0)))
235                    .child(file_row(2, false, rems_from_px(44.0)))
236                    .child(file_row(1, true, rems_from_px(34.0)))
237                    .child(file_row(2, false, rems_from_px(48.0)))
238                    .child(file_row(2, true, rems_from_px(26.0)))
239                    .child(file_row(3, false, rems_from_px(40.0)))
240                    .child(file_row(3, false, rems_from_px(56.0)))
241                    .child(file_row(1, false, rems_from_px(38.0)))
242                    .child(file_row(0, true, rems_from_px(30.0)))
243                    .child(file_row(1, false, rems_from_px(46.0)))
244                    .child(file_row(1, false, rems_from_px(32.0))),
245            );
246
247        let workspace = div()
248            .absolute()
249            .top_8()
250            .grid()
251            .grid_cols(7)
252            .w(rems_from_px(380.))
253            .rounded_t_sm()
254            .border_1()
255            .border_color(cx.theme().colors().border.opacity(0.5))
256            .shadow_md()
257            .child(agents)
258            .child(thread_view)
259            .child(project_panel);
260
261        h_flex()
262            .relative()
263            .h(rems_from_px(180.))
264            .bg(cx.theme().colors().editor_background.opacity(0.6))
265            .justify_center()
266            .items_end()
267            .rounded_t_md()
268            .overflow_hidden()
269            .bg(gpui::black().opacity(0.2))
270            .child(workspace)
271    }
272}