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, 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 div()
95 .border_t_1()
96 .border_color(cx.theme().colors().border)
97 .child(
98 Callout::new()
99 .icon(icon)
100 .title(title)
101 .description(message)
102 .primary_action(
103 Button::new("upgrade", button_text)
104 .label_size(LabelSize::Small)
105 .on_click(move |_, _, cx| {
106 cx.open_url(&url);
107 }),
108 ),
109 )
110 .into_any_element()
111 }
112}
113
114impl Component for UsageCallout {
115 fn scope() -> ComponentScope {
116 ComponentScope::Agent
117 }
118
119 fn sort_name() -> &'static str {
120 "AgentUsageCallout"
121 }
122
123 fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
124 let free_examples = example_group_with_title(
125 "Free Plan",
126 vec![
127 single_example(
128 "Approaching limit (90%)",
129 UsageCallout::new(
130 Plan::ZedFree,
131 RequestUsage {
132 limit: UsageLimit::Limited(50),
133 amount: 45, // 90% of limit
134 },
135 )
136 .into_any_element(),
137 ),
138 single_example(
139 "Limit reached (100%)",
140 UsageCallout::new(
141 Plan::ZedFree,
142 RequestUsage {
143 limit: UsageLimit::Limited(50),
144 amount: 50, // 100% of limit
145 },
146 )
147 .into_any_element(),
148 ),
149 ],
150 );
151
152 let trial_examples = example_group_with_title(
153 "Zed Pro Trial",
154 vec![
155 single_example(
156 "Approaching limit (90%)",
157 UsageCallout::new(
158 Plan::ZedProTrial,
159 RequestUsage {
160 limit: UsageLimit::Limited(150),
161 amount: 135, // 90% of limit
162 },
163 )
164 .into_any_element(),
165 ),
166 single_example(
167 "Limit reached (100%)",
168 UsageCallout::new(
169 Plan::ZedProTrial,
170 RequestUsage {
171 limit: UsageLimit::Limited(150),
172 amount: 150, // 100% of limit
173 },
174 )
175 .into_any_element(),
176 ),
177 ],
178 );
179
180 let pro_examples = example_group_with_title(
181 "Zed Pro",
182 vec![
183 single_example(
184 "Limit reached (100%)",
185 UsageCallout::new(
186 Plan::ZedPro,
187 RequestUsage {
188 limit: UsageLimit::Limited(500),
189 amount: 500, // 100% of limit
190 },
191 )
192 .into_any_element(),
193 ),
194 empty_example("Unlimited plan (no callout shown)"),
195 ],
196 );
197
198 Some(
199 v_flex()
200 .p_4()
201 .gap_4()
202 .child(free_examples)
203 .child(trial_examples)
204 .child(pro_examples)
205 .into_any_element(),
206 )
207 }
208}