Update plan text (#38731)

Release Notes:

- N/A

---------

Co-authored-by: David Kleingeld <davidsk@zed.dev>
This commit is contained in:
Marshall Bowers 2025-09-23 13:44:43 -04:00 committed by GitHub
parent 3646aa6bba
commit f78699eb71
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 106 additions and 46 deletions

1
Cargo.lock generated
View file

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

View file

@ -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(),

View file

@ -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,
),
),

View file

@ -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(),

View file

@ -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 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 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_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_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 {
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"))
})
}
}
}

View file

@ -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()

View file

@ -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;

View file

@ -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"] }

View file

@ -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,
};
@ -973,12 +974,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."
@ -986,9 +988,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."
@ -1038,7 +1047,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()