onboarding: Remove accept AI ToS from within Zed (#36612)

Users now accept ToS from Zed's website when they sign in to Zed the
first time. So it's no longer possible that a signed in account could
not have accepted the ToS.


Release Notes:

- N/A

---------

Co-authored-by: Mikayla Maki <mikayla.c.maki@gmail.com>
This commit is contained in:
Anthony Eid 2025-08-22 11:45:47 -04:00 committed by GitHub
parent 3d2fa72d1f
commit 8204ef1e51
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 44 additions and 499 deletions

View file

@ -1595,11 +1595,6 @@ impl ActiveThread {
return;
};
if model.provider.must_accept_terms(cx) {
cx.notify();
return;
}
let edited_text = state.editor.read(cx).text(cx);
let creases = state.editor.update(cx, extract_message_creases);

View file

@ -93,14 +93,6 @@ impl AgentConfiguration {
let scroll_handle = ScrollHandle::new();
let scrollbar_state = ScrollbarState::new(scroll_handle.clone());
let mut expanded_provider_configurations = HashMap::default();
if LanguageModelRegistry::read_global(cx)
.provider(&ZED_CLOUD_PROVIDER_ID)
.is_some_and(|cloud_provider| cloud_provider.must_accept_terms(cx))
{
expanded_provider_configurations.insert(ZED_CLOUD_PROVIDER_ID, true);
}
let mut this = Self {
fs,
language_registry,
@ -109,7 +101,7 @@ impl AgentConfiguration {
configuration_views_by_provider: HashMap::default(),
context_server_store,
expanded_context_server_tools: HashMap::default(),
expanded_provider_configurations,
expanded_provider_configurations: HashMap::default(),
tools,
_registry_subscription: registry_subscription,
scroll_handle,

View file

@ -54,9 +54,7 @@ use gpui::{
Pixels, Subscription, Task, UpdateGlobal, WeakEntity, prelude::*, pulsating_between,
};
use language::LanguageRegistry;
use language_model::{
ConfigurationError, ConfiguredModel, LanguageModelProviderTosView, LanguageModelRegistry,
};
use language_model::{ConfigurationError, ConfiguredModel, LanguageModelRegistry};
use project::{DisableAiSettings, Project, ProjectPath, Worktree};
use prompt_store::{PromptBuilder, PromptStore, UserPromptId};
use rules_library::{RulesLibrary, open_rules_library};
@ -3203,17 +3201,6 @@ impl AgentPanel {
ConfigurationError::ModelNotFound
| ConfigurationError::ProviderNotAuthenticated(_)
| ConfigurationError::NoProvider => callout.into_any_element(),
ConfigurationError::ProviderPendingTermsAcceptance(provider) => {
Banner::new()
.severity(Severity::Warning)
.child(h_flex().w_full().children(
provider.render_accept_terms(
LanguageModelProviderTosView::ThreadEmptyState,
cx,
),
))
.into_any_element()
}
}
}

View file

@ -378,18 +378,13 @@ impl MessageEditor {
}
fn send_to_model(&mut self, window: &mut Window, cx: &mut Context<Self>) {
let Some(ConfiguredModel { model, provider }) = self
let Some(ConfiguredModel { model, .. }) = self
.thread
.update(cx, |thread, cx| thread.get_or_init_configured_model(cx))
else {
return;
};
if provider.must_accept_terms(cx) {
cx.notify();
return;
}
let (user_message, user_message_creases) = self.editor.update(cx, |editor, cx| {
let creases = extract_message_creases(editor, cx);
let text = editor.text(cx);

View file

@ -190,7 +190,6 @@ pub struct TextThreadEditor {
invoked_slash_command_creases: HashMap<InvokedSlashCommandId, CreaseId>,
_subscriptions: Vec<Subscription>,
last_error: Option<AssistError>,
show_accept_terms: bool,
pub(crate) slash_menu_handle:
PopoverMenuHandle<Picker<slash_command_picker::SlashCommandDelegate>>,
// dragged_file_worktrees is used to keep references to worktrees that were added
@ -289,7 +288,6 @@ impl TextThreadEditor {
invoked_slash_command_creases: HashMap::default(),
_subscriptions,
last_error: None,
show_accept_terms: false,
slash_menu_handle: Default::default(),
dragged_file_worktrees: Vec::new(),
language_model_selector: cx.new(|cx| {
@ -367,20 +365,7 @@ impl TextThreadEditor {
}
fn send_to_model(&mut self, window: &mut Window, cx: &mut Context<Self>) {
let provider = LanguageModelRegistry::read_global(cx)
.default_model()
.map(|default| default.provider);
if provider
.as_ref()
.is_some_and(|provider| provider.must_accept_terms(cx))
{
self.show_accept_terms = true;
cx.notify();
return;
}
self.last_error = None;
if let Some(user_message) = self.context.update(cx, |context, cx| context.assist(cx)) {
let new_selection = {
let cursor = user_message
@ -1930,7 +1915,6 @@ impl TextThreadEditor {
ConfigurationError::NoProvider
| ConfigurationError::ModelNotFound
| ConfigurationError::ProviderNotAuthenticated(_) => true,
ConfigurationError::ProviderPendingTermsAcceptance(_) => self.show_accept_terms,
}
}

View file

@ -19,7 +19,7 @@ use std::sync::Arc;
use client::{Client, UserStore, zed_urls};
use gpui::{AnyElement, Entity, IntoElement, ParentElement};
use ui::{Divider, RegisterComponent, TintColor, Tooltip, prelude::*};
use ui::{Divider, RegisterComponent, Tooltip, prelude::*};
#[derive(PartialEq)]
pub enum SignInStatus {
@ -43,12 +43,10 @@ impl From<client::Status> for SignInStatus {
#[derive(RegisterComponent, IntoElement)]
pub struct ZedAiOnboarding {
pub sign_in_status: SignInStatus,
pub has_accepted_terms_of_service: bool,
pub plan: Option<Plan>,
pub account_too_young: bool,
pub continue_with_zed_ai: Arc<dyn Fn(&mut Window, &mut App)>,
pub sign_in: Arc<dyn Fn(&mut Window, &mut App)>,
pub accept_terms_of_service: Arc<dyn Fn(&mut Window, &mut App)>,
pub dismiss_onboarding: Option<Arc<dyn Fn(&mut Window, &mut App)>>,
}
@ -64,17 +62,9 @@ impl ZedAiOnboarding {
Self {
sign_in_status: status.into(),
has_accepted_terms_of_service: store.has_accepted_terms_of_service(),
plan: store.plan(),
account_too_young: store.account_too_young(),
continue_with_zed_ai,
accept_terms_of_service: Arc::new({
let store = user_store.clone();
move |_window, cx| {
let task = store.update(cx, |store, cx| store.accept_terms_of_service(cx));
task.detach_and_log_err(cx);
}
}),
sign_in: Arc::new(move |_window, cx| {
cx.spawn({
let client = client.clone();
@ -94,42 +84,6 @@ impl ZedAiOnboarding {
self
}
fn render_accept_terms_of_service(&self) -> AnyElement {
v_flex()
.gap_1()
.w_full()
.child(Headline::new("Accept Terms of Service"))
.child(
Label::new("We dont sell your data, track you across the web, or compromise your privacy.")
.color(Color::Muted)
.mb_2(),
)
.child(
Button::new("terms_of_service", "Review Terms of Service")
.full_width()
.style(ButtonStyle::Outlined)
.icon(IconName::ArrowUpRight)
.icon_color(Color::Muted)
.icon_size(IconSize::Small)
.on_click(move |_, _window, cx| {
telemetry::event!("Review Terms of Service Clicked");
cx.open_url(&zed_urls::terms_of_service(cx))
}),
)
.child(
Button::new("accept_terms", "Accept")
.full_width()
.style(ButtonStyle::Tinted(TintColor::Accent))
.on_click({
let callback = self.accept_terms_of_service.clone();
move |_, window, cx| {
telemetry::event!("Terms of Service Accepted");
(callback)(window, cx)}
}),
)
.into_any_element()
}
fn render_sign_in_disclaimer(&self, _cx: &mut App) -> AnyElement {
let signing_in = matches!(self.sign_in_status, SignInStatus::SigningIn);
let plan_definitions = PlanDefinitions;
@ -359,14 +313,10 @@ impl ZedAiOnboarding {
impl RenderOnce for ZedAiOnboarding {
fn render(self, _window: &mut ui::Window, cx: &mut App) -> impl IntoElement {
if matches!(self.sign_in_status, SignInStatus::SignedIn) {
if self.has_accepted_terms_of_service {
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),
}
} else {
self.render_accept_terms_of_service()
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),
}
} else {
self.render_sign_in_disclaimer(cx)
@ -390,18 +340,15 @@ impl Component for ZedAiOnboarding {
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
fn onboarding(
sign_in_status: SignInStatus,
has_accepted_terms_of_service: bool,
plan: Option<Plan>,
account_too_young: bool,
) -> AnyElement {
ZedAiOnboarding {
sign_in_status,
has_accepted_terms_of_service,
plan,
account_too_young,
continue_with_zed_ai: Arc::new(|_, _| {}),
sign_in: Arc::new(|_, _| {}),
accept_terms_of_service: Arc::new(|_, _| {}),
dismiss_onboarding: None,
}
.into_any_element()
@ -415,27 +362,23 @@ impl Component for ZedAiOnboarding {
.children(vec![
single_example(
"Not Signed-in",
onboarding(SignInStatus::SignedOut, false, None, false),
),
single_example(
"Not Accepted ToS",
onboarding(SignInStatus::SignedIn, false, None, false),
onboarding(SignInStatus::SignedOut, None, false),
),
single_example(
"Young Account",
onboarding(SignInStatus::SignedIn, true, None, true),
onboarding(SignInStatus::SignedIn, None, true),
),
single_example(
"Free Plan",
onboarding(SignInStatus::SignedIn, true, Some(Plan::ZedFree), false),
onboarding(SignInStatus::SignedIn, Some(Plan::ZedFree), false),
),
single_example(
"Pro Trial",
onboarding(SignInStatus::SignedIn, true, Some(Plan::ZedProTrial), false),
onboarding(SignInStatus::SignedIn, Some(Plan::ZedProTrial), false),
),
single_example(
"Pro Plan",
onboarding(SignInStatus::SignedIn, true, Some(Plan::ZedPro), false),
onboarding(SignInStatus::SignedIn, Some(Plan::ZedPro), false),
),
])
.into_any_element(),

View file

@ -1,5 +1,5 @@
use super::{Client, Status, TypedEnvelope, proto};
use anyhow::{Context as _, Result, anyhow};
use anyhow::{Context as _, Result};
use chrono::{DateTime, Utc};
use cloud_api_client::websocket_protocol::MessageToClient;
use cloud_api_client::{GetAuthenticatedUserResponse, PlanInfo};
@ -116,7 +116,6 @@ pub struct UserStore {
edit_prediction_usage: Option<EditPredictionUsage>,
plan_info: Option<PlanInfo>,
current_user: watch::Receiver<Option<Arc<User>>>,
accepted_tos_at: Option<Option<cloud_api_client::Timestamp>>,
contacts: Vec<Arc<Contact>>,
incoming_contact_requests: Vec<Arc<User>>,
outgoing_contact_requests: Vec<Arc<User>>,
@ -194,7 +193,6 @@ impl UserStore {
plan_info: None,
model_request_usage: None,
edit_prediction_usage: None,
accepted_tos_at: None,
contacts: Default::default(),
incoming_contact_requests: Default::default(),
participant_indices: Default::default(),
@ -271,7 +269,6 @@ impl UserStore {
Status::SignedOut => {
current_user_tx.send(None).await.ok();
this.update(cx, |this, cx| {
this.accepted_tos_at = None;
cx.emit(Event::PrivateUserInfoUpdated);
cx.notify();
this.clear_contacts()
@ -791,19 +788,6 @@ impl UserStore {
.set_authenticated_user_info(Some(response.user.metrics_id.clone()), staff);
}
let accepted_tos_at = {
#[cfg(debug_assertions)]
if std::env::var("ZED_IGNORE_ACCEPTED_TOS").is_ok() {
None
} else {
response.user.accepted_tos_at
}
#[cfg(not(debug_assertions))]
response.user.accepted_tos_at
};
self.accepted_tos_at = Some(accepted_tos_at);
self.model_request_usage = Some(ModelRequestUsage(RequestUsage {
limit: response.plan.usage.model_requests.limit,
amount: response.plan.usage.model_requests.used as i32,
@ -846,32 +830,6 @@ impl UserStore {
self.current_user.clone()
}
pub fn has_accepted_terms_of_service(&self) -> bool {
self.accepted_tos_at
.is_some_and(|accepted_tos_at| accepted_tos_at.is_some())
}
pub fn accept_terms_of_service(&self, cx: &Context<Self>) -> Task<Result<()>> {
if self.current_user().is_none() {
return Task::ready(Err(anyhow!("no current user")));
};
let client = self.client.clone();
cx.spawn(async move |this, cx| -> anyhow::Result<()> {
let client = client.upgrade().context("client not found")?;
let response = client
.cloud_client()
.accept_terms_of_service()
.await
.context("error accepting tos")?;
this.update(cx, |this, cx| {
this.accepted_tos_at = Some(response.user.accepted_tos_at);
cx.emit(Event::PrivateUserInfoUpdated);
})?;
Ok(())
})
}
fn load_users(
&self,
request: impl RequestMessage<Response = UsersResponse>,

View file

@ -115,34 +115,6 @@ impl CloudApiClient {
}))
}
pub async fn accept_terms_of_service(&self) -> Result<AcceptTermsOfServiceResponse> {
let request = self.build_request(
Request::builder().method(Method::POST).uri(
self.http_client
.build_zed_cloud_url("/client/terms_of_service/accept", &[])?
.as_ref(),
),
AsyncBody::default(),
)?;
let mut response = self.http_client.send(request).await?;
if !response.status().is_success() {
let mut body = String::new();
response.body_mut().read_to_string(&mut body).await?;
anyhow::bail!(
"Failed to accept terms of service.\nStatus: {:?}\nBody: {body}",
response.status()
)
}
let mut body = String::new();
response.body_mut().read_to_string(&mut body).await?;
Ok(serde_json::from_str(&body)?)
}
pub async fn create_llm_token(
&self,
system_id: Option<String>,

View file

@ -89,9 +89,6 @@ pub trait EditPredictionProvider: 'static + Sized {
debounce: bool,
cx: &mut Context<Self>,
);
fn needs_terms_acceptance(&self, _cx: &App) -> bool {
false
}
fn cycle(
&mut self,
buffer: Entity<Buffer>,
@ -124,7 +121,6 @@ pub trait EditPredictionProviderHandle {
fn data_collection_state(&self, cx: &App) -> DataCollectionState;
fn usage(&self, cx: &App) -> Option<EditPredictionUsage>;
fn toggle_data_collection(&self, cx: &mut App);
fn needs_terms_acceptance(&self, cx: &App) -> bool;
fn is_refreshing(&self, cx: &App) -> bool;
fn refresh(
&self,
@ -196,10 +192,6 @@ where
self.read(cx).is_enabled(buffer, cursor_position, cx)
}
fn needs_terms_acceptance(&self, cx: &App) -> bool {
self.read(cx).needs_terms_acceptance(cx)
}
fn is_refreshing(&self, cx: &App) -> bool {
self.read(cx).is_refreshing()
}

View file

@ -242,13 +242,9 @@ impl Render for EditPredictionButton {
IconName::ZedPredictDisabled
};
if zeta::should_show_upsell_modal(&self.user_store, cx) {
if zeta::should_show_upsell_modal() {
let tooltip_meta = if self.user_store.read(cx).current_user().is_some() {
if self.user_store.read(cx).has_accepted_terms_of_service() {
"Choose a Plan"
} else {
"Accept the Terms of Service"
}
"Choose a Plan"
} else {
"Sign In"
};

View file

@ -253,7 +253,6 @@ pub type RenderDiffHunkControlsFn = Arc<
enum ReportEditorEvent {
Saved { auto_saved: bool },
EditorOpened,
ZetaTosClicked,
Closed,
}
@ -262,7 +261,6 @@ impl ReportEditorEvent {
match self {
Self::Saved { .. } => "Editor Saved",
Self::EditorOpened => "Editor Opened",
Self::ZetaTosClicked => "Edit Prediction Provider ToS Clicked",
Self::Closed => "Editor Closed",
}
}
@ -9180,45 +9178,6 @@ impl Editor {
let provider = self.edit_prediction_provider.as_ref()?;
let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
if provider.provider.needs_terms_acceptance(cx) {
return Some(
h_flex()
.min_w(min_width)
.flex_1()
.px_2()
.py_1()
.gap_3()
.elevation_2(cx)
.hover(|style| style.bg(cx.theme().colors().element_hover))
.id("accept-terms")
.cursor_pointer()
.on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
.on_click(cx.listener(|this, _event, window, cx| {
cx.stop_propagation();
this.report_editor_event(ReportEditorEvent::ZetaTosClicked, None, cx);
window.dispatch_action(
zed_actions::OpenZedPredictOnboarding.boxed_clone(),
cx,
);
}))
.child(
h_flex()
.flex_1()
.gap_2()
.child(Icon::new(provider_icon))
.child(Label::new("Accept Terms of Service"))
.child(div().w_full())
.child(
Icon::new(IconName::ArrowUpRight)
.color(Color::Muted)
.size(IconSize::Small),
)
.into_any_element(),
)
.into_any(),
);
}
let is_refreshing = provider.provider.is_refreshing(cx);
fn pending_completion_container(icon: IconName) -> Div {

View file

@ -14,7 +14,7 @@ use client::Client;
use cloud_llm_client::{CompletionMode, CompletionRequestStatus};
use futures::FutureExt;
use futures::{StreamExt, future::BoxFuture, stream::BoxStream};
use gpui::{AnyElement, AnyView, App, AsyncApp, SharedString, Task, Window};
use gpui::{AnyView, App, AsyncApp, SharedString, Task, Window};
use http_client::{StatusCode, http};
use icons::IconName;
use parking_lot::Mutex;
@ -640,16 +640,6 @@ pub trait LanguageModelProvider: 'static {
window: &mut Window,
cx: &mut App,
) -> AnyView;
fn must_accept_terms(&self, _cx: &App) -> bool {
false
}
fn render_accept_terms(
&self,
_view: LanguageModelProviderTosView,
_cx: &mut App,
) -> Option<AnyElement> {
None
}
fn reset_credentials(&self, cx: &mut App) -> Task<Result<()>>;
}

View file

@ -24,9 +24,6 @@ pub enum ConfigurationError {
ModelNotFound,
#[error("{} LLM provider is not configured.", .0.name().0)]
ProviderNotAuthenticated(Arc<dyn LanguageModelProvider>),
#[error("Using the {} LLM provider requires accepting the Terms of Service.",
.0.name().0)]
ProviderPendingTermsAcceptance(Arc<dyn LanguageModelProvider>),
}
impl std::fmt::Debug for ConfigurationError {
@ -37,9 +34,6 @@ impl std::fmt::Debug for ConfigurationError {
Self::ProviderNotAuthenticated(provider) => {
write!(f, "ProviderNotAuthenticated({})", provider.id())
}
Self::ProviderPendingTermsAcceptance(provider) => {
write!(f, "ProviderPendingTermsAcceptance({})", provider.id())
}
}
}
}
@ -198,12 +192,6 @@ impl LanguageModelRegistry {
return Some(ConfigurationError::ProviderNotAuthenticated(model.provider));
}
if model.provider.must_accept_terms(cx) {
return Some(ConfigurationError::ProviderPendingTermsAcceptance(
model.provider,
));
}
None
}

View file

@ -23,9 +23,9 @@ use language_model::{
AuthenticateError, LanguageModel, LanguageModelCacheConfiguration,
LanguageModelCompletionError, LanguageModelCompletionEvent, LanguageModelId, LanguageModelName,
LanguageModelProvider, LanguageModelProviderId, LanguageModelProviderName,
LanguageModelProviderState, LanguageModelProviderTosView, LanguageModelRequest,
LanguageModelToolChoice, LanguageModelToolSchemaFormat, LlmApiToken,
ModelRequestLimitReachedError, PaymentRequiredError, RateLimiter, RefreshLlmTokenListener,
LanguageModelProviderState, LanguageModelRequest, LanguageModelToolChoice,
LanguageModelToolSchemaFormat, LlmApiToken, ModelRequestLimitReachedError,
PaymentRequiredError, RateLimiter, RefreshLlmTokenListener,
};
use release_channel::AppVersion;
use schemars::JsonSchema;
@ -118,7 +118,6 @@ pub struct State {
llm_api_token: LlmApiToken,
user_store: Entity<UserStore>,
status: client::Status,
accept_terms_of_service_task: Option<Task<Result<()>>>,
models: Vec<Arc<cloud_llm_client::LanguageModel>>,
default_model: Option<Arc<cloud_llm_client::LanguageModel>>,
default_fast_model: Option<Arc<cloud_llm_client::LanguageModel>>,
@ -142,7 +141,6 @@ impl State {
llm_api_token: LlmApiToken::default(),
user_store,
status,
accept_terms_of_service_task: None,
models: Vec::new(),
default_model: None,
default_fast_model: None,
@ -197,24 +195,6 @@ impl State {
state.update(cx, |_, cx| cx.notify())
})
}
fn has_accepted_terms_of_service(&self, cx: &App) -> bool {
self.user_store.read(cx).has_accepted_terms_of_service()
}
fn accept_terms_of_service(&mut self, cx: &mut Context<Self>) {
let user_store = self.user_store.clone();
self.accept_terms_of_service_task = Some(cx.spawn(async move |this, cx| {
let _ = user_store
.update(cx, |store, cx| store.accept_terms_of_service(cx))?
.await;
this.update(cx, |this, cx| {
this.accept_terms_of_service_task = None;
cx.notify()
})
}));
}
fn update_models(&mut self, response: ListModelsResponse, cx: &mut Context<Self>) {
let mut models = Vec::new();
@ -384,7 +364,7 @@ impl LanguageModelProvider for CloudLanguageModelProvider {
fn is_authenticated(&self, cx: &App) -> bool {
let state = self.state.read(cx);
!state.is_signed_out(cx) && state.has_accepted_terms_of_service(cx)
!state.is_signed_out(cx)
}
fn authenticate(&self, _cx: &mut App) -> Task<Result<(), AuthenticateError>> {
@ -401,112 +381,11 @@ impl LanguageModelProvider for CloudLanguageModelProvider {
.into()
}
fn must_accept_terms(&self, cx: &App) -> bool {
!self.state.read(cx).has_accepted_terms_of_service(cx)
}
fn render_accept_terms(
&self,
view: LanguageModelProviderTosView,
cx: &mut App,
) -> Option<AnyElement> {
let state = self.state.read(cx);
if state.has_accepted_terms_of_service(cx) {
return None;
}
Some(
render_accept_terms(view, state.accept_terms_of_service_task.is_some(), {
let state = self.state.clone();
move |_window, cx| {
state.update(cx, |state, cx| state.accept_terms_of_service(cx));
}
})
.into_any_element(),
)
}
fn reset_credentials(&self, _cx: &mut App) -> Task<Result<()>> {
Task::ready(Ok(()))
}
}
fn render_accept_terms(
view_kind: LanguageModelProviderTosView,
accept_terms_of_service_in_progress: bool,
accept_terms_callback: impl Fn(&mut Window, &mut App) + 'static,
) -> impl IntoElement {
let thread_fresh_start = matches!(view_kind, LanguageModelProviderTosView::ThreadFreshStart);
let thread_empty_state = matches!(view_kind, LanguageModelProviderTosView::ThreadEmptyState);
let terms_button = Button::new("terms_of_service", "Terms of Service")
.style(ButtonStyle::Subtle)
.icon(IconName::ArrowUpRight)
.icon_color(Color::Muted)
.icon_size(IconSize::Small)
.when(thread_empty_state, |this| this.label_size(LabelSize::Small))
.on_click(move |_, _window, cx| cx.open_url("https://zed.dev/terms-of-service"));
let button_container = h_flex().child(
Button::new("accept_terms", "I accept the Terms of Service")
.when(!thread_empty_state, |this| {
this.full_width()
.style(ButtonStyle::Tinted(TintColor::Accent))
.icon(IconName::Check)
.icon_position(IconPosition::Start)
.icon_size(IconSize::Small)
})
.when(thread_empty_state, |this| {
this.style(ButtonStyle::Tinted(TintColor::Warning))
.label_size(LabelSize::Small)
})
.disabled(accept_terms_of_service_in_progress)
.on_click(move |_, window, cx| (accept_terms_callback)(window, cx)),
);
if thread_empty_state {
h_flex()
.w_full()
.flex_wrap()
.justify_between()
.child(
h_flex()
.child(
Label::new("To start using Zed AI, please read and accept the")
.size(LabelSize::Small),
)
.child(terms_button),
)
.child(button_container)
} else {
v_flex()
.w_full()
.gap_2()
.child(
h_flex()
.flex_wrap()
.when(thread_fresh_start, |this| this.justify_center())
.child(Label::new(
"To start using Zed AI, please read and accept the",
))
.child(terms_button),
)
.child({
match view_kind {
LanguageModelProviderTosView::TextThreadPopup => {
button_container.w_full().justify_end()
}
LanguageModelProviderTosView::Configuration => {
button_container.w_full().justify_start()
}
LanguageModelProviderTosView::ThreadFreshStart => {
button_container.w_full().justify_center()
}
LanguageModelProviderTosView::ThreadEmptyState => div().w_0(),
}
})
}
}
pub struct CloudLanguageModel {
id: LanguageModelId,
model: Arc<cloud_llm_client::LanguageModel>,
@ -1107,10 +986,7 @@ struct ZedAiConfiguration {
plan: Option<Plan>,
subscription_period: Option<(DateTime<Utc>, DateTime<Utc>)>,
eligible_for_trial: bool,
has_accepted_terms_of_service: bool,
account_too_young: bool,
accept_terms_of_service_in_progress: bool,
accept_terms_of_service_callback: Arc<dyn Fn(&mut Window, &mut App) + Send + Sync>,
sign_in_callback: Arc<dyn Fn(&mut Window, &mut App) + Send + Sync>,
}
@ -1176,58 +1052,30 @@ impl RenderOnce for ZedAiConfiguration {
);
}
v_flex()
.gap_2()
.w_full()
.when(!self.has_accepted_terms_of_service, |this| {
this.child(render_accept_terms(
LanguageModelProviderTosView::Configuration,
self.accept_terms_of_service_in_progress,
{
let callback = self.accept_terms_of_service_callback.clone();
move |window, cx| (callback)(window, cx)
},
))
})
.map(|this| {
if self.has_accepted_terms_of_service && self.account_too_young {
this.child(young_account_banner).child(
Button::new("upgrade", "Upgrade to Pro")
.style(ui::ButtonStyle::Tinted(ui::TintColor::Accent))
.full_width()
.on_click(|_, _, cx| {
cx.open_url(&zed_urls::upgrade_to_zed_pro_url(cx))
}),
)
} else if self.has_accepted_terms_of_service {
this.text_sm()
.child(subscription_text)
.child(manage_subscription_buttons)
} else {
this
}
})
.when(self.has_accepted_terms_of_service, |this| this)
v_flex().gap_2().w_full().map(|this| {
if self.account_too_young {
this.child(young_account_banner).child(
Button::new("upgrade", "Upgrade to Pro")
.style(ui::ButtonStyle::Tinted(ui::TintColor::Accent))
.full_width()
.on_click(|_, _, cx| cx.open_url(&zed_urls::upgrade_to_zed_pro_url(cx))),
)
} else {
this.text_sm()
.child(subscription_text)
.child(manage_subscription_buttons)
}
})
}
}
struct ConfigurationView {
state: Entity<State>,
accept_terms_of_service_callback: Arc<dyn Fn(&mut Window, &mut App) + Send + Sync>,
sign_in_callback: Arc<dyn Fn(&mut Window, &mut App) + Send + Sync>,
}
impl ConfigurationView {
fn new(state: Entity<State>) -> Self {
let accept_terms_of_service_callback = Arc::new({
let state = state.clone();
move |_window: &mut Window, cx: &mut App| {
state.update(cx, |state, cx| {
state.accept_terms_of_service(cx);
});
}
});
let sign_in_callback = Arc::new({
let state = state.clone();
move |_window: &mut Window, cx: &mut App| {
@ -1239,7 +1087,6 @@ impl ConfigurationView {
Self {
state,
accept_terms_of_service_callback,
sign_in_callback,
}
}
@ -1255,10 +1102,7 @@ impl Render for ConfigurationView {
plan: user_store.plan(),
subscription_period: user_store.subscription_period(),
eligible_for_trial: user_store.trial_started_at().is_none(),
has_accepted_terms_of_service: state.has_accepted_terms_of_service(cx),
account_too_young: user_store.account_too_young(),
accept_terms_of_service_in_progress: state.accept_terms_of_service_task.is_some(),
accept_terms_of_service_callback: self.accept_terms_of_service_callback.clone(),
sign_in_callback: self.sign_in_callback.clone(),
}
}
@ -1283,7 +1127,6 @@ impl Component for ZedAiConfiguration {
plan: Option<Plan>,
eligible_for_trial: bool,
account_too_young: bool,
has_accepted_terms_of_service: bool,
) -> AnyElement {
ZedAiConfiguration {
is_connected,
@ -1292,10 +1135,7 @@ impl Component for ZedAiConfiguration {
.is_some()
.then(|| (Utc::now(), Utc::now() + chrono::Duration::days(7))),
eligible_for_trial,
has_accepted_terms_of_service,
account_too_young,
accept_terms_of_service_in_progress: false,
accept_terms_of_service_callback: Arc::new(|_, _| {}),
sign_in_callback: Arc::new(|_, _| {}),
}
.into_any_element()
@ -1306,33 +1146,30 @@ impl Component for ZedAiConfiguration {
.p_4()
.gap_4()
.children(vec![
single_example(
"Not connected",
configuration(false, None, false, false, true),
),
single_example("Not connected", configuration(false, None, false, false)),
single_example(
"Accept Terms of Service",
configuration(true, None, true, false, false),
configuration(true, None, true, false),
),
single_example(
"No Plan - Not eligible for trial",
configuration(true, None, false, false, true),
configuration(true, None, false, false),
),
single_example(
"No Plan - Eligible for trial",
configuration(true, None, true, false, true),
configuration(true, None, true, false),
),
single_example(
"Free Plan",
configuration(true, Some(Plan::ZedFree), true, false, true),
configuration(true, Some(Plan::ZedFree), true, false),
),
single_example(
"Zed Pro Trial Plan",
configuration(true, Some(Plan::ZedProTrial), true, false, true),
configuration(true, Some(Plan::ZedProTrial), true, false),
),
single_example(
"Zed Pro Plan",
configuration(true, Some(Plan::ZedPro), true, false, true),
configuration(true, Some(Plan::ZedPro), true, false),
),
])
.into_any_element(),

View file

@ -75,13 +75,10 @@ pub fn init(client: Arc<Client>, user_store: Entity<UserStore>, cx: &mut App) {
let new_provider = all_language_settings(None, cx).edit_predictions.provider;
if new_provider != provider {
let tos_accepted = user_store.read(cx).has_accepted_terms_of_service();
telemetry::event!(
"Edit Prediction Provider Changed",
from = provider,
to = new_provider,
zed_ai_tos_accepted = tos_accepted,
);
provider = new_provider;
@ -92,28 +89,6 @@ pub fn init(client: Arc<Client>, user_store: Entity<UserStore>, cx: &mut App) {
user_store.clone(),
cx,
);
if !tos_accepted {
match provider {
EditPredictionProvider::Zed => {
let Some(window) = cx.active_window() else {
return;
};
window
.update(cx, |_, window, cx| {
window.dispatch_action(
Box::new(zed_actions::OpenZedPredictOnboarding),
cx,
);
})
.ok();
}
EditPredictionProvider::None
| EditPredictionProvider::Copilot
| EditPredictionProvider::Supermaven => {}
}
}
}
}
})

View file

@ -118,12 +118,8 @@ impl Dismissable for ZedPredictUpsell {
}
}
pub fn should_show_upsell_modal(user_store: &Entity<UserStore>, cx: &App) -> bool {
if user_store.read(cx).has_accepted_terms_of_service() {
!ZedPredictUpsell::dismissed()
} else {
true
}
pub fn should_show_upsell_modal() -> bool {
!ZedPredictUpsell::dismissed()
}
#[derive(Clone)]
@ -1547,16 +1543,6 @@ impl edit_prediction::EditPredictionProvider for ZetaEditPredictionProvider {
) -> bool {
true
}
fn needs_terms_acceptance(&self, cx: &App) -> bool {
!self
.zeta
.read(cx)
.user_store
.read(cx)
.has_accepted_terms_of_service()
}
fn is_refreshing(&self) -> bool {
!self.pending_completions.is_empty()
}
@ -1569,10 +1555,6 @@ impl edit_prediction::EditPredictionProvider for ZetaEditPredictionProvider {
_debounce: bool,
cx: &mut Context<Self>,
) {
if self.needs_terms_acceptance(cx) {
return;
}
if self.zeta.read(cx).update_required {
return;
}