Detailed changes
@@ -3959,6 +3959,7 @@ dependencies = [
"util",
"uuid",
"workspace",
+ "zed_predict_tos",
]
[[package]]
@@ -6304,6 +6305,7 @@ name = "inline_completion_button"
version = "0.1.0"
dependencies = [
"anyhow",
+ "client",
"copilot",
"editor",
"feature_flags",
@@ -6323,6 +6325,7 @@ dependencies = [
"ui",
"workspace",
"zed_actions",
+ "zed_predict_tos",
"zeta",
]
@@ -16337,6 +16340,7 @@ dependencies = [
"winresource",
"workspace",
"zed_actions",
+ "zed_predict_tos",
"zeta",
]
@@ -16450,6 +16454,17 @@ dependencies = [
"zed_extension_api 0.1.0",
]
+[[package]]
+name = "zed_predict_tos"
+version = "0.1.0"
+dependencies = [
+ "client",
+ "gpui",
+ "menu",
+ "ui",
+ "workspace",
+]
+
[[package]]
name = "zed_prisma"
version = "0.0.4"
@@ -2,6 +2,7 @@
resolver = "2"
members = [
"crates/activity_indicator",
+ "crates/zed_predict_tos",
"crates/anthropic",
"crates/assets",
"crates/assistant",
@@ -198,6 +199,7 @@ edition = "2021"
activity_indicator = { path = "crates/activity_indicator" }
ai = { path = "crates/ai" }
+zed_predict_tos = { path = "crates/zed_predict_tos" }
anthropic = { path = "crates/anthropic" }
assets = { path = "crates/assets" }
assistant = { path = "crates/assistant" }
@@ -875,5 +875,12 @@
"cmd-shift-enter": "zeta::ThumbsUpActiveCompletion",
"cmd-shift-backspace": "zeta::ThumbsDownActiveCompletion"
}
+ },
+ {
+ "context": "ZedPredictTos",
+ "use_key_equivalents": true,
+ "bindings": {
+ "escape": "menu::Cancel"
+ }
}
]
@@ -122,6 +122,9 @@ pub enum Event {
},
ShowContacts,
ParticipantIndicesChanged,
+ TermsStatusUpdated {
+ accepted: bool,
+ },
}
#[derive(Clone, Copy)]
@@ -210,10 +213,24 @@ impl UserStore {
staff,
);
- this.update(cx, |this, _| {
- this.set_current_user_accepted_tos_at(
- info.accepted_tos_at,
- );
+ this.update(cx, |this, cx| {
+ let accepted_tos_at = {
+ #[cfg(debug_assertions)]
+ if std::env::var("ZED_IGNORE_ACCEPTED_TOS").is_ok()
+ {
+ None
+ } else {
+ info.accepted_tos_at
+ }
+
+ #[cfg(not(debug_assertions))]
+ info.accepted_tos_at
+ };
+
+ this.set_current_user_accepted_tos_at(accepted_tos_at);
+ cx.emit(Event::TermsStatusUpdated {
+ accepted: accepted_tos_at.is_some(),
+ });
})
} else {
anyhow::Ok(())
@@ -704,8 +721,9 @@ impl UserStore {
.await
.context("error accepting tos")?;
- this.update(&mut cx, |this, _| {
- this.set_current_user_accepted_tos_at(Some(response.accepted_tos_at))
+ this.update(&mut cx, |this, cx| {
+ this.set_current_user_accepted_tos_at(Some(response.accepted_tos_at));
+ cx.emit(Event::TermsStatusUpdated { accepted: true });
})
} else {
Err(anyhow!("client not found"))
@@ -88,6 +88,7 @@ url.workspace = true
util.workspace = true
uuid.workspace = true
workspace.workspace = true
+zed_predict_tos.workspace = true
[dev-dependencies]
ctor.workspace = true
@@ -616,6 +616,25 @@ impl CompletionsMenu {
)
})),
),
+ CompletionEntry::InlineCompletionHint(
+ hint @ InlineCompletionMenuHint::PendingTermsAcceptance,
+ ) => div().min_w(px(250.)).max_w(px(500.)).child(
+ ListItem::new("inline-completion")
+ .inset(true)
+ .toggle_state(item_ix == selected_item)
+ .start_slot(Icon::new(IconName::ZedPredict))
+ .child(
+ base_label.child(
+ StyledText::new(hint.label())
+ .with_highlights(&style.text, None),
+ ),
+ )
+ .on_click(cx.listener(move |editor, _event, cx| {
+ cx.stop_propagation();
+ editor.toggle_zed_predict_tos(cx);
+ })),
+ ),
+
CompletionEntry::InlineCompletionHint(
hint @ InlineCompletionMenuHint::Loaded { .. },
) => div().min_w(px(250.)).max_w(px(500.)).child(
@@ -70,6 +70,7 @@ pub use element::{
};
use futures::{future, FutureExt};
use fuzzy::StringMatchCandidate;
+use zed_predict_tos::ZedPredictTos;
use code_context_menus::{
AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
@@ -459,6 +460,7 @@ type CompletionId = usize;
enum InlineCompletionMenuHint {
Loading,
Loaded { text: InlineCompletionText },
+ PendingTermsAcceptance,
None,
}
@@ -468,6 +470,7 @@ impl InlineCompletionMenuHint {
InlineCompletionMenuHint::Loading | InlineCompletionMenuHint::Loaded { .. } => {
"Edit Prediction"
}
+ InlineCompletionMenuHint::PendingTermsAcceptance => "Accept Terms of Service",
InlineCompletionMenuHint::None => "No Prediction",
}
}
@@ -3828,6 +3831,14 @@ impl Editor {
self.do_completion(action.item_ix, CompletionIntent::Compose, cx)
}
+ fn toggle_zed_predict_tos(&mut self, cx: &mut ViewContext<Self>) {
+ let (Some(workspace), Some(project)) = (self.workspace(), self.project.as_ref()) else {
+ return;
+ };
+
+ ZedPredictTos::toggle(workspace, project.read(cx).user_store().clone(), cx);
+ }
+
fn do_completion(
&mut self,
item_ix: Option<usize>,
@@ -3851,6 +3862,14 @@ impl Editor {
self.context_menu_next(&Default::default(), cx);
return Some(Task::ready(Ok(())));
}
+ Some(CompletionEntry::InlineCompletionHint(
+ InlineCompletionMenuHint::PendingTermsAcceptance,
+ )) => {
+ drop(entries);
+ drop(context_menu);
+ self.toggle_zed_predict_tos(cx);
+ return Some(Task::ready(Ok(())));
+ }
_ => {}
}
}
@@ -4974,6 +4993,8 @@ impl Editor {
Some(InlineCompletionMenuHint::Loaded { text })
} else if provider.is_refreshing(cx) {
Some(InlineCompletionMenuHint::Loading)
+ } else if provider.needs_terms_acceptance(cx) {
+ Some(InlineCompletionMenuHint::PendingTermsAcceptance)
} else {
Some(InlineCompletionMenuHint::None)
}
@@ -36,6 +36,9 @@ pub trait InlineCompletionProvider: 'static + Sized {
debounce: bool,
cx: &mut ModelContext<Self>,
);
+ fn needs_terms_acceptance(&self, _cx: &AppContext) -> bool {
+ false
+ }
fn cycle(
&mut self,
buffer: Model<Buffer>,
@@ -64,6 +67,7 @@ pub trait InlineCompletionProviderHandle {
) -> bool;
fn show_completions_in_menu(&self) -> bool;
fn show_completions_in_normal_mode(&self) -> bool;
+ fn needs_terms_acceptance(&self, cx: &AppContext) -> bool;
fn is_refreshing(&self, cx: &AppContext) -> bool;
fn refresh(
&self,
@@ -118,6 +122,10 @@ where
self.read(cx).is_enabled(buffer, cursor_position, cx)
}
+ fn needs_terms_acceptance(&self, cx: &AppContext) -> bool {
+ self.read(cx).needs_terms_acceptance(cx)
+ }
+
fn is_refreshing(&self, cx: &AppContext) -> bool {
self.read(cx).is_refreshing()
}
@@ -28,6 +28,8 @@ ui.workspace = true
workspace.workspace = true
zed_actions.workspace = true
zeta.workspace = true
+client.workspace = true
+zed_predict_tos.workspace = true
[dev-dependencies]
copilot = { workspace = true, features = ["test-support"] }
@@ -1,12 +1,13 @@
use anyhow::Result;
+use client::UserStore;
use copilot::{Copilot, Status};
use editor::{scroll::Autoscroll, Editor};
use feature_flags::{FeatureFlagAppExt, PredictEditsFeatureFlag};
use fs::Fs;
use gpui::{
actions, div, pulsating_between, Action, Animation, AnimationExt, AppContext,
- AsyncWindowContext, Corner, Entity, IntoElement, ParentElement, Render, Subscription, View,
- ViewContext, WeakView, WindowContext,
+ AsyncWindowContext, Corner, Entity, IntoElement, Model, ParentElement, Render, Subscription,
+ View, ViewContext, WeakView, WindowContext,
};
use language::{
language_settings::{
@@ -17,6 +18,7 @@ use language::{
use settings::{update_settings_file, Settings, SettingsStore};
use std::{path::Path, sync::Arc, time::Duration};
use supermaven::{AccountStatus, Supermaven};
+use ui::{ActiveTheme as _, ButtonLike, Color, Icon, IconWithIndicator, Indicator};
use workspace::{
create_and_open_local_file,
item::ItemHandle,
@@ -27,6 +29,7 @@ use workspace::{
StatusItemView, Toast, Workspace,
};
use zed_actions::OpenBrowser;
+use zed_predict_tos::ZedPredictTos;
use zeta::RateCompletionModal;
actions!(zeta, [RateCompletions]);
@@ -43,6 +46,7 @@ pub struct InlineCompletionButton {
inline_completion_provider: Option<Arc<dyn inline_completion::InlineCompletionProviderHandle>>,
fs: Arc<dyn Fs>,
workspace: WeakView<Workspace>,
+ user_store: Model<UserStore>,
}
enum SupermavenButtonStatus {
@@ -206,6 +210,45 @@ impl Render for InlineCompletionButton {
return div();
}
+ if !self
+ .user_store
+ .read(cx)
+ .current_user_has_accepted_terms()
+ .unwrap_or(false)
+ {
+ let workspace = self.workspace.clone();
+ let user_store = self.user_store.clone();
+
+ return div().child(
+ ButtonLike::new("zeta-pending-tos-icon")
+ .child(
+ IconWithIndicator::new(
+ Icon::new(IconName::ZedPredict),
+ Some(Indicator::dot().color(Color::Error)),
+ )
+ .indicator_border_color(Some(
+ cx.theme().colors().status_bar_background,
+ ))
+ .into_any_element(),
+ )
+ .tooltip(|cx| {
+ Tooltip::with_meta(
+ "Edit Predictions",
+ None,
+ "Read Terms of Service",
+ cx,
+ )
+ })
+ .on_click(cx.listener(move |_, _, cx| {
+ let user_store = user_store.clone();
+
+ if let Some(workspace) = workspace.upgrade() {
+ ZedPredictTos::toggle(workspace, user_store, cx);
+ }
+ })),
+ );
+ }
+
let this = cx.view().clone();
let button = IconButton::new("zeta", IconName::ZedPredict)
.tooltip(|cx| Tooltip::text("Edit Prediction", cx));
@@ -244,6 +287,7 @@ impl InlineCompletionButton {
pub fn new(
workspace: WeakView<Workspace>,
fs: Arc<dyn Fs>,
+ user_store: Model<UserStore>,
cx: &mut ViewContext<Self>,
) -> Self {
if let Some(copilot) = Copilot::global(cx) {
@@ -261,6 +305,7 @@ impl InlineCompletionButton {
inline_completion_provider: None,
workspace,
fs,
+ user_store,
}
}
@@ -68,6 +68,8 @@ pub enum IconSize {
#[default]
/// 16px
Medium,
+ /// 48px
+ XLarge,
}
impl IconSize {
@@ -77,6 +79,7 @@ impl IconSize {
IconSize::XSmall => rems_from_px(12.),
IconSize::Small => rems_from_px(14.),
IconSize::Medium => rems_from_px(16.),
+ IconSize::XLarge => rems_from_px(48.),
}
}
@@ -92,6 +95,7 @@ impl IconSize {
IconSize::XSmall => DynamicSpacing::Base02.px(cx),
IconSize::Small => DynamicSpacing::Base02.px(cx),
IconSize::Medium => DynamicSpacing::Base02.px(cx),
+ IconSize::XLarge => DynamicSpacing::Base02.px(cx),
};
(icon_size, padding)
@@ -16,6 +16,7 @@ path = "src/main.rs"
[dependencies]
activity_indicator.workspace = true
+zed_predict_tos.workspace = true
anyhow.workspace = true
assets.workspace = true
assistant.workspace = true
@@ -438,7 +438,11 @@ fn main() {
cx,
);
snippet_provider::init(cx);
- inline_completion_registry::init(app_state.client.clone(), cx);
+ inline_completion_registry::init(
+ app_state.client.clone(),
+ app_state.user_store.clone(),
+ cx,
+ );
let prompt_builder = assistant::init(
app_state.fs.clone(),
app_state.client.clone(),
@@ -168,6 +168,7 @@ pub fn initialize_workspace(
inline_completion_button::InlineCompletionButton::new(
workspace.weak_handle(),
app_state.fs.clone(),
+ app_state.user_store.clone(),
cx,
)
});
@@ -1,20 +1,23 @@
use std::{cell::RefCell, rc::Rc, sync::Arc};
-use client::Client;
+use client::{Client, UserStore};
use collections::HashMap;
use copilot::{Copilot, CopilotCompletionProvider};
use editor::{Editor, EditorMode};
use feature_flags::{FeatureFlagAppExt, PredictEditsFeatureFlag};
-use gpui::{AnyWindowHandle, AppContext, Context, ViewContext, WeakView};
+use gpui::{AnyWindowHandle, AppContext, Context, Model, ViewContext, WeakView};
use language::language_settings::{all_language_settings, InlineCompletionProvider};
use settings::SettingsStore;
use supermaven::{Supermaven, SupermavenCompletionProvider};
+use workspace::Workspace;
+use zed_predict_tos::ZedPredictTos;
-pub fn init(client: Arc<Client>, cx: &mut AppContext) {
+pub fn init(client: Arc<Client>, user_store: Model<UserStore>, cx: &mut AppContext) {
let editors: Rc<RefCell<HashMap<WeakView<Editor>, AnyWindowHandle>>> = Rc::default();
cx.observe_new_views({
let editors = editors.clone();
let client = client.clone();
+ let user_store = user_store.clone();
move |editor: &mut Editor, cx: &mut ViewContext<Editor>| {
if editor.mode() != EditorMode::Full {
return;
@@ -35,7 +38,7 @@ pub fn init(client: Arc<Client>, cx: &mut AppContext) {
.borrow_mut()
.insert(editor_handle, cx.window_handle());
let provider = all_language_settings(None, cx).inline_completions.provider;
- assign_inline_completion_provider(editor, provider, &client, cx);
+ assign_inline_completion_provider(editor, provider, &client, user_store.clone(), cx);
}
})
.detach();
@@ -44,7 +47,13 @@ pub fn init(client: Arc<Client>, cx: &mut AppContext) {
for (editor, window) in editors.borrow().iter() {
_ = window.update(cx, |_window, cx| {
_ = editor.update(cx, |editor, cx| {
- assign_inline_completion_provider(editor, provider, &client, cx);
+ assign_inline_completion_provider(
+ editor,
+ provider,
+ &client,
+ user_store.clone(),
+ cx,
+ );
})
});
}
@@ -56,9 +65,10 @@ pub fn init(client: Arc<Client>, cx: &mut AppContext) {
cx.observe_flag::<PredictEditsFeatureFlag, _>({
let editors = editors.clone();
let client = client.clone();
+ let user_store = user_store.clone();
move |active, cx| {
let provider = all_language_settings(None, cx).inline_completions.provider;
- assign_inline_completion_providers(&editors, provider, &client, cx);
+ assign_inline_completion_providers(&editors, provider, &client, user_store.clone(), cx);
if active && !cx.is_action_available(&zeta::ClearHistory) {
cx.on_action(clear_zeta_edit_history);
}
@@ -69,11 +79,48 @@ pub fn init(client: Arc<Client>, cx: &mut AppContext) {
cx.observe_global::<SettingsStore>({
let editors = editors.clone();
let client = client.clone();
+ let user_store = user_store.clone();
move |cx| {
let new_provider = all_language_settings(None, cx).inline_completions.provider;
if new_provider != provider {
provider = new_provider;
- assign_inline_completion_providers(&editors, provider, &client, cx)
+ assign_inline_completion_providers(
+ &editors,
+ provider,
+ &client,
+ user_store.clone(),
+ cx,
+ );
+
+ if !user_store
+ .read(cx)
+ .current_user_has_accepted_terms()
+ .unwrap_or(false)
+ {
+ match provider {
+ InlineCompletionProvider::Zed => {
+ let Some(window) = cx.active_window() else {
+ return;
+ };
+
+ let Some(workspace) = window
+ .downcast::<Workspace>()
+ .and_then(|w| w.root_view(cx).ok())
+ else {
+ return;
+ };
+
+ window
+ .update(cx, |_, cx| {
+ ZedPredictTos::toggle(workspace, user_store.clone(), cx);
+ })
+ .ok();
+ }
+ InlineCompletionProvider::None
+ | InlineCompletionProvider::Copilot
+ | InlineCompletionProvider::Supermaven => {}
+ }
+ }
}
}
})
@@ -90,12 +137,19 @@ fn assign_inline_completion_providers(
editors: &Rc<RefCell<HashMap<WeakView<Editor>, AnyWindowHandle>>>,
provider: InlineCompletionProvider,
client: &Arc<Client>,
+ user_store: Model<UserStore>,
cx: &mut AppContext,
) {
for (editor, window) in editors.borrow().iter() {
_ = window.update(cx, |_window, cx| {
_ = editor.update(cx, |editor, cx| {
- assign_inline_completion_provider(editor, provider, &client, cx);
+ assign_inline_completion_provider(
+ editor,
+ provider,
+ &client,
+ user_store.clone(),
+ cx,
+ );
})
});
}
@@ -141,6 +195,7 @@ fn assign_inline_completion_provider(
editor: &mut Editor,
provider: language::language_settings::InlineCompletionProvider,
client: &Arc<Client>,
+ user_store: Model<UserStore>,
cx: &mut ViewContext<Editor>,
) {
match provider {
@@ -169,7 +224,7 @@ fn assign_inline_completion_provider(
if cx.has_flag::<PredictEditsFeatureFlag>()
|| (cfg!(debug_assertions) && client.status().borrow().is_connected())
{
- let zeta = zeta::Zeta::register(client.clone(), cx);
+ let zeta = zeta::Zeta::register(client.clone(), user_store, cx);
if let Some(buffer) = editor.buffer().read(cx).as_singleton() {
if buffer.read(cx).file().is_some() {
zeta.update(cx, |zeta, cx| {
@@ -0,0 +1,23 @@
+[package]
+name = "zed_predict_tos"
+version = "0.1.0"
+edition = "2021"
+publish = false
+license = "GPL-3.0-or-later"
+
+[lints]
+workspace = true
+
+[lib]
+path = "src/zed_predict_tos.rs"
+doctest = false
+
+[features]
+test-support = []
+
+[dependencies]
+client.workspace = true
+gpui.workspace = true
+ui.workspace = true
+workspace.workspace = true
+menu.workspace = true
@@ -0,0 +1 @@
+../../LICENSE-GPL
@@ -0,0 +1,152 @@
+//! AI service Terms of Service acceptance modal.
+
+use client::UserStore;
+use gpui::{
+ AppContext, ClickEvent, DismissEvent, EventEmitter, FocusHandle, FocusableView, Model,
+ MouseDownEvent, Render, View,
+};
+use ui::{prelude::*, TintColor};
+use workspace::{ModalView, Workspace};
+
+/// Terms of acceptance for AI inline prediction.
+pub struct ZedPredictTos {
+ focus_handle: FocusHandle,
+ user_store: Model<UserStore>,
+ workspace: View<Workspace>,
+ viewed: bool,
+}
+
+impl ZedPredictTos {
+ fn new(
+ workspace: View<Workspace>,
+ user_store: Model<UserStore>,
+ cx: &mut ViewContext<Self>,
+ ) -> Self {
+ ZedPredictTos {
+ viewed: false,
+ focus_handle: cx.focus_handle(),
+ user_store,
+ workspace,
+ }
+ }
+ pub fn toggle(
+ workspace: View<Workspace>,
+ user_store: Model<UserStore>,
+ cx: &mut WindowContext,
+ ) {
+ workspace.update(cx, |this, cx| {
+ let workspace = cx.view().clone();
+ this.toggle_modal(cx, |cx| ZedPredictTos::new(workspace, user_store, cx));
+ });
+ }
+
+ fn view_terms(&mut self, _: &ClickEvent, cx: &mut ViewContext<Self>) {
+ self.viewed = true;
+ cx.open_url("https://zed.dev/terms-of-service");
+ cx.notify();
+ }
+
+ fn accept_terms(&mut self, _: &ClickEvent, cx: &mut ViewContext<Self>) {
+ let task = self
+ .user_store
+ .update(cx, |this, cx| this.accept_terms_of_service(cx));
+
+ let workspace = self.workspace.clone();
+
+ cx.spawn(|this, mut cx| async move {
+ match task.await {
+ Ok(_) => this.update(&mut cx, |_, cx| {
+ cx.emit(DismissEvent);
+ }),
+ Err(err) => workspace.update(&mut cx, |this, cx| {
+ this.show_error(&err, cx);
+ }),
+ }
+ })
+ .detach_and_log_err(cx);
+ }
+
+ fn cancel(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
+ cx.emit(DismissEvent);
+ }
+}
+
+impl EventEmitter<DismissEvent> for ZedPredictTos {}
+
+impl FocusableView for ZedPredictTos {
+ fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
+ self.focus_handle.clone()
+ }
+}
+
+impl ModalView for ZedPredictTos {}
+
+impl Render for ZedPredictTos {
+ fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
+ v_flex()
+ .id("zed predict tos")
+ .track_focus(&self.focus_handle(cx))
+ .on_action(cx.listener(Self::cancel))
+ .key_context("ZedPredictTos")
+ .elevation_3(cx)
+ .w_96()
+ .items_center()
+ .p_4()
+ .gap_2()
+ .on_action(cx.listener(|_, _: &menu::Cancel, cx| {
+ cx.emit(DismissEvent);
+ }))
+ .on_any_mouse_down(cx.listener(|this, _: &MouseDownEvent, cx| {
+ cx.focus(&this.focus_handle);
+ }))
+ .child(
+ h_flex()
+ .w_full()
+ .justify_between()
+ .child(
+ v_flex()
+ .gap_0p5()
+ .child(
+ Label::new("Zed AI")
+ .size(LabelSize::Small)
+ .color(Color::Muted),
+ )
+ .child(Headline::new("Edit Prediction")),
+ )
+ .child(Icon::new(IconName::ZedPredict).size(IconSize::XLarge)),
+ )
+ .child(
+ Label::new(
+ "To use Zed AI's Edit Prediction feature, please read and accept our Terms of Service.",
+ )
+ .color(Color::Muted),
+ )
+ .child(
+ v_flex()
+ .mt_2()
+ .gap_0p5()
+ .w_full()
+ .child(if self.viewed {
+ Button::new("accept-tos", "I've Read and Accept the Terms of Service")
+ .style(ButtonStyle::Tinted(TintColor::Accent))
+ .full_width()
+ .on_click(cx.listener(Self::accept_terms))
+ } else {
+ Button::new("view-tos", "Read Terms of Service")
+ .style(ButtonStyle::Tinted(TintColor::Accent))
+ .icon(IconName::ArrowUpRight)
+ .icon_size(IconSize::XSmall)
+ .icon_position(IconPosition::End)
+ .full_width()
+ .on_click(cx.listener(Self::view_terms))
+ })
+ .child(
+ Button::new("cancel", "Cancel")
+ .full_width()
+ .on_click(cx.listener(|_, _: &ClickEvent, cx| {
+ cx.emit(DismissEvent);
+ })),
+ ),
+ )
+ }
+}
@@ -6,7 +6,7 @@ pub use rate_completion_modal::*;
use anyhow::{anyhow, Context as _, Result};
use arrayvec::ArrayVec;
-use client::Client;
+use client::{Client, UserStore};
use collections::{HashMap, HashSet, VecDeque};
use futures::AsyncReadExt;
use gpui::{
@@ -162,6 +162,8 @@ pub struct Zeta {
rated_completions: HashSet<InlineCompletionId>,
llm_token: LlmApiToken,
_llm_token_subscription: Subscription,
+ tos_accepted: bool, // Terms of service accepted
+ _user_store_subscription: Subscription,
}
impl Zeta {
@@ -169,9 +171,13 @@ impl Zeta {
cx.try_global::<ZetaGlobal>().map(|global| global.0.clone())
}
- pub fn register(client: Arc<Client>, cx: &mut AppContext) -> Model<Self> {
+ pub fn register(
+ client: Arc<Client>,
+ user_store: Model<UserStore>,
+ cx: &mut AppContext,
+ ) -> Model<Self> {
Self::global(cx).unwrap_or_else(|| {
- let model = cx.new_model(|cx| Self::new(client, cx));
+ let model = cx.new_model(|cx| Self::new(client, user_store, cx));
cx.set_global(ZetaGlobal(model.clone()));
model
})
@@ -181,7 +187,7 @@ impl Zeta {
self.events.clear();
}
- fn new(client: Arc<Client>, cx: &mut ModelContext<Self>) -> Self {
+ fn new(client: Arc<Client>, user_store: Model<UserStore>, cx: &mut ModelContext<Self>) -> Self {
let refresh_llm_token_listener = language_models::RefreshLlmTokenListener::global(cx);
Self {
@@ -203,6 +209,16 @@ impl Zeta {
.detach_and_log_err(cx);
},
),
+ tos_accepted: user_store
+ .read(cx)
+ .current_user_has_accepted_terms()
+ .unwrap_or(false),
+ _user_store_subscription: cx.subscribe(&user_store, |this, _, event, _| match event {
+ client::user::Event::TermsStatusUpdated { accepted } => {
+ this.tos_accepted = *accepted;
+ }
+ _ => {}
+ }),
}
}
@@ -1021,6 +1037,10 @@ impl inline_completion::InlineCompletionProvider for ZetaInlineCompletionProvide
settings.inline_completions_enabled(language.as_ref(), file.map(|f| f.path().as_ref()), cx)
}
+ fn needs_terms_acceptance(&self, cx: &AppContext) -> bool {
+ !self.zeta.read(cx).tos_accepted
+ }
+
fn is_refreshing(&self) -> bool {
!self.pending_completions.is_empty()
}
@@ -1032,6 +1052,10 @@ impl inline_completion::InlineCompletionProvider for ZetaInlineCompletionProvide
debounce: bool,
cx: &mut ModelContext<Self>,
) {
+ if !self.zeta.read(cx).tos_accepted {
+ return;
+ }
+
let pending_completion_id = self.next_pending_completion_id;
self.next_pending_completion_id += 1;
@@ -1337,8 +1361,9 @@ mod tests {
RefreshLlmTokenListener::register(client.clone(), cx);
});
let server = FakeServer::for_client(42, &client, cx).await;
+ let user_store = cx.new_model(|cx| UserStore::new(client.clone(), cx));
+ let zeta = cx.new_model(|cx| Zeta::new(client, user_store, cx));
- let zeta = cx.new_model(|cx| Zeta::new(client, cx));
let buffer = cx.new_model(|cx| Buffer::local(buffer_content, cx));
let cursor = buffer.read_with(cx, |buffer, _| buffer.anchor_before(Point::new(1, 0)));
let completion_task =