Skip to main content

ui/components/ai/
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::Sparkle,
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}