usage_banner.rs

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