From d4112ffa0e2ad3bb9549a90dfc1bece5eb77d4b9 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Tue, 23 Sep 2025 13:44:43 -0400 Subject: [PATCH] Update plan text (#38731) Release Notes: - N/A --------- Co-authored-by: David Kleingeld --- 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 +++++++++++++------ .../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(-) diff --git a/Cargo.lock b/Cargo.lock index 3a7a9568928481689223c33fbbae8c046b1592c3..6a8a03853d3e70ad80a43a7751322287f794cc8d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9560,6 +9560,7 @@ dependencies = [ "credentials_provider", "deepseek", "editor", + "feature_flags", "fs", "futures 0.3.31", "google_ai", diff --git a/crates/agent_ui/src/ui/end_trial_upsell.rs b/crates/agent_ui/src/ui/end_trial_upsell.rs index 4db9244469cf1ad7fab414874a64e45f3b97e377..9c25519659056354ed5f575be885a46151497c2e 100644 --- a/crates/agent_ui/src/ui/end_trial_upsell.rs +++ b/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(), diff --git a/crates/ai_onboarding/src/ai_onboarding.rs b/crates/ai_onboarding/src/ai_onboarding.rs index e131a60b12c2b330ff3a6c099713fa4a4a083358..35e5832d3eeae4450ab9d91deed1c3a08b564aab 100644 --- a/crates/ai_onboarding/src/ai_onboarding.rs +++ b/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, ), ), diff --git a/crates/ai_onboarding/src/ai_upsell_card.rs b/crates/ai_onboarding/src/ai_upsell_card.rs index 51758dd9ac123b309450018bd254a2aae31d68af..f6a47596664b3963c7a8d104c7b53fd4a4100e78 100644 --- a/crates/ai_onboarding/src/ai_upsell_card.rs +++ b/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(), diff --git a/crates/ai_onboarding/src/plan_definitions.rs b/crates/ai_onboarding/src/plan_definitions.rs index dce67d421006ce918018923b86dbe22012efef01..11f563117132bd6860d8aef655bd0ed6b392e0e7 100644 --- a/crates/ai_onboarding/src/plan_definitions.rs +++ b/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")) + }) + } } } diff --git a/crates/ai_onboarding/src/young_account_banner.rs b/crates/ai_onboarding/src/young_account_banner.rs index ae13b9556885c1552f7e90935f844347cd76a778..fde9a31c49bc950db2fb77702cfd0befe61d4fa3 100644 --- a/crates/ai_onboarding/src/young_account_banner.rs +++ b/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() diff --git a/crates/feature_flags/src/flags.rs b/crates/feature_flags/src/flags.rs index eaae9ce42519a8ebfd40d73d242536c5188fa3dd..d8c8bec873699328b9d2740c97ffc97ffe190429 100644 --- a/crates/feature_flags/src/flags.rs +++ b/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; diff --git a/crates/language_models/Cargo.toml b/crates/language_models/Cargo.toml index 8a2a681c26ede21ce948b6667b4aaea589724dcf..d4ae4b26b6626684e458193e0fb6ca628aa6c087 100644 --- a/crates/language_models/Cargo.toml +++ b/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"] } diff --git a/crates/language_models/src/provider/cloud.rs b/crates/language_models/src/provider/cloud.rs index 24a4ba6cc260a91de170bb665c86756c8d7a25ca..a13ce622466049d0fb8cd09333fe36ffb0332408 100644 --- a/crates/language_models/src/provider/cloud.rs +++ b/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::(), + )) + .child( Button::new("upgrade", "Upgrade to Pro") .style(ui::ButtonStyle::Tinted(ui::TintColor::Accent)) .full_width()