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