Update plan text (#38731)

Marshall Bowers and David Kleingeld created

Release Notes:

- N/A

---------

Co-authored-by: David Kleingeld <davidsk@zed.dev>

Change summary

Cargo.lock                                       |  1 
crates/agent_ui/src/ui/end_trial_upsell.rs       |  4 
crates/ai_onboarding/src/ai_onboarding.rs        |  8 
crates/ai_onboarding/src/ai_upsell_card.rs       | 14 +-
crates/ai_onboarding/src/plan_definitions.rs     | 75 ++++++++++++-----
crates/ai_onboarding/src/young_account_banner.rs | 19 +++
crates/feature_flags/src/flags.rs                |  4 
crates/language_models/Cargo.toml                |  1 
crates/language_models/src/provider/cloud.rs     | 22 ++++-
9 files changed, 104 insertions(+), 44 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -9560,6 +9560,7 @@ dependencies = [
  "credentials_provider",
  "deepseek",
  "editor",
+ "feature_flags",
  "fs",
  "futures 0.3.31",
  "google_ai",

crates/agent_ui/src/ui/end_trial_upsell.rs 🔗

@@ -2,7 +2,7 @@ use std::sync::Arc;
 
 use ai_onboarding::{AgentPanelOnboardingCard, PlanDefinitions};
 use client::zed_urls;
-use cloud_llm_client::{Plan, PlanV1};
+use cloud_llm_client::{Plan, PlanV2};
 use gpui::{AnyElement, App, IntoElement, RenderOnce, Window};
 use ui::{Divider, Tooltip, prelude::*};
 
@@ -112,7 +112,7 @@ impl Component for EndTrialUpsell {
         Some(
             v_flex()
                 .child(EndTrialUpsell {
-                    plan: Plan::V1(PlanV1::ZedFree),
+                    plan: Plan::V2(PlanV2::ZedFree),
                     dismiss_upsell: Arc::new(|_, _| {}),
                 })
                 .into_any_element(),

crates/ai_onboarding/src/ai_onboarding.rs 🔗

@@ -120,7 +120,7 @@ impl ZedAiOnboarding {
                 .max_w_full()
                 .gap_1()
                 .child(Headline::new("Welcome to Zed AI"))
-                .child(YoungAccountBanner)
+                .child(YoungAccountBanner::new(is_v2))
                 .child(
                     v_flex()
                         .mt_2()
@@ -372,7 +372,7 @@ impl Component for ZedAiOnboarding {
                         "Free Plan",
                         onboarding(
                             SignInStatus::SignedIn,
-                            Some(Plan::V1(PlanV1::ZedFree)),
+                            Some(Plan::V2(PlanV2::ZedFree)),
                             false,
                         ),
                     ),
@@ -380,7 +380,7 @@ impl Component for ZedAiOnboarding {
                         "Pro Trial",
                         onboarding(
                             SignInStatus::SignedIn,
-                            Some(Plan::V1(PlanV1::ZedProTrial)),
+                            Some(Plan::V2(PlanV2::ZedProTrial)),
                             false,
                         ),
                     ),
@@ -388,7 +388,7 @@ impl Component for ZedAiOnboarding {
                         "Pro Plan",
                         onboarding(
                             SignInStatus::SignedIn,
-                            Some(Plan::V1(PlanV1::ZedPro)),
+                            Some(Plan::V2(PlanV2::ZedPro)),
                             false,
                         ),
                     ),

crates/ai_onboarding/src/ai_upsell_card.rs 🔗

@@ -175,7 +175,7 @@ impl RenderOnce for AiUpsellCard {
                     .child(Label::new("Try Zed AI").size(LabelSize::Large))
                     .map(|this| {
                         if self.account_too_young {
-                            this.child(YoungAccountBanner).child(
+                            this.child(YoungAccountBanner::new(is_v2_plan)).child(
                                 v_flex()
                                     .mt_2()
                                     .gap_1()
@@ -215,7 +215,7 @@ impl RenderOnce for AiUpsellCard {
                             .child(
                                 footer_container
                                     .child(
-                                        Button::new("start_trial", "Start 14-day Free Pro Trial")
+                                        Button::new("start_trial", "Start Pro Trial")
                                             .full_width()
                                             .style(ButtonStyle::Tinted(ui::TintColor::Accent))
                                             .when_some(self.tab_index, |this, tab_index| {
@@ -230,7 +230,7 @@ impl RenderOnce for AiUpsellCard {
                                             }),
                                     )
                                     .child(
-                                        Label::new("No credit card required")
+                                        Label::new("14 days, no credit card required")
                                             .size(LabelSize::Small)
                                             .color(Color::Muted),
                                     ),
@@ -327,7 +327,7 @@ impl Component for AiUpsellCard {
                                 sign_in_status: SignInStatus::SignedIn,
                                 sign_in: Arc::new(|_, _| {}),
                                 account_too_young: false,
-                                user_plan: Some(Plan::V1(PlanV1::ZedFree)),
+                                user_plan: Some(Plan::V2(PlanV2::ZedFree)),
                                 tab_index: Some(1),
                             }
                             .into_any_element(),
@@ -338,7 +338,7 @@ impl Component for AiUpsellCard {
                                 sign_in_status: SignInStatus::SignedIn,
                                 sign_in: Arc::new(|_, _| {}),
                                 account_too_young: true,
-                                user_plan: Some(Plan::V1(PlanV1::ZedFree)),
+                                user_plan: Some(Plan::V2(PlanV2::ZedFree)),
                                 tab_index: Some(1),
                             }
                             .into_any_element(),
@@ -349,7 +349,7 @@ impl Component for AiUpsellCard {
                                 sign_in_status: SignInStatus::SignedIn,
                                 sign_in: Arc::new(|_, _| {}),
                                 account_too_young: false,
-                                user_plan: Some(Plan::V1(PlanV1::ZedProTrial)),
+                                user_plan: Some(Plan::V2(PlanV2::ZedProTrial)),
                                 tab_index: Some(1),
                             }
                             .into_any_element(),
@@ -360,7 +360,7 @@ impl Component for AiUpsellCard {
                                 sign_in_status: SignInStatus::SignedIn,
                                 sign_in: Arc::new(|_, _| {}),
                                 account_too_young: false,
-                                user_plan: Some(Plan::V1(PlanV1::ZedPro)),
+                                user_plan: Some(Plan::V2(PlanV2::ZedPro)),
                                 tab_index: Some(1),
                             }
                             .into_any_element(),

crates/ai_onboarding/src/plan_definitions.rs 🔗

@@ -7,33 +7,62 @@ pub struct PlanDefinitions;
 impl PlanDefinitions {
     pub const AI_DESCRIPTION: &'static str = "Zed offers a complete agentic experience, with robust editing and reviewing features to collaborate with AI.";
 
-    pub fn free_plan(&self, _is_v2: bool) -> impl IntoElement {
-        List::new()
-            .child(ListBulletItem::new("50 prompts with Claude models"))
-            .child(ListBulletItem::new("2,000 accepted edit predictions"))
+    pub fn free_plan(&self, is_v2: bool) -> impl IntoElement {
+        if is_v2 {
+            List::new()
+                .child(ListBulletItem::new("2,000 accepted edit predictions"))
+                .child(ListBulletItem::new(
+                    "Unlimited prompts with your AI API keys",
+                ))
+                .child(ListBulletItem::new(
+                    "Unlimited use of external agents like Claude Code",
+                ))
+        } else {
+            List::new()
+                .child(ListBulletItem::new("50 prompts with Claude models"))
+                .child(ListBulletItem::new("2,000 accepted edit predictions"))
+        }
     }
 
-    pub fn pro_trial(&self, _is_v2: bool, period: bool) -> impl IntoElement {
-        List::new()
-            .child(ListBulletItem::new("150 prompts with Claude models"))
-            .child(ListBulletItem::new(
-                "Unlimited edit predictions with Zeta, our open-source model",
-            ))
-            .when(period, |this| {
-                this.child(ListBulletItem::new(
-                    "Try it out for 14 days for free, no credit card required",
+    pub fn pro_trial(&self, is_v2: bool, period: bool) -> impl IntoElement {
+        if is_v2 {
+            List::new()
+                .child(ListBulletItem::new("Unlimited edit predictions"))
+                .child(ListBulletItem::new("$20 of tokens"))
+                .when(period, |this| {
+                    this.child(ListBulletItem::new(
+                        "Try it out for 14 days, no credit card required",
+                    ))
+                })
+        } else {
+            List::new()
+                .child(ListBulletItem::new("150 prompts with Claude models"))
+                .child(ListBulletItem::new(
+                    "Unlimited edit predictions with Zeta, our open-source model",
                 ))
-            })
+                .when(period, |this| {
+                    this.child(ListBulletItem::new(
+                        "Try it out for 14 days, no credit card required",
+                    ))
+                })
+        }
     }
 
-    pub fn pro_plan(&self, _is_v2: bool, price: bool) -> impl IntoElement {
-        List::new()
-            .child(ListBulletItem::new("500 prompts with Claude models"))
-            .child(ListBulletItem::new(
-                "Unlimited edit predictions with Zeta, our open-source model",
-            ))
-            .when(price, |this| {
-                this.child(ListBulletItem::new("$20 USD per month"))
-            })
+    pub fn pro_plan(&self, is_v2: bool, price: bool) -> impl IntoElement {
+        if is_v2 {
+            List::new()
+                .child(ListBulletItem::new("Unlimited edit predictions"))
+                .child(ListBulletItem::new("$5 of tokens"))
+                .child(ListBulletItem::new("Usage-based billing beyond $5"))
+        } else {
+            List::new()
+                .child(ListBulletItem::new("500 prompts with Claude models"))
+                .child(ListBulletItem::new(
+                    "Unlimited edit predictions with Zeta, our open-source model",
+                ))
+                .when(price, |this| {
+                    this.child(ListBulletItem::new("$20 USD per month"))
+                })
+        }
     }
 }

crates/ai_onboarding/src/young_account_banner.rs 🔗

@@ -2,17 +2,30 @@ use gpui::{IntoElement, ParentElement};
 use ui::{Banner, prelude::*};
 
 #[derive(IntoElement)]
-pub struct YoungAccountBanner;
+pub struct YoungAccountBanner {
+    is_v2: bool,
+}
+
+impl YoungAccountBanner {
+    pub fn new(is_v2: bool) -> Self {
+        Self { is_v2 }
+    }
+}
 
 impl RenderOnce for YoungAccountBanner {
     fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
-        const YOUNG_ACCOUNT_DISCLAIMER: &str = "To prevent abuse of our service, GitHub accounts created fewer than 30 days ago are not eligible for free plan usage or Pro plan free trial. To request an exception, reach out to billing-support@zed.dev.";
+        const YOUNG_ACCOUNT_DISCLAIMER: &str = "To prevent abuse of our service, GitHub accounts created fewer than 30 days ago are not eligible for free plan usage or Pro plan free trial. You can request an exception by reaching out to billing-support@zed.dev";
+        const YOUNG_ACCOUNT_DISCLAIMER_V2: &str = "To prevent abuse of our service, GitHub accounts created fewer than 30 days ago are not eligible for the Pro trial. You can request an exception by reaching out to billing-support@zed.dev";
 
         let label = div()
             .w_full()
             .text_sm()
             .text_color(cx.theme().colors().text_muted)
-            .child(YOUNG_ACCOUNT_DISCLAIMER);
+            .child(if self.is_v2 {
+                YOUNG_ACCOUNT_DISCLAIMER_V2
+            } else {
+                YOUNG_ACCOUNT_DISCLAIMER
+            });
 
         div()
             .max_w_full()

crates/feature_flags/src/flags.rs 🔗

@@ -10,6 +10,10 @@ pub struct BillingV2FeatureFlag {}
 
 impl FeatureFlag for BillingV2FeatureFlag {
     const NAME: &'static str = "billing-v2";
+
+    fn enabled_for_all() -> bool {
+        true
+    }
 }
 
 pub struct NotebookFeatureFlag;

crates/language_models/Cargo.toml 🔗

@@ -29,6 +29,7 @@ copilot.workspace = true
 credentials_provider.workspace = true
 deepseek = { workspace = true, features = ["schemars"] }
 editor.workspace = true
+feature_flags.workspace = true
 fs.workspace = true
 futures.workspace = true
 google_ai = { workspace = true, features = ["schemars"] }

crates/language_models/src/provider/cloud.rs 🔗

@@ -11,6 +11,7 @@ use cloud_llm_client::{
     SUBSCRIPTION_LIMIT_RESOURCE_HEADER_NAME, TOOL_USE_LIMIT_REACHED_HEADER_NAME,
     ZED_VERSION_HEADER_NAME,
 };
+use feature_flags::{BillingV2FeatureFlag, FeatureFlagAppExt};
 use futures::{
     AsyncBufReadExt, FutureExt, Stream, StreamExt, future::BoxFuture, stream::BoxStream,
 };
@@ -1010,12 +1011,13 @@ struct ZedAiConfiguration {
 }
 
 impl RenderOnce for ZedAiConfiguration {
-    fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
-        let young_account_banner = YoungAccountBanner;
-
+    fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
         let is_pro = self.plan.is_some_and(|plan| {
             matches!(plan, Plan::V1(PlanV1::ZedPro) | Plan::V2(PlanV2::ZedPro))
         });
+        let is_free_v2 = self
+            .plan
+            .is_some_and(|plan| plan == Plan::V2(PlanV2::ZedFree));
         let subscription_text = match (self.plan, self.subscription_period) {
             (Some(Plan::V1(PlanV1::ZedPro) | Plan::V2(PlanV2::ZedPro)), Some(_)) => {
                 "You have access to Zed's hosted models through your Pro subscription."
@@ -1023,9 +1025,16 @@ impl RenderOnce for ZedAiConfiguration {
             (Some(Plan::V1(PlanV1::ZedProTrial) | Plan::V2(PlanV2::ZedProTrial)), Some(_)) => {
                 "You have access to Zed's hosted models through your Pro trial."
             }
-            (Some(Plan::V1(PlanV1::ZedFree) | Plan::V2(PlanV2::ZedFree)), Some(_)) => {
+            (Some(Plan::V1(PlanV1::ZedFree)), Some(_)) => {
                 "You have basic access to Zed's hosted models through the Free plan."
             }
+            (Some(Plan::V2(PlanV2::ZedFree)), Some(_)) => {
+                if self.eligible_for_trial {
+                    "Subscribe for access to Zed's hosted models. Start with a 14 day free trial."
+                } else {
+                    "Subscribe for access to Zed's hosted models."
+                }
+            }
             _ => {
                 if self.eligible_for_trial {
                     "Subscribe for access to Zed's hosted models. Start with a 14 day free trial."
@@ -1075,7 +1084,10 @@ impl RenderOnce for ZedAiConfiguration {
 
         v_flex().gap_2().w_full().map(|this| {
             if self.account_too_young {
-                this.child(young_account_banner).child(
+                this.child(YoungAccountBanner::new(
+                    is_free_v2 || cx.has_flag::<BillingV2FeatureFlag>(),
+                ))
+                .child(
                     Button::new("upgrade", "Upgrade to Pro")
                         .style(ui::ButtonStyle::Tinted(ui::TintColor::Accent))
                         .full_width()