usage_callout.rs

  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}