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}