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:
parent
3d2fa72d1f
commit
8204ef1e51
16 changed files with 44 additions and 499 deletions
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 don’t 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(),
|
||||
|
|
|
@ -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>,
|
||||
|
|
|
@ -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>,
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
};
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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<()>>;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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 => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue