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",