onboarding_banner.rs

  1use chrono::Utc;
  2use feature_flags::{FeatureFlagAppExt as _, PredictEditsFeatureFlag};
  3use gpui::Subscription;
  4use language::language_settings::{all_language_settings, InlineCompletionProvider};
  5use settings::SettingsStore;
  6use ui::{prelude::*, ButtonLike, Tooltip};
  7use util::ResultExt;
  8
  9use crate::onboarding_event;
 10
 11/// Prompts the user to try Zed's Edit Prediction feature
 12pub struct ZedPredictBanner {
 13    dismissed: bool,
 14    _subscription: Subscription,
 15}
 16
 17impl ZedPredictBanner {
 18    pub fn new(cx: &mut Context<Self>) -> Self {
 19        Self {
 20            dismissed: get_dismissed(),
 21            _subscription: cx.observe_global::<SettingsStore>(Self::handle_settings_changed),
 22        }
 23    }
 24
 25    fn should_show(&self, cx: &mut App) -> bool {
 26        if !cx.has_flag::<PredictEditsFeatureFlag>() || self.dismissed {
 27            return false;
 28        }
 29
 30        let provider = all_language_settings(None, cx).inline_completions.provider;
 31
 32        match provider {
 33            InlineCompletionProvider::None
 34            | InlineCompletionProvider::Copilot
 35            | InlineCompletionProvider::Supermaven => true,
 36            InlineCompletionProvider::Zed => false,
 37        }
 38    }
 39
 40    fn handle_settings_changed(&mut self, cx: &mut Context<Self>) {
 41        if self.dismissed {
 42            return;
 43        }
 44
 45        let provider = all_language_settings(None, cx).inline_completions.provider;
 46
 47        match provider {
 48            InlineCompletionProvider::None
 49            | InlineCompletionProvider::Copilot
 50            | InlineCompletionProvider::Supermaven => {}
 51            InlineCompletionProvider::Zed => {
 52                self.dismiss(cx);
 53            }
 54        }
 55    }
 56
 57    fn dismiss(&mut self, cx: &mut Context<Self>) {
 58        onboarding_event!("Banner Dismissed");
 59        persist_dismissed(cx);
 60        self.dismissed = true;
 61        cx.notify();
 62    }
 63}
 64
 65const DISMISSED_AT_KEY: &str = "zed_predict_banner_dismissed_at";
 66
 67pub(crate) fn get_dismissed() -> bool {
 68    db::kvp::KEY_VALUE_STORE
 69        .read_kvp(DISMISSED_AT_KEY)
 70        .log_err()
 71        .map_or(false, |dismissed| dismissed.is_some())
 72}
 73
 74pub(crate) fn persist_dismissed(cx: &mut App) {
 75    cx.spawn(|_| {
 76        let time = Utc::now().to_rfc3339();
 77        db::kvp::KEY_VALUE_STORE.write_kvp(DISMISSED_AT_KEY.into(), time)
 78    })
 79    .detach_and_log_err(cx);
 80}
 81
 82impl Render for ZedPredictBanner {
 83    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
 84        if !self.should_show(cx) {
 85            return div();
 86        }
 87
 88        let border_color = cx.theme().colors().editor_foreground.opacity(0.3);
 89        let banner = h_flex()
 90            .rounded_md()
 91            .border_1()
 92            .border_color(border_color)
 93            .child(
 94                ButtonLike::new("try-zed-predict")
 95                    .child(
 96                        h_flex()
 97                            .h_full()
 98                            .items_center()
 99                            .gap_1p5()
100                            .child(Icon::new(IconName::ZedPredict).size(IconSize::Small))
101                            .child(
102                                h_flex()
103                                    .gap_0p5()
104                                    .child(
105                                        Label::new("Introducing:")
106                                            .size(LabelSize::Small)
107                                            .color(Color::Muted),
108                                    )
109                                    .child(Label::new("Edit Prediction").size(LabelSize::Small)),
110                            ),
111                    )
112                    .on_click(|_, window, cx| {
113                        onboarding_event!("Banner Clicked");
114                        window.dispatch_action(Box::new(zed_actions::OpenZedPredictOnboarding), cx)
115                    }),
116            )
117            .child(
118                div().border_l_1().border_color(border_color).child(
119                    IconButton::new("close", IconName::Close)
120                        .icon_size(IconSize::Indicator)
121                        .on_click(cx.listener(|this, _, _window, cx| this.dismiss(cx)))
122                        .tooltip(|window, cx| {
123                            Tooltip::with_meta(
124                                "Close Announcement Banner",
125                                None,
126                                "It won't show again for this feature",
127                                window,
128                                cx,
129                            )
130                        }),
131                ),
132            );
133
134        div().pr_2().child(banner)
135    }
136}