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 scope() -> ComponentScope {
102        ComponentScope::Agent
103    }
104
105    fn sort_name() -> &'static str {
106        "AgentUsageBanner"
107    }
108
109    fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
110        let trial_limit = Plan::ZedProTrial.model_requests_limit();
111        let trial_examples = vec![
112            single_example(
113                "Zed Pro Trial - New User",
114                div()
115                    .size_full()
116                    .child(UsageBanner::new(
117                        Plan::ZedProTrial,
118                        RequestUsage {
119                            limit: trial_limit,
120                            amount: 10,
121                        },
122                    ))
123                    .into_any_element(),
124            ),
125            single_example(
126                "Zed Pro Trial - Approaching Limit",
127                div()
128                    .size_full()
129                    .child(UsageBanner::new(
130                        Plan::ZedProTrial,
131                        RequestUsage {
132                            limit: trial_limit,
133                            amount: 135,
134                        },
135                    ))
136                    .into_any_element(),
137            ),
138            single_example(
139                "Zed Pro Trial - Request Limit Reached",
140                div()
141                    .size_full()
142                    .child(UsageBanner::new(
143                        Plan::ZedProTrial,
144                        RequestUsage {
145                            limit: trial_limit,
146                            amount: 150,
147                        },
148                    ))
149                    .into_any_element(),
150            ),
151        ];
152
153        let free_limit = Plan::Free.model_requests_limit();
154        let free_examples = vec![
155            single_example(
156                "Free - Normal Usage",
157                div()
158                    .size_full()
159                    .child(UsageBanner::new(
160                        Plan::Free,
161                        RequestUsage {
162                            limit: free_limit,
163                            amount: 25,
164                        },
165                    ))
166                    .into_any_element(),
167            ),
168            single_example(
169                "Free - Approaching Limit",
170                div()
171                    .size_full()
172                    .child(UsageBanner::new(
173                        Plan::Free,
174                        RequestUsage {
175                            limit: free_limit,
176                            amount: 45,
177                        },
178                    ))
179                    .into_any_element(),
180            ),
181            single_example(
182                "Free - Request Limit Reached",
183                div()
184                    .size_full()
185                    .child(UsageBanner::new(
186                        Plan::Free,
187                        RequestUsage {
188                            limit: free_limit,
189                            amount: 50,
190                        },
191                    ))
192                    .into_any_element(),
193            ),
194        ];
195
196        let zed_pro_limit = Plan::ZedPro.model_requests_limit();
197        let zed_pro_examples = vec![
198            single_example(
199                "Zed Pro - Normal Usage",
200                div()
201                    .size_full()
202                    .child(UsageBanner::new(
203                        Plan::ZedPro,
204                        RequestUsage {
205                            limit: zed_pro_limit,
206                            amount: 250,
207                        },
208                    ))
209                    .into_any_element(),
210            ),
211            single_example(
212                "Zed Pro - Approaching Limit",
213                div()
214                    .size_full()
215                    .child(UsageBanner::new(
216                        Plan::ZedPro,
217                        RequestUsage {
218                            limit: zed_pro_limit,
219                            amount: 450,
220                        },
221                    ))
222                    .into_any_element(),
223            ),
224            single_example(
225                "Zed Pro - Request Limit Reached",
226                div()
227                    .size_full()
228                    .child(UsageBanner::new(
229                        Plan::ZedPro,
230                        RequestUsage {
231                            limit: zed_pro_limit,
232                            amount: 500,
233                        },
234                    ))
235                    .into_any_element(),
236            ),
237        ];
238
239        Some(
240            v_flex()
241                .gap_6()
242                .p_4()
243                .children(vec![
244                    Label::new("Trial Plan")
245                        .size(LabelSize::Large)
246                        .into_any_element(),
247                    example_group(trial_examples).vertical().into_any_element(),
248                    Label::new("Free Plan")
249                        .size(LabelSize::Large)
250                        .into_any_element(),
251                    example_group(free_examples).vertical().into_any_element(),
252                    Label::new("Pro Plan")
253                        .size(LabelSize::Large)
254                        .into_any_element(),
255                    example_group(zed_pro_examples)
256                        .vertical()
257                        .into_any_element(),
258                ])
259                .into_any_element(),
260        )
261    }
262}