From 78a8a58ee2c352c7ca929b3b30159269df81e418 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Thu, 2 May 2024 13:01:21 -0400 Subject: [PATCH] Iterate on Assistant 2 composer UI (#11306) - Change project index tool rendering in composer - Update composer UI style Release Notes: - N/A --------- Co-authored-by: Marshall Bowers <1486634+maxdeviant@users.noreply.github.com> Co-authored-by: Marshall Bowers --- assets/icons/code.svg | 1 + assets/icons/spinner.svg | 13 ++ crates/assistant2/src/assistant2.rs | 8 +- crates/assistant2/src/tools/project_index.rs | 73 +++++++-- crates/assistant2/src/ui/composer.rs | 159 +++++++------------ crates/ui/src/components/icon.rs | 4 + 6 files changed, 141 insertions(+), 117 deletions(-) create mode 100644 assets/icons/code.svg create mode 100644 assets/icons/spinner.svg diff --git a/assets/icons/code.svg b/assets/icons/code.svg new file mode 100644 index 0000000000..757c5a1cb6 --- /dev/null +++ b/assets/icons/code.svg @@ -0,0 +1 @@ + diff --git a/assets/icons/spinner.svg b/assets/icons/spinner.svg new file mode 100644 index 0000000000..4f4034ae89 --- /dev/null +++ b/assets/icons/spinner.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/crates/assistant2/src/assistant2.rs b/crates/assistant2/src/assistant2.rs index 359f947f6d..74ab6d3049 100644 --- a/crates/assistant2/src/assistant2.rs +++ b/crates/assistant2/src/assistant2.rs @@ -269,7 +269,7 @@ impl AssistantChat { composer_editor: cx.new_view(|cx| { let mut editor = Editor::auto_height(80, cx); editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx); - editor.set_placeholder_text("Type a message to the assistant", cx); + editor.set_placeholder_text("Send a message…", cx); editor }), list_state, @@ -372,10 +372,6 @@ impl AssistantChat { })); } - fn can_submit(&self) -> bool { - self.pending_completion.is_none() - } - fn debug_project_index(&mut self, _: &DebugProjectIndex, cx: &mut ViewContext) { if let Some(index) = &self.project_index { index.update(cx, |project_index, cx| { @@ -594,7 +590,6 @@ impl AssistantChat { element.child(Composer::new( body.clone(), self.user_store.read(cx).current_user(), - true, self.tool_registry.clone(), crate::ui::ModelSelector::new( cx.view().downgrade(), @@ -773,7 +768,6 @@ impl Render for AssistantChat { .child(Composer::new( self.composer_editor.clone(), self.user_store.read(cx).current_user(), - self.can_submit(), self.tool_registry.clone(), crate::ui::ModelSelector::new(cx.view().downgrade(), self.model.clone()) .into_any_element(), diff --git a/crates/assistant2/src/tools/project_index.rs b/crates/assistant2/src/tools/project_index.rs index d9f3e8271d..072ff87c1f 100644 --- a/crates/assistant2/src/tools/project_index.rs +++ b/crates/assistant2/src/tools/project_index.rs @@ -1,14 +1,14 @@ use anyhow::Result; use assistant_tooling::LanguageModelTool; -use gpui::{prelude::*, AnyView, Model, Task}; +use gpui::{percentage, prelude::*, Animation, AnimationExt, AnyView, Model, Task, Transformation}; use project::Fs; use schemars::JsonSchema; use semantic_index::{ProjectIndex, Status}; use serde::Deserialize; -use std::sync::Arc; +use std::{sync::Arc, time::Duration}; use ui::{ - div, prelude::*, CollapsibleContainer, Color, Icon, IconName, Label, SharedString, - WindowContext, + div, prelude::*, ButtonLike, CollapsibleContainer, Color, Icon, IconName, Indicator, Label, + SharedString, Tooltip, WindowContext, }; use util::ResultExt as _; @@ -255,12 +255,63 @@ impl Render for ProjectIndexStatusView { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { let status = self.project_index.read(cx).status(); - h_flex().gap_2().map(|element| match status { - Status::Idle => element.child(Label::new("Project index ready")), - Status::Loading => element.child(Label::new("Project index loading...")), - Status::Scanning { remaining_count } => element.child(Label::new(format!( - "Project index scanning: {remaining_count} remaining..." - ))), - }) + let is_enabled = match status { + Status::Idle => true, + _ => false, + }; + + let icon = match status { + Status::Idle => Icon::new(IconName::Code) + .size(IconSize::XSmall) + .color(Color::Default), + Status::Loading => Icon::new(IconName::Code) + .size(IconSize::XSmall) + .color(Color::Muted), + Status::Scanning { .. } => Icon::new(IconName::Code) + .size(IconSize::XSmall) + .color(Color::Muted), + }; + + let indicator = match status { + Status::Idle => Some(Indicator::dot().color(Color::Success)), + Status::Scanning { .. } => Some(Indicator::dot().color(Color::Warning)), + Status::Loading => Some(Indicator::icon( + Icon::new(IconName::Spinner) + .color(Color::Accent) + .with_animation( + "arrow-circle", + Animation::new(Duration::from_secs(2)).repeat(), + |icon, delta| icon.transform(Transformation::rotate(percentage(delta))), + ), + )), + }; + + ButtonLike::new("project-index") + .disabled(!is_enabled) + .child( + ui::IconWithIndicator::new(icon, indicator) + .indicator_border_color(Some(gpui::transparent_black())), + ) + .tooltip({ + move |cx| { + let (tooltip, meta) = match status { + Status::Idle => ( + "Project index ready".to_string(), + Some("Click to disable".to_string()), + ), + Status::Loading => ("Project index loading...".to_string(), None), + Status::Scanning { remaining_count } => ( + "Project index scanning...".to_string(), + Some(format!("{} remaining...", remaining_count)), + ), + }; + + if let Some(meta) = meta { + Tooltip::with_meta(tooltip, None, meta, cx) + } else { + Tooltip::text(tooltip, cx) + } + } + }) } } diff --git a/crates/assistant2/src/ui/composer.rs b/crates/assistant2/src/ui/composer.rs index 7e07be4c30..105b3a242a 100644 --- a/crates/assistant2/src/ui/composer.rs +++ b/crates/assistant2/src/ui/composer.rs @@ -7,13 +7,12 @@ use std::sync::Arc; use theme::ThemeSettings; use ui::{popover_menu, prelude::*, Avatar, ButtonLike, ContextMenu, Tooltip}; -use crate::{AssistantChat, CompletionProvider, Submit, SubmitMode}; +use crate::{AssistantChat, CompletionProvider}; #[derive(IntoElement)] pub struct Composer { editor: View, player: Option>, - can_submit: bool, tool_registry: Arc, model_selector: AnyElement, } @@ -22,14 +21,12 @@ impl Composer { pub fn new( editor: View, player: Option>, - can_submit: bool, tool_registry: Arc, model_selector: AnyElement, ) -> Self { Self { editor, player, - can_submit, tool_registry, model_selector, } @@ -55,100 +52,56 @@ impl RenderOnce for Composer { .gap_3() .child(player_avatar) .child( - v_flex() - .size_full() - .gap_1() - .pr_4() - .child( - v_flex() - .w_full() - .p_4() - .bg(cx.theme().colors().editor_background) - .rounded_lg() - .child( - v_flex() - .justify_between() - .w_full() - .gap_2() - .child({ - let settings = ThemeSettings::get_global(cx); - let text_style = TextStyle { - color: cx.theme().colors().editor_foreground, - font_family: settings.buffer_font.family.clone(), - font_features: settings.buffer_font.features.clone(), - font_size: font_size.into(), - font_weight: FontWeight::NORMAL, - font_style: FontStyle::Normal, - line_height: line_height.into(), - background_color: None, - underline: None, - strikethrough: None, - white_space: WhiteSpace::Normal, - }; + v_flex().size_full().gap_1().child( + v_flex() + .w_full() + .p_4() + .bg(cx.theme().colors().editor_background) + .rounded_lg() + .child( + v_flex() + .justify_between() + .w_full() + .gap_2() + .child({ + let settings = ThemeSettings::get_global(cx); + let text_style = TextStyle { + color: cx.theme().colors().editor_foreground, + font_family: settings.buffer_font.family.clone(), + font_features: settings.buffer_font.features.clone(), + font_size: font_size.into(), + font_weight: FontWeight::NORMAL, + font_style: FontStyle::Normal, + line_height: line_height.into(), + background_color: None, + underline: None, + strikethrough: None, + white_space: WhiteSpace::Normal, + }; - EditorElement::new( - &self.editor, - EditorStyle { - background: cx.theme().colors().editor_background, - local_player: cx.theme().players().local(), - text: text_style, - ..Default::default() - }, - ) - }) - .child( - h_flex() - .flex_none() - .gap_2() - .justify_between() - .w_full() - .child( - h_flex().gap_1().child( - // IconButton/button - // Toggle - if enabled, .selected(true).selected_style(IconButtonStyle::Filled) - // - // match status - // Tooltip::with_meta("some label explaining project index + status", "click to enable") - IconButton::new( - "add-context", - IconName::FileDoc, - ) - .icon_color(Color::Muted), - ), // .child( - // IconButton::new( - // "add-context", - // IconName::Plus, - // ) - // .icon_color(Color::Muted), - // ), - ) - .child( - Button::new("send-button", "Send") - .style(ButtonStyle::Filled) - .disabled(!self.can_submit) - .on_click(|_, cx| { - cx.dispatch_action(Box::new(Submit( - SubmitMode::Codebase, - ))) - }) - .tooltip(|cx| { - Tooltip::for_action( - "Submit message", - &Submit(SubmitMode::Codebase), - cx, - ) - }), - ), - ), - ), - ) - .child( - h_flex() - .w_full() - .justify_between() - .child(self.model_selector) - .children(self.tool_registry.status_views().iter().cloned()), - ), + EditorElement::new( + &self.editor, + EditorStyle { + background: cx.theme().colors().editor_background, + local_player: cx.theme().players().local(), + text: text_style, + ..Default::default() + }, + ) + }) + .child( + h_flex() + .flex_none() + .gap_2() + .justify_between() + .w_full() + .child(h_flex().gap_1().children( + self.tool_registry.status_views().iter().cloned(), + )) + .child(h_flex().gap_1().child(self.model_selector)), + ), + ), + ), ) } } @@ -205,10 +158,18 @@ impl RenderOnce for ModelSelector { .overflow_x_hidden() .flex_grow() .whitespace_nowrap() - .child(Label::new(self.model)), + .child( + Label::new(self.model) + .size(LabelSize::Small) + .color(Color::Muted), + ), ) .child( - div().child(Icon::new(IconName::ChevronDown).color(Color::Muted)), + div().child( + Icon::new(IconName::ChevronDown) + .color(Color::Muted) + .size(IconSize::XSmall), + ), ), ) .style(ButtonStyle::Subtle) diff --git a/crates/ui/src/components/icon.rs b/crates/ui/src/components/icon.rs index 369a14a3ba..bc05a8f3d3 100644 --- a/crates/ui/src/components/icon.rs +++ b/crates/ui/src/components/icon.rs @@ -87,6 +87,7 @@ pub enum IconName { ChevronUp, ExpandVertical, Close, + Code, Collab, Command, Control, @@ -153,6 +154,7 @@ pub enum IconName { Snip, Space, Split, + Spinner, Tab, Terminal, Trash, @@ -191,6 +193,7 @@ impl IconName { IconName::ChevronUp => "icons/chevron_up.svg", IconName::ExpandVertical => "icons/expand_vertical.svg", IconName::Close => "icons/x.svg", + IconName::Code => "icons/code.svg", IconName::Collab => "icons/user_group_16.svg", IconName::Command => "icons/command.svg", IconName::Control => "icons/control.svg", @@ -257,6 +260,7 @@ impl IconName { IconName::Snip => "icons/snip.svg", IconName::Space => "icons/space.svg", IconName::Split => "icons/split.svg", + IconName::Spinner => "icons/spinner.svg", IconName::Tab => "icons/tab.svg", IconName::Terminal => "icons/terminal.svg", IconName::Trash => "icons/trash.svg",