Cargo.lock 🔗
@@ -482,6 +482,7 @@ dependencies = [
"client",
"cloud_llm_client",
"component",
+ "feature_flags",
"gpui",
"language_model",
"serde",
Marshall Bowers created
This PR adds new variants to the `Plan` enum.
Release Notes:
- N/A
Cargo.lock | 1
crates/agent_ui/src/acp/thread_view.rs | 1
crates/agent_ui/src/agent_configuration.rs | 6 +
crates/agent_ui/src/agent_panel.rs | 1
crates/agent_ui/src/ui/end_trial_upsell.rs | 7 +-
crates/agent_ui/src/ui/preview/usage_callouts.rs | 4
crates/ai_onboarding/Cargo.toml | 1
crates/ai_onboarding/src/ai_onboarding.rs | 39 ++++++++---------
crates/ai_onboarding/src/ai_upsell_card.rs | 23 +++++-----
crates/ai_onboarding/src/plan_definitions.rs | 6 +-
crates/cloud_llm_client/src/cloud_llm_client.rs | 8 +++
crates/feature_flags/src/feature_flags.rs | 6 ++
crates/language_model/src/model/cloud_model.rs | 1
crates/title_bar/src/title_bar.rs | 8 ++
14 files changed, 68 insertions(+), 44 deletions(-)
@@ -482,6 +482,7 @@ dependencies = [
"client",
"cloud_llm_client",
"component",
+ "feature_flags",
"gpui",
"language_model",
"serde",
@@ -5005,6 +5005,7 @@ impl AcpThreadView {
cloud_llm_client::Plan::ZedProTrial | cloud_llm_client::Plan::ZedFree => {
"Upgrade to Zed Pro for more prompts."
}
+ cloud_llm_client::Plan::ZedProV2 | cloud_llm_client::Plan::ZedProTrialV2 => "",
};
Callout::new()
@@ -516,8 +516,10 @@ impl AgentConfiguration {
let (plan_name, label_color, bg_color) = match plan {
Plan::ZedFree => ("Free", Color::Default, free_chip_bg),
- Plan::ZedProTrial => ("Pro Trial", Color::Accent, pro_chip_bg),
- Plan::ZedPro => ("Pro", Color::Accent, pro_chip_bg),
+ Plan::ZedProTrial | Plan::ZedProTrialV2 => {
+ ("Pro Trial", Color::Accent, pro_chip_bg)
+ }
+ Plan::ZedPro | Plan::ZedProV2 => ("Pro", Color::Accent, pro_chip_bg),
};
Chip::new(plan_name.to_string())
@@ -3518,6 +3518,7 @@ impl AgentPanel {
let error_message = match plan {
Plan::ZedPro => "Upgrade to usage-based billing for more prompts.",
Plan::ZedProTrial | Plan::ZedFree => "Upgrade to Zed Pro for more prompts.",
+ Plan::ZedProV2 | Plan::ZedProTrialV2 => "",
};
Callout::new()
@@ -2,6 +2,7 @@ use std::sync::Arc;
use ai_onboarding::{AgentPanelOnboardingCard, PlanDefinitions};
use client::zed_urls;
+use feature_flags::{BillingV2FeatureFlag, FeatureFlagAppExt as _};
use gpui::{AnyElement, App, IntoElement, RenderOnce, Window};
use ui::{Divider, Tooltip, prelude::*};
@@ -18,8 +19,6 @@ impl EndTrialUpsell {
impl RenderOnce for EndTrialUpsell {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
- let plan_definitions = PlanDefinitions;
-
let pro_section = v_flex()
.gap_1()
.child(
@@ -33,7 +32,7 @@ impl RenderOnce for EndTrialUpsell {
)
.child(Divider::horizontal()),
)
- .child(plan_definitions.pro_plan(false))
+ .child(PlanDefinitions.pro_plan(cx.has_flag::<BillingV2FeatureFlag>(), false))
.child(
Button::new("cta-button", "Upgrade to Zed Pro")
.full_width()
@@ -64,7 +63,7 @@ impl RenderOnce for EndTrialUpsell {
)
.child(Divider::horizontal()),
)
- .child(plan_definitions.free_plan());
+ .child(PlanDefinitions.free_plan(cx.has_flag::<BillingV2FeatureFlag>()));
AgentPanelOnboardingCard::new()
.child(Headline::new("Your Zed Pro Trial has expired"))
@@ -45,13 +45,13 @@ impl RenderOnce for UsageCallout {
"Upgrade",
zed_urls::account_url(cx),
),
- Plan::ZedProTrial => (
+ Plan::ZedProTrial | Plan::ZedProTrialV2 => (
"Out of trial prompts",
"Upgrade to Zed Pro to continue, or switch to API key.".to_string(),
"Upgrade",
zed_urls::account_url(cx),
),
- Plan::ZedPro => (
+ Plan::ZedPro | Plan::ZedProV2 => (
"Out of included prompts",
"Enable usage-based billing to continue.".to_string(),
"Manage",
@@ -18,6 +18,7 @@ default = []
client.workspace = true
cloud_llm_client.workspace = true
component.workspace = true
+feature_flags.workspace = true
gpui.workspace = true
language_model.workspace = true
serde.workspace = true
@@ -18,6 +18,7 @@ pub use young_account_banner::YoungAccountBanner;
use std::sync::Arc;
use client::{Client, UserStore, zed_urls};
+use feature_flags::{BillingV2FeatureFlag, FeatureFlagAppExt as _};
use gpui::{AnyElement, Entity, IntoElement, ParentElement};
use ui::{Divider, RegisterComponent, Tooltip, prelude::*};
@@ -84,9 +85,8 @@ impl ZedAiOnboarding {
self
}
- fn render_sign_in_disclaimer(&self, _cx: &mut App) -> AnyElement {
+ fn render_sign_in_disclaimer(&self, cx: &mut App) -> AnyElement {
let signing_in = matches!(self.sign_in_status, SignInStatus::SigningIn);
- let plan_definitions = PlanDefinitions;
v_flex()
.gap_1()
@@ -96,7 +96,7 @@ impl ZedAiOnboarding {
.color(Color::Muted)
.mb_2(),
)
- .child(plan_definitions.pro_plan(false))
+ .child(PlanDefinitions.pro_plan(cx.has_flag::<BillingV2FeatureFlag>(), false))
.child(
Button::new("sign_in", "Try Zed Pro for Free")
.disabled(signing_in)
@@ -114,16 +114,13 @@ impl ZedAiOnboarding {
}
fn render_free_plan_state(&self, cx: &mut App) -> AnyElement {
- let young_account_banner = YoungAccountBanner;
- let plan_definitions = PlanDefinitions;
-
if self.account_too_young {
v_flex()
.relative()
.max_w_full()
.gap_1()
.child(Headline::new("Welcome to Zed AI"))
- .child(young_account_banner)
+ .child(YoungAccountBanner)
.child(
v_flex()
.mt_2()
@@ -139,7 +136,9 @@ impl ZedAiOnboarding {
)
.child(Divider::horizontal()),
)
- .child(plan_definitions.pro_plan(true))
+ .child(
+ PlanDefinitions.pro_plan(cx.has_flag::<BillingV2FeatureFlag>(), true),
+ )
.child(
Button::new("pro", "Get Started")
.full_width()
@@ -182,7 +181,7 @@ impl ZedAiOnboarding {
)
.child(Divider::horizontal()),
)
- .child(plan_definitions.free_plan()),
+ .child(PlanDefinitions.free_plan(cx.has_flag::<BillingV2FeatureFlag>())),
)
.when_some(
self.dismiss_onboarding.as_ref(),
@@ -220,7 +219,9 @@ impl ZedAiOnboarding {
)
.child(Divider::horizontal()),
)
- .child(plan_definitions.pro_trial(true))
+ .child(
+ PlanDefinitions.pro_trial(cx.has_flag::<BillingV2FeatureFlag>(), true),
+ )
.child(
Button::new("pro", "Start Free Trial")
.full_width()
@@ -238,9 +239,7 @@ impl ZedAiOnboarding {
}
}
- fn render_trial_state(&self, _cx: &mut App) -> AnyElement {
- let plan_definitions = PlanDefinitions;
-
+ fn render_trial_state(&self, is_v2: bool, _cx: &mut App) -> AnyElement {
v_flex()
.relative()
.gap_1()
@@ -250,7 +249,7 @@ impl ZedAiOnboarding {
.color(Color::Muted)
.mb_2(),
)
- .child(plan_definitions.pro_trial(false))
+ .child(PlanDefinitions.pro_trial(is_v2, false))
.when_some(
self.dismiss_onboarding.as_ref(),
|this, dismiss_callback| {
@@ -274,9 +273,7 @@ impl ZedAiOnboarding {
.into_any_element()
}
- fn render_pro_plan_state(&self, _cx: &mut App) -> AnyElement {
- let plan_definitions = PlanDefinitions;
-
+ fn render_pro_plan_state(&self, is_v2: bool, _cx: &mut App) -> AnyElement {
v_flex()
.gap_1()
.child(Headline::new("Welcome to Zed Pro"))
@@ -285,7 +282,7 @@ impl ZedAiOnboarding {
.color(Color::Muted)
.mb_2(),
)
- .child(plan_definitions.pro_plan(false))
+ .child(PlanDefinitions.pro_plan(is_v2, false))
.when_some(
self.dismiss_onboarding.as_ref(),
|this, dismiss_callback| {
@@ -315,8 +312,10 @@ impl RenderOnce for ZedAiOnboarding {
if matches!(self.sign_in_status, SignInStatus::SignedIn) {
match self.plan {
None | Some(Plan::ZedFree) => self.render_free_plan_state(cx),
- Some(Plan::ZedProTrial) => self.render_trial_state(cx),
- Some(Plan::ZedPro) => self.render_pro_plan_state(cx),
+ Some(Plan::ZedProTrial) => self.render_trial_state(false, cx),
+ Some(Plan::ZedProTrialV2) => self.render_trial_state(true, cx),
+ Some(Plan::ZedPro) => self.render_pro_plan_state(false, cx),
+ Some(Plan::ZedProV2) => self.render_pro_plan_state(true, cx),
}
} else {
self.render_sign_in_disclaimer(cx)
@@ -2,6 +2,7 @@ use std::sync::Arc;
use client::{Client, UserStore, zed_urls};
use cloud_llm_client::Plan;
+use feature_flags::{BillingV2FeatureFlag, FeatureFlagAppExt};
use gpui::{AnyElement, App, Entity, IntoElement, RenderOnce, Window};
use ui::{CommonAnimationExt, Divider, Vector, VectorName, prelude::*};
@@ -49,9 +50,6 @@ impl AiUpsellCard {
impl RenderOnce for AiUpsellCard {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
- let plan_definitions = PlanDefinitions;
- let young_account_banner = YoungAccountBanner;
-
let pro_section = v_flex()
.flex_grow()
.w_full()
@@ -67,7 +65,7 @@ impl RenderOnce for AiUpsellCard {
)
.child(Divider::horizontal()),
)
- .child(plan_definitions.pro_plan(false));
+ .child(PlanDefinitions.pro_plan(cx.has_flag::<BillingV2FeatureFlag>(), false));
let free_section = v_flex()
.flex_grow()
@@ -84,7 +82,7 @@ impl RenderOnce for AiUpsellCard {
)
.child(Divider::horizontal()),
)
- .child(plan_definitions.free_plan());
+ .child(PlanDefinitions.free_plan(cx.has_flag::<BillingV2FeatureFlag>()));
let grid_bg = h_flex()
.absolute()
@@ -173,7 +171,7 @@ impl RenderOnce for AiUpsellCard {
.child(Label::new("Try Zed AI").size(LabelSize::Large))
.map(|this| {
if self.account_too_young {
- this.child(young_account_banner).child(
+ this.child(YoungAccountBanner).child(
v_flex()
.mt_2()
.gap_1()
@@ -188,7 +186,10 @@ impl RenderOnce for AiUpsellCard {
)
.child(Divider::horizontal()),
)
- .child(plan_definitions.pro_plan(true))
+ .child(
+ PlanDefinitions
+ .pro_plan(cx.has_flag::<BillingV2FeatureFlag>(), true),
+ )
.child(
Button::new("pro", "Get Started")
.full_width()
@@ -235,7 +236,7 @@ impl RenderOnce for AiUpsellCard {
)
}
}),
- Some(Plan::ZedProTrial) => card
+ Some(plan @ Plan::ZedProTrial | plan @ Plan::ZedProTrialV2) => card
.child(pro_trial_stamp)
.child(Label::new("You're in the Zed Pro Trial").size(LabelSize::Large))
.child(
@@ -243,8 +244,8 @@ impl RenderOnce for AiUpsellCard {
.color(Color::Muted)
.mb_2(),
)
- .child(plan_definitions.pro_trial(false)),
- Some(Plan::ZedPro) => card
+ .child(PlanDefinitions.pro_trial(plan == Plan::ZedProTrialV2, false)),
+ Some(plan @ Plan::ZedPro | plan @ Plan::ZedProV2) => card
.child(certified_user_stamp)
.child(Label::new("You're in the Zed Pro plan").size(LabelSize::Large))
.child(
@@ -252,7 +253,7 @@ impl RenderOnce for AiUpsellCard {
.color(Color::Muted)
.mb_2(),
)
- .child(plan_definitions.pro_plan(false)),
+ .child(PlanDefinitions.pro_plan(plan == Plan::ZedProV2, false)),
},
// Signed Out State
_ => card
@@ -7,13 +7,13 @@ 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) -> impl IntoElement {
+ 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, period: bool) -> impl IntoElement {
+ 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(
@@ -26,7 +26,7 @@ impl PlanDefinitions {
})
}
- pub fn pro_plan(&self, price: bool) -> impl IntoElement {
+ 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(
@@ -82,8 +82,10 @@ pub enum Plan {
ZedFree,
#[serde(alias = "ZedPro")]
ZedPro,
+ ZedProV2,
#[serde(alias = "ZedProTrial")]
ZedProTrial,
+ ZedProTrialV2,
}
impl FromStr for Plan {
@@ -327,6 +329,12 @@ mod tests {
let plan = serde_json::from_value::<Plan>(json!("zed_pro_trial")).unwrap();
assert_eq!(plan, Plan::ZedProTrial);
+
+ let plan = serde_json::from_value::<Plan>(json!("zed_pro_v2")).unwrap();
+ assert_eq!(plan, Plan::ZedProV2);
+
+ let plan = serde_json::from_value::<Plan>(json!("zed_pro_trial_v2")).unwrap();
+ assert_eq!(plan, Plan::ZedProTrialV2);
}
#[test]
@@ -66,6 +66,12 @@ impl FeatureFlag for LlmClosedBetaFeatureFlag {
const NAME: &'static str = "llm-closed-beta";
}
+pub struct BillingV2FeatureFlag {}
+
+impl FeatureFlag for BillingV2FeatureFlag {
+ const NAME: &'static str = "billing-v2";
+}
+
pub struct NotebookFeatureFlag;
impl FeatureFlag for NotebookFeatureFlag {
@@ -36,6 +36,7 @@ impl fmt::Display for ModelRequestLimitReachedError {
Plan::ZedProTrial => {
"Model request limit reached. Upgrade to Zed Pro for more requests."
}
+ Plan::ZedProV2 | Plan::ZedProTrialV2 => "Model request limit reached.",
};
write!(f, "{message}")
@@ -660,8 +660,12 @@ impl TitleBar {
let (plan_name, label_color, bg_color) = match plan {
None | Some(Plan::ZedFree) => ("Free", Color::Default, free_chip_bg),
- Some(Plan::ZedProTrial) => ("Pro Trial", Color::Accent, pro_chip_bg),
- Some(Plan::ZedPro) => ("Pro", Color::Accent, pro_chip_bg),
+ Some(Plan::ZedProTrial | Plan::ZedProTrialV2) => {
+ ("Pro Trial", Color::Accent, pro_chip_bg)
+ }
+ Some(Plan::ZedPro | Plan::ZedProV2) => {
+ ("Pro", Color::Accent, pro_chip_bg)
+ }
};
menu.custom_entry(