From 5c400dac8d0de2c063f7fd56f4a3a0f40b5b4aa6 Mon Sep 17 00:00:00 2001
From: Danilo Leal <67129314+danilo-leal@users.noreply.github.com>
Date: Thu, 27 Feb 2025 11:33:53 -0300
Subject: [PATCH] assistant2: Adjust empty state layout (#25745)
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.
Release Notes:
- N/A
---
.../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 +++-
.../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(-)
diff --git a/crates/assistant2/src/assistant_configuration.rs b/crates/assistant2/src/assistant_configuration.rs
index 45fde2686cc520fb6a2a05f20fd752a6723c26eb..b5598d0243fdf9b67f0f1546b627329b096bd7fc 100644
--- a/crates/assistant2/src/assistant_configuration.rs
+++ b/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)
diff --git a/crates/assistant2/src/assistant_panel.rs b/crates/assistant2/src/assistant_panel.rs
index 221c8562d272c3e11b6d68c906a6f0b728206e39..0f610260a0f4ab164f8cf220f3600314383cf220 100644
--- a/crates/assistant2/src/assistant_panel.rs
+++ b/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);
- }),
- ),
- )
})
}
diff --git a/crates/assistant2/src/message_editor.rs b/crates/assistant2/src/message_editor.rs
index 0be0af53a4ea42abe631b7f3b0d28dcd1479871d..fe0e1351fc388c5f7e9b388be45f01fede8a6ceb 100644
--- a/crates/assistant2/src/message_editor.rs
+++ b/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 {
diff --git a/crates/assistant2/src/thread_history.rs b/crates/assistant2/src/thread_history.rs
index 460fb6f420f83d87a8a335353d03f7e2c8767cb8..80cb8dd44b08231226bcb9717c36df1a13b66dec 100644
--- a/crates/assistant2/src/thread_history.rs
+++ b/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)
diff --git a/crates/language_models/src/provider/bedrock.rs b/crates/language_models/src/provider/bedrock.rs
index 4294a1ba723e281d5a4899d034275010b0556a4e..bcb4c28d41c6572b43c2b71885dc3d18b9eaa54e 100644
--- a/crates/language_models/src/provider/bedrock.rs
+++ b/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(
diff --git a/crates/language_models/src/provider/cloud.rs b/crates/language_models/src/provider/cloud.rs
index 8037e8c33798bcb09e27496b72532c1e7db20baa..499d6da9dbf126378c57ca868dcc9f8bb9ba41fe 100644
--- a/crates/language_models/src/provider/cloud.rs
+++ b/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()
+ }
}
});
diff --git a/crates/ui/src/components/list/list_item.rs b/crates/ui/src/components/list/list_item.rs
index 2d2e506e62d4e3c8c35083eed0d27078e56bb829..7045ae87d8f9e2b9255ab4e0a2619245536ff45b 100644
--- a/crates/ui/src/components/list/list_item.rs
+++ b/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,
}
@@ -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 {