From 0fba36469b6471841b2a89aa73454be61f6b4e93 Mon Sep 17 00:00:00 2001 From: Thorsten Ball Date: Mon, 5 Aug 2024 13:21:16 +0200 Subject: [PATCH] assistant panel: Show Zed AI notice & configuration hint (#15798) This adds two possible notices to the assistant panel: - Shows notice if currently selected provider is not configured - Shows notice if user is signed-out and (does not have provider OR provider is zed.dev) and tells user to sign in Design needs to be tweaked. cc @iamnbutler ![screenshot-2024-08-05-13 11 21@2x](https://github.com/user-attachments/assets/ada2d881-2f81-49ed-bebf-2efbf06e7d82) Release Notes: - N/A Co-authored-by: Bennet --- crates/assistant/src/assistant_panel.rs | 172 +++++++++++++++++++----- 1 file changed, 136 insertions(+), 36 deletions(-) diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index a866dba6d1..5a31199312 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -18,7 +18,7 @@ use crate::{ use crate::{ContextStoreEvent, ShowConfiguration}; use anyhow::{anyhow, Result}; use assistant_slash_command::{SlashCommand, SlashCommandOutputSection}; -use client::proto; +use client::{proto, Client, Status}; use collections::{BTreeSet, HashMap, HashSet}; use editor::{ actions::{FoldAt, MoveToEndOfLine, Newline, ShowCompletions, UnfoldAt}, @@ -147,7 +147,7 @@ pub struct AssistantPanel { authenticate_provider_task: Option<(LanguageModelProviderId, Task<()>)>, configuration_subscription: Option, watch_client_status: Option>, - nudge_sign_in: bool, + show_zed_ai_notice: bool, } #[derive(Clone)] @@ -419,37 +419,7 @@ impl AssistantPanel { ), ]; - let mut status_rx = workspace.client().clone().status(); - - let watch_client_status = cx.spawn(|this, mut cx| async move { - let mut old_status = None; - while let Some(status) = status_rx.next().await { - if old_status.is_none() - || old_status.map_or(false, |old_status| old_status != status) - { - if status.is_signed_out() { - this.update(&mut cx, |this, cx| { - let active_provider = - LanguageModelRegistry::read_global(cx).active_provider(); - - // If we're signed out and don't have a provider configured, or we're signed-out AND Zed.dev is - // the provider, we want to show a nudge to sign in. - if active_provider - .map_or(true, |provider| provider.id().0 == PROVIDER_ID) - { - println!("TODO: Nudge the user to sign in and use Zed AI"); - this.nudge_sign_in = true; - } - }) - .log_err(); - }; - - old_status = Some(status); - } - } - this.update(&mut cx, |this, _cx| this.watch_client_status = None) - .log_err(); - }); + let watch_client_status = Self::watch_client_status(workspace.client().clone(), cx); let mut this = Self { pane, @@ -466,13 +436,34 @@ impl AssistantPanel { authenticate_provider_task: None, configuration_subscription: None, watch_client_status: Some(watch_client_status), - // TODO: This is unused! - nudge_sign_in: false, + show_zed_ai_notice: false, }; this.new_context(cx); this } + fn watch_client_status(client: Arc, cx: &mut ViewContext) -> Task<()> { + let mut status_rx = client.status(); + + cx.spawn(|this, mut cx| async move { + let mut old_status = None; + while let Some(status) = status_rx.next().await { + if old_status.is_none() + || old_status.map_or(false, |old_status| old_status != status) + { + this.update(&mut cx, |this, cx| { + this.handle_client_status_change(status, cx) + }) + .log_err(); + + old_status = Some(status); + } + } + this.update(&mut cx, |this, _cx| this.watch_client_status = None) + .log_err(); + }) + } + fn handle_pane_event( &mut self, pane: View, @@ -563,6 +554,18 @@ impl AssistantPanel { } } + fn handle_client_status_change(&mut self, client_status: Status, cx: &mut ViewContext) { + let active_provider = LanguageModelRegistry::read_global(cx).active_provider(); + + // If we're signed out and don't have a provider configured, or we're signed-out AND Zed.dev is + // the provider, we want to show a nudge to sign in. + let show_zed_ai_notice = client_status.is_signed_out() + && active_provider.map_or(true, |provider| provider.id().0 == PROVIDER_ID); + + self.show_zed_ai_notice = show_zed_ai_notice; + cx.notify(); + } + fn handle_toolbar_event( &mut self, _: View, @@ -2314,6 +2317,69 @@ impl ContextEditor { .unwrap_or_else(|| Cow::Borrowed(DEFAULT_TAB_TITLE)) } + fn render_notice(&self, cx: &mut ViewContext) -> Option { + let nudge = self + .assistant_panel + .upgrade() + .map(|assistant_panel| assistant_panel.read(cx).show_zed_ai_notice); + + if nudge.unwrap_or(false) { + Some( + v_flex() + .elevation_3(cx) + .p_4() + .gap_2() + .child(Label::new("Use Zed AI")) + .child( + div() + .id("sign-in") + .child(Label::new("Sign in to use Zed AI")) + .cursor_pointer() + .on_click(cx.listener(|this, _event, cx| { + let client = this + .workspace + .update(cx, |workspace, _| workspace.client().clone()) + .log_err(); + + if let Some(client) = client { + cx.spawn(|this, mut cx| async move { + client.authenticate_and_connect(true, &mut cx).await?; + this.update(&mut cx, |_, cx| cx.notify()) + }) + .detach_and_log_err(cx) + } + })), + ), + ) + } else if let Some(configuration_error) = configuration_error(cx) { + let label = match configuration_error { + ConfigurationError::NoProvider => "No provider configured", + ConfigurationError::ProviderNotAuthenticated => "Provider is not configured", + }; + Some( + v_flex() + .elevation_3(cx) + .p_4() + .gap_2() + .child(Label::new(label)) + .child( + div() + .id("open-configuration") + .child(Label::new("Open configuration")) + .cursor_pointer() + .on_click({ + let focus_handle = self.focus_handle(cx).clone(); + move |_event, cx| { + focus_handle.dispatch_action(&ShowConfiguration, cx); + } + }), + ), + ) + } else { + None + } + } + fn render_send_button(&self, cx: &mut ViewContext) -> impl IntoElement { let focus_handle = self.focus_handle(cx).clone(); let button_text = match self.workflow_step_for_cursor(cx) { @@ -2419,7 +2485,15 @@ impl Render for ContextEditor { .bottom_0() .p_4() .justify_end() - .child(self.render_send_button(cx)), + .child( + v_flex() + .gap_2() + .items_end() + .when_some(self.render_notice(cx), |this, notice| { + this.child(notice) + }) + .child(self.render_send_button(cx)), + ), ), ) } @@ -3305,3 +3379,29 @@ fn token_state(context: &Model, cx: &AppContext) -> Option }; Some(token_state) } + +enum ConfigurationError { + NoProvider, + ProviderNotAuthenticated, +} + +fn configuration_error(cx: &AppContext) -> Option { + let provider = LanguageModelRegistry::read_global(cx).active_provider(); + let is_authenticated = provider + .as_ref() + .map_or(false, |provider| provider.is_authenticated(cx)); + + if provider.is_some() && is_authenticated { + return None; + } + + if provider.is_none() { + return Some(ConfigurationError::NoProvider); + } + + if !is_authenticated { + return Some(ConfigurationError::ProviderNotAuthenticated); + } + + None +}