1use client::zed_urls;
2use component::{empty_example, example_group_with_title, single_example};
3use gpui::{AnyElement, App, IntoElement, RenderOnce, Window};
4use language_model::RequestUsage;
5use ui::{Callout, Color, Icon, IconName, IconSize, prelude::*};
6use zed_llm_client::{Plan, UsageLimit};
7
8#[derive(IntoElement, RegisterComponent)]
9pub struct UsageCallout {
10 plan: Plan,
11 usage: RequestUsage,
12}
13
14impl UsageCallout {
15 pub fn new(plan: Plan, usage: RequestUsage) -> Self {
16 Self { plan, usage }
17 }
18}
19
20impl RenderOnce for UsageCallout {
21 fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
22 let (is_limit_reached, is_approaching_limit, remaining) = match self.usage.limit {
23 UsageLimit::Limited(limit) => {
24 let percentage = self.usage.amount as f32 / limit as f32;
25 let is_limit_reached = percentage >= 1.0;
26 let is_near_limit = percentage >= 0.9 && percentage < 1.0;
27 (
28 is_limit_reached,
29 is_near_limit,
30 limit.saturating_sub(self.usage.amount),
31 )
32 }
33 UsageLimit::Unlimited => (false, false, 0),
34 };
35
36 if !is_limit_reached && !is_approaching_limit {
37 return div().into_any_element();
38 }
39
40 let (title, message, button_text, url) = if is_limit_reached {
41 match self.plan {
42 Plan::ZedFree => (
43 "Out of free prompts",
44 "Upgrade to continue, wait for the next reset, or switch to API key."
45 .to_string(),
46 "Upgrade",
47 zed_urls::account_url(cx),
48 ),
49 Plan::ZedProTrial => (
50 "Out of trial prompts",
51 "Upgrade to Zed Pro to continue, or switch to API key.".to_string(),
52 "Upgrade",
53 zed_urls::account_url(cx),
54 ),
55 Plan::ZedPro => (
56 "Out of included prompts",
57 "Enable usage-based billing to continue.".to_string(),
58 "Manage",
59 zed_urls::account_url(cx),
60 ),
61 }
62 } else {
63 match self.plan {
64 Plan::ZedFree => (
65 "Reaching free plan limit soon",
66 format!(
67 "{remaining} remaining - Upgrade to increase limit, or switch providers",
68 ),
69 "Upgrade",
70 zed_urls::account_url(cx),
71 ),
72 Plan::ZedProTrial => (
73 "Reaching trial limit soon",
74 format!(
75 "{remaining} remaining - Upgrade to increase limit, or switch providers",
76 ),
77 "Upgrade",
78 zed_urls::account_url(cx),
79 ),
80 _ => return div().into_any_element(),
81 }
82 };
83
84 let icon = if is_limit_reached {
85 Icon::new(IconName::X)
86 .color(Color::Error)
87 .size(IconSize::XSmall)
88 } else {
89 Icon::new(IconName::Warning)
90 .color(Color::Warning)
91 .size(IconSize::XSmall)
92 };
93
94 Callout::multi_line(
95 title,
96 message,
97 icon,
98 button_text,
99 Box::new(move |_, _, cx| {
100 cx.open_url(&url);
101 }),
102 )
103 .into_any_element()
104 }
105}
106
107impl Component for UsageCallout {
108 fn scope() -> ComponentScope {
109 ComponentScope::Agent
110 }
111
112 fn sort_name() -> &'static str {
113 "AgentUsageCallout"
114 }
115
116 fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
117 let free_examples = example_group_with_title(
118 "Free Plan",
119 vec![
120 single_example(
121 "Approaching limit (90%)",
122 UsageCallout::new(
123 Plan::ZedFree,
124 RequestUsage {
125 limit: UsageLimit::Limited(50),
126 amount: 45, // 90% of limit
127 },
128 )
129 .into_any_element(),
130 ),
131 single_example(
132 "Limit reached (100%)",
133 UsageCallout::new(
134 Plan::ZedFree,
135 RequestUsage {
136 limit: UsageLimit::Limited(50),
137 amount: 50, // 100% of limit
138 },
139 )
140 .into_any_element(),
141 ),
142 ],
143 );
144
145 let trial_examples = example_group_with_title(
146 "Zed Pro Trial",
147 vec![
148 single_example(
149 "Approaching limit (90%)",
150 UsageCallout::new(
151 Plan::ZedProTrial,
152 RequestUsage {
153 limit: UsageLimit::Limited(150),
154 amount: 135, // 90% of limit
155 },
156 )
157 .into_any_element(),
158 ),
159 single_example(
160 "Limit reached (100%)",
161 UsageCallout::new(
162 Plan::ZedProTrial,
163 RequestUsage {
164 limit: UsageLimit::Limited(150),
165 amount: 150, // 100% of limit
166 },
167 )
168 .into_any_element(),
169 ),
170 ],
171 );
172
173 let pro_examples = example_group_with_title(
174 "Zed Pro",
175 vec![
176 single_example(
177 "Limit reached (100%)",
178 UsageCallout::new(
179 Plan::ZedPro,
180 RequestUsage {
181 limit: UsageLimit::Limited(500),
182 amount: 500, // 100% of limit
183 },
184 )
185 .into_any_element(),
186 ),
187 empty_example("Unlimited plan (no callout shown)"),
188 ],
189 );
190
191 Some(
192 div()
193 .p_4()
194 .flex()
195 .flex_col()
196 .gap_4()
197 .child(free_examples)
198 .child(trial_examples)
199 .child(pro_examples)
200 .into_any_element(),
201 )
202 }
203}