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).child(
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}