usage_callouts.rs

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