usage_callouts.rs

  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}