assistant2: Adjust empty state when there is no provider (#23609)

Danilo Leal created

This PR add a "Configure a Provider" button if the user gets to the
assistant panel with no provider configured. Then, upon configuring it,
they'll see a similar welcome message.

| No provider | Empty state |
|--------|--------|
| <img width="1233" alt="Screenshot 2025-01-24 at 12 25 48 PM"
src="https://github.com/user-attachments/assets/2f3c602f-9e46-4c79-95cd-4bb3717f68a3"
/> | <img width="1233" alt="Screenshot 2025-01-24 at 12 26 01 PM"
src="https://github.com/user-attachments/assets/a4a204dd-9531-45ab-89a2-f1d84f375a7b"
/> |

Release Notes:

- N/A

Change summary

crates/assistant2/src/assistant_panel.rs              | 74 ++++++++++++
crates/assistant_context_editor/src/context_editor.rs |  2 
2 files changed, 73 insertions(+), 3 deletions(-)

Detailed changes

crates/assistant2/src/assistant_panel.rs 🔗

@@ -3,8 +3,8 @@ use std::sync::Arc;
 
 use anyhow::{anyhow, Result};
 use assistant_context_editor::{
-    make_lsp_adapter_delegate, AssistantPanelDelegate, ContextEditor, ContextHistory,
-    SlashCommandCompletionProvider,
+    make_lsp_adapter_delegate, AssistantPanelDelegate, ConfigurationError, ContextEditor,
+    ContextHistory, SlashCommandCompletionProvider,
 };
 use assistant_settings::{AssistantDockPosition, AssistantSettings};
 use assistant_slash_command::SlashCommandWorkingSet;
@@ -662,11 +662,35 @@ impl AssistantPanel {
         self.thread.clone().into_any()
     }
 
+    fn configuration_error(&self, cx: &AppContext) -> Option<ConfigurationError> {
+        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 !is_authenticated {
+            return Some(ConfigurationError::ProviderNotAuthenticated);
+        }
+
+        None
+    }
+
     fn render_thread_empty_state(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
         let recent_threads = self
             .thread_store
             .update(cx, |this, _cx| this.recent_threads(3));
 
+        let create_welcome_heading = || {
+            h_flex()
+                .w_full()
+                .justify_center()
+                .child(Headline::new("Welcome to the Assistant Panel").size(HeadlineSize::Small))
+        };
+
         v_flex()
             .gap_2()
             .child(
@@ -680,6 +704,52 @@ impl AssistantPanel {
                         .mb_4(),
                 ),
             )
+            .when(
+                matches!(
+                    self.configuration_error(cx),
+                    Some(ConfigurationError::ProviderNotAuthenticated)
+                ),
+                |parent| {
+                    parent.child(
+                        v_flex()
+                            .gap_0p5()
+                            .child(create_welcome_heading())
+                            .child(
+                                h_flex().mb_2().w_full().justify_center().child(
+                                    Label::new(
+                                        "To start using the assistant, configure at least one LLM provider.",
+                                    )
+                                    .color(Color::Muted),
+                                ),
+                            )
+                            .child(
+                                h_flex().w_full().justify_center().child(
+                                    Button::new("open-configuration", "Configure a Provider")
+                                        .size(ButtonSize::Compact)
+                                        .icon(Some(IconName::Sliders))
+                                        .icon_size(IconSize::Small)
+                                        .icon_position(IconPosition::Start)
+                                        .on_click(cx.listener(|this, _, cx| {
+                                            this.open_configuration(cx);
+                                        })),
+                                ),
+                            ),
+                    )
+                },
+            )
+            .when(
+                recent_threads.is_empty() && self.configuration_error(cx).is_none(),
+                |parent| {
+                    parent.child(
+                        v_flex().gap_0p5().child(create_welcome_heading()).child(
+                            h_flex().w_full().justify_center().child(
+                                Label::new("Start typing to chat with your codebase")
+                                    .color(Color::Muted),
+                            ),
+                        ),
+                    )
+                },
+            )
             .when(!recent_threads.is_empty(), |parent| {
                 parent
                     .child(