usage_banner.rs

  1use client::zed_urls;
  2use language_model::RequestUsage;
  3use ui::{Banner, ProgressBar, Severity, prelude::*};
  4use zed_llm_client::{Plan, UsageLimit};
  5
  6#[derive(IntoElement, RegisterComponent)]
  7pub struct UsageBanner {
  8    plan: Plan,
  9    usage: RequestUsage,
 10}
 11
 12impl UsageBanner {
 13    pub fn new(plan: Plan, usage: RequestUsage) -> Self {
 14        Self { plan, usage }
 15    }
 16}
 17
 18impl RenderOnce for UsageBanner {
 19    fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
 20        let used_percentage = match self.usage.limit {
 21            UsageLimit::Limited(limit) => Some((self.usage.amount as f32 / limit as f32) * 100.),
 22            UsageLimit::Unlimited => None,
 23        };
 24
 25        let (severity, message) = match self.usage.limit {
 26            UsageLimit::Limited(limit) => {
 27                if self.usage.amount >= limit {
 28                    let message = match self.plan {
 29                        Plan::ZedPro => "Monthly request limit reached",
 30                        Plan::ZedProTrial => "Trial request limit reached",
 31                        Plan::Free => "Free tier request limit reached",
 32                    };
 33
 34                    (Severity::Error, message)
 35                } else if (self.usage.amount as f32 / limit as f32) >= 0.9 {
 36                    (Severity::Warning, "Approaching request limit")
 37                } else {
 38                    let message = match self.plan {
 39                        Plan::ZedPro => "Zed Pro",
 40                        Plan::ZedProTrial => "Zed Pro (Trial)",
 41                        Plan::Free => "Zed Free",
 42                    };
 43
 44                    (Severity::Info, message)
 45                }
 46            }
 47            UsageLimit::Unlimited => {
 48                let message = match self.plan {
 49                    Plan::ZedPro => "Zed Pro",
 50                    Plan::ZedProTrial => "Zed Pro (Trial)",
 51                    Plan::Free => "Zed Free",
 52                };
 53
 54                (Severity::Info, message)
 55            }
 56        };
 57
 58        let action = match self.plan {
 59            Plan::ZedProTrial | Plan::Free => {
 60                Button::new("upgrade", "Upgrade").on_click(|_, _window, cx| {
 61                    cx.open_url(&zed_urls::account_url(cx));
 62                })
 63            }
 64            Plan::ZedPro => Button::new("manage", "Manage").on_click(|_, _window, cx| {
 65                cx.open_url(&zed_urls::account_url(cx));
 66            }),
 67        };
 68
 69        Banner::new().severity(severity).children(
 70            h_flex().flex_1().gap_1().child(Label::new(message)).child(
 71                h_flex()
 72                    .flex_1()
 73                    .justify_end()
 74                    .gap_1p5()
 75                    .children(used_percentage.map(|percent| {
 76                        h_flex()
 77                            .items_center()
 78                            .w_full()
 79                            .max_w(px(180.))
 80                            .child(ProgressBar::new("usage", percent, 100., cx))
 81                    }))
 82                    .child(
 83                        Label::new(match self.usage.limit {
 84                            UsageLimit::Limited(limit) => {
 85                                format!("{} / {limit}", self.usage.amount)
 86                            }
 87                            UsageLimit::Unlimited => format!("{} / ∞", self.usage.amount),
 88                        })
 89                        .size(LabelSize::Small)
 90                        .color(Color::Muted),
 91                    )
 92                    // Note: This should go in the banner's `action_slot`, but doing that messes with the size of the
 93                    // progress bar.
 94                    .child(action),
 95            ),
 96        )
 97    }
 98}
 99
100impl Component for UsageBanner {
101    fn sort_name() -> &'static str {
102        "AgentUsageBanner"
103    }
104
105    fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
106        let trial_limit = Plan::ZedProTrial.model_requests_limit();
107        let trial_examples = vec![
108            single_example(
109                "Zed Pro Trial - New User",
110                div()
111                    .size_full()
112                    .child(UsageBanner::new(
113                        Plan::ZedProTrial,
114                        RequestUsage {
115                            limit: trial_limit,
116                            amount: 10,
117                        },
118                    ))
119                    .into_any_element(),
120            ),
121            single_example(
122                "Zed Pro Trial - Approaching Limit",
123                div()
124                    .size_full()
125                    .child(UsageBanner::new(
126                        Plan::ZedProTrial,
127                        RequestUsage {
128                            limit: trial_limit,
129                            amount: 135,
130                        },
131                    ))
132                    .into_any_element(),
133            ),
134            single_example(
135                "Zed Pro Trial - Request Limit Reached",
136                div()
137                    .size_full()
138                    .child(UsageBanner::new(
139                        Plan::ZedProTrial,
140                        RequestUsage {
141                            limit: trial_limit,
142                            amount: 150,
143                        },
144                    ))
145                    .into_any_element(),
146            ),
147        ];
148
149        let free_limit = Plan::Free.model_requests_limit();
150        let free_examples = vec![
151            single_example(
152                "Free - Normal Usage",
153                div()
154                    .size_full()
155                    .child(UsageBanner::new(
156                        Plan::Free,
157                        RequestUsage {
158                            limit: free_limit,
159                            amount: 25,
160                        },
161                    ))
162                    .into_any_element(),
163            ),
164            single_example(
165                "Free - Approaching Limit",
166                div()
167                    .size_full()
168                    .child(UsageBanner::new(
169                        Plan::Free,
170                        RequestUsage {
171                            limit: free_limit,
172                            amount: 45,
173                        },
174                    ))
175                    .into_any_element(),
176            ),
177            single_example(
178                "Free - Request Limit Reached",
179                div()
180                    .size_full()
181                    .child(UsageBanner::new(
182                        Plan::Free,
183                        RequestUsage {
184                            limit: free_limit,
185                            amount: 50,
186                        },
187                    ))
188                    .into_any_element(),
189            ),
190        ];
191
192        let zed_pro_limit = Plan::ZedPro.model_requests_limit();
193        let zed_pro_examples = vec![
194            single_example(
195                "Zed Pro - Normal Usage",
196                div()
197                    .size_full()
198                    .child(UsageBanner::new(
199                        Plan::ZedPro,
200                        RequestUsage {
201                            limit: zed_pro_limit,
202                            amount: 250,
203                        },
204                    ))
205                    .into_any_element(),
206            ),
207            single_example(
208                "Zed Pro - Approaching Limit",
209                div()
210                    .size_full()
211                    .child(UsageBanner::new(
212                        Plan::ZedPro,
213                        RequestUsage {
214                            limit: zed_pro_limit,
215                            amount: 450,
216                        },
217                    ))
218                    .into_any_element(),
219            ),
220            single_example(
221                "Zed Pro - Request Limit Reached",
222                div()
223                    .size_full()
224                    .child(UsageBanner::new(
225                        Plan::ZedPro,
226                        RequestUsage {
227                            limit: zed_pro_limit,
228                            amount: 500,
229                        },
230                    ))
231                    .into_any_element(),
232            ),
233        ];
234
235        Some(
236            v_flex()
237                .gap_6()
238                .p_4()
239                .children(vec![
240                    Label::new("Trial Plan")
241                        .size(LabelSize::Large)
242                        .into_any_element(),
243                    example_group(trial_examples).vertical().into_any_element(),
244                    Label::new("Free Plan")
245                        .size(LabelSize::Large)
246                        .into_any_element(),
247                    example_group(free_examples).vertical().into_any_element(),
248                    Label::new("Pro Plan")
249                        .size(LabelSize::Large)
250                        .into_any_element(),
251                    example_group(zed_pro_examples)
252                        .vertical()
253                        .into_any_element(),
254                ])
255                .into_any_element(),
256        )
257    }
258}