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