assistant2: Adjust empty state layout (#25745)

Danilo Leal created

Going for a different, arguably simpler design for the Assistant 2 empty
state here. Also took the opportunity to adjust other elements like the
toolbar, message editor, and some items in the configuration page.

<img
src="https://github.com/user-attachments/assets/03fd1d48-a675-4eac-b694-bbe4eeaf06e9"
width="700px"/>

Release Notes:

- N/A

Change summary

crates/assistant2/src/assistant_configuration.rs |  12 
crates/assistant2/src/assistant_panel.rs         | 214 +++++++++--------
crates/assistant2/src/message_editor.rs          |   2 
crates/assistant2/src/thread_history.rs          |  44 ++-
crates/language_models/src/provider/bedrock.rs   |  26 +
crates/language_models/src/provider/cloud.rs     |  15 
crates/ui/src/components/list/list_item.rs       |   9 
7 files changed, 186 insertions(+), 136 deletions(-)

Detailed changes

crates/assistant2/src/assistant_configuration.rs πŸ”—

@@ -158,8 +158,16 @@ impl Render for AssistantConfiguration {
             .child(
                 v_flex()
                     .p(DynamicSpacing::Base16.rems(cx))
-                    .gap_1()
-                    .child(Headline::new("Prompt Library").size(HeadlineSize::Small))
+                    .gap_2()
+                    .child(
+                        v_flex()
+                            .gap_0p5()
+                            .child(Headline::new("Prompt Library").size(HeadlineSize::Small))
+                            .child(
+                                Label::new("Create reusable prompts and tag which ones you want sent in every LLM interaction.")
+                                    .color(Color::Muted),
+                            ),
+                    )
                     .child(
                         Button::new("open-prompt-library", "Open Prompt Library")
                             .style(ButtonStyle::Filled)

crates/assistant2/src/assistant_panel.rs πŸ”—

@@ -14,7 +14,7 @@ use client::zed_urls;
 use editor::Editor;
 use fs::Fs;
 use gpui::{
-    prelude::*, px, svg, Action, AnyElement, App, AsyncWindowContext, Corner, Entity, EventEmitter,
+    prelude::*, Action, AnyElement, App, AsyncWindowContext, Corner, Entity, EventEmitter,
     FocusHandle, Focusable, FontWeight, Pixels, Subscription, Task, UpdateGlobal, WeakEntity,
 };
 use language::LanguageRegistry;
@@ -596,7 +596,6 @@ impl AssistantPanel {
 
         h_flex()
             .id("assistant-toolbar")
-            .px(DynamicSpacing::Base08.rems(cx))
             .h(Tab::container_height(cx))
             .flex_none()
             .justify_between()
@@ -604,72 +603,86 @@ impl AssistantPanel {
             .bg(cx.theme().colors().tab_bar_background)
             .border_b_1()
             .border_color(cx.theme().colors().border)
+            .child(
+                div()
+                    .id("title")
+                    .overflow_x_scroll()
+                    .px(DynamicSpacing::Base08.rems(cx))
+                    .child(Label::new(title).text_ellipsis()),
+            )
             .child(
                 h_flex()
-                    .w_full()
-                    .gap_1()
-                    .justify_between()
-                    .child(Label::new(title))
+                    .h_full()
+                    .pl_2()
+                    .gap_2()
+                    .bg(cx.theme().colors().tab_bar_background)
                     .children(if matches!(self.active_view, ActiveView::PromptEditor) {
                         self.context_editor
                             .as_ref()
                             .and_then(|editor| render_remaining_tokens(editor, cx))
                     } else {
                         None
-                    }),
-            )
-            .child(
-                h_flex()
-                    .h_full()
-                    .pl_1p5()
-                    .border_l_1()
-                    .border_color(cx.theme().colors().border)
-                    .gap(DynamicSpacing::Base02.rems(cx))
+                    })
                     .child(
-                        PopoverMenu::new("assistant-toolbar-new-popover-menu")
-                            .trigger_with_tooltip(
-                                IconButton::new("new", IconName::Plus)
+                        h_flex()
+                            .h_full()
+                            .px(DynamicSpacing::Base08.rems(cx))
+                            .border_l_1()
+                            .border_color(cx.theme().colors().border)
+                            .gap(DynamicSpacing::Base02.rems(cx))
+                            .child(
+                                PopoverMenu::new("assistant-toolbar-new-popover-menu")
+                                    .trigger_with_tooltip(
+                                        IconButton::new("new", IconName::Plus)
+                                            .icon_size(IconSize::Small)
+                                            .style(ButtonStyle::Subtle),
+                                        Tooltip::text("New…"),
+                                    )
+                                    .anchor(Corner::TopRight)
+                                    .with_handle(self.new_item_context_menu_handle.clone())
+                                    .menu(move |window, cx| {
+                                        Some(ContextMenu::build(
+                                            window,
+                                            cx,
+                                            |menu, _window, _cx| {
+                                                menu.action("New Thread", NewThread.boxed_clone())
+                                                    .action(
+                                                        "New Prompt Editor",
+                                                        NewPromptEditor.boxed_clone(),
+                                                    )
+                                            },
+                                        ))
+                                    }),
+                            )
+                            .child(
+                                IconButton::new("open-history", IconName::HistoryRerun)
                                     .icon_size(IconSize::Small)
-                                    .style(ButtonStyle::Subtle),
-                                Tooltip::text("New…"),
+                                    .style(ButtonStyle::Subtle)
+                                    .tooltip({
+                                        let focus_handle = self.focus_handle(cx);
+                                        move |window, cx| {
+                                            Tooltip::for_action_in(
+                                                "History",
+                                                &OpenHistory,
+                                                &focus_handle,
+                                                window,
+                                                cx,
+                                            )
+                                        }
+                                    })
+                                    .on_click(move |_event, window, cx| {
+                                        window.dispatch_action(OpenHistory.boxed_clone(), cx);
+                                    }),
                             )
-                            .anchor(Corner::TopRight)
-                            .with_handle(self.new_item_context_menu_handle.clone())
-                            .menu(move |window, cx| {
-                                Some(ContextMenu::build(window, cx, |menu, _window, _cx| {
-                                    menu.action("New Thread", NewThread.boxed_clone())
-                                        .action("New Prompt Editor", NewPromptEditor.boxed_clone())
-                                }))
-                            }),
-                    )
-                    .child(
-                        IconButton::new("open-history", IconName::HistoryRerun)
-                            .icon_size(IconSize::Small)
-                            .style(ButtonStyle::Subtle)
-                            .tooltip({
-                                let focus_handle = self.focus_handle(cx);
-                                move |window, cx| {
-                                    Tooltip::for_action_in(
-                                        "History",
-                                        &OpenHistory,
-                                        &focus_handle,
-                                        window,
-                                        cx,
-                                    )
-                                }
-                            })
-                            .on_click(move |_event, window, cx| {
-                                window.dispatch_action(OpenHistory.boxed_clone(), cx);
-                            }),
-                    )
-                    .child(
-                        IconButton::new("configure-assistant", IconName::Settings)
-                            .icon_size(IconSize::Small)
-                            .style(ButtonStyle::Subtle)
-                            .tooltip(Tooltip::text("Assistant Settings"))
-                            .on_click(move |_event, window, cx| {
-                                window.dispatch_action(OpenConfiguration.boxed_clone(), cx);
-                            }),
+                            .child(
+                                IconButton::new("configure-assistant", IconName::Settings)
+                                    .icon_size(IconSize::Small)
+                                    .style(ButtonStyle::Subtle)
+                                    .tooltip(Tooltip::text("Assistant Settings"))
+                                    .on_click(move |_event, window, cx| {
+                                        window.dispatch_action(OpenConfiguration.boxed_clone(), cx);
+                                    }),
+                            ),
                     ),
             )
     }
@@ -711,12 +724,11 @@ impl AssistantPanel {
     ) -> impl IntoElement {
         let recent_history = self
             .history_store
-            .update(cx, |this, cx| this.recent_entries(3, cx));
+            .update(cx, |this, cx| this.recent_entries(6, cx));
 
         let create_welcome_heading = || {
             h_flex()
                 .w_full()
-                .justify_center()
                 .child(Headline::new("Welcome to the Assistant Panel").size(HeadlineSize::Small))
         };
 
@@ -724,36 +736,27 @@ impl AssistantPanel {
         let no_error = configuration_error.is_none();
 
         v_flex()
-            .gap_2()
-            .child(
-                v_flex().w_full().child(
-                    svg()
-                        .path("icons/logo_96.svg")
-                        .text_color(cx.theme().colors().text)
-                        .w(px(40.))
-                        .h(px(40.))
-                        .mx_auto()
-                        .mb_4(),
-                ),
-            )
+            .p_1p5()
+            .size_full()
+            .justify_end()
+            .gap_1()
             .map(|parent| {
                 match configuration_error {
                     Some(ConfigurationError::ProviderNotAuthenticated)
                     | Some(ConfigurationError::NoProvider) => {
                         parent.child(
                             v_flex()
+                                .px_1p5()
                                 .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),
-                                    ),
+                                    Label::new(
+                                        "To start using the assistant, configure at least one LLM provider.",
+                                    )
+                                    .color(Color::Muted),
                                 )
                                 .child(
-                                    h_flex().w_full().justify_center().child(
+                                    h_flex().mt_1().w_full().child(
                                         Button::new("open-configuration", "Configure a Provider")
                                             .size(ButtonSize::Compact)
                                             .icon(Some(IconName::Sliders))
@@ -767,7 +770,7 @@ impl AssistantPanel {
                         )
                     }
                     Some(ConfigurationError::ProviderPendingTermsAcceptance(provider)) => parent
-                        .child(v_flex().gap_0p5().child(create_welcome_heading()).children(
+                        .child(v_flex().px_1p5().gap_0p5().child(create_welcome_heading()).children(
                             provider.render_accept_terms(
                                 LanguageModelProviderTosView::ThreadEmptyState,
                                 cx,
@@ -778,21 +781,40 @@ impl AssistantPanel {
             })
             .when(recent_history.is_empty() && no_error, |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),
-                    ),
+                    Label::new("Start typing to chat with your codebase").color(Color::Muted),
                 ))
             })
             .when(!recent_history.is_empty(), |parent| {
                 parent
                     .child(
-                        h_flex().w_full().justify_center().child(
-                            Label::new("Recent Threads:")
-                                .size(LabelSize::Small)
-                                .color(Color::Muted),
-                        ),
+                        h_flex()
+                            .pl_1p5()
+                            .pb_1()
+                            .w_full()
+                            .justify_between()
+                            .border_b_1()
+                            .border_color(cx.theme().colors().border_variant)
+                            .child(
+                                Label::new("Past Interactions")
+                                    .size(LabelSize::Small)
+                                    .color(Color::Muted),
+                            )
+                            .child(
+                                Button::new("view-history", "View All")
+                                    .style(ButtonStyle::Subtle)
+                                    .label_size(LabelSize::Small)
+                                    .key_binding(KeyBinding::for_action_in(
+                                        &OpenHistory,
+                                        &self.focus_handle(cx),
+                                        window,
+                                        cx,
+                                    ))
+                                    .on_click(move |_event, window, cx| {
+                                        window.dispatch_action(OpenHistory.boxed_clone(), cx);
+                                    }),
+                            ),
                     )
-                    .child(v_flex().mx_auto().w_4_5().gap_2().children(
+                    .child(v_flex().gap_1().children(
                         recent_history.into_iter().map(|entry| {
                             // TODO: Add keyboard navigation.
                             match entry {
@@ -807,22 +829,6 @@ impl AssistantPanel {
                             }
                         }),
                     ))
-                    .child(
-                        h_flex().w_full().justify_center().child(
-                            Button::new("view-all-past-threads", "View All Past Threads")
-                                .style(ButtonStyle::Subtle)
-                                .label_size(LabelSize::Small)
-                                .key_binding(KeyBinding::for_action_in(
-                                    &OpenHistory,
-                                    &self.focus_handle(cx),
-                                    window,
-                                    cx,
-                                ))
-                                .on_click(move |_event, window, cx| {
-                                    window.dispatch_action(OpenHistory.boxed_clone(), cx);
-                                }),
-                        ),
-                    )
             })
     }
 

crates/assistant2/src/message_editor.rs πŸ”—

@@ -314,7 +314,7 @@ impl Render for MessageEditor {
             .child(self.context_strip.clone())
             .child(
                 v_flex()
-                    .gap_4()
+                    .gap_5()
                     .child({
                         let settings = ThemeSettings::get_global(cx);
                         let text_style = TextStyle {

crates/assistant2/src/thread_history.rs πŸ”—

@@ -254,18 +254,28 @@ impl RenderOnce for PastThread {
         );
 
         ListItem::new(SharedString::from(self.thread.id.to_string()))
-            .outlined()
+            .rounded()
             .toggle_state(self.selected)
+            .spacing(ListItemSpacing::Sparse)
             .start_slot(
-                Icon::new(IconName::MessageCircle)
-                    .size(IconSize::Small)
-                    .color(Color::Muted),
+                div()
+                    .max_w_4_5()
+                    .child(Label::new(summary).size(LabelSize::Small).text_ellipsis()),
             )
-            .spacing(ListItemSpacing::Sparse)
-            .child(Label::new(summary).size(LabelSize::Small).text_ellipsis())
             .end_slot(
                 h_flex()
                     .gap_1p5()
+                    .child(
+                        Label::new("Thread")
+                            .color(Color::Muted)
+                            .size(LabelSize::XSmall),
+                    )
+                    .child(
+                        div()
+                            .size(px(3.))
+                            .rounded_full()
+                            .bg(cx.theme().colors().text_disabled),
+                    )
                     .child(
                         Label::new(thread_timestamp)
                             .color(Color::Muted)
@@ -340,18 +350,28 @@ impl RenderOnce for PastContext {
         ListItem::new(SharedString::from(
             self.context.path.to_string_lossy().to_string(),
         ))
-        .outlined()
+        .rounded()
         .toggle_state(self.selected)
+        .spacing(ListItemSpacing::Sparse)
         .start_slot(
-            Icon::new(IconName::Code)
-                .size(IconSize::Small)
-                .color(Color::Muted),
+            div()
+                .max_w_4_5()
+                .child(Label::new(summary).size(LabelSize::Small).text_ellipsis()),
         )
-        .spacing(ListItemSpacing::Sparse)
-        .child(Label::new(summary).size(LabelSize::Small).text_ellipsis())
         .end_slot(
             h_flex()
                 .gap_1p5()
+                .child(
+                    Label::new("Prompt Editor")
+                        .color(Color::Muted)
+                        .size(LabelSize::XSmall),
+                )
+                .child(
+                    div()
+                        .size(px(3.))
+                        .rounded_full()
+                        .bg(cx.theme().colors().text_disabled),
+                )
                 .child(
                     Label::new(context_timestamp)
                         .color(Color::Muted)

crates/language_models/src/provider/bedrock.rs πŸ”—

@@ -960,17 +960,30 @@ impl Render for ConfigurationView {
         ];
         let env_var_set = self.state.read(cx).credentials_from_env;
 
+        let bg_color = cx.theme().colors().editor_background;
+        let border_color = cx.theme().colors().border_variant;
+        let input_base_styles = || {
+            h_flex()
+                .w_full()
+                .px_2()
+                .py_1()
+                .bg(bg_color)
+                .border_1()
+                .border_color(border_color)
+                .rounded_md()
+        };
+
         if self.load_credentials_task.is_some() {
             div().child(Label::new("Loading credentials...")).into_any()
         } else if self.should_render_editor(cx) {
             v_flex()
                 .size_full()
-                .on_action(cx.listener(Self::save_credentials))
+                .on_action(cx.listener(ConfigurationView::save_credentials))
                 .child(Label::new(INSTRUCTIONS[0]))
                 .child(h_flex().child(Label::new(INSTRUCTIONS[1])).child(
                     Button::new("iam_console", IAM_CONSOLE_URL)
                         .style(ButtonStyle::Subtle)
-                        .icon(IconName::ExternalLink)
+                        .icon(IconName::ArrowUpRight)
                         .icon_size(IconSize::XSmall)
                         .icon_color(Color::Muted)
                         .on_click(move |_, _window, cx| cx.open_url(IAM_CONSOLE_URL))
@@ -978,11 +991,12 @@ impl Render for ConfigurationView {
                 )
                 .child(Label::new(INSTRUCTIONS[2]))
                 .child(
-                    h_flex()
+                    v_flex()
+                        .my_2()
                         .gap_1()
-                        .child(self.render_aa_id_editor(cx))
-                        .child(self.render_sk_editor(cx))
-                        .child(self.render_region_editor(cx))
+                        .child(input_base_styles().child(self.render_aa_id_editor(cx)))
+                        .child(input_base_styles().child(self.render_sk_editor(cx)))
+                        .child(input_base_styles().child(self.render_region_editor(cx)))
                 )
                 .child(
                     Label::new(

crates/language_models/src/provider/cloud.rs πŸ”—

@@ -386,17 +386,10 @@ fn render_accept_terms(
     let form = v_flex()
         .w_full()
         .gap_2()
-        .when(
-            view_kind == LanguageModelProviderTosView::ThreadEmptyState,
-            |form| form.items_center(),
-        )
         .child(
             h_flex()
                 .flex_wrap()
-                .when(
-                    view_kind == LanguageModelProviderTosView::ThreadEmptyState,
-                    |form| form.justify_center(),
-                )
+                .items_start()
                 .child(Label::new(text))
                 .child(terms_button),
         )
@@ -416,9 +409,11 @@ fn render_accept_terms(
             );
 
             match view_kind {
-                LanguageModelProviderTosView::ThreadEmptyState => button_container.justify_center(),
                 LanguageModelProviderTosView::PromptEditorPopup => button_container.justify_end(),
-                LanguageModelProviderTosView::Configuration => button_container.justify_start(),
+                LanguageModelProviderTosView::Configuration
+                | LanguageModelProviderTosView::ThreadEmptyState => {
+                    button_container.justify_start()
+                }
             }
         });
 

crates/ui/src/components/list/list_item.rs πŸ”—

@@ -38,6 +38,7 @@ pub struct ListItem {
     children: SmallVec<[AnyElement; 2]>,
     selectable: bool,
     outlined: bool,
+    rounded: bool,
     overflow_x: bool,
     focused: Option<bool>,
 }
@@ -63,6 +64,7 @@ impl ListItem {
             children: SmallVec::new(),
             selectable: true,
             outlined: false,
+            rounded: false,
             overflow_x: false,
             focused: None,
         }
@@ -147,6 +149,11 @@ impl ListItem {
         self
     }
 
+    pub fn rounded(mut self) -> Self {
+        self.rounded = true;
+        self
+    }
+
     pub fn overflow_x(mut self) -> Self {
         self.overflow_x = true;
         self
@@ -210,13 +217,13 @@ impl RenderOnce for ListItem {
                             })
                     })
             })
+            .when(self.rounded, |this| this.rounded_md())
             .child(
                 h_flex()
                     .id("inner_list_item")
                     .group("list_item")
                     .w_full()
                     .relative()
-                    .items_center()
                     .gap_1()
                     .px(DynamicSpacing::Base06.rems(cx))
                     .map(|this| match self.spacing {