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}