Detailed changes
@@ -11,7 +11,7 @@ use postage::{sink::Sink, watch};
use rpc::proto::{RequestMessage, UsersResponse};
use std::sync::{Arc, Weak};
use text::ReplicaId;
-use util::TryFutureExt as _;
+use util::{TryFutureExt as _, maybe};
pub type UserId = u64;
@@ -101,6 +101,7 @@ pub struct UserStore {
participant_indices: HashMap<u64, ParticipantIndex>,
update_contacts_tx: mpsc::UnboundedSender<UpdateContacts>,
current_plan: Option<proto::Plan>,
+ subscription_period: Option<(DateTime<Utc>, DateTime<Utc>)>,
trial_started_at: Option<DateTime<Utc>>,
model_request_usage_amount: Option<u32>,
model_request_usage_limit: Option<proto::UsageLimit>,
@@ -166,6 +167,7 @@ impl UserStore {
by_github_login: Default::default(),
current_user: current_user_rx,
current_plan: None,
+ subscription_period: None,
trial_started_at: None,
model_request_usage_amount: None,
model_request_usage_limit: None,
@@ -333,6 +335,13 @@ impl UserStore {
) -> Result<()> {
this.update(&mut cx, |this, cx| {
this.current_plan = Some(message.payload.plan());
+ this.subscription_period = maybe!({
+ let period = message.payload.subscription_period?;
+ let started_at = DateTime::from_timestamp(period.started_at as i64, 0)?;
+ let ended_at = DateTime::from_timestamp(period.ended_at as i64, 0)?;
+
+ Some((started_at, ended_at))
+ });
this.trial_started_at = message
.payload
.trial_started_at
@@ -713,6 +722,10 @@ impl UserStore {
self.current_plan
}
+ pub fn subscription_period(&self) -> Option<(DateTime<Utc>, DateTime<Utc>)> {
+ self.subscription_period
+ }
+
pub fn trial_started_at(&self) -> Option<DateTime<Utc>> {
self.trial_started_at
}
@@ -2709,7 +2709,7 @@ async fn update_user_plan(user_id: UserId, session: &Session) -> Result<()> {
let billing_customer = db.get_billing_customer_by_user_id(user_id).await?;
let billing_preferences = db.get_billing_preferences(user_id).await?;
- let usage = if let Some(llm_db) = session.app_state.llm_db.clone() {
+ let (subscription_period, usage) = if let Some(llm_db) = session.app_state.llm_db.clone() {
let subscription = db.get_active_billing_subscription(user_id).await?;
let subscription_period = crate::db::billing_subscription::Model::current_period(
@@ -2717,15 +2717,17 @@ async fn update_user_plan(user_id: UserId, session: &Session) -> Result<()> {
session.is_staff(),
);
- if let Some((period_start_at, period_end_at)) = subscription_period {
+ let usage = if let Some((period_start_at, period_end_at)) = subscription_period {
llm_db
.get_subscription_usage_for_period(user_id, period_start_at, period_end_at)
.await?
} else {
None
- }
+ };
+
+ (subscription_period, usage)
} else {
- None
+ (None, None)
};
session
@@ -2743,6 +2745,12 @@ async fn update_user_plan(user_id: UserId, session: &Session) -> Result<()> {
billing_preferences
.map(|preferences| preferences.model_request_overages_enabled)
},
+ subscription_period: subscription_period.map(|(started_at, ended_at)| {
+ proto::SubscriptionPeriod {
+ started_at: started_at.timestamp() as u64,
+ ended_at: ended_at.timestamp() as u64,
+ }
+ }),
usage: usage.map(|usage| {
let plan = match plan {
proto::Plan::Free => zed_llm_client::Plan::Free,
@@ -2,7 +2,7 @@ use anthropic::{AnthropicModelMode, parse_prompt_too_long};
use anyhow::{Result, anyhow};
use client::{Client, UserStore, zed_urls};
use collections::BTreeMap;
-use feature_flags::{FeatureFlagAppExt, LlmClosedBetaFeatureFlag, ZedProFeatureFlag};
+use feature_flags::{FeatureFlagAppExt, LlmClosedBetaFeatureFlag};
use futures::{
AsyncBufReadExt, FutureExt, Stream, StreamExt, future::BoxFuture, stream::BoxStream,
};
@@ -1036,48 +1036,56 @@ impl ConfigurationView {
impl Render for ConfigurationView {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
- const ZED_AI_URL: &str = "https://zed.dev/ai";
+ const ZED_PRICING_URL: &str = "https://zed.dev/pricing";
let is_connected = !self.state.read(cx).is_signed_out();
- let plan = self.state.read(cx).user_store.read(cx).current_plan();
+ let user_store = self.state.read(cx).user_store.read(cx);
+ let plan = user_store.current_plan();
+ let subscription_period = user_store.subscription_period();
+ let eligible_for_trial = user_store.trial_started_at().is_none();
let has_accepted_terms = self.state.read(cx).has_accepted_terms_of_service(cx);
let is_pro = plan == Some(proto::Plan::ZedPro);
- let subscription_text = Label::new(if is_pro {
- "You have access to Zed's hosted LLMs through your Zed Pro subscription."
+ let subscription_text = match (plan, subscription_period) {
+ (Some(proto::Plan::ZedPro), Some(_)) => {
+ "You have access to Zed's hosted LLMs through your Zed Pro subscription."
+ }
+ (Some(proto::Plan::ZedProTrial), Some(_)) => {
+ "You have access to Zed's hosted LLMs through your Zed Pro trial."
+ }
+ (Some(proto::Plan::Free), Some(_)) => {
+ "You have basic access to Zed's hosted LLMs through your Zed Free subscription."
+ }
+ _ => {
+ if eligible_for_trial {
+ "Subscribe for access to Zed's hosted LLMs. Start with a 14 day free trial."
+ } else {
+ "Subscribe for access to Zed's hosted LLMs."
+ }
+ }
+ };
+ let manage_subscription_buttons = if is_pro {
+ h_flex().child(
+ Button::new("manage_settings", "Manage Subscription")
+ .style(ButtonStyle::Tinted(TintColor::Accent))
+ .on_click(cx.listener(|_, _, _, cx| cx.open_url(&zed_urls::account_url(cx)))),
+ )
} else {
- "You have basic access to models from Anthropic through the Zed AI Free plan."
- });
- let manage_subscription_button = if is_pro {
- Some(
- h_flex().child(
- Button::new("manage_settings", "Manage Subscription")
- .style(ButtonStyle::Tinted(TintColor::Accent))
+ h_flex()
+ .gap_2()
+ .child(
+ Button::new("learn_more", "Learn more")
+ .style(ButtonStyle::Subtle)
+ .on_click(cx.listener(|_, _, _, cx| cx.open_url(ZED_PRICING_URL))),
+ )
+ .child(
+ Button::new("upgrade", "Upgrade")
+ .style(ButtonStyle::Subtle)
+ .color(Color::Accent)
.on_click(
cx.listener(|_, _, _, cx| cx.open_url(&zed_urls::account_url(cx))),
),
- ),
- )
- } else if cx.has_flag::<ZedProFeatureFlag>() {
- Some(
- h_flex()
- .gap_2()
- .child(
- Button::new("learn_more", "Learn more")
- .style(ButtonStyle::Subtle)
- .on_click(cx.listener(|_, _, _, cx| cx.open_url(ZED_AI_URL))),
- )
- .child(
- Button::new("upgrade", "Upgrade")
- .style(ButtonStyle::Subtle)
- .color(Color::Accent)
- .on_click(
- cx.listener(|_, _, _, cx| cx.open_url(&zed_urls::account_url(cx))),
- ),
- ),
- )
- } else {
- None
+ )
};
if is_connected {
@@ -1091,7 +1099,7 @@ impl Render for ConfigurationView {
))
.when(has_accepted_terms, |this| {
this.child(subscription_text)
- .children(manage_subscription_button)
+ .child(manage_subscription_buttons)
})
} else {
v_flex()
@@ -26,6 +26,12 @@ message UpdateUserPlan {
optional uint64 trial_started_at = 2;
optional bool is_usage_based_billing_enabled = 3;
optional SubscriptionUsage usage = 4;
+ optional SubscriptionPeriod subscription_period = 5;
+}
+
+message SubscriptionPeriod {
+ uint64 started_at = 1;
+ uint64 ended_at = 2;
}
message SubscriptionUsage {