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