1use std::sync::Arc;
2
3use client::{Client, zed_urls};
4use cloud_llm_client::Plan;
5use gpui::{AnyElement, App, IntoElement, RenderOnce, Window};
6use ui::{Divider, List, Vector, VectorName, prelude::*};
7
8use crate::{BulletItem, SignInStatus};
9
10#[derive(IntoElement, RegisterComponent)]
11pub struct AiUpsellCard {
12 pub sign_in_status: SignInStatus,
13 pub sign_in: Arc<dyn Fn(&mut Window, &mut App)>,
14 pub user_plan: Option<Plan>,
15}
16
17impl AiUpsellCard {
18 pub fn new(client: Arc<Client>, user_plan: Option<Plan>) -> Self {
19 let status = *client.status().borrow();
20
21 Self {
22 user_plan,
23 sign_in_status: status.into(),
24 sign_in: Arc::new(move |_window, cx| {
25 cx.spawn({
26 let client = client.clone();
27 async move |cx| {
28 client.authenticate_and_connect(true, cx).await;
29 }
30 })
31 .detach();
32 }),
33 }
34 }
35}
36
37impl RenderOnce for AiUpsellCard {
38 fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
39 let pro_section = v_flex()
40 .flex_grow()
41 .w_full()
42 .gap_1()
43 .child(
44 h_flex()
45 .gap_2()
46 .child(
47 Label::new("Pro")
48 .size(LabelSize::Small)
49 .color(Color::Accent)
50 .buffer_font(cx),
51 )
52 .child(Divider::horizontal()),
53 )
54 .child(
55 List::new()
56 .child(BulletItem::new("500 prompts with Claude models"))
57 .child(BulletItem::new(
58 "Unlimited edit predictions with Zeta, our open-source model",
59 )),
60 );
61
62 let free_section = v_flex()
63 .flex_grow()
64 .w_full()
65 .gap_1()
66 .child(
67 h_flex()
68 .gap_2()
69 .child(
70 Label::new("Free")
71 .size(LabelSize::Small)
72 .color(Color::Muted)
73 .buffer_font(cx),
74 )
75 .child(Divider::horizontal()),
76 )
77 .child(
78 List::new()
79 .child(BulletItem::new("50 prompts with Claude models"))
80 .child(BulletItem::new("2,000 accepted edit predictions")),
81 );
82
83 let grid_bg = h_flex().absolute().inset_0().w_full().h(px(240.)).child(
84 Vector::new(VectorName::Grid, rems_from_px(500.), rems_from_px(240.))
85 .color(Color::Custom(cx.theme().colors().border.opacity(0.05))),
86 );
87
88 let gradient_bg = div()
89 .absolute()
90 .inset_0()
91 .size_full()
92 .bg(gpui::linear_gradient(
93 180.,
94 gpui::linear_color_stop(
95 cx.theme().colors().elevated_surface_background.opacity(0.8),
96 0.,
97 ),
98 gpui::linear_color_stop(
99 cx.theme().colors().elevated_surface_background.opacity(0.),
100 0.8,
101 ),
102 ));
103
104 const DESCRIPTION: &str = "Zed offers a complete agentic experience, with robust editing and reviewing features to collaborate with AI.";
105
106 let footer_buttons = match self.sign_in_status {
107 SignInStatus::SignedIn => v_flex()
108 .items_center()
109 .gap_1()
110 .child(
111 Button::new("sign_in", "Start 14-day Free Pro Trial")
112 .full_width()
113 .style(ButtonStyle::Tinted(ui::TintColor::Accent))
114 .on_click(move |_, _window, cx| {
115 telemetry::event!("Start Trial Clicked", state = "post-sign-in");
116 cx.open_url(&zed_urls::start_trial_url(cx))
117 }),
118 )
119 .child(
120 Label::new("No credit card required")
121 .size(LabelSize::Small)
122 .color(Color::Muted),
123 )
124 .into_any_element(),
125 _ => Button::new("sign_in", "Sign In")
126 .full_width()
127 .style(ButtonStyle::Tinted(ui::TintColor::Accent))
128 .on_click({
129 let callback = self.sign_in.clone();
130 move |_, window, cx| {
131 telemetry::event!("Start Trial Clicked", state = "pre-sign-in");
132 callback(window, cx)
133 }
134 })
135 .into_any_element(),
136 };
137
138 v_flex()
139 .relative()
140 .p_4()
141 .pt_3()
142 .border_1()
143 .border_color(cx.theme().colors().border)
144 .rounded_lg()
145 .overflow_hidden()
146 .child(grid_bg)
147 .child(gradient_bg)
148 .child(Label::new("Try Zed AI").size(LabelSize::Large))
149 .child(
150 div()
151 .max_w_3_4()
152 .mb_2()
153 .child(Label::new(DESCRIPTION).color(Color::Muted)),
154 )
155 .child(
156 h_flex()
157 .w_full()
158 .mt_1p5()
159 .mb_2p5()
160 .items_start()
161 .gap_6()
162 .child(free_section)
163 .child(pro_section),
164 )
165 .child(footer_buttons)
166 }
167}
168
169impl Component for AiUpsellCard {
170 fn scope() -> ComponentScope {
171 ComponentScope::Agent
172 }
173
174 fn name() -> &'static str {
175 "AI Upsell Card"
176 }
177
178 fn sort_name() -> &'static str {
179 "AI Upsell Card"
180 }
181
182 fn description() -> Option<&'static str> {
183 Some("A card presenting the Zed AI product during user's first-open onboarding flow.")
184 }
185
186 fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
187 Some(
188 v_flex()
189 .p_4()
190 .gap_4()
191 .children(vec![example_group(vec![
192 single_example(
193 "Signed Out State",
194 AiUpsellCard {
195 sign_in_status: SignInStatus::SignedOut,
196 sign_in: Arc::new(|_, _| {}),
197 user_plan: None,
198 }
199 .into_any_element(),
200 ),
201 single_example(
202 "Signed In State",
203 AiUpsellCard {
204 sign_in_status: SignInStatus::SignedIn,
205 sign_in: Arc::new(|_, _| {}),
206 user_plan: None,
207 }
208 .into_any_element(),
209 ),
210 ])])
211 .into_any_element(),
212 )
213 }
214}