1use client::{ModelRequestUsage, RequestUsage, zed_urls};
2use cloud_llm_client::{Plan, UsageLimit};
3use component::{empty_example, example_group_with_title, single_example};
4use gpui::{AnyElement, App, IntoElement, RenderOnce, Window};
5use ui::{Callout, prelude::*};
6
7#[derive(IntoElement, RegisterComponent)]
8pub struct UsageCallout {
9 plan: Plan,
10 usage: ModelRequestUsage,
11}
12
13impl UsageCallout {
14 pub fn new(plan: Plan, usage: ModelRequestUsage) -> 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::ZedFree => (
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 zed_urls::account_url(cx),
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 zed_urls::account_url(cx),
53 ),
54 Plan::ZedPro => (
55 "Out of included prompts",
56 "Enable usage-based billing to continue.".to_string(),
57 "Manage",
58 zed_urls::account_url(cx),
59 ),
60 }
61 } else {
62 match self.plan {
63 Plan::ZedFree => (
64 "Reaching free plan limit soon",
65 format!(
66 "{remaining} remaining - Upgrade to increase limit, or switch providers",
67 ),
68 "Upgrade",
69 zed_urls::account_url(cx),
70 ),
71 Plan::ZedProTrial => (
72 "Reaching trial limit soon",
73 format!(
74 "{remaining} remaining - Upgrade to increase limit, or switch providers",
75 ),
76 "Upgrade",
77 zed_urls::account_url(cx),
78 ),
79 _ => return div().into_any_element(),
80 }
81 };
82
83 let (icon, severity) = if is_limit_reached {
84 (IconName::Close, Severity::Error)
85 } else {
86 (IconName::Warning, Severity::Warning)
87 };
88
89 div()
90 .border_t_1()
91 .border_color(cx.theme().colors().border)
92 .child(
93 Callout::new()
94 .icon(icon)
95 .severity(severity)
96 .icon(icon)
97 .title(title)
98 .description(message)
99 .actions_slot(
100 Button::new("upgrade", button_text)
101 .label_size(LabelSize::Small)
102 .on_click(move |_, _, cx| {
103 cx.open_url(&url);
104 }),
105 ),
106 )
107 .into_any_element()
108 }
109}
110
111impl Component for UsageCallout {
112 fn scope() -> ComponentScope {
113 ComponentScope::Agent
114 }
115
116 fn sort_name() -> &'static str {
117 "AgentUsageCallout"
118 }
119
120 fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
121 let free_examples = example_group_with_title(
122 "Free Plan",
123 vec![
124 single_example(
125 "Approaching limit (90%)",
126 UsageCallout::new(
127 Plan::ZedFree,
128 ModelRequestUsage(RequestUsage {
129 limit: UsageLimit::Limited(50),
130 amount: 45, // 90% of limit
131 }),
132 )
133 .into_any_element(),
134 ),
135 single_example(
136 "Limit reached (100%)",
137 UsageCallout::new(
138 Plan::ZedFree,
139 ModelRequestUsage(RequestUsage {
140 limit: UsageLimit::Limited(50),
141 amount: 50, // 100% of limit
142 }),
143 )
144 .into_any_element(),
145 ),
146 ],
147 );
148
149 let trial_examples = example_group_with_title(
150 "Zed Pro Trial",
151 vec![
152 single_example(
153 "Approaching limit (90%)",
154 UsageCallout::new(
155 Plan::ZedProTrial,
156 ModelRequestUsage(RequestUsage {
157 limit: UsageLimit::Limited(150),
158 amount: 135, // 90% of limit
159 }),
160 )
161 .into_any_element(),
162 ),
163 single_example(
164 "Limit reached (100%)",
165 UsageCallout::new(
166 Plan::ZedProTrial,
167 ModelRequestUsage(RequestUsage {
168 limit: UsageLimit::Limited(150),
169 amount: 150, // 100% of limit
170 }),
171 )
172 .into_any_element(),
173 ),
174 ],
175 );
176
177 let pro_examples = example_group_with_title(
178 "Zed Pro",
179 vec![
180 single_example(
181 "Limit reached (100%)",
182 UsageCallout::new(
183 Plan::ZedPro,
184 ModelRequestUsage(RequestUsage {
185 limit: UsageLimit::Limited(500),
186 amount: 500, // 100% of limit
187 }),
188 )
189 .into_any_element(),
190 ),
191 empty_example("Unlimited plan (no callout shown)"),
192 ],
193 );
194
195 Some(
196 v_flex()
197 .p_4()
198 .gap_4()
199 .child(free_examples)
200 .child(trial_examples)
201 .child(pro_examples)
202 .into_any_element(),
203 )
204 }
205}