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}