use chrono::Utc; use feature_flags::{FeatureFlagAppExt as _, PredictEditsFeatureFlag}; use gpui::Subscription; use language::language_settings::{all_language_settings, EditPredictionProvider}; use settings::SettingsStore; use ui::{prelude::*, ButtonLike, Tooltip}; use util::ResultExt; use crate::onboarding_event; /// Prompts the user to try Zed's Edit Prediction feature pub struct ZedPredictBanner { dismissed: bool, provider: EditPredictionProvider, _subscription: Subscription, } impl ZedPredictBanner { pub fn new(cx: &mut Context) -> Self { Self { dismissed: get_dismissed(), provider: all_language_settings(None, cx).edit_predictions.provider, _subscription: cx.observe_global::(Self::handle_settings_changed), } } fn should_show(&self, cx: &mut App) -> bool { cx.has_flag::() && !self.dismissed && !self.provider.is_zed() } fn handle_settings_changed(&mut self, cx: &mut Context) { let new_provider = all_language_settings(None, cx).edit_predictions.provider; if new_provider == self.provider { return; } if new_provider.is_zed() { self.dismiss(cx); } else { self.dismissed = get_dismissed(); } self.provider = new_provider; cx.notify(); } fn dismiss(&mut self, cx: &mut Context) { onboarding_event!("Banner Dismissed"); persist_dismissed(cx); self.dismissed = true; cx.notify(); } } const DISMISSED_AT_KEY: &str = "zed_predict_banner_dismissed_at"; fn get_dismissed() -> bool { db::kvp::KEY_VALUE_STORE .read_kvp(DISMISSED_AT_KEY) .log_err() .map_or(false, |dismissed| dismissed.is_some()) } fn persist_dismissed(cx: &mut App) { cx.spawn(|_| { let time = Utc::now().to_rfc3339(); db::kvp::KEY_VALUE_STORE.write_kvp(DISMISSED_AT_KEY.into(), time) }) .detach_and_log_err(cx); } pub(crate) fn clear_dismissed(cx: &mut App) { cx.spawn(|_| db::kvp::KEY_VALUE_STORE.delete_kvp(DISMISSED_AT_KEY.into())) .detach_and_log_err(cx); } impl Render for ZedPredictBanner { fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { if !self.should_show(cx) { return div(); } let border_color = cx.theme().colors().editor_foreground.opacity(0.3); let banner = h_flex() .rounded_md() .border_1() .border_color(border_color) .child( ButtonLike::new("try-zed-predict") .child( h_flex() .h_full() .items_center() .gap_1p5() .child(Icon::new(IconName::ZedPredict).size(IconSize::Small)) .child( h_flex() .gap_0p5() .child( Label::new("Introducing:") .size(LabelSize::Small) .color(Color::Muted), ) .child(Label::new("Edit Prediction").size(LabelSize::Small)), ), ) .on_click(|_, window, cx| { onboarding_event!("Banner Clicked"); window.dispatch_action(Box::new(zed_actions::OpenZedPredictOnboarding), cx) }), ) .child( div().border_l_1().border_color(border_color).child( IconButton::new("close", IconName::Close) .icon_size(IconSize::Indicator) .on_click(cx.listener(|this, _, _window, cx| this.dismiss(cx))) .tooltip(|window, cx| { Tooltip::with_meta( "Close Announcement Banner", None, "It won't show again for this feature", window, cx, ) }), ), ); div().pr_2().child(banner) } }